diff options
Diffstat (limited to '')
156 files changed, 19837 insertions, 0 deletions
diff --git a/xbmc/input/AppTranslator.cpp b/xbmc/input/AppTranslator.cpp new file mode 100644 index 0000000..11d62a4 --- /dev/null +++ b/xbmc/input/AppTranslator.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-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 "AppTranslator.h" + +#include "Key.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <map> + +namespace +{ + +using ActionName = std::string; +using CommandID = uint32_t; + +#ifdef TARGET_WINDOWS +static const std::map<ActionName, CommandID> AppCommands = { + {"browser_back", APPCOMMAND_BROWSER_BACKWARD}, + {"browser_forward", APPCOMMAND_BROWSER_FORWARD}, + {"browser_refresh", APPCOMMAND_BROWSER_REFRESH}, + {"browser_stop", APPCOMMAND_BROWSER_STOP}, + {"browser_search", APPCOMMAND_BROWSER_SEARCH}, + {"browser_favorites", APPCOMMAND_BROWSER_FAVORITES}, + {"browser_home", APPCOMMAND_BROWSER_HOME}, + {"volume_mute", APPCOMMAND_VOLUME_MUTE}, + {"volume_down", APPCOMMAND_VOLUME_DOWN}, + {"volume_up", APPCOMMAND_VOLUME_UP}, + {"next_track", APPCOMMAND_MEDIA_NEXTTRACK}, + {"prev_track", APPCOMMAND_MEDIA_PREVIOUSTRACK}, + {"stop", APPCOMMAND_MEDIA_STOP}, + {"play_pause", APPCOMMAND_MEDIA_PLAY_PAUSE}, + {"launch_mail", APPCOMMAND_LAUNCH_MAIL}, + {"launch_media_select", APPCOMMAND_LAUNCH_MEDIA_SELECT}, + {"launch_app1", APPCOMMAND_LAUNCH_APP1}, + {"launch_app2", APPCOMMAND_LAUNCH_APP2}, + {"play", APPCOMMAND_MEDIA_PLAY}, + {"pause", APPCOMMAND_MEDIA_PAUSE}, + {"fastforward", APPCOMMAND_MEDIA_FAST_FORWARD}, + {"rewind", APPCOMMAND_MEDIA_REWIND}, + {"channelup", APPCOMMAND_MEDIA_CHANNEL_UP}, + {"channeldown", APPCOMMAND_MEDIA_CHANNEL_DOWN}}; +#endif + +} // anonymous namespace + +uint32_t CAppTranslator::TranslateAppCommand(const std::string& szButton) +{ +#ifdef TARGET_WINDOWS + std::string strAppCommand = szButton; + StringUtils::ToLower(strAppCommand); + + auto it = AppCommands.find(strAppCommand); + if (it != AppCommands.end()) + return it->second | KEY_APPCOMMAND; + + CLog::Log(LOGERROR, "{}: Can't find appcommand {}", __FUNCTION__, szButton); +#endif + + return 0; +} diff --git a/xbmc/input/AppTranslator.h b/xbmc/input/AppTranslator.h new file mode 100644 index 0000000..3b331e1 --- /dev/null +++ b/xbmc/input/AppTranslator.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <stdint.h> +#include <string> + +class CAppTranslator +{ +public: + static uint32_t TranslateAppCommand(const std::string& szButton); +}; 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; + } +} diff --git a/xbmc/input/ButtonTranslator.h b/xbmc/input/ButtonTranslator.h new file mode 100644 index 0000000..3f33c27 --- /dev/null +++ b/xbmc/input/ButtonTranslator.h @@ -0,0 +1,93 @@ +/* + * 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. + */ + +#pragma once + +#include "input/actions/Action.h" +#include "network/EventClient.h" + +#include <map> +#include <set> +#include <string> + +class CKey; +class TiXmlNode; +class CCustomControllerTranslator; +class CTouchTranslator; +class IButtonMapper; +class IWindowKeymap; + +/// singleton class to map from buttons to actions +/// Warning: _not_ threadsafe! +class CButtonTranslator +{ + friend class EVENTCLIENT::CEventButtonState; + +public: + CButtonTranslator() = default; + CButtonTranslator(const CButtonTranslator&) = delete; + CButtonTranslator const& operator=(CButtonTranslator const&) = delete; + virtual ~CButtonTranslator() = default; + + // Add/remove a HID device with custom mappings + bool AddDevice(const std::string& strDevice); + bool RemoveDevice(const std::string& strDevice); + + /// loads Keymap.xml + bool Load(); + + /// clears the maps + void Clear(); + + /*! \brief Finds out if a longpress mapping exists for this key + \param window id of the current window + \param key to search a mapping for + \return true if a longpress mapping exists + */ + bool HasLongpressMapping(int window, const CKey& key); + + /*! \brief Obtain the action configured for a given window and key + \param window the window id + \param key the key to query the action for + \param fallback if no action is directly configured for the given window, obtain the action from + fallback window, if exists or from global config as last resort + \return the action matching the key + */ + CAction GetAction(int window, const CKey& key, bool fallback = true); + + void RegisterMapper(const std::string& device, IButtonMapper* mapper); + void UnregisterMapper(const IButtonMapper* mapper); + + static uint32_t TranslateString(const std::string& strMap, const std::string& strButton); + +private: + struct CButtonAction + { + unsigned int id; + std::string strID; // needed for "ActivateWindow()" type actions + }; + + typedef std::multimap<uint32_t, CButtonAction> buttonMap; // our button map to fill in + + // m_translatorMap contains all mappings i.e. m_BaseMap + HID device mappings + std::map<int, buttonMap> m_translatorMap; + + // m_deviceList contains the list of connected HID devices + std::set<std::string> m_deviceList; + + unsigned int GetActionCode(int window, const CKey& key, std::string& strAction) const; + + void MapWindowActions(const TiXmlNode* pWindow, int wWindowID); + void MapAction(uint32_t buttonCode, const std::string& szAction, buttonMap& map); + + bool LoadKeymap(const std::string& keymapPath); + + bool HasLongpressMapping_Internal(int window, const CKey& key); + + std::map<std::string, IButtonMapper*> m_buttonMappers; +}; diff --git a/xbmc/input/CMakeLists.txt b/xbmc/input/CMakeLists.txt new file mode 100644 index 0000000..3408cf0 --- /dev/null +++ b/xbmc/input/CMakeLists.txt @@ -0,0 +1,59 @@ +set(SOURCES AppTranslator.cpp + ButtonTranslator.cpp + CustomControllerTranslator.cpp + GamepadTranslator.cpp + InertialScrollingHandler.cpp + InputCodingTableBasePY.cpp + InputCodingTableFactory.cpp + InputCodingTableKorean.cpp + InputManager.cpp + InputTranslator.cpp + IRTranslator.cpp + JoystickMapper.cpp + Key.cpp + KeyboardLayout.cpp + KeyboardLayoutManager.cpp + KeyboardStat.cpp + KeyboardTranslator.cpp + Keymap.cpp + KeymapEnvironment.cpp + TouchTranslator.cpp + WindowKeymap.cpp + WindowTranslator.cpp + XBMC_keytable.cpp) + +set(HEADERS hardware/IHardwareInput.h + remote/IRRemote.h + AppTranslator.h + ButtonTranslator.h + CustomControllerTranslator.h + GamepadTranslator.h + IButtonMapper.h + IKeymap.h + IKeymapEnvironment.h + InertialScrollingHandler.h + InputCodingTable.h + InputCodingTableBasePY.h + InputCodingTableFactory.h + InputCodingTableKorean.h + InputManager.h + InputTranslator.h + InputTypes.h + IRTranslator.h + JoystickMapper.h + Key.h + KeyboardLayout.h + KeyboardLayoutManager.h + KeyboardStat.h + KeyboardTranslator.h + Keymap.h + KeymapEnvironment.h + TouchTranslator.h + WindowTranslator.h + WindowKeymap.h + XBMC_keyboard.h + XBMC_keysym.h + XBMC_keytable.h + XBMC_vkeys.h) + +core_add_library(input) diff --git a/xbmc/input/CustomControllerTranslator.cpp b/xbmc/input/CustomControllerTranslator.cpp new file mode 100644 index 0000000..9d25a6e --- /dev/null +++ b/xbmc/input/CustomControllerTranslator.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017-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 "CustomControllerTranslator.h" + +#include "WindowTranslator.h" //! @todo +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +void CCustomControllerTranslator::MapActions(int windowID, const TiXmlNode* pCustomController) +{ + CustomControllerButtonMap buttonMap; + std::string controllerName; + + const TiXmlElement* pController = pCustomController->ToElement(); + if (pController != nullptr) + { + // Transform loose name to new family, including altnames + const char* name = pController->Attribute("name"); + if (name != nullptr) + controllerName = name; + } + + if (controllerName.empty()) + { + CLog::Log(LOGERROR, "Missing attribute \"name\" for tag \"customcontroller\""); + return; + } + + // Parse map + const TiXmlElement* pButton = pCustomController->FirstChildElement(); + int id = 0; + while (pButton != nullptr) + { + std::string action; + if (!pButton->NoChildren()) + action = pButton->FirstChild()->ValueStr(); + + if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS) && id >= 0) + { + buttonMap[id] = action; + } + else + CLog::Log(LOGERROR, "Error reading customController map element, Invalid id: {}", id); + + pButton = pButton->NextSiblingElement(); + } + + // Add/overwrite button with mapped actions + for (auto button : buttonMap) + m_customControllersMap[controllerName][windowID][button.first] = std::move(button.second); +} + +void CCustomControllerTranslator::Clear() +{ + m_customControllersMap.clear(); +} + +bool CCustomControllerTranslator::TranslateCustomControllerString(int windowId, + const std::string& controllerName, + int buttonId, + int& action, + std::string& strAction) +{ + unsigned int actionId = ACTION_NONE; + + // handle virtual windows + windowId = CWindowTranslator::GetVirtualWindow(windowId); + + // Try to get the action from the current window + if (!TranslateString(windowId, controllerName, buttonId, actionId, strAction)) + { + // if it's invalid, try to get it from fallback windows or the global map (windowId == -1) + while (actionId == ACTION_NONE && windowId > -1) + { + windowId = CWindowTranslator::GetFallbackWindow(windowId); + TranslateString(windowId, controllerName, buttonId, actionId, strAction); + } + } + + action = actionId; + return actionId != ACTION_NONE; +} + +bool CCustomControllerTranslator::TranslateString(int windowId, + const std::string& controllerName, + int buttonId, + unsigned int& actionId, + std::string& strAction) +{ + // Resolve the correct custom controller + auto it = m_customControllersMap.find(controllerName); + if (it == m_customControllersMap.end()) + return false; + + const CustomControllerWindowMap& windowMap = it->second; + auto it2 = windowMap.find(windowId); + if (it2 != windowMap.end()) + { + const CustomControllerButtonMap& buttonMap = it2->second; + auto it3 = buttonMap.find(buttonId); + if (it3 != buttonMap.end()) + { + strAction = it3->second; + CActionTranslator::TranslateString(strAction, actionId); + } + } + + return actionId != ACTION_NONE; +} diff --git a/xbmc/input/CustomControllerTranslator.h b/xbmc/input/CustomControllerTranslator.h new file mode 100644 index 0000000..5c06fe7 --- /dev/null +++ b/xbmc/input/CustomControllerTranslator.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "IButtonMapper.h" + +#include <map> +#include <string> + +class TiXmlNode; + +class CCustomControllerTranslator : public IButtonMapper +{ +public: + CCustomControllerTranslator() = default; + + // implementation of IButtonMapper + void MapActions(int windowID, const TiXmlNode* pDevice) override; + void Clear() override; + + bool TranslateCustomControllerString(int windowId, + const std::string& controllerName, + int buttonId, + int& action, + std::string& strAction); + +private: + bool TranslateString(int windowId, + const std::string& controllerName, + int buttonId, + unsigned int& actionId, + std::string& strAction); + + // Maps button id to action + using CustomControllerButtonMap = std::map<int, std::string>; + + // Maps window id to controller button map + using CustomControllerWindowMap = std::map<int, CustomControllerButtonMap>; + + // Maps custom controller name to controller Window map + std::map<std::string, CustomControllerWindowMap> m_customControllersMap; +}; diff --git a/xbmc/input/GamepadTranslator.cpp b/xbmc/input/GamepadTranslator.cpp new file mode 100644 index 0000000..9d2db28 --- /dev/null +++ b/xbmc/input/GamepadTranslator.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017-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 "GamepadTranslator.h" + +#include "Key.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <string> + +uint32_t CGamepadTranslator::TranslateString(std::string strButton) +{ + if (strButton.empty()) + return 0; + + StringUtils::ToLower(strButton); + + uint32_t buttonCode = 0; + if (strButton == "a") + buttonCode = KEY_BUTTON_A; + else if (strButton == "b") + buttonCode = KEY_BUTTON_B; + else if (strButton == "x") + buttonCode = KEY_BUTTON_X; + else if (strButton == "y") + buttonCode = KEY_BUTTON_Y; + else if (strButton == "white") + buttonCode = KEY_BUTTON_WHITE; + else if (strButton == "black") + buttonCode = KEY_BUTTON_BLACK; + else if (strButton == "start") + buttonCode = KEY_BUTTON_START; + else if (strButton == "back") + buttonCode = KEY_BUTTON_BACK; + else if (strButton == "leftthumbbutton") + buttonCode = KEY_BUTTON_LEFT_THUMB_BUTTON; + else if (strButton == "rightthumbbutton") + buttonCode = KEY_BUTTON_RIGHT_THUMB_BUTTON; + else if (strButton == "leftthumbstick") + buttonCode = KEY_BUTTON_LEFT_THUMB_STICK; + else if (strButton == "leftthumbstickup") + buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_UP; + else if (strButton == "leftthumbstickdown") + buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_DOWN; + else if (strButton == "leftthumbstickleft") + buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_LEFT; + else if (strButton == "leftthumbstickright") + buttonCode = KEY_BUTTON_LEFT_THUMB_STICK_RIGHT; + else if (strButton == "rightthumbstick") + buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK; + else if (strButton == "rightthumbstickup") + buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_UP; + else if (strButton == "rightthumbstickdown") + buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_DOWN; + else if (strButton == "rightthumbstickleft") + buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_LEFT; + else if (strButton == "rightthumbstickright") + buttonCode = KEY_BUTTON_RIGHT_THUMB_STICK_RIGHT; + else if (strButton == "lefttrigger") + buttonCode = KEY_BUTTON_LEFT_TRIGGER; + else if (strButton == "righttrigger") + buttonCode = KEY_BUTTON_RIGHT_TRIGGER; + else if (strButton == "leftanalogtrigger") + buttonCode = KEY_BUTTON_LEFT_ANALOG_TRIGGER; + else if (strButton == "rightanalogtrigger") + buttonCode = KEY_BUTTON_RIGHT_ANALOG_TRIGGER; + else if (strButton == "dpadleft") + buttonCode = KEY_BUTTON_DPAD_LEFT; + else if (strButton == "dpadright") + buttonCode = KEY_BUTTON_DPAD_RIGHT; + else if (strButton == "dpadup") + buttonCode = KEY_BUTTON_DPAD_UP; + else if (strButton == "dpaddown") + buttonCode = KEY_BUTTON_DPAD_DOWN; + else + CLog::Log(LOGERROR, "Gamepad Translator: Can't find button {}", strButton); + + return buttonCode; +} diff --git a/xbmc/input/GamepadTranslator.h b/xbmc/input/GamepadTranslator.h new file mode 100644 index 0000000..7dd339d --- /dev/null +++ b/xbmc/input/GamepadTranslator.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <stdint.h> +#include <string> + +class CGamepadTranslator +{ +public: + static uint32_t TranslateString(std::string strButton); +}; diff --git a/xbmc/input/IButtonMapper.h b/xbmc/input/IButtonMapper.h new file mode 100644 index 0000000..e535df1 --- /dev/null +++ b/xbmc/input/IButtonMapper.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +class TiXmlNode; + +/*! + * \brief Interface for classes that can map buttons to Kodi actions + */ +class IButtonMapper +{ +public: + virtual ~IButtonMapper() = default; + + virtual void MapActions(int windowId, const TiXmlNode* pDevice) = 0; + + virtual void Clear() = 0; +}; diff --git a/xbmc/input/IKeymap.h b/xbmc/input/IKeymap.h new file mode 100644 index 0000000..22c0b6e --- /dev/null +++ b/xbmc/input/IKeymap.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +#include <string> + +class IKeymapEnvironment; + +/*! + * \brief Interface for mapping buttons to Kodi actions + * + * \sa CJoystickUtils::MakeKeyName + */ +class IKeymap +{ +public: + virtual ~IKeymap() = default; + + /*! + * \brief The controller ID + * + * This is required because key names are specific to each controller + */ + virtual std::string ControllerID() const = 0; + + /*! + * \brief Access properties of the keymapping environment + */ + virtual const IKeymapEnvironment* Environment() const = 0; + + /*! + * \brief Get the actions for a given key name + * + * \param keyName The key name created by CJoystickUtils::MakeKeyName() + * + * \return A list of actions associated with the given key + */ + virtual const KODI::JOYSTICK::KeymapActionGroup& GetActions(const std::string& keyName) const = 0; +}; + +/*! + * \brief Interface for mapping buttons to Kodi actions for specific windows + * + * \sa CJoystickUtils::MakeKeyName + */ +class IWindowKeymap +{ +public: + virtual ~IWindowKeymap() = default; + + /*! + * \brief The controller ID + * + * This is required because key names are specific to each controller + */ + virtual std::string ControllerID() const = 0; + + /*! + * \brief Add an action to the keymap for a given key name and window ID + * + * \param windowId The window ID to look up + * \param keyName The key name created by CJoystickUtils::MakeKeyName() + * \param action The action to map + */ + virtual void MapAction(int windowId, + const std::string& keyName, + KODI::JOYSTICK::KeymapAction action) = 0; + + /*! + * \brief Get the actions for a given key name and window ID + * + * \param windowId The window ID to look up + * \param keyName The key name created by CJoystickUtils::MakeKeyName() + * + * \return A list of actions associated with the given key for the given window + */ + virtual const KODI::JOYSTICK::KeymapActionGroup& GetActions(int windowId, + const std::string& keyName) const = 0; +}; diff --git a/xbmc/input/IKeymapEnvironment.h b/xbmc/input/IKeymapEnvironment.h new file mode 100644 index 0000000..8d327fd --- /dev/null +++ b/xbmc/input/IKeymapEnvironment.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +/*! + * \brief Customizes the environment in which keymapping is performed + * + * By overriding GetWindowID() and GetFallthrough(), an agent can customize + * the behavior of the keymap by forcing a window and preventing the use of + * a fallback window, respectively. + * + * An agent can also inform the keymap that it isn't accepting input currently, + * allowing the input to fall through to the next input handler. + */ +class IKeymapEnvironment +{ +public: + virtual ~IKeymapEnvironment() = default; + + /*! + * \brief Get the window ID for which actions should be translated + * + * \return The window ID + */ + virtual int GetWindowID() const = 0; + + /*! + * \brief Set the window ID + * + * \param The window ID, used for translating actions + */ + virtual void SetWindowID(int windowId) = 0; + + /*! + * \brief Get the fallthrough window to when a key definition is missing + * + * \param windowId The window ID + * + * \return The window ID, or -1 for no fallthrough + */ + virtual int GetFallthrough(int windowId) const = 0; + + /*! + * \brief Specify if the global keymap should be used when the window and + * fallback window are undefined + */ + virtual bool UseGlobalFallthrough() const = 0; + + /*! + * \brief Specify if the agent should monitor for easter egg presses + */ + virtual bool UseEasterEgg() const = 0; +}; diff --git a/xbmc/input/IRTranslator.cpp b/xbmc/input/IRTranslator.cpp new file mode 100644 index 0000000..906e4f5 --- /dev/null +++ b/xbmc/input/IRTranslator.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2017-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 "IRTranslator.h" + +#include "ServiceBroker.h" +#include "input/remote/IRRemote.h" +#include "profiles/ProfileManager.h" +#include "settings/SettingsComponent.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <stdlib.h> +#include <vector> + +void CIRTranslator::Load(const std::string& irMapName) +{ + if (irMapName.empty()) + return; + + Clear(); + + bool success = false; + + std::string irMapPath = URIUtils::AddFileToFolder("special://xbmc/system/", irMapName); + if (CFileUtils::Exists(irMapPath)) + success |= LoadIRMap(irMapPath); + else + CLog::Log(LOGDEBUG, "CIRTranslator::Load - no system {} found, skipping", irMapName); + + irMapPath = + CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetUserDataItem(irMapName); + if (CFileUtils::Exists(irMapPath)) + success |= LoadIRMap(irMapPath); + else + CLog::Log(LOGDEBUG, "CIRTranslator::Load - no userdata {} found, skipping", irMapName); + + if (!success) + CLog::Log(LOGERROR, "CIRTranslator::Load - unable to load remote map {}", irMapName); +} + +bool CIRTranslator::LoadIRMap(const std::string& irMapPath) +{ + std::string remoteMapTag = URIUtils::GetFileName(irMapPath); + size_t lastindex = remoteMapTag.find_last_of('.'); + if (lastindex != std::string::npos) + remoteMapTag = remoteMapTag.substr(0, lastindex); + StringUtils::ToLower(remoteMapTag); + + // Load our xml file, and fill up our mapping tables + CXBMCTinyXML xmlDoc; + + // Load the config file + CLog::Log(LOGINFO, "Loading {}", irMapPath); + if (!xmlDoc.LoadFile(irMapPath)) + { + CLog::Log(LOGERROR, "{}, Line {}\n{}", irMapPath, xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + return false; + } + + TiXmlElement* pRoot = xmlDoc.RootElement(); + std::string strValue = pRoot->Value(); + if (strValue != remoteMapTag) + { + CLog::Log(LOGERROR, "{} Doesn't contain <{}>", irMapPath, remoteMapTag); + return false; + } + + // Run through our window groups + TiXmlNode* pRemote = pRoot->FirstChild(); + while (pRemote != nullptr) + { + if (pRemote->Type() == TiXmlNode::TINYXML_ELEMENT) + { + const char* szRemote = pRemote->Value(); + if (szRemote != nullptr) + { + TiXmlAttribute* pAttr = pRemote->ToElement()->FirstAttribute(); + if (pAttr != nullptr) + MapRemote(pRemote, pAttr->Value()); + } + } + pRemote = pRemote->NextSibling(); + } + + return true; +} + +void CIRTranslator::MapRemote(TiXmlNode* pRemote, const std::string& szDevice) +{ + CLog::Log(LOGINFO, "* Adding remote mapping for device '{}'", szDevice); + + std::vector<std::string> remoteNames; + + auto it = m_irRemotesMap.find(szDevice); + if (it == m_irRemotesMap.end()) + m_irRemotesMap[szDevice].reset(new IRButtonMap); + + const std::shared_ptr<IRButtonMap>& buttons = m_irRemotesMap[szDevice]; + + TiXmlElement* pButton = pRemote->FirstChildElement(); + while (pButton != nullptr) + { + if (!pButton->NoChildren()) + { + if (pButton->ValueStr() == "altname") + remoteNames.push_back(pButton->FirstChild()->ValueStr()); + else + (*buttons)[pButton->FirstChild()->ValueStr()] = pButton->ValueStr(); + } + pButton = pButton->NextSiblingElement(); + } + + for (const auto& remoteName : remoteNames) + { + CLog::Log(LOGINFO, "* Linking remote mapping for '{}' to '{}'", szDevice, remoteName); + m_irRemotesMap[remoteName] = buttons; + } +} + +void CIRTranslator::Clear() +{ + m_irRemotesMap.clear(); +} + +unsigned int CIRTranslator::TranslateButton(const std::string& szDevice, + const std::string& szButton) +{ + // Find the device + auto it = m_irRemotesMap.find(szDevice); + if (it == m_irRemotesMap.end()) + return 0; + + // Find the button + auto it2 = (*it).second->find(szButton); + if (it2 == (*it).second->end()) + return 0; + + // Convert the button to code + if (StringUtils::CompareNoCase((*it2).second, "obc", 3) == 0) + return TranslateUniversalRemoteString((*it2).second); + + return TranslateString((*it2).second); +} + +uint32_t CIRTranslator::TranslateString(std::string strButton) +{ + if (strButton.empty()) + return 0; + + uint32_t buttonCode = 0; + + StringUtils::ToLower(strButton); + + if (strButton == "left") + buttonCode = XINPUT_IR_REMOTE_LEFT; + else if (strButton == "right") + buttonCode = XINPUT_IR_REMOTE_RIGHT; + else if (strButton == "up") + buttonCode = XINPUT_IR_REMOTE_UP; + else if (strButton == "down") + buttonCode = XINPUT_IR_REMOTE_DOWN; + else if (strButton == "select") + buttonCode = XINPUT_IR_REMOTE_SELECT; + else if (strButton == "back") + buttonCode = XINPUT_IR_REMOTE_BACK; + else if (strButton == "menu") + buttonCode = XINPUT_IR_REMOTE_MENU; + else if (strButton == "info") + buttonCode = XINPUT_IR_REMOTE_INFO; + else if (strButton == "display") + buttonCode = XINPUT_IR_REMOTE_DISPLAY; + else if (strButton == "title") + buttonCode = XINPUT_IR_REMOTE_TITLE; + else if (strButton == "play") + buttonCode = XINPUT_IR_REMOTE_PLAY; + else if (strButton == "pause") + buttonCode = XINPUT_IR_REMOTE_PAUSE; + else if (strButton == "reverse") + buttonCode = XINPUT_IR_REMOTE_REVERSE; + else if (strButton == "forward") + buttonCode = XINPUT_IR_REMOTE_FORWARD; + else if (strButton == "skipplus") + buttonCode = XINPUT_IR_REMOTE_SKIP_PLUS; + else if (strButton == "skipminus") + buttonCode = XINPUT_IR_REMOTE_SKIP_MINUS; + else if (strButton == "stop") + buttonCode = XINPUT_IR_REMOTE_STOP; + else if (strButton == "zero") + buttonCode = XINPUT_IR_REMOTE_0; + else if (strButton == "one") + buttonCode = XINPUT_IR_REMOTE_1; + else if (strButton == "two") + buttonCode = XINPUT_IR_REMOTE_2; + else if (strButton == "three") + buttonCode = XINPUT_IR_REMOTE_3; + else if (strButton == "four") + buttonCode = XINPUT_IR_REMOTE_4; + else if (strButton == "five") + buttonCode = XINPUT_IR_REMOTE_5; + else if (strButton == "six") + buttonCode = XINPUT_IR_REMOTE_6; + else if (strButton == "seven") + buttonCode = XINPUT_IR_REMOTE_7; + else if (strButton == "eight") + buttonCode = XINPUT_IR_REMOTE_8; + else if (strButton == "nine") + buttonCode = XINPUT_IR_REMOTE_9; + // Additional keys from the media center extender for xbox remote + else if (strButton == "power") + buttonCode = XINPUT_IR_REMOTE_POWER; + else if (strButton == "mytv") + buttonCode = XINPUT_IR_REMOTE_MY_TV; + else if (strButton == "mymusic") + buttonCode = XINPUT_IR_REMOTE_MY_MUSIC; + else if (strButton == "mypictures") + buttonCode = XINPUT_IR_REMOTE_MY_PICTURES; + else if (strButton == "myvideo") + buttonCode = XINPUT_IR_REMOTE_MY_VIDEOS; + else if (strButton == "record") + buttonCode = XINPUT_IR_REMOTE_RECORD; + else if (strButton == "start") + buttonCode = XINPUT_IR_REMOTE_START; + else if (strButton == "volumeplus") + buttonCode = XINPUT_IR_REMOTE_VOLUME_PLUS; + else if (strButton == "volumeminus") + buttonCode = XINPUT_IR_REMOTE_VOLUME_MINUS; + else if (strButton == "channelplus") + buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS; + else if (strButton == "channelminus") + buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS; + else if (strButton == "pageplus") + buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS; + else if (strButton == "pageminus") + buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS; + else if (strButton == "mute") + buttonCode = XINPUT_IR_REMOTE_MUTE; + else if (strButton == "recordedtv") + buttonCode = XINPUT_IR_REMOTE_RECORDED_TV; + else if (strButton == "guide") + buttonCode = XINPUT_IR_REMOTE_GUIDE; + else if (strButton == "livetv") + buttonCode = XINPUT_IR_REMOTE_LIVE_TV; + else if (strButton == "liveradio") + buttonCode = XINPUT_IR_REMOTE_LIVE_RADIO; + else if (strButton == "epgsearch") + buttonCode = XINPUT_IR_REMOTE_EPG_SEARCH; + else if (strButton == "star") + buttonCode = XINPUT_IR_REMOTE_STAR; + else if (strButton == "hash") + buttonCode = XINPUT_IR_REMOTE_HASH; + else if (strButton == "clear") + buttonCode = XINPUT_IR_REMOTE_CLEAR; + else if (strButton == "enter") + buttonCode = XINPUT_IR_REMOTE_ENTER; + else if (strButton == "xbox") + buttonCode = XINPUT_IR_REMOTE_DISPLAY; // Same as display + else if (strButton == "playlist") + buttonCode = XINPUT_IR_REMOTE_PLAYLIST; + else if (strButton == "teletext") + buttonCode = XINPUT_IR_REMOTE_TELETEXT; + else if (strButton == "red") + buttonCode = XINPUT_IR_REMOTE_RED; + else if (strButton == "green") + buttonCode = XINPUT_IR_REMOTE_GREEN; + else if (strButton == "yellow") + buttonCode = XINPUT_IR_REMOTE_YELLOW; + else if (strButton == "blue") + buttonCode = XINPUT_IR_REMOTE_BLUE; + else if (strButton == "subtitle") + buttonCode = XINPUT_IR_REMOTE_SUBTITLE; + else if (strButton == "language") + buttonCode = XINPUT_IR_REMOTE_LANGUAGE; + else if (strButton == "eject") + buttonCode = XINPUT_IR_REMOTE_EJECT; + else if (strButton == "contentsmenu") + buttonCode = XINPUT_IR_REMOTE_CONTENTS_MENU; + else if (strButton == "rootmenu") + buttonCode = XINPUT_IR_REMOTE_ROOT_MENU; + else if (strButton == "topmenu") + buttonCode = XINPUT_IR_REMOTE_TOP_MENU; + else if (strButton == "dvdmenu") + buttonCode = XINPUT_IR_REMOTE_DVD_MENU; + else if (strButton == "print") + buttonCode = XINPUT_IR_REMOTE_PRINT; + else + CLog::Log(LOGERROR, "Remote Translator: Can't find button {}", strButton); + return buttonCode; +} + +uint32_t CIRTranslator::TranslateUniversalRemoteString(const std::string& szButton) +{ + if (szButton.empty() || szButton.length() < 4 || StringUtils::CompareNoCase(szButton, "obc", 3)) + return 0; + + const char* szCode = szButton.c_str() + 3; + + // Button Code is 255 - OBC (Original Button Code) of the button + uint32_t buttonCode = 255 - atol(szCode); + if (buttonCode > 255) + buttonCode = 0; + + return buttonCode; +} diff --git a/xbmc/input/IRTranslator.h b/xbmc/input/IRTranslator.h new file mode 100644 index 0000000..d32363e --- /dev/null +++ b/xbmc/input/IRTranslator.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <map> +#include <memory> +#include <string> + +class TiXmlNode; + +class CIRTranslator +{ +public: + CIRTranslator() = default; + + /*! + * \brief Loads Lircmap.xml/IRSSmap.xml + */ + void Load(const std::string& irMapName); + + /*! + * \brief Clears the map + */ + void Clear(); + + unsigned int TranslateButton(const std::string& szDevice, const std::string& szButton); + + static uint32_t TranslateString(std::string strButton); + static uint32_t TranslateUniversalRemoteString(const std::string& szButton); + +private: + bool LoadIRMap(const std::string& irMapPath); + void MapRemote(TiXmlNode* pRemote, const std::string& szDevice); + + using IRButtonMap = std::map<std::string, std::string>; + + std::map<std::string, std::shared_ptr<IRButtonMap>> m_irRemotesMap; +}; diff --git a/xbmc/input/InertialScrollingHandler.cpp b/xbmc/input/InertialScrollingHandler.cpp new file mode 100644 index 0000000..1248722 --- /dev/null +++ b/xbmc/input/InertialScrollingHandler.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2011-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 "InertialScrollingHandler.h" + +#include "ServiceBroker.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "input/touch/generic/GenericTouchInputHandler.h" +#include "utils/TimeUtils.h" +#include "windowing/WinSystem.h" + +#include <cmath> +#include <numeric> + +// time for reaching velocity 0 in secs +#define TIME_TO_ZERO_SPEED 1.0f +// minimum speed for doing inertial scroll is 100 pixels / s +#define MINIMUM_SPEED_FOR_INERTIA 200 +// maximum speed for reducing time to zero +#define MAXIMUM_SPEED_FOR_REDUCTION 750 +// maximum time between last movement and gesture end in ms to consider as moving +#define MAXIMUM_DELAY_FOR_INERTIA 200 + +CInertialScrollingHandler::CInertialScrollingHandler() : m_iLastGesturePoint(CPoint(0, 0)) +{ +} + +unsigned int CInertialScrollingHandler::PanPoint::TimeElapsed() const +{ + return CTimeUtils::GetFrameTime() - time; +} + +bool CInertialScrollingHandler::CheckForInertialScrolling(const CAction* action) +{ + bool ret = false; // return value - false no inertial scrolling - true - inertial scrolling + + if (CServiceBroker::GetWinSystem()->HasInertialGestures()) + { + return ret; // no need for emulating inertial scrolling - windowing does support it natively. + } + + // reset screensaver during pan + if (action->GetID() == ACTION_GESTURE_PAN) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + if (appPower) + appPower->ResetScreenSaver(); + if (!m_bScrolling) + { + m_panPoints.emplace_back(CTimeUtils::GetFrameTime(), + CVector{action->GetAmount(4), action->GetAmount(5)}); + } + return false; + } + + // mouse click aborts scrolling + if (m_bScrolling && action->GetID() == ACTION_MOUSE_LEFT_CLICK) + { + ret = true; + m_bAborting = true; // lets abort + } + + // trim saved pan points to time range that qualifies for inertial scrolling + while (!m_panPoints.empty() && m_panPoints.front().TimeElapsed() > MAXIMUM_DELAY_FOR_INERTIA) + m_panPoints.pop_front(); + + // on begin/tap stop all inertial scrolling + if (action->GetID() == ACTION_GESTURE_BEGIN) + { + // release any former exclusive mouse mode + // for making switching between multiple lists + // possible + CGUIMessage message(GUI_MSG_EXCLUSIVE_MOUSE, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + m_bScrolling = false; + // wakeup screensaver on pan begin + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + } + else if (action->GetID() == ACTION_GESTURE_END && + !m_panPoints.empty()) // do we need to animate inertial scrolling? + { + // Calculate velocity in the last MAXIMUM_DELAY_FOR_INERTIA milliseconds. + // Do not use the velocity given by the ACTION_GESTURE_END data - it is calculated + // for the whole duration of the touch and thus useless for inertia. The user + // may scroll around for a few seconds and then only at the end flick in one + // direction. Only the last flick should be relevant here. + auto velocitySum = + std::accumulate(m_panPoints.cbegin(), m_panPoints.cend(), CVector{}, + [](CVector val, PanPoint const& p) { return val + p.velocity; }); + auto velocityX = velocitySum.x / m_panPoints.size(); + auto velocityY = velocitySum.y / m_panPoints.size(); + + m_timeToZero = TIME_TO_ZERO_SPEED; + auto velocityMax = std::max(std::abs(velocityX), std::abs(velocityY)); +#ifdef TARGET_DARWIN_OSX + float dpiScale = 1.0; +#else + float dpiScale = CGenericTouchInputHandler::GetInstance().GetScreenDPI() / 160.0f; +#endif + if (velocityMax > MINIMUM_SPEED_FOR_INERTIA * dpiScale) + { + if (velocityMax < MAXIMUM_SPEED_FOR_REDUCTION * dpiScale) + m_timeToZero = (m_timeToZero * velocityMax) / (MAXIMUM_SPEED_FOR_REDUCTION * dpiScale); + + bool inertialRequested = false; + CGUIMessage message(GUI_MSG_GESTURE_NOTIFY, 0, 0, static_cast<int>(velocityX), + static_cast<int>(velocityY)); + + // ask if the control wants inertial scrolling + if (CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message)) + { + int result = 0; + if (message.GetPointer()) + { + int* p = static_cast<int*>(message.GetPointer()); + message.SetPointer(nullptr); + result = *p; + delete p; + } + if (result == EVENT_RESULT_PAN_HORIZONTAL || result == EVENT_RESULT_PAN_VERTICAL) + { + inertialRequested = true; + } + } + + if (inertialRequested) + { + m_iFlickVelocity.x = velocityX; // in pixels per sec + m_iFlickVelocity.y = velocityY; // in pixels per sec + m_iLastGesturePoint.x = action->GetAmount(2); // last gesture point x + m_iLastGesturePoint.y = action->GetAmount(3); // last gesture point y + + // calc deacceleration for fullstop in TIME_TO_ZERO_SPEED secs + // v = a*t + v0 -> set v = 0 because we want to stop scrolling + // a = -v0 / t + m_inertialDeacceleration.x = -1 * m_iFlickVelocity.x / m_timeToZero; + m_inertialDeacceleration.y = -1 * m_iFlickVelocity.y / m_timeToZero; + + m_inertialStartTime = CTimeUtils::GetFrameTime(); // start time of inertial scrolling + ret = true; + m_bScrolling = true; // activate the inertial scrolling animation + } + } + } + + if (action->GetID() == ACTION_GESTURE_BEGIN || action->GetID() == ACTION_GESTURE_END || + action->GetID() == ACTION_GESTURE_ABORT) + { + m_panPoints.clear(); + } + + return ret; +} + +bool CInertialScrollingHandler::ProcessInertialScroll(float frameTime) +{ + // do inertial scroll animation by sending gesture_pan + if (m_bScrolling) + { + float xMovement = 0.0; + float yMovement = 0.0; + + // decrease based on negative acceleration + // calc the overall inertial scrolling time in secs + float absoluteInertialTime = (CTimeUtils::GetFrameTime() - m_inertialStartTime) / (float)1000; + + // as long as we aren't over the overall inertial scroll time - do the deacceleration + if (absoluteInertialTime < m_timeToZero) + { + // v = s/t -> s = t * v + xMovement = frameTime * m_iFlickVelocity.x; + yMovement = frameTime * m_iFlickVelocity.y; + + // save new gesture point + m_iLastGesturePoint.x += xMovement; + m_iLastGesturePoint.y += yMovement; + + // fire the pan action + if (!g_application.OnAction(CAction(ACTION_GESTURE_PAN, 0, m_iLastGesturePoint.x, + m_iLastGesturePoint.y, xMovement, yMovement, + m_iFlickVelocity.x, m_iFlickVelocity.y))) + { + m_bAborting = true; // we are done + } + + // calc new velocity based on deacceleration + // v = a*t + v0 + m_iFlickVelocity.x = m_inertialDeacceleration.x * frameTime + m_iFlickVelocity.x; + m_iFlickVelocity.y = m_inertialDeacceleration.y * frameTime + m_iFlickVelocity.y; + + // check if the signs are equal - which would mean we deaccelerated to long and reversed the + // direction + if ((m_inertialDeacceleration.x < 0) == (m_iFlickVelocity.x < 0)) + { + m_iFlickVelocity.x = 0; + } + if ((m_inertialDeacceleration.y < 0) == (m_iFlickVelocity.y < 0)) + { + m_iFlickVelocity.y = 0; + } + } + else // no movement -> done + { + m_bAborting = true; // we are done + } + } + + // if we are done - or we where aborted + if (m_bAborting) + { + // fire gesture end action + g_application.OnAction(CAction(ACTION_GESTURE_END, 0, 0.0f, 0.0f, 0.0f, 0.0f)); + m_bAborting = false; + m_bScrolling = false; // stop scrolling + m_iFlickVelocity.x = 0; + m_iFlickVelocity.y = 0; + } + + return true; +} diff --git a/xbmc/input/InertialScrollingHandler.h b/xbmc/input/InertialScrollingHandler.h new file mode 100644 index 0000000..cff8516 --- /dev/null +++ b/xbmc/input/InertialScrollingHandler.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011-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. + */ + +#pragma once + +#include "utils/Geometry.h" +#include "utils/Vector.h" + +#include <deque> + +class CApplication; +class CAction; + +class CInertialScrollingHandler +{ + friend class CApplication; + +public: + CInertialScrollingHandler(); + + bool IsScrolling() { return m_bScrolling; } + +private: + bool CheckForInertialScrolling(const CAction* action); + bool ProcessInertialScroll(float frameTime); + + /* + * vars for inertial scrolling animation with gestures + */ + + // flag indicating that we currently do the inertial scrolling emulation + bool m_bScrolling = false; + + // flag indicating an abort of scrolling + bool m_bAborting = false; + + CVector m_iFlickVelocity; + + struct PanPoint + { + unsigned int time; + CVector velocity; + PanPoint(unsigned int time, CVector velocity) : time(time), velocity(velocity) {} + unsigned int TimeElapsed() const; + }; + std::deque<PanPoint> m_panPoints; + CPoint m_iLastGesturePoint; + CVector m_inertialDeacceleration; + unsigned int m_inertialStartTime = 0; + float m_timeToZero = 0.0f; +}; diff --git a/xbmc/input/InputCodingTable.h b/xbmc/input/InputCodingTable.h new file mode 100644 index 0000000..a071356 --- /dev/null +++ b/xbmc/input/InputCodingTable.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +class IInputCodingTable +{ +public: + enum + { + TYPE_WORD_LIST, + TYPE_CONVERT_STRING + }; + virtual int GetType() { return TYPE_WORD_LIST; } + + virtual ~IInputCodingTable() = default; + /*! \brief Called for the active keyboard layout when it's loaded, stick any initialization here + + This won't be needed for most implementations so we don't set it =0 but provide a default + implementation. + */ + virtual void Initialize() {} + + /*! \brief Called for the active keyboard layout when it's unloaded, stick any cleanup here + + This won't be needed for most implementations so we don't set it =0 but provide a default + implementation. + */ + virtual void Deinitialize() {} + + /*! \brief Can be overridden if initialization is expensive to avoid calling initialize more than + needed + + \return true if initialization has been done and was successful, false otherwise. + */ + virtual bool IsInitialized() const { return true; } + virtual bool GetWordListPage(const std::string& strCode, bool isFirstPage) = 0; + virtual std::vector<std::wstring> GetResponse(int response) = 0; + const std::string& GetCodeChars() const { return m_codechars; } + + virtual void SetTextPrev(const std::string& strTextPrev) {} + virtual std::string ConvertString(const std::string& strCode) { return std::string(""); } + +protected: + std::string m_codechars; +}; + +typedef std::shared_ptr<IInputCodingTable> IInputCodingTablePtr; diff --git a/xbmc/input/InputCodingTableBasePY.cpp b/xbmc/input/InputCodingTableBasePY.cpp new file mode 100644 index 0000000..0ac41a3 --- /dev/null +++ b/xbmc/input/InputCodingTableBasePY.cpp @@ -0,0 +1,1314 @@ +/* + * 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 "InputCodingTableBasePY.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "utils/CharsetConverter.h" + +#include <stdlib.h> + +static std::map<std::string, std::wstring> codemap = { + {"a", // L"啊阿吖嗄腌锕" + {0x554a, 0x963f, 0x5416, 0x55c4, 0x814c, 0x9515}}, + {"ai", // L"爱矮挨哎碍癌艾唉哀蔼隘埃皑嗌嫒瑷暧捱砹嗳锿霭" + {0x7231, 0x77ee, 0x6328, 0x54ce, 0x788d, 0x764c, 0x827e, 0x5509, 0x54c0, 0x853c, 0x9698, + 0x57c3, 0x7691, 0x55cc, 0x5ad2, 0x7477, 0x66a7, 0x6371, 0x7839, 0x55f3, 0x953f, 0x972d}}, + {"an", // L"按安暗岸俺案鞍氨胺庵揞犴铵桉谙鹌埯黯" + {0x6309, 0x5b89, 0x6697, 0x5cb8, 0x4ffa, 0x6848, 0x978d, 0x6c28, 0x80fa, 0x5eb5, 0x63de, + 0x72b4, 0x94f5, 0x6849, 0x8c19, 0x9e4c, 0x57ef, 0x9eef}}, + {"ang", // L"昂肮盎" + {0x6602, 0x80ae, 0x76ce}}, + {"ao", // L"袄凹傲奥熬懊敖翱澳拗媪廒骜嗷坳遨聱螯獒鏊鳌鏖岙" + {0x8884, 0x51f9, 0x50b2, 0x5965, 0x71ac, 0x61ca, 0x6556, 0x7ff1, + 0x6fb3, 0x62d7, 0x5aaa, 0x5ed2, 0x9a9c, 0x55f7, 0x5773, 0x9068, + 0x8071, 0x87af, 0x7352, 0x93ca, 0x9ccc, 0x93d6, 0x5c99}}, + {"ba", // L"把八吧爸拔罢跋巴芭扒坝霸叭靶笆疤耙捌粑茇岜鲅钯魃菝灞" + {0x628a, 0x516b, 0x5427, 0x7238, 0x62d4, 0x7f62, 0x8dcb, 0x5df4, 0x82ad, + 0x6252, 0x575d, 0x9738, 0x53ed, 0x9776, 0x7b06, 0x75a4, 0x8019, 0x634c, + 0x7c91, 0x8307, 0x5c9c, 0x9c85, 0x94af, 0x9b43, 0x83dd, 0x705e}}, + {"bai", // L"百白摆败柏拜佰伯稗捭掰" + {0x767e, 0x767d, 0x6446, 0x8d25, 0x67cf, 0x62dc, 0x4f70, 0x4f2f, 0x7a17, 0x636d, 0x63b0}}, + {"ban", // L"半办班般拌搬版斑板伴扳扮瓣颁绊癍坂钣舨阪瘢" + {0x534a, 0x529e, 0x73ed, 0x822c, 0x62cc, 0x642c, 0x7248, 0x6591, 0x677f, 0x4f34, 0x6273, + 0x626e, 0x74e3, 0x9881, 0x7eca, 0x764d, 0x5742, 0x94a3, 0x8228, 0x962a, 0x7622}}, + {"bang", // L"帮棒绑磅镑邦榜蚌傍梆膀谤浜蒡" + {0x5e2e, 0x68d2, 0x7ed1, 0x78c5, 0x9551, 0x90a6, 0x699c, 0x868c, 0x508d, 0x6886, 0x8180, + 0x8c24, 0x6d5c, 0x84a1}}, + {"bao", // L"包抱报饱保暴薄宝爆剥豹刨雹褒堡苞胞鲍炮龅孢煲褓鸨趵葆勹" + {0x5305, 0x62b1, 0x62a5, 0x9971, 0x4fdd, 0x66b4, 0x8584, 0x5b9d, 0x7206, + 0x5265, 0x8c79, 0x5228, 0x96f9, 0x8912, 0x5821, 0x82de, 0x80de, 0x9c8d, + 0x70ae, 0x9f85, 0x5b62, 0x7172, 0x8913, 0x9e28, 0x8db5, 0x8446, 0x52f9}}, + {"bei", //L"被北倍杯背悲备碑卑贝辈钡焙狈惫臂褙悖蓓鹎鐾呗邶鞴孛陂碚埤萆" + {0x88ab, 0x5317, 0x500d, 0x676f, 0x80cc, 0x60b2, 0x5907, 0x7891, 0x5351, 0x8d1d, + 0x8f88, 0x94a1, 0x7119, 0x72c8, 0x60eb, 0x81c2, 0x8919, 0x6096, 0x84d3, 0x9e4e, + 0x943e, 0x5457, 0x90b6, 0x97b4, 0x5b5b, 0x9642, 0x789a, 0x57e4, 0x8406}}, + {"ben", //L"本奔苯笨锛贲畚坌" + {0x672c, 0x5954, 0x82ef, 0x7b28, 0x951b, 0x8d32, 0x755a, 0x574c}}, + {"beng", //L"蹦绷甭崩迸蚌泵甏嘣堋" + {0x8e66, 0x7ef7, 0x752d, 0x5d29, 0x8ff8, 0x868c, 0x6cf5, 0x750f, 0x5623, 0x580b}}, + {"bi", // L"比笔闭鼻碧必避逼毕臂彼鄙壁蓖币弊辟蔽毙庇敝陛毖痹秘泌秕薜荸芘萆匕裨畀俾嬖狴筚箅篦舭荜襞庳铋跸吡愎贲滗濞璧哔髀弼妣婢埤" + {0x6bd4, 0x7b14, 0x95ed, 0x9f3b, 0x78a7, 0x5fc5, 0x907f, 0x903c, 0x6bd5, 0x81c2, + 0x5f7c, 0x9119, 0x58c1, 0x84d6, 0x5e01, 0x5f0a, 0x8f9f, 0x853d, 0x6bd9, 0x5e87, + 0x655d, 0x965b, 0x6bd6, 0x75f9, 0x79d8, 0x6ccc, 0x79d5, 0x859c, 0x8378, 0x8298, + 0x8406, 0x5315, 0x88e8, 0x7540, 0x4ffe, 0x5b16, 0x72f4, 0x7b5a, 0x7b85, 0x7be6, + 0x822d, 0x835c, 0x895e, 0x5eb3, 0x94cb, 0x8df8, 0x5421, 0x610e, 0x8d32, 0x6ed7, + 0x6fde, 0x74a7, 0x54d4, 0x9ac0, 0x5f3c, 0x59a3, 0x5a62, 0x57e4}}, + {"bian", // L"边变便遍编辩扁贬鞭卞辨辫忭砭匾汴碥蝙褊鳊笾苄窆弁缏煸" + {0x8fb9, 0x53d8, 0x4fbf, 0x904d, 0x7f16, 0x8fa9, 0x6241, 0x8d2c, 0x97ad, + 0x535e, 0x8fa8, 0x8fab, 0x5fed, 0x782d, 0x533e, 0x6c74, 0x78a5, 0x8759, + 0x890a, 0x9cca, 0x7b3e, 0x82c4, 0x7a86, 0x5f01, 0x7f0f, 0x7178}}, + {"biao", // L"表标彪膘杓婊飑飙鳔瘭飚镳裱骠镖髟" + {0x8868, 0x6807, 0x5f6a, 0x8198, 0x6753, 0x5a4a, 0x98d1, 0x98d9, 0x9cd4, 0x762d, 0x98da, + 0x9573, 0x88f1, 0x9aa0, 0x9556, 0x9adf}}, + {"bie", // L"别憋鳖瘪蹩" + {0x522b, 0x618b, 0x9cd6, 0x762a, 0x8e69}}, + {"bin", // L"宾濒摈彬斌滨豳膑殡缤髌傧槟鬓镔玢" + {0x5bbe, 0x6fd2, 0x6448, 0x5f6c, 0x658c, 0x6ee8, 0x8c73, 0x8191, 0x6ba1, 0x7f24, 0x9acc, + 0x50a7, 0x69df, 0x9b13, 0x9554, 0x73a2}}, + {"bing", // L"并病兵冰丙饼屏秉柄炳摒槟禀邴" + {0x5e76, 0x75c5, 0x5175, 0x51b0, 0x4e19, 0x997c, 0x5c4f, 0x79c9, 0x67c4, 0x70b3, 0x6452, + 0x69df, 0x7980, 0x90b4}}, + {"bo", // L"拨波播泊博伯驳玻剥薄勃菠钵搏脖帛柏舶渤铂箔膊卜礴跛檗亳鹁踣啵蕃簸钹饽擘孛百趵" + {0x62e8, 0x6ce2, 0x64ad, 0x6cca, 0x535a, 0x4f2f, 0x9a73, 0x73bb, 0x5265, 0x8584, + 0x52c3, 0x83e0, 0x94b5, 0x640f, 0x8116, 0x5e1b, 0x67cf, 0x8236, 0x6e24, 0x94c2, + 0x7b94, 0x818a, 0x535c, 0x7934, 0x8ddb, 0x6a97, 0x4eb3, 0x9e41, 0x8e23, 0x5575, + 0x8543, 0x7c38, 0x94b9, 0x997d, 0x64d8, 0x5b5b, 0x767e, 0x8db5}}, + {"bu", // L"不步补布部捕卜簿哺堡埠怖埔瓿逋晡钸钚醭卟" + {0x4e0d, 0x6b65, 0x8865, 0x5e03, 0x90e8, 0x6355, 0x535c, 0x7c3f, 0x54fa, 0x5821, + 0x57e0, 0x6016, 0x57d4, 0x74ff, 0x900b, 0x6661, 0x94b8, 0x949a, 0x91ad, 0x535f}}, + {"ca", // L"擦礤嚓" + {0x64e6, 0x7924, 0x5693}}, + {"cai", // L"才菜采材财裁猜踩睬蔡彩" + {0x624d, 0x83dc, 0x91c7, 0x6750, 0x8d22, 0x88c1, 0x731c, 0x8e29, 0x776c, 0x8521, 0x5f69}}, + {"can", // L"蚕残掺参惨惭餐灿骖璨孱黪粲" + {0x8695, 0x6b8b, 0x63ba, 0x53c2, 0x60e8, 0x60ed, 0x9910, 0x707f, 0x9a96, 0x74a8, 0x5b71, + 0x9eea, 0x7cb2}}, + {"cang", // L"藏仓沧舱苍伧" + {0x85cf, 0x4ed3, 0x6ca7, 0x8231, 0x82cd, 0x4f27}}, + {"cao", // L"草操曹槽糙嘈艚螬漕艹" + {0x8349, 0x64cd, 0x66f9, 0x69fd, 0x7cd9, 0x5608, 0x825a, 0x87ac, 0x6f15, 0x8279}}, + {"ce", // L"册侧策测厕恻" + {0x518c, 0x4fa7, 0x7b56, 0x6d4b, 0x5395, 0x607b}}, + {"cen", // L"参岑涔" + {0x53c2, 0x5c91, 0x6d94}}, + {"ceng", // L"曾层蹭噌" + {0x66fe, 0x5c42, 0x8e6d, 0x564c}}, + {"cha", // L"查插叉茶差岔搽察茬碴刹诧楂槎镲衩汊馇檫姹杈锸嚓猹" + {0x67e5, 0x63d2, 0x53c9, 0x8336, 0x5dee, 0x5c94, 0x643d, 0x5bdf, + 0x832c, 0x78b4, 0x5239, 0x8be7, 0x6942, 0x69ce, 0x9572, 0x8869, + 0x6c4a, 0x9987, 0x6aab, 0x59f9, 0x6748, 0x9538, 0x5693, 0x7339}}, + {"chai", // L"柴拆差豺钗瘥虿侪龇" + {0x67f4, 0x62c6, 0x5dee, 0x8c7a, 0x9497, 0x7625, 0x867f, 0x4faa, 0x9f87}}, + {"chan", // L"产缠掺搀阐颤铲谗蝉单馋觇婵蒇谄冁廛孱蟾羼镡忏潺禅躔澶" + {0x4ea7, 0x7f20, 0x63ba, 0x6400, 0x9610, 0x98a4, 0x94f2, 0x8c17, 0x8749, + 0x5355, 0x998b, 0x89c7, 0x5a75, 0x8487, 0x8c04, 0x5181, 0x5edb, 0x5b71, + 0x87fe, 0x7fbc, 0x9561, 0x5fcf, 0x6f7a, 0x7985, 0x8e94, 0x6fb6}}, + {"chang", // L"长唱常场厂尝肠畅昌敞倡偿猖鲳氅菖惝嫦徜鬯阊怅伥昶苌娼" + {0x957f, 0x5531, 0x5e38, 0x573a, 0x5382, 0x5c1d, 0x80a0, 0x7545, 0x660c, + 0x655e, 0x5021, 0x507f, 0x7316, 0x9cb3, 0x6c05, 0x83d6, 0x60dd, 0x5ae6, + 0x5f9c, 0x9b2f, 0x960a, 0x6005, 0x4f25, 0x6636, 0x82cc, 0x5a3c}}, + {"chao", // L"朝抄超吵潮巢炒嘲剿绰钞怊耖晁" + {0x671d, 0x6284, 0x8d85, 0x5435, 0x6f6e, 0x5de2, 0x7092, 0x5632, 0x527f, 0x7ef0, 0x949e, + 0x600a, 0x8016, 0x6641}}, + {"che", // L"车撤扯掣彻澈坼砗屮" + {0x8f66, 0x64a4, 0x626f, 0x63a3, 0x5f7b, 0x6f88, 0x577c, 0x7817, 0x5c6e}}, + {"chen", // L"趁称辰臣尘晨沉陈衬忱郴榇抻谌碜谶宸龀嗔琛" + {0x8d81, 0x79f0, 0x8fb0, 0x81e3, 0x5c18, 0x6668, 0x6c89, 0x9648, 0x886c, 0x5ff1, + 0x90f4, 0x6987, 0x62bb, 0x8c0c, 0x789c, 0x8c36, 0x5bb8, 0x9f80, 0x55d4, 0x741b}}, + {"cheng", // L"成乘盛撑称城程呈诚秤惩逞骋澄橙承塍柽埕铖噌铛酲裎枨蛏丞瞠徵" + {0x6210, 0x4e58, 0x76db, 0x6491, 0x79f0, 0x57ce, 0x7a0b, 0x5448, 0x8bda, 0x79e4, + 0x60e9, 0x901e, 0x9a8b, 0x6f84, 0x6a59, 0x627f, 0x584d, 0x67fd, 0x57d5, 0x94d6, + 0x564c, 0x94db, 0x9172, 0x88ce, 0x67a8, 0x86cf, 0x4e1e, 0x77a0, 0x5fb5}}, + {"chi", // L"吃尺迟池翅痴赤齿耻持斥侈弛驰炽匙踟坻茌墀饬媸豉褫敕哧瘛蚩啻鸱眵螭篪魑叱彳笞嗤傺" + {0x5403, 0x5c3a, 0x8fdf, 0x6c60, 0x7fc5, 0x75f4, 0x8d64, 0x9f7f, 0x803b, 0x6301, + 0x65a5, 0x4f88, 0x5f1b, 0x9a70, 0x70bd, 0x5319, 0x8e1f, 0x577b, 0x830c, 0x5880, + 0x996c, 0x5ab8, 0x8c49, 0x892b, 0x6555, 0x54e7, 0x761b, 0x86a9, 0x557b, 0x9e31, + 0x7735, 0x87ad, 0x7bea, 0x9b51, 0x53f1, 0x5f73, 0x7b1e, 0x55e4, 0x50ba}}, + {"chong", // L"冲重虫充宠崇种艟忡舂铳憧茺" + {0x51b2, 0x91cd, 0x866b, 0x5145, 0x5ba0, 0x5d07, 0x79cd, 0x825f, 0x5fe1, 0x8202, 0x94f3, + 0x61a7, 0x833a}}, + {"chou", // L"抽愁臭仇丑稠绸酬筹踌畴瞅惆俦帱瘳雠" + {0x62bd, 0x6101, 0x81ed, 0x4ec7, 0x4e11, 0x7a20, 0x7ef8, 0x916c, 0x7b79, 0x8e0c, 0x7574, + 0x7785, 0x60c6, 0x4fe6, 0x5e31, 0x7633, 0x96e0}}, + {"chu", // L"出处初锄除触橱楚础储畜滁矗搐躇厨雏楮杵刍怵绌亍憷蹰黜蜍樗褚" + {0x51fa, 0x5904, 0x521d, 0x9504, 0x9664, 0x89e6, 0x6a71, 0x695a, 0x7840, 0x50a8, + 0x755c, 0x6ec1, 0x77d7, 0x6410, 0x8e87, 0x53a8, 0x96cf, 0x696e, 0x6775, 0x520d, + 0x6035, 0x7ecc, 0x4e8d, 0x61b7, 0x8e70, 0x9edc, 0x870d, 0x6a17, 0x891a}}, + {"chuai", // L"揣膪嘬搋踹" + {0x63e3, 0x81aa, 0x562c, 0x640b, 0x8e39}}, + {"chuan", // L"穿船传串川喘椽氚遄钏舡舛巛" + {0x7a7f, 0x8239, 0x4f20, 0x4e32, 0x5ddd, 0x5598, 0x693d, 0x6c1a, 0x9044, 0x948f, 0x8221, + 0x821b, 0x5ddb}}, + {"chuang", // L"窗床闯创疮幢怆" + {0x7a97, 0x5e8a, 0x95ef, 0x521b, 0x75ae, 0x5e62, 0x6006}}, + {"chui", // L"吹垂炊锤捶槌棰陲" + {0x5439, 0x5782, 0x708a, 0x9524, 0x6376, 0x69cc, 0x68f0, 0x9672}}, + {"chun", // L"春唇纯蠢醇淳椿蝽莼鹑" + {0x6625, 0x5507, 0x7eaf, 0x8822, 0x9187, 0x6df3, 0x693f, 0x877d, 0x83bc, 0x9e51}}, + {"chuo", // L"戳绰踔啜龊辍辶" + {0x6233, 0x7ef0, 0x8e14, 0x555c, 0x9f8a, 0x8f8d, 0x8fb6}}, + {"ci", // L"次此词瓷慈雌磁辞刺茨伺疵赐差兹呲鹚祠糍粢茈" + {0x6b21, 0x6b64, 0x8bcd, 0x74f7, 0x6148, 0x96cc, 0x78c1, 0x8f9e, 0x523a, 0x8328, 0x4f3a, + 0x75b5, 0x8d50, 0x5dee, 0x5179, 0x5472, 0x9e5a, 0x7960, 0x7ccd, 0x7ca2, 0x8308}}, + {"cong", // L"从丛葱匆聪囱琮枞淙璁骢苁" + {0x4ece, 0x4e1b, 0x8471, 0x5306, 0x806a, 0x56f1, 0x742e, 0x679e, 0x6dd9, 0x7481, 0x9aa2, + 0x82c1}}, + {"cou", // L"凑楱辏腠" + {0x51d1, 0x6971, 0x8f8f, 0x8160}}, + {"cu", // L"粗醋簇促徂猝蔟蹙酢殂蹴" + {0x7c97, 0x918b, 0x7c07, 0x4fc3, 0x5f82, 0x731d, 0x851f, 0x8e59, 0x9162, 0x6b82, 0x8e74}}, + {"cuan", // L"窜蹿篡攒汆爨镩撺" + {0x7a9c, 0x8e7f, 0x7be1, 0x6512, 0x6c46, 0x7228, 0x9569, 0x64ba}}, + {"cui", // L"催脆摧翠崔淬瘁粹璀啐悴萃毳榱隹" + {0x50ac, 0x8106, 0x6467, 0x7fe0, 0x5d14, 0x6dec, 0x7601, 0x7cb9, 0x7480, 0x5550, 0x60b4, + 0x8403, 0x6bf3, 0x69b1, 0x96b9}}, + {"cun", // L"村寸存忖皴" + {0x6751, 0x5bf8, 0x5b58, 0x5fd6, 0x76b4}}, + {"cuo", // L"错撮搓挫措磋嵯厝鹾脞痤蹉瘥锉矬躜" + {0x9519, 0x64ae, 0x6413, 0x632b, 0x63aa, 0x78cb, 0x5d6f, 0x539d, 0x9e7e, 0x811e, 0x75e4, + 0x8e49, 0x7625, 0x9509, 0x77ec, 0x8e9c}}, + {"da", // L"大答达打搭瘩笪耷哒褡疸怛靼妲沓嗒鞑" + {0x5927, 0x7b54, 0x8fbe, 0x6253, 0x642d, 0x7629, 0x7b2a, 0x8037, 0x54d2, 0x8921, 0x75b8, + 0x601b, 0x977c, 0x59b2, 0x6c93, 0x55d2, 0x9791}}, + {"dai", // L"带代呆戴待袋逮歹贷怠傣大殆呔玳迨岱甙黛骀绐埭" + {0x5e26, 0x4ee3, 0x5446, 0x6234, 0x5f85, 0x888b, 0x902e, 0x6b79, 0x8d37, 0x6020, 0x50a3, + 0x5927, 0x6b86, 0x5454, 0x73b3, 0x8fe8, 0x5cb1, 0x7519, 0x9edb, 0x9a80, 0x7ed0, 0x57ed}}, + {"dan", // L"但单蛋担弹掸胆淡丹耽旦氮诞郸惮石疸澹瘅萏殚眈聃箪赕儋啖赡" + {0x4f46, 0x5355, 0x86cb, 0x62c5, 0x5f39, 0x63b8, 0x80c6, 0x6de1, 0x4e39, 0x803d, + 0x65e6, 0x6c2e, 0x8bde, 0x90f8, 0x60ee, 0x77f3, 0x75b8, 0x6fb9, 0x7605, 0x840f, + 0x6b9a, 0x7708, 0x8043, 0x7baa, 0x8d55, 0x510b, 0x5556, 0x8d61}}, + {"dang", // L"当党挡档荡谠铛宕菪凼裆砀" + {0x5f53, 0x515a, 0x6321, 0x6863, 0x8361, 0x8c20, 0x94db, 0x5b95, 0x83ea, 0x51fc, 0x88c6, + 0x7800}}, + {"dao", // L"到道倒刀岛盗稻捣悼导蹈祷帱纛忉焘氘叨刂" + {0x5230, 0x9053, 0x5012, 0x5200, 0x5c9b, 0x76d7, 0x7a3b, 0x6363, 0x60bc, 0x5bfc, 0x8e48, + 0x7977, 0x5e31, 0x7e9b, 0x5fc9, 0x7118, 0x6c18, 0x53e8, 0x5202}}, + {"de", // L"的地得德锝" + {0x7684, 0x5730, 0x5f97, 0x5fb7, 0x951d}}, + {"dei", // L"得" + {0x5f97}}, + {"deng", // L"等灯邓登澄瞪凳蹬磴镫噔嶝戥簦" + {0x7b49, 0x706f, 0x9093, 0x767b, 0x6f84, 0x77aa, 0x51f3, 0x8e6c, 0x78f4, 0x956b, 0x5654, + 0x5d9d, 0x6225, 0x7c26}}, + {"di", // L"地第底低敌抵滴帝递嫡弟缔堤的涤提笛迪狄翟蒂觌邸谛诋嘀柢骶羝氐棣睇娣荻碲镝坻籴砥" + {0x5730, 0x7b2c, 0x5e95, 0x4f4e, 0x654c, 0x62b5, 0x6ef4, 0x5e1d, 0x9012, 0x5ae1, + 0x5f1f, 0x7f14, 0x5824, 0x7684, 0x6da4, 0x63d0, 0x7b1b, 0x8fea, 0x72c4, 0x7fdf, + 0x8482, 0x89cc, 0x90b8, 0x8c1b, 0x8bcb, 0x5600, 0x67e2, 0x9ab6, 0x7f9d, 0x6c10, + 0x68e3, 0x7747, 0x5a23, 0x837b, 0x78b2, 0x955d, 0x577b, 0x7c74, 0x7825}}, + {"dia", // L"嗲" + {0x55f2}}, + {"dian", // L"点电店殿淀掂颠垫碘惦奠典佃靛滇甸踮钿坫阽癫簟玷巅癜" + {0x70b9, 0x7535, 0x5e97, 0x6bbf, 0x6dc0, 0x6382, 0x98a0, 0x57ab, 0x7898, + 0x60e6, 0x5960, 0x5178, 0x4f43, 0x975b, 0x6ec7, 0x7538, 0x8e2e, 0x94bf, + 0x576b, 0x963d, 0x766b, 0x7c1f, 0x73b7, 0x5dc5, 0x765c}}, + {"diao", // L"掉钓叼吊雕调刁碉凋铞铫鲷貂" + {0x6389, 0x9493, 0x53fc, 0x540a, 0x96d5, 0x8c03, 0x5201, 0x7889, 0x51cb, 0x94de, 0x94eb, + 0x9cb7, 0x8c82}}, + {"die", // L"爹跌叠碟蝶迭谍牒堞瓞揲蹀耋鲽垤喋" + {0x7239, 0x8dcc, 0x53e0, 0x789f, 0x8776, 0x8fed, 0x8c0d, 0x7252, 0x581e, 0x74de, 0x63f2, + 0x8e40, 0x800b, 0x9cbd, 0x57a4, 0x558b}}, + {"ding", // L"顶定盯订叮丁钉鼎锭町玎铤腚碇疔仃耵酊啶" + {0x9876, 0x5b9a, 0x76ef, 0x8ba2, 0x53ee, 0x4e01, 0x9489, 0x9f0e, 0x952d, 0x753a, 0x738e, + 0x94e4, 0x815a, 0x7887, 0x7594, 0x4ec3, 0x8035, 0x914a, 0x5576}}, + {"diu", // L"丢铥" + {0x4e22, 0x94e5}}, + {"dong", // L"动东懂洞冻冬董栋侗恫峒鸫胨胴硐氡岽咚" + {0x52a8, 0x4e1c, 0x61c2, 0x6d1e, 0x51bb, 0x51ac, 0x8463, 0x680b, 0x4f97, 0x606b, 0x5cd2, + 0x9e2b, 0x80e8, 0x80f4, 0x7850, 0x6c21, 0x5cbd, 0x549a}}, + {"dou", // L"都斗豆逗陡抖痘兜蚪窦篼蔸" + {0x90fd, 0x6597, 0x8c46, 0x9017, 0x9661, 0x6296, 0x75d8, 0x515c, 0x86aa, 0x7aa6, 0x7bfc, + 0x8538}}, + {"du", // L"读度毒渡堵独肚镀赌睹杜督都犊妒蠹笃嘟渎椟牍黩髑芏" + {0x8bfb, 0x5ea6, 0x6bd2, 0x6e21, 0x5835, 0x72ec, 0x809a, 0x9540, + 0x8d4c, 0x7779, 0x675c, 0x7763, 0x90fd, 0x728a, 0x5992, 0x8839, + 0x7b03, 0x561f, 0x6e0e, 0x691f, 0x724d, 0x9ee9, 0x9ad1, 0x828f}}, + {"duan", // L"段短断端锻缎椴煅簖" + {0x6bb5, 0x77ed, 0x65ad, 0x7aef, 0x953b, 0x7f0e, 0x6934, 0x7145, 0x7c16}}, + {"dui", // L"对队堆兑碓怼憝" + {0x5bf9, 0x961f, 0x5806, 0x5151, 0x7893, 0x603c, 0x619d}}, + {"dun", // L"吨顿蹲墩敦钝盾囤遁趸沌盹镦礅炖砘" + {0x5428, 0x987f, 0x8e72, 0x58a9, 0x6566, 0x949d, 0x76fe, 0x56e4, 0x9041, 0x8db8, 0x6c8c, + 0x76f9, 0x9566, 0x7905, 0x7096, 0x7818}}, + {"duo", // L"多朵夺舵剁垛跺惰堕掇哆驮度躲踱沲咄铎裰哚缍" + {0x591a, 0x6735, 0x593a, 0x8235, 0x5241, 0x579b, 0x8dfa, 0x60f0, 0x5815, 0x6387, 0x54c6, + 0x9a6e, 0x5ea6, 0x8eb2, 0x8e31, 0x6cb2, 0x5484, 0x94ce, 0x88f0, 0x54da, 0x7f0d}}, + {"e", // L"饿哦额鹅蛾扼俄讹阿遏峨娥恶厄鄂锇谔垩锷萼苊轭婀莪鳄颚腭愕呃噩鹗屙" + {0x997f, 0x54e6, 0x989d, 0x9e45, 0x86fe, 0x627c, 0x4fc4, 0x8bb9, 0x963f, 0x904f, 0x5ce8, + 0x5a25, 0x6076, 0x5384, 0x9102, 0x9507, 0x8c14, 0x57a9, 0x9537, 0x843c, 0x82ca, 0x8f6d, + 0x5a40, 0x83aa, 0x9cc4, 0x989a, 0x816d, 0x6115, 0x5443, 0x5669, 0x9e57, 0x5c59}}, + {"ei", // L"诶" + {0x8bf6}}, + {"en", // L"恩摁蒽嗯" + {0x6069, 0x6441, 0x84bd, 0x55ef}}, + {"er", // L"而二耳儿饵尔贰洱珥鲕鸸迩铒" + {0x800c, 0x4e8c, 0x8033, 0x513f, 0x9975, 0x5c14, 0x8d30, 0x6d31, 0x73e5, 0x9c95, 0x9e38, + 0x8fe9, 0x94d2}}, + {"fa", // L"发法罚伐乏筏阀珐垡砝" + {0x53d1, 0x6cd5, 0x7f5a, 0x4f10, 0x4e4f, 0x7b4f, 0x9600, 0x73d0, 0x57a1, 0x781d}}, + {"fan", // L"反饭翻番犯凡帆返泛繁烦贩范樊藩矾钒燔蘩畈蕃蹯梵幡" + {0x53cd, 0x996d, 0x7ffb, 0x756a, 0x72af, 0x51e1, 0x5e06, 0x8fd4, + 0x6cdb, 0x7e41, 0x70e6, 0x8d29, 0x8303, 0x6a0a, 0x85e9, 0x77fe, + 0x9492, 0x71d4, 0x8629, 0x7548, 0x8543, 0x8e6f, 0x68b5, 0x5e61}}, + {"fang", // L"放房防纺芳方访仿坊妨肪钫邡枋舫鲂匚" + {0x653e, 0x623f, 0x9632, 0x7eba, 0x82b3, 0x65b9, 0x8bbf, 0x4eff, 0x574a, 0x59a8, 0x80aa, + 0x94ab, 0x90a1, 0x678b, 0x822b, 0x9c82, 0x531a}}, + {"fei", // L"非飞肥费肺废匪吠沸菲诽啡篚蜚腓扉妃斐狒芾悱镄霏翡榧淝鲱绯痱砩" + {0x975e, 0x98de, 0x80a5, 0x8d39, 0x80ba, 0x5e9f, 0x532a, 0x5420, 0x6cb8, 0x83f2, + 0x8bfd, 0x5561, 0x7bda, 0x871a, 0x8153, 0x6249, 0x5983, 0x6590, 0x72d2, 0x82be, + 0x60b1, 0x9544, 0x970f, 0x7fe1, 0x69a7, 0x6ddd, 0x9cb1, 0x7eef, 0x75f1, 0x7829}}, + {"fen", // L"分份芬粉坟奋愤纷忿粪酚焚吩氛汾棼瀵鲼玢偾鼢贲" + {0x5206, 0x4efd, 0x82ac, 0x7c89, 0x575f, 0x594b, 0x6124, 0x7eb7, 0x5fff, 0x7caa, 0x915a, + 0x711a, 0x5429, 0x6c1b, 0x6c7e, 0x68fc, 0x7035, 0x9cbc, 0x73a2, 0x507e, 0x9f22, 0x8d32}}, + {"feng", // L"风封逢缝蜂丰枫疯冯奉讽凤峰锋烽砜俸酆葑沣唪" + {0x98ce, 0x5c01, 0x9022, 0x7f1d, 0x8702, 0x4e30, 0x67ab, 0x75af, 0x51af, 0x5949, 0x8bbd, + 0x51e4, 0x5cf0, 0x950b, 0x70fd, 0x781c, 0x4ff8, 0x9146, 0x8451, 0x6ca3, 0x552a}}, + {"fo", // L"佛" + {0x4f5b}}, + {"fou", // L"否缶" + {0x5426, 0x7f36}}, + {"fu", // L"副幅扶浮富福负伏付复服附俯斧赴缚拂夫父符孵敷赋辅府腐腹妇抚覆辐肤氟佛俘傅讣弗涪袱甫釜脯腑阜咐黼砩苻趺跗蚨芾鲋幞茯滏蜉拊菔蝠鳆蝮绂绋赙罘稃匐麸凫桴莩孚馥驸怫祓呋郛芙艴黻哺阝" + {0x526f, 0x5e45, 0x6276, 0x6d6e, 0x5bcc, 0x798f, 0x8d1f, 0x4f0f, 0x4ed8, 0x590d, 0x670d, + 0x9644, 0x4fef, 0x65a7, 0x8d74, 0x7f1a, 0x62c2, 0x592b, 0x7236, 0x7b26, 0x5b75, 0x6577, + 0x8d4b, 0x8f85, 0x5e9c, 0x8150, 0x8179, 0x5987, 0x629a, 0x8986, 0x8f90, 0x80a4, 0x6c1f, + 0x4f5b, 0x4fd8, 0x5085, 0x8ba3, 0x5f17, 0x6daa, 0x88b1, 0x752b, 0x91dc, 0x812f, 0x8151, + 0x961c, 0x5490, 0x9efc, 0x7829, 0x82fb, 0x8dba, 0x8dd7, 0x86a8, 0x82be, 0x9c8b, 0x5e5e, + 0x832f, 0x6ecf, 0x8709, 0x62ca, 0x83d4, 0x8760, 0x9cc6, 0x876e, 0x7ec2, 0x7ecb, 0x8d59, + 0x7f58, 0x7a03, 0x5310, 0x9eb8, 0x51eb, 0x6874, 0x83a9, 0x5b5a, 0x99a5, 0x9a78, 0x602b, + 0x7953, 0x544b, 0x90db, 0x8299, 0x8274, 0x9efb, 0x54fa, 0x961d}}, + {"ga", // L"噶夹嘎咖钆伽旮尬尕尜" + {0x5676, 0x5939, 0x560e, 0x5496, 0x9486, 0x4f3d, 0x65ee, 0x5c2c, 0x5c15, 0x5c1c}}, + {"gai", // L"该改盖概钙芥溉戤垓丐陔赅胲" + {0x8be5, 0x6539, 0x76d6, 0x6982, 0x9499, 0x82a5, 0x6e89, 0x6224, 0x5793, 0x4e10, 0x9654, + 0x8d45, 0x80f2}}, + {"gan", // L"赶干感敢竿甘肝柑杆赣秆旰酐矸疳泔苷擀绀橄澉淦尴坩" + {0x8d76, 0x5e72, 0x611f, 0x6562, 0x7aff, 0x7518, 0x809d, 0x67d1, + 0x6746, 0x8d63, 0x79c6, 0x65f0, 0x9150, 0x77f8, 0x75b3, 0x6cd4, + 0x82f7, 0x64c0, 0x7ec0, 0x6a44, 0x6f89, 0x6de6, 0x5c34, 0x5769}}, + {"gang", // L"刚钢纲港缸岗杠冈肛扛筻罡戆" + {0x521a, 0x94a2, 0x7eb2, 0x6e2f, 0x7f38, 0x5c97, 0x6760, 0x5188, 0x809b, 0x625b, 0x7b7b, + 0x7f61, 0x6206}}, + {"gao", // L"高搞告稿膏篙羔糕镐皋郜诰杲缟睾槔锆槁藁" + {0x9ad8, 0x641e, 0x544a, 0x7a3f, 0x818f, 0x7bd9, 0x7f94, 0x7cd5, 0x9550, 0x768b, 0x90dc, + 0x8bf0, 0x6772, 0x7f1f, 0x777e, 0x69d4, 0x9506, 0x69c1, 0x85c1}}, + {"ge", // L"个各歌割哥搁格阁隔革咯胳葛蛤戈鸽疙盖合铬骼袼塥虼圪镉仡舸鬲嗝膈搿纥哿铪" + {0x4e2a, 0x5404, 0x6b4c, 0x5272, 0x54e5, 0x6401, 0x683c, 0x9601, 0x9694, + 0x9769, 0x54af, 0x80f3, 0x845b, 0x86e4, 0x6208, 0x9e3d, 0x7599, 0x76d6, + 0x5408, 0x94ec, 0x9abc, 0x88bc, 0x5865, 0x867c, 0x572a, 0x9549, 0x4ee1, + 0x8238, 0x9b32, 0x55dd, 0x8188, 0x643f, 0x7ea5, 0x54ff, 0x94ea}}, + {"gei", // L"给" + {0x7ed9}}, + {"gen", // L"跟根哏茛亘艮" + {0x8ddf, 0x6839, 0x54cf, 0x831b, 0x4e98, 0x826e}}, + {"geng", // L"更耕颈梗耿庚羹埂赓鲠哽绠" + {0x66f4, 0x8015, 0x9888, 0x6897, 0x803f, 0x5e9a, 0x7fb9, 0x57c2, 0x8d53, 0x9ca0, 0x54fd, + 0x7ee0}}, + {"gong", // L"工公功共弓攻宫供恭拱贡躬巩汞龚肱觥珙蚣廾" + {0x5de5, 0x516c, 0x529f, 0x5171, 0x5f13, 0x653b, 0x5bab, 0x4f9b, 0x606d, 0x62f1, + 0x8d21, 0x8eac, 0x5de9, 0x6c5e, 0x9f9a, 0x80b1, 0x89e5, 0x73d9, 0x86a3, 0x5efe}}, + {"gou", // L"够沟狗钩勾购构苟垢岣彀枸鞲觏缑笱诟遘媾篝佝" + {0x591f, 0x6c9f, 0x72d7, 0x94a9, 0x52fe, 0x8d2d, 0x6784, 0x82df, 0x57a2, 0x5ca3, 0x5f40, + 0x67b8, 0x97b2, 0x89cf, 0x7f11, 0x7b31, 0x8bdf, 0x9058, 0x5abe, 0x7bdd, 0x4f5d}}, + {"gu", // L"古股鼓谷故孤箍姑顾固雇估咕骨辜沽蛊贾菇梏鸪汩轱崮菰鹄鹘钴臌酤鲴诂牯瞽毂锢牿痼觚蛄罟嘏" + {0x53e4, 0x80a1, 0x9f13, 0x8c37, 0x6545, 0x5b64, 0x7b8d, 0x59d1, 0x987e, 0x56fa, 0x96c7, + 0x4f30, 0x5495, 0x9aa8, 0x8f9c, 0x6cbd, 0x86ca, 0x8d3e, 0x83c7, 0x688f, 0x9e2a, 0x6c69, + 0x8f71, 0x5d2e, 0x83f0, 0x9e44, 0x9e58, 0x94b4, 0x81cc, 0x9164, 0x9cb4, 0x8bc2, 0x726f, + 0x77bd, 0x6bc2, 0x9522, 0x727f, 0x75fc, 0x89da, 0x86c4, 0x7f5f, 0x560f}}, + {"gua", // L"挂刮瓜寡剐褂卦呱胍鸹栝诖" + {0x6302, 0x522e, 0x74dc, 0x5be1, 0x5250, 0x8902, 0x5366, 0x5471, 0x80cd, 0x9e39, 0x681d, + 0x8bd6}}, + {"guai", // L"怪拐乖掴" + {0x602a, 0x62d0, 0x4e56, 0x63b4}}, + {"guan", // L"关管官观馆惯罐灌冠贯棺纶盥莞掼涫鳏鹳倌" + {0x5173, 0x7ba1, 0x5b98, 0x89c2, 0x9986, 0x60ef, 0x7f50, 0x704c, 0x51a0, 0x8d2f, 0x68fa, + 0x7eb6, 0x76e5, 0x839e, 0x63bc, 0x6dab, 0x9ccf, 0x9e73, 0x500c}}, + {"guang", // L"光广逛桄犷咣胱" + {0x5149, 0x5e7f, 0x901b, 0x6844, 0x72b7, 0x54a3, 0x80f1}}, + {"gui", // L"归贵鬼跪轨规硅桂柜龟诡闺瑰圭刽癸炔庋宄桧刿鳜鲑皈匦妫晷簋炅" + {0x5f52, 0x8d35, 0x9b3c, 0x8dea, 0x8f68, 0x89c4, 0x7845, 0x6842, 0x67dc, 0x9f9f, + 0x8be1, 0x95fa, 0x7470, 0x572d, 0x523d, 0x7678, 0x7094, 0x5e8b, 0x5b84, 0x6867, + 0x523f, 0x9cdc, 0x9c91, 0x7688, 0x5326, 0x59ab, 0x6677, 0x7c0b, 0x7085}}, + {"gun", // L"滚棍辊鲧衮磙绲" + {0x6eda, 0x68cd, 0x8f8a, 0x9ca7, 0x886e, 0x78d9, 0x7ef2}}, + {"guo", // L"过国果裹锅郭涡埚椁聒馘猓崞掴帼呙虢蜾蝈锞" + {0x8fc7, 0x56fd, 0x679c, 0x88f9, 0x9505, 0x90ed, 0x6da1, 0x57da, 0x6901, 0x8052, + 0x9998, 0x7313, 0x5d1e, 0x63b4, 0x5e3c, 0x5459, 0x8662, 0x873e, 0x8748, 0x951e}}, + {"ha", // L"哈蛤铪" + {0x54c8, 0x86e4, 0x94ea}}, + {"hai", // L"还海害咳氦孩骇咳骸亥嗨醢胲" + {0x8fd8, 0x6d77, 0x5bb3, 0x54b3, 0x6c26, 0x5b69, 0x9a87, 0x54b3, 0x9ab8, 0x4ea5, 0x55e8, + 0x91a2, 0x80f2}}, + {"han", // L"喊含汗寒汉旱酣韩焊涵函憨翰罕撼捍憾悍邯邗菡撖瀚顸蚶焓颔晗鼾" + {0x558a, 0x542b, 0x6c57, 0x5bd2, 0x6c49, 0x65f1, 0x9163, 0x97e9, 0x710a, 0x6db5, + 0x51fd, 0x61a8, 0x7ff0, 0x7f55, 0x64bc, 0x634d, 0x61be, 0x608d, 0x90af, 0x9097, + 0x83e1, 0x6496, 0x701a, 0x9878, 0x86b6, 0x7113, 0x9894, 0x6657, 0x9f3e}}, + {"hang", // L"行巷航夯杭吭颃沆绗" + {0x884c, 0x5df7, 0x822a, 0x592f, 0x676d, 0x542d, 0x9883, 0x6c86, 0x7ed7}}, + {"hao", // L"好号浩嚎壕郝毫豪耗貉镐昊颢灏嚆蚝嗥皓蒿濠薅" + {0x597d, 0x53f7, 0x6d69, 0x568e, 0x58d5, 0x90dd, 0x6beb, 0x8c6a, 0x8017, 0x8c89, 0x9550, + 0x660a, 0x98a2, 0x704f, 0x5686, 0x869d, 0x55e5, 0x7693, 0x84bf, 0x6fe0, 0x8585}}, + {"he", // L"和喝合河禾核何呵荷贺赫褐盒鹤菏貉阂涸吓嗬劾盍翮阖颌壑诃纥曷蚵" + {0x548c, 0x559d, 0x5408, 0x6cb3, 0x79be, 0x6838, 0x4f55, 0x5475, 0x8377, 0x8d3a, + 0x8d6b, 0x8910, 0x76d2, 0x9e64, 0x83cf, 0x8c89, 0x9602, 0x6db8, 0x5413, 0x55ec, + 0x52be, 0x76cd, 0x7fee, 0x9616, 0x988c, 0x58d1, 0x8bc3, 0x7ea5, 0x66f7, 0x86b5}}, + {"hei", // L"黑嘿" + {0x9ed1, 0x563f}}, + {"hen", // L"很狠恨痕" + {0x5f88, 0x72e0, 0x6068, 0x75d5}}, + {"heng", // L"横恒哼衡亨桁珩蘅" + {0x6a2a, 0x6052, 0x54fc, 0x8861, 0x4ea8, 0x6841, 0x73e9, 0x8605}}, + {"hong", // L"红轰哄虹洪宏烘鸿弘讧訇蕻闳薨黉荭泓" + {0x7ea2, 0x8f70, 0x54c4, 0x8679, 0x6d2a, 0x5b8f, 0x70d8, 0x9e3f, 0x5f18, 0x8ba7, 0x8a07, + 0x857b, 0x95f3, 0x85a8, 0x9ec9, 0x836d, 0x6cd3}}, + {"hou", // L"后厚吼喉侯候猴鲎篌堠後逅糇骺瘊" + {0x540e, 0x539a, 0x543c, 0x5589, 0x4faf, 0x5019, 0x7334, 0x9c8e, 0x7bcc, 0x5820, 0x5f8c, + 0x9005, 0x7cc7, 0x9aba, 0x760a}}, + {"hu", // L"湖户呼虎壶互胡护糊弧忽狐蝴葫沪乎核瑚唬鹕冱怙鹱笏戽扈鹘浒祜醐琥囫烀轷瓠煳斛鹄猢惚岵滹觳唿槲虍" + {0x6e56, 0x6237, 0x547c, 0x864e, 0x58f6, 0x4e92, 0x80e1, 0x62a4, 0x7cca, 0x5f27, + 0x5ffd, 0x72d0, 0x8774, 0x846b, 0x6caa, 0x4e4e, 0x6838, 0x745a, 0x552c, 0x9e55, + 0x51b1, 0x6019, 0x9e71, 0x7b0f, 0x623d, 0x6248, 0x9e58, 0x6d52, 0x795c, 0x9190, + 0x7425, 0x56eb, 0x70c0, 0x8f77, 0x74e0, 0x7173, 0x659b, 0x9e44, 0x7322, 0x60da, + 0x5cb5, 0x6ef9, 0x89f3, 0x553f, 0x69f2, 0x864d}}, + {"hua", // L"话花化画华划滑哗猾铧桦骅砉" + {0x8bdd, 0x82b1, 0x5316, 0x753b, 0x534e, 0x5212, 0x6ed1, 0x54d7, 0x733e, 0x94e7, 0x6866, + 0x9a85, 0x7809}}, + {"huai", // L"坏怀淮槐徊踝" + {0x574f, 0x6000, 0x6dee, 0x69d0, 0x5f8a, 0x8e1d}}, + {"huan", // L"换还唤环患缓欢幻宦涣焕豢桓痪漶獾擐逭鲩郇鬟寰奂锾圜洹萑缳浣垸" + {0x6362, 0x8fd8, 0x5524, 0x73af, 0x60a3, 0x7f13, 0x6b22, 0x5e7b, 0x5ba6, 0x6da3, + 0x7115, 0x8c62, 0x6853, 0x75ea, 0x6f36, 0x737e, 0x64d0, 0x902d, 0x9ca9, 0x90c7, + 0x9b1f, 0x5bf0, 0x5942, 0x953e, 0x571c, 0x6d39, 0x8411, 0x7f33, 0x6d63, 0x57b8}}, + {"huang", // L"黄慌晃荒簧凰皇谎惶蝗磺恍煌幌隍肓潢篁徨鳇遑癀湟蟥璜" + {0x9ec4, 0x614c, 0x6643, 0x8352, 0x7c27, 0x51f0, 0x7687, 0x8c0e, 0x60f6, + 0x8757, 0x78fa, 0x604d, 0x714c, 0x5e4c, 0x968d, 0x8093, 0x6f62, 0x7bc1, + 0x5fa8, 0x9cc7, 0x9051, 0x7640, 0x6e5f, 0x87e5, 0x749c}}, + {"hui", // L"回会灰绘挥汇辉毁悔惠晦徽恢秽慧贿蛔讳卉烩诲彗浍蕙喙恚哕晖隳麾诙蟪茴洄咴虺荟缋桧" + {0x56de, 0x4f1a, 0x7070, 0x7ed8, 0x6325, 0x6c47, 0x8f89, 0x6bc1, 0x6094, 0x60e0, + 0x6666, 0x5fbd, 0x6062, 0x79fd, 0x6167, 0x8d3f, 0x86d4, 0x8bb3, 0x5349, 0x70e9, + 0x8bf2, 0x5f57, 0x6d4d, 0x8559, 0x5599, 0x605a, 0x54d5, 0x6656, 0x96b3, 0x9ebe, + 0x8bd9, 0x87ea, 0x8334, 0x6d04, 0x54b4, 0x867a, 0x835f, 0x7f0b, 0x6867}}, + {"hun", // L"混昏荤浑婚魂阍珲馄溷诨" + {0x6df7, 0x660f, 0x8364, 0x6d51, 0x5a5a, 0x9b42, 0x960d, 0x73f2, 0x9984, 0x6eb7, 0x8be8}}, + {"huo", // L"或活火伙货和获祸豁霍惑嚯镬耠劐藿攉锪蠖钬夥" + {0x6216, 0x6d3b, 0x706b, 0x4f19, 0x8d27, 0x548c, 0x83b7, 0x7978, 0x8c41, 0x970d, 0x60d1, + 0x56af, 0x956c, 0x8020, 0x5290, 0x85ff, 0x6509, 0x952a, 0x8816, 0x94ac, 0x5925}}, + {"ji", // L"几及急既即机鸡积记级极计挤己季寄纪系基激吉脊际汲肌嫉姬绩缉饥迹棘蓟技冀辑伎祭剂悸济籍寂奇忌妓继集给击圾箕讥畸稽疾墼洎鲚屐齑戟鲫嵇矶稷戢虮笈暨笄剞叽蒺跻嵴掎跽霁唧畿荠瘠玑羁丌偈芨佶赍楫髻咭蕺觊麂骥殛岌亟犄乩芰哜彐萁藉" + {0x51e0, 0x53ca, 0x6025, 0x65e2, 0x5373, 0x673a, 0x9e21, 0x79ef, 0x8bb0, 0x7ea7, 0x6781, + 0x8ba1, 0x6324, 0x5df1, 0x5b63, 0x5bc4, 0x7eaa, 0x7cfb, 0x57fa, 0x6fc0, 0x5409, 0x810a, + 0x9645, 0x6c72, 0x808c, 0x5ac9, 0x59ec, 0x7ee9, 0x7f09, 0x9965, 0x8ff9, 0x68d8, 0x84df, + 0x6280, 0x5180, 0x8f91, 0x4f0e, 0x796d, 0x5242, 0x60b8, 0x6d4e, 0x7c4d, 0x5bc2, 0x5947, + 0x5fcc, 0x5993, 0x7ee7, 0x96c6, 0x7ed9, 0x51fb, 0x573e, 0x7b95, 0x8ba5, 0x7578, 0x7a3d, + 0x75be, 0x58bc, 0x6d0e, 0x9c9a, 0x5c50, 0x9f51, 0x621f, 0x9cab, 0x5d47, 0x77f6, 0x7a37, + 0x6222, 0x866e, 0x7b08, 0x66a8, 0x7b04, 0x525e, 0x53fd, 0x84ba, 0x8dfb, 0x5d74, 0x638e, + 0x8dfd, 0x9701, 0x5527, 0x757f, 0x8360, 0x7620, 0x7391, 0x7f81, 0x4e0c, 0x5048, 0x82a8, + 0x4f76, 0x8d4d, 0x696b, 0x9afb, 0x54ad, 0x857a, 0x89ca, 0x9e82, 0x9aa5, 0x6b9b, 0x5c8c, + 0x4e9f, 0x7284, 0x4e69, 0x82b0, 0x54dc, 0x5f50, 0x8401, 0x85c9}}, + {"jia", // L"家加假价架甲佳夹嘉驾嫁枷荚颊钾稼茄贾铗葭迦戛浃镓痂恝岬跏嘏伽胛笳珈瘕郏袈蛱袷铪" + {0x5bb6, 0x52a0, 0x5047, 0x4ef7, 0x67b6, 0x7532, 0x4f73, 0x5939, 0x5609, 0x9a7e, + 0x5ac1, 0x67b7, 0x835a, 0x988a, 0x94be, 0x7a3c, 0x8304, 0x8d3e, 0x94d7, 0x846d, + 0x8fe6, 0x621b, 0x6d43, 0x9553, 0x75c2, 0x605d, 0x5cac, 0x8dcf, 0x560f, 0x4f3d, + 0x80db, 0x7b33, 0x73c8, 0x7615, 0x90cf, 0x8888, 0x86f1, 0x88b7, 0x94ea}}, + {"jian", // L"见件减尖间键贱肩兼建检箭煎简剪歼监坚奸健艰荐剑渐溅涧鉴践捡柬笺俭碱硷拣舰槛缄茧饯翦鞯戋谏牮枧腱趼缣搛戬毽菅鲣笕谫楗囝蹇裥踺睑謇鹣蒹僭锏湔犍谮" + {0x89c1, 0x4ef6, 0x51cf, 0x5c16, 0x95f4, 0x952e, 0x8d31, 0x80a9, 0x517c, 0x5efa, + 0x68c0, 0x7bad, 0x714e, 0x7b80, 0x526a, 0x6b7c, 0x76d1, 0x575a, 0x5978, 0x5065, + 0x8270, 0x8350, 0x5251, 0x6e10, 0x6e85, 0x6da7, 0x9274, 0x8df5, 0x6361, 0x67ec, + 0x7b3a, 0x4fed, 0x78b1, 0x7877, 0x62e3, 0x8230, 0x69db, 0x7f04, 0x8327, 0x996f, + 0x7fe6, 0x97af, 0x620b, 0x8c0f, 0x726e, 0x67a7, 0x8171, 0x8dbc, 0x7f23, 0x641b, + 0x622c, 0x6bfd, 0x83c5, 0x9ca3, 0x7b15, 0x8c2b, 0x6957, 0x56dd, 0x8e47, 0x88e5, + 0x8e3a, 0x7751, 0x8b07, 0x9e63, 0x84b9, 0x50ed, 0x950f, 0x6e54, 0x728d, 0x8c2e}}, + {"jiang", // L"将讲江奖降浆僵姜酱蒋疆匠强桨虹豇礓缰犟耩绛茳糨洚" + {0x5c06, 0x8bb2, 0x6c5f, 0x5956, 0x964d, 0x6d46, 0x50f5, 0x59dc, + 0x9171, 0x848b, 0x7586, 0x5320, 0x5f3a, 0x6868, 0x8679, 0x8c47, + 0x7913, 0x7f30, 0x729f, 0x8029, 0x7edb, 0x8333, 0x7ce8, 0x6d1a}}, + {"jiao", // L"叫脚交角教较缴觉焦胶娇绞校搅骄狡浇矫郊嚼蕉轿窖椒礁饺铰酵侥剿徼艽僬蛟敫峤跤姣皎茭鹪噍醮佼鲛挢" + {0x53eb, 0x811a, 0x4ea4, 0x89d2, 0x6559, 0x8f83, 0x7f34, 0x89c9, 0x7126, 0x80f6, + 0x5a07, 0x7ede, 0x6821, 0x6405, 0x9a84, 0x72e1, 0x6d47, 0x77eb, 0x90ca, 0x56bc, + 0x8549, 0x8f7f, 0x7a96, 0x6912, 0x7901, 0x997a, 0x94f0, 0x9175, 0x4fa5, 0x527f, + 0x5fbc, 0x827d, 0x50ec, 0x86df, 0x656b, 0x5ce4, 0x8de4, 0x59e3, 0x768e, 0x832d, + 0x9e6a, 0x564d, 0x91ae, 0x4f7c, 0x9c9b, 0x6322}}, + {"jie", // L"接节街借皆截解界结届姐揭戒介阶劫芥竭洁疥藉秸桔杰捷诫睫偈桀喈拮骱羯蚧嗟颉鲒婕碣讦孑疖诘卩锴" + {0x63a5, 0x8282, 0x8857, 0x501f, 0x7686, 0x622a, 0x89e3, 0x754c, 0x7ed3, + 0x5c4a, 0x59d0, 0x63ed, 0x6212, 0x4ecb, 0x9636, 0x52ab, 0x82a5, 0x7aed, + 0x6d01, 0x75a5, 0x85c9, 0x79f8, 0x6854, 0x6770, 0x6377, 0x8beb, 0x776b, + 0x5048, 0x6840, 0x5588, 0x62ee, 0x9ab1, 0x7faf, 0x86a7, 0x55df, 0x9889, + 0x9c92, 0x5a55, 0x78a3, 0x8ba6, 0x5b51, 0x7596, 0x8bd8, 0x5369, 0x9534}}, + {"jin", // L"进近今仅紧金斤尽劲禁浸锦晋筋津谨巾襟烬靳廑瑾馑槿衿堇荩矜噤缙卺妗赆觐钅" + {0x8fdb, 0x8fd1, 0x4eca, 0x4ec5, 0x7d27, 0x91d1, 0x65a4, 0x5c3d, 0x52b2, + 0x7981, 0x6d78, 0x9526, 0x664b, 0x7b4b, 0x6d25, 0x8c28, 0x5dfe, 0x895f, + 0x70ec, 0x9773, 0x5ed1, 0x747e, 0x9991, 0x69ff, 0x887f, 0x5807, 0x8369, + 0x77dc, 0x5664, 0x7f19, 0x537a, 0x5997, 0x8d46, 0x89d0, 0x9485}}, + {"jing", // L"竟静井惊经镜京净敬精景警竞境径荆晶鲸粳颈兢茎睛劲痉靖肼獍阱腈弪刭憬婧胫菁儆旌迳靓泾陉" + {0x7adf, 0x9759, 0x4e95, 0x60ca, 0x7ecf, 0x955c, 0x4eac, 0x51c0, 0x656c, 0x7cbe, 0x666f, + 0x8b66, 0x7ade, 0x5883, 0x5f84, 0x8346, 0x6676, 0x9cb8, 0x7cb3, 0x9888, 0x5162, 0x830e, + 0x775b, 0x52b2, 0x75c9, 0x9756, 0x80bc, 0x734d, 0x9631, 0x8148, 0x5f2a, 0x522d, 0x61ac, + 0x5a67, 0x80eb, 0x83c1, 0x5106, 0x65cc, 0x8ff3, 0x9753, 0x6cfe, 0x9649}}, + {"jiong", // L"窘炯扃迥冂" + {0x7a98, 0x70af, 0x6243, 0x8fe5, 0x5182}}, + {"jiu", // L"就九酒旧久揪救纠舅究韭厩臼玖灸咎疚赳鹫僦柩桕鬏鸠阄啾" + {0x5c31, 0x4e5d, 0x9152, 0x65e7, 0x4e45, 0x63ea, 0x6551, 0x7ea0, 0x8205, + 0x7a76, 0x97ed, 0x53a9, 0x81fc, 0x7396, 0x7078, 0x548e, 0x759a, 0x8d73, + 0x9e6b, 0x50e6, 0x67e9, 0x6855, 0x9b0f, 0x9e20, 0x9604, 0x557e}}, + {"ju", // L"句举巨局具距锯剧居聚拘菊矩沮拒惧鞠狙驹据柜俱车咀疽踞炬倨醵裾屦犋苴窭飓锔椐苣琚掬榘龃趄莒雎遽橘踽榉鞫钜讵枸瞿蘧" + {0x53e5, 0x4e3e, 0x5de8, 0x5c40, 0x5177, 0x8ddd, 0x952f, 0x5267, 0x5c45, 0x805a, 0x62d8, + 0x83ca, 0x77e9, 0x6cae, 0x62d2, 0x60e7, 0x97a0, 0x72d9, 0x9a79, 0x636e, 0x67dc, 0x4ff1, + 0x8f66, 0x5480, 0x75bd, 0x8e1e, 0x70ac, 0x5028, 0x91b5, 0x88fe, 0x5c66, 0x728b, 0x82f4, + 0x7aad, 0x98d3, 0x9514, 0x6910, 0x82e3, 0x741a, 0x63ac, 0x6998, 0x9f83, 0x8d84, 0x8392, + 0x96ce, 0x907d, 0x6a58, 0x8e3d, 0x6989, 0x97ab, 0x949c, 0x8bb5, 0x67b8, 0x77bf, 0x8627}}, + {"juan", // L"卷圈倦鹃捐娟眷绢鄄锩蠲镌狷桊涓隽" + {0x5377, 0x5708, 0x5026, 0x9e43, 0x6350, 0x5a1f, 0x7737, 0x7ee2, 0x9104, 0x9529, 0x8832, + 0x954c, 0x72f7, 0x684a, 0x6d93, 0x96bd}}, + {"jue", // L"决绝觉角爵掘诀撅倔抉攫嚼脚桷橛觖劂爝矍镢獗珏崛蕨噘谲蹶孓厥阙" + {0x51b3, 0x7edd, 0x89c9, 0x89d2, 0x7235, 0x6398, 0x8bc0, 0x6485, 0x5014, 0x6289, + 0x652b, 0x56bc, 0x811a, 0x6877, 0x6a5b, 0x89d6, 0x5282, 0x721d, 0x77cd, 0x9562, + 0x7357, 0x73cf, 0x5d1b, 0x8568, 0x5658, 0x8c32, 0x8e76, 0x5b53, 0x53a5, 0x9619}}, + {"jun", // L"军君均菌俊峻龟竣骏钧浚郡筠麇皲捃" + {0x519b, 0x541b, 0x5747, 0x83cc, 0x4fca, 0x5cfb, 0x9f9f, 0x7ae3, 0x9a8f, 0x94a7, 0x6d5a, + 0x90e1, 0x7b60, 0x9e87, 0x76b2, 0x6343}}, + {"ka", // L"卡喀咯咖胩咔佧" + {0x5361, 0x5580, 0x54af, 0x5496, 0x80e9, 0x5494, 0x4f67}}, + {"kai", // L"开揩凯慨楷垲剀锎铠锴忾恺蒈" + {0x5f00, 0x63e9, 0x51ef, 0x6168, 0x6977, 0x57b2, 0x5240, 0x950e, 0x94e0, 0x9534, 0x5ffe, + 0x607a, 0x8488}}, + {"kan", // L"看砍堪刊坎槛勘龛戡侃瞰莰阚凵" + {0x770b, 0x780d, 0x582a, 0x520a, 0x574e, 0x69db, 0x52d8, 0x9f9b, 0x6221, 0x4f83, 0x77b0, + 0x83b0, 0x961a, 0x51f5}}, + {"kang", // L"抗炕扛糠康慷亢钪闶伉" + {0x6297, 0x7095, 0x625b, 0x7ce0, 0x5eb7, 0x6177, 0x4ea2, 0x94aa, 0x95f6, 0x4f09}}, + {"kao", // L"靠考烤拷栲犒尻铐" + {0x9760, 0x8003, 0x70e4, 0x62f7, 0x6832, 0x7292, 0x5c3b, 0x94d0}}, + {"ke", // L"可克棵科颗刻课客壳渴苛柯磕咳坷呵恪岢蝌缂蚵轲窠钶氪颏瞌锞稞珂髁疴嗑溘骒铪" + {0x53ef, 0x514b, 0x68f5, 0x79d1, 0x9897, 0x523b, 0x8bfe, 0x5ba2, 0x58f3, + 0x6e34, 0x82db, 0x67ef, 0x78d5, 0x54b3, 0x5777, 0x5475, 0x606a, 0x5ca2, + 0x874c, 0x7f02, 0x86b5, 0x8f72, 0x7aa0, 0x94b6, 0x6c2a, 0x988f, 0x778c, + 0x951e, 0x7a1e, 0x73c2, 0x9ac1, 0x75b4, 0x55d1, 0x6e98, 0x9a92, 0x94ea}}, + {"ken", // L"肯啃恳垦裉" + {0x80af, 0x5543, 0x6073, 0x57a6, 0x88c9}}, + {"keng", // L"坑吭铿胫铒" + {0x5751, 0x542d, 0x94ff, 0x80eb, 0x94d2}}, + {"kong", // L"空孔控恐倥崆箜" + {0x7a7a, 0x5b54, 0x63a7, 0x6050, 0x5025, 0x5d06, 0x7b9c}}, + {"kou", // L"口扣抠寇蔻芤眍筘叩" + {0x53e3, 0x6263, 0x62a0, 0x5bc7, 0x853b, 0x82a4, 0x770d, 0x7b58, 0x53e9}}, + {"ku", // L"哭库苦枯裤窟酷刳骷喾堀绔" + {0x54ed, 0x5e93, 0x82e6, 0x67af, 0x88e4, 0x7a9f, 0x9177, 0x5233, 0x9ab7, 0x55be, 0x5800, + 0x7ed4}}, + {"kua", // L"跨垮挎夸胯侉锞" + {0x8de8, 0x57ae, 0x630e, 0x5938, 0x80ef, 0x4f89, 0x951e}}, + {"kuai", // L"快块筷会侩哙蒯郐狯脍" + {0x5feb, 0x5757, 0x7b77, 0x4f1a, 0x4fa9, 0x54d9, 0x84af, 0x90d0, 0x72ef, 0x810d}}, + {"kuan", // L"宽款髋" + {0x5bbd, 0x6b3e, 0x9acb}}, + {"kuang", // L"矿筐狂框况旷匡眶诳邝纩夼诓圹贶哐" + {0x77ff, 0x7b50, 0x72c2, 0x6846, 0x51b5, 0x65f7, 0x5321, 0x7736, 0x8bf3, 0x909d, 0x7ea9, + 0x593c, 0x8bd3, 0x5739, 0x8d36, 0x54d0}}, + {"kui", // L"亏愧奎窥溃葵魁馈盔傀岿匮愦揆睽跬聩篑喹逵暌蒉悝喟馗蝰隗夔" + {0x4e8f, 0x6127, 0x594e, 0x7aa5, 0x6e83, 0x8475, 0x9b41, 0x9988, 0x76d4, 0x5080, + 0x5cbf, 0x532e, 0x6126, 0x63c6, 0x777d, 0x8dec, 0x8069, 0x7bd1, 0x55b9, 0x9035, + 0x668c, 0x8489, 0x609d, 0x559f, 0x9997, 0x8770, 0x9697, 0x5914}}, + {"kun", // L"捆困昆坤鲲锟髡琨醌阃悃顽" + {0x6346, 0x56f0, 0x6606, 0x5764, 0x9cb2, 0x951f, 0x9ae1, 0x7428, 0x918c, 0x9603, 0x6083, + 0x987d}}, + {"kuo", // L"阔扩括廓蛞" + {0x9614, 0x6269, 0x62ec, 0x5ed3, 0x86de}}, + {"la", // L"拉啦辣蜡腊喇垃落瘌邋砬剌旯" + {0x62c9, 0x5566, 0x8fa3, 0x8721, 0x814a, 0x5587, 0x5783, 0x843d, 0x760c, 0x908b, 0x782c, + 0x524c, 0x65ef}}, + {"lai", // L"来赖莱濑赉崃涞铼籁徕癞睐" + {0x6765, 0x8d56, 0x83b1, 0x6fd1, 0x8d49, 0x5d03, 0x6d9e, 0x94fc, 0x7c41, 0x5f95, 0x765e, + 0x7750}}, + {"lan", // L"蓝兰烂拦篮懒栏揽缆滥阑谰婪澜览榄岚褴镧斓罱漤" + {0x84dd, 0x5170, 0x70c2, 0x62e6, 0x7bee, 0x61d2, 0x680f, 0x63fd, 0x7f06, 0x6ee5, 0x9611, + 0x8c30, 0x5a6a, 0x6f9c, 0x89c8, 0x6984, 0x5c9a, 0x8934, 0x9567, 0x6593, 0x7f71, 0x6f24}}, + {"lang", // L"浪狼廊郎朗榔琅稂螂莨啷锒阆蒗" + {0x6d6a, 0x72fc, 0x5eca, 0x90ce, 0x6717, 0x6994, 0x7405, 0x7a02, 0x8782, 0x83a8, 0x5577, + 0x9512, 0x9606, 0x8497}}, + {"lao", // L"老捞牢劳烙涝落姥酪络佬耢铹醪铑唠栳崂痨" + {0x8001, 0x635e, 0x7262, 0x52b3, 0x70d9, 0x6d9d, 0x843d, 0x59e5, 0x916a, 0x7edc, 0x4f6c, + 0x8022, 0x94f9, 0x91aa, 0x94d1, 0x5520, 0x6833, 0x5d02, 0x75e8}}, + {"le", // L"了乐勒鳓仂叻泐" + {0x4e86, 0x4e50, 0x52d2, 0x9cd3, 0x4ec2, 0x53fb, 0x6cd0}}, + {"lei", // L"类累泪雷垒勒擂蕾肋镭儡磊缧诔耒酹羸嫘檑嘞" + {0x7c7b, 0x7d2f, 0x6cea, 0x96f7, 0x5792, 0x52d2, 0x64c2, 0x857e, 0x808b, 0x956d, + 0x5121, 0x78ca, 0x7f27, 0x8bd4, 0x8012, 0x9179, 0x7fb8, 0x5ad8, 0x6a91, 0x561e}}, + {"leng", // L"冷棱楞愣塄" + {0x51b7, 0x68f1, 0x695e, 0x6123, 0x5844}}, + {"li", // L"里离力立李例哩理利梨厘礼历丽吏砾漓莉傈荔俐痢狸粒沥隶栗璃鲤厉励犁黎篱郦鹂笠坜苈鳢缡跞蜊锂澧粝蓠枥蠡鬲呖砺嫠篥疠疬猁藜溧鲡戾栎唳醴轹詈骊罹逦俪喱雳黧莅俚蛎娌砬" + {0x91cc, 0x79bb, 0x529b, 0x7acb, 0x674e, 0x4f8b, 0x54e9, 0x7406, 0x5229, 0x68a8, + 0x5398, 0x793c, 0x5386, 0x4e3d, 0x540f, 0x783e, 0x6f13, 0x8389, 0x5088, 0x8354, + 0x4fd0, 0x75e2, 0x72f8, 0x7c92, 0x6ca5, 0x96b6, 0x6817, 0x7483, 0x9ca4, 0x5389, + 0x52b1, 0x7281, 0x9ece, 0x7bf1, 0x90e6, 0x9e42, 0x7b20, 0x575c, 0x82c8, 0x9ce2, + 0x7f21, 0x8dde, 0x870a, 0x9502, 0x6fa7, 0x7c9d, 0x84e0, 0x67a5, 0x8821, 0x9b32, + 0x5456, 0x783a, 0x5ae0, 0x7be5, 0x75a0, 0x75ac, 0x7301, 0x85dc, 0x6ea7, 0x9ca1, + 0x623e, 0x680e, 0x5533, 0x91b4, 0x8f79, 0x8a48, 0x9a8a, 0x7f79, 0x9026, 0x4fea, + 0x55b1, 0x96f3, 0x9ee7, 0x8385, 0x4fda, 0x86ce, 0x5a0c, 0x782c}}, + {"lia", // L"俩" + {0x4fe9}}, + {"lian", // L"连联练莲恋脸炼链敛怜廉帘镰涟蠊琏殓蔹鲢奁潋臁裢濂裣楝" + {0x8fde, 0x8054, 0x7ec3, 0x83b2, 0x604b, 0x8138, 0x70bc, 0x94fe, 0x655b, + 0x601c, 0x5ec9, 0x5e18, 0x9570, 0x6d9f, 0x880a, 0x740f, 0x6b93, 0x8539, + 0x9ca2, 0x5941, 0x6f4b, 0x81c1, 0x88e2, 0x6fc2, 0x88e3, 0x695d}}, + {"liang", // L"两亮辆凉粮梁量良晾谅俩粱墚踉椋魉莨" + {0x4e24, 0x4eae, 0x8f86, 0x51c9, 0x7cae, 0x6881, 0x91cf, 0x826f, 0x667e, 0x8c05, 0x4fe9, + 0x7cb1, 0x589a, 0x8e09, 0x690b, 0x9b49, 0x83a8}}, + {"liao", // L"了料撩聊撂疗廖燎辽僚寥镣潦钌蓼尥寮缭獠鹩嘹" + {0x4e86, 0x6599, 0x64a9, 0x804a, 0x6482, 0x7597, 0x5ed6, 0x71ce, 0x8fbd, 0x50da, 0x5be5, + 0x9563, 0x6f66, 0x948c, 0x84fc, 0x5c25, 0x5bee, 0x7f2d, 0x7360, 0x9e69, 0x5639}}, + {"lie", // L"列裂猎劣烈咧埒捩鬣趔躐冽洌" + {0x5217, 0x88c2, 0x730e, 0x52a3, 0x70c8, 0x54a7, 0x57d2, 0x6369, 0x9b23, 0x8d94, 0x8e90, + 0x51bd, 0x6d0c}}, + {"lin", // L"林临淋邻磷鳞赁吝拎琳霖凛遴嶙蔺粼麟躏辚廪懔瞵檩膦啉" + {0x6797, 0x4e34, 0x6dcb, 0x90bb, 0x78f7, 0x9cde, 0x8d41, 0x541d, 0x62ce, + 0x7433, 0x9716, 0x51db, 0x9074, 0x5d99, 0x853a, 0x7cbc, 0x9e9f, 0x8e8f, + 0x8f9a, 0x5eea, 0x61d4, 0x77b5, 0x6aa9, 0x81a6, 0x5549}}, + {"ling", // L"另令领零铃玲灵岭龄凌陵菱伶羚棱翎蛉苓绫瓴酃呤泠棂柃鲮聆囹" + {0x53e6, 0x4ee4, 0x9886, 0x96f6, 0x94c3, 0x73b2, 0x7075, 0x5cad, 0x9f84, 0x51cc, + 0x9675, 0x83f1, 0x4f36, 0x7f9a, 0x68f1, 0x7fce, 0x86c9, 0x82d3, 0x7eeb, 0x74f4, + 0x9143, 0x5464, 0x6ce0, 0x68c2, 0x67c3, 0x9cae, 0x8046, 0x56f9}}, + {"liu", // L"六流留刘柳溜硫瘤榴琉馏碌陆绺锍鎏镏浏骝旒鹨熘遛" + {0x516d, 0x6d41, 0x7559, 0x5218, 0x67f3, 0x6e9c, 0x786b, 0x7624, + 0x69b4, 0x7409, 0x998f, 0x788c, 0x9646, 0x7efa, 0x950d, 0x938f, + 0x954f, 0x6d4f, 0x9a9d, 0x65d2, 0x9e68, 0x7198, 0x905b}}, + {"lo", // L"咯" + {0x54af}}, + {"long", // L"龙拢笼聋隆垄弄咙窿陇垅胧珑茏泷栊癃砻" + {0x9f99, 0x62e2, 0x7b3c, 0x804b, 0x9686, 0x5784, 0x5f04, 0x5499, 0x7abf, 0x9647, 0x5785, + 0x80e7, 0x73d1, 0x830f, 0x6cf7, 0x680a, 0x7643, 0x783b}}, + {"lou", // L"楼搂漏陋露娄篓偻蝼镂蒌耧髅喽瘘嵝" + {0x697c, 0x6402, 0x6f0f, 0x964b, 0x9732, 0x5a04, 0x7bd3, 0x507b, 0x877c, 0x9542, 0x848c, + 0x8027, 0x9ac5, 0x55bd, 0x7618, 0x5d5d}}, + {"lu", // L"路露录鹿陆炉卢鲁卤芦颅庐碌掳绿虏赂戮潞禄麓六鲈栌渌逯泸轳氇簏橹辂垆胪噜镥辘漉撸璐鸬鹭舻" + {0x8def, 0x9732, 0x5f55, 0x9e7f, 0x9646, 0x7089, 0x5362, 0x9c81, 0x5364, 0x82a6, 0x9885, + 0x5e90, 0x788c, 0x63b3, 0x7eff, 0x864f, 0x8d42, 0x622e, 0x6f5e, 0x7984, 0x9e93, 0x516d, + 0x9c88, 0x680c, 0x6e0c, 0x902f, 0x6cf8, 0x8f73, 0x6c07, 0x7c0f, 0x6a79, 0x8f82, 0x5786, + 0x80ea, 0x565c, 0x9565, 0x8f98, 0x6f09, 0x64b8, 0x7490, 0x9e2c, 0x9e6d, 0x823b}}, + {"luan", // L"乱卵滦峦孪挛栾銮脔娈鸾" + {0x4e71, 0x5375, 0x6ee6, 0x5ce6, 0x5b6a, 0x631b, 0x683e, 0x92ae, 0x8114, 0x5a08, 0x9e3e}}, + {"lue", // L"略掠锊" + {0x7565, 0x63a0, 0x950a}}, + {"lun", // L"论轮抡伦沦仑纶囵" + {0x8bba, 0x8f6e, 0x62a1, 0x4f26, 0x6ca6, 0x4ed1, 0x7eb6, 0x56f5}}, + {"luo", // L"落罗锣裸骡烙箩螺萝洛骆逻络咯荦漯蠃雒倮硌椤捋脶瘰摞泺珞镙猡铬" + {0x843d, 0x7f57, 0x9523, 0x88f8, 0x9aa1, 0x70d9, 0x7ba9, 0x87ba, 0x841d, 0x6d1b, + 0x9a86, 0x903b, 0x7edc, 0x54af, 0x8366, 0x6f2f, 0x8803, 0x96d2, 0x502e, 0x784c, + 0x6924, 0x634b, 0x8136, 0x7630, 0x645e, 0x6cfa, 0x73de, 0x9559, 0x7321, 0x94ec}}, + {"lv", // L"绿率铝驴旅屡滤吕律氯缕侣虑履偻膂榈闾捋褛稆" + {0x7eff, 0x7387, 0x94dd, 0x9a74, 0x65c5, 0x5c61, 0x6ee4, 0x5415, 0x5f8b, 0x6c2f, 0x7f15, + 0x4fa3, 0x8651, 0x5c65, 0x507b, 0x8182, 0x6988, 0x95fe, 0x634b, 0x891b, 0x7a06}}, + {"lve", // L"略掠锊" + {0x7565, 0x63a0, 0x950a}}, + {"m", // L"呒" + {0x5452}}, + {"ma", // L"吗妈马嘛麻骂抹码玛蚂摩唛蟆犸嬷杩" + {0x5417, 0x5988, 0x9a6c, 0x561b, 0x9ebb, 0x9a82, 0x62b9, 0x7801, 0x739b, 0x8682, 0x6469, + 0x551b, 0x87c6, 0x72b8, 0x5b37, 0x6769}}, + {"mai", // L"买卖迈埋麦脉劢霾荬" + {0x4e70, 0x5356, 0x8fc8, 0x57cb, 0x9ea6, 0x8109, 0x52a2, 0x973e, 0x836c}}, + {"man", // L"满慢瞒漫蛮蔓曼馒埋谩幔鳗墁螨镘颟鞔缦熳" + {0x6ee1, 0x6162, 0x7792, 0x6f2b, 0x86ee, 0x8513, 0x66fc, 0x9992, 0x57cb, 0x8c29, 0x5e54, + 0x9cd7, 0x5881, 0x87a8, 0x9558, 0x989f, 0x9794, 0x7f26, 0x71b3}}, + {"mang", // L"忙芒盲莽茫氓硭邙蟒漭" + {0x5fd9, 0x8292, 0x76f2, 0x83bd, 0x832b, 0x6c13, 0x786d, 0x9099, 0x87d2, 0x6f2d}}, + {"mao", // L"毛冒帽猫矛卯貌茂贸铆锚茅耄茆瑁蝥髦懋昴牦瞀峁袤蟊旄泖" + {0x6bdb, 0x5192, 0x5e3d, 0x732b, 0x77db, 0x536f, 0x8c8c, 0x8302, 0x8d38, + 0x94c6, 0x951a, 0x8305, 0x8004, 0x8306, 0x7441, 0x8765, 0x9ae6, 0x61cb, + 0x6634, 0x7266, 0x7780, 0x5cc1, 0x88a4, 0x87ca, 0x65c4, 0x6cd6}}, + {"me", // L"么" + {0x4e48}}, + {"mei", // L"没每煤镁美酶妹枚霉玫眉梅寐昧媒媚嵋猸袂湄浼鹛莓魅镅楣" + {0x6ca1, 0x6bcf, 0x7164, 0x9541, 0x7f8e, 0x9176, 0x59b9, 0x679a, 0x9709, + 0x73ab, 0x7709, 0x6885, 0x5bd0, 0x6627, 0x5a92, 0x5a9a, 0x5d4b, 0x7338, + 0x8882, 0x6e44, 0x6d7c, 0x9e5b, 0x8393, 0x9b45, 0x9545, 0x6963}}, + {"men", // L"门们闷懑扪钔焖" + {0x95e8, 0x4eec, 0x95f7, 0x61d1, 0x626a, 0x9494, 0x7116}}, + {"meng", // L"猛梦蒙锰孟盟檬萌礞蜢勐懵甍蠓虻朦艋艨瞢" + {0x731b, 0x68a6, 0x8499, 0x9530, 0x5b5f, 0x76df, 0x6aac, 0x840c, 0x791e, 0x8722, 0x52d0, + 0x61f5, 0x750d, 0x8813, 0x867b, 0x6726, 0x824b, 0x8268, 0x77a2}}, + {"mi", // L"米密迷眯蜜谜觅秘弥幂靡糜泌醚蘼縻咪汨麋祢猕弭谧芈脒宓敉嘧糸冖" + {0x7c73, 0x5bc6, 0x8ff7, 0x772f, 0x871c, 0x8c1c, 0x89c5, 0x79d8, 0x5f25, 0x5e42, + 0x9761, 0x7cdc, 0x6ccc, 0x919a, 0x863c, 0x7e3b, 0x54aa, 0x6c68, 0x9e8b, 0x7962, + 0x7315, 0x5f2d, 0x8c27, 0x8288, 0x8112, 0x5b93, 0x6549, 0x5627, 0x7cf8, 0x5196}}, + {"mian", // L"面棉免绵眠缅勉冕娩腼湎眄沔渑宀" + {0x9762, 0x68c9, 0x514d, 0x7ef5, 0x7720, 0x7f05, 0x52c9, 0x5195, 0x5a29, 0x817c, 0x6e4e, + 0x7704, 0x6c94, 0x6e11, 0x5b80}}, + {"miao", // L"秒苗庙妙描瞄藐渺眇缪缈淼喵杪鹋邈" + {0x79d2, 0x82d7, 0x5e99, 0x5999, 0x63cf, 0x7784, 0x85d0, 0x6e3a, 0x7707, 0x7f2a, 0x7f08, + 0x6dfc, 0x55b5, 0x676a, 0x9e4b, 0x9088}}, + {"mie", // L"灭蔑咩篾蠛乜" + {0x706d, 0x8511, 0x54a9, 0x7bfe, 0x881b, 0x4e5c}}, + {"min", // L"民抿敏闽皿悯珉愍缗闵玟苠泯黾鳘岷" + {0x6c11, 0x62bf, 0x654f, 0x95fd, 0x76bf, 0x60af, 0x73c9, 0x610d, 0x7f17, 0x95f5, 0x739f, + 0x82e0, 0x6cef, 0x9efe, 0x9cd8, 0x5cb7}}, + {"ming", // L"名明命鸣铭螟冥瞑暝茗溟酩" + {0x540d, 0x660e, 0x547d, 0x9e23, 0x94ed, 0x879f, 0x51a5, 0x7791, 0x669d, 0x8317, 0x6e9f, + 0x9169}}, + {"miu", // L"谬缪" + {0x8c2c, 0x7f2a}}, + {"mo", // L"摸磨抹末膜墨没莫默魔模摩摹漠陌蘑脉沫万寞秣瘼殁镆嫫谟蓦貊貘麽茉馍耱" + {0x6478, 0x78e8, 0x62b9, 0x672b, 0x819c, 0x58a8, 0x6ca1, 0x83ab, 0x9ed8, 0x9b54, 0x6a21, + 0x6469, 0x6479, 0x6f20, 0x964c, 0x8611, 0x8109, 0x6cab, 0x4e07, 0x5bde, 0x79e3, 0x763c, + 0x6b81, 0x9546, 0x5aeb, 0x8c1f, 0x84e6, 0x8c8a, 0x8c98, 0x9ebd, 0x8309, 0x998d, 0x8031}}, + {"mou", // L"某谋牟眸蛑鍪侔缪哞" + {0x67d0, 0x8c0b, 0x725f, 0x7738, 0x86d1, 0x936a, 0x4f94, 0x7f2a, 0x54de}}, + {"mu", // L"木母亩幕目墓牧牟模穆暮牡拇募慕睦姆钼毪坶沐仫苜" + {0x6728, 0x6bcd, 0x4ea9, 0x5e55, 0x76ee, 0x5893, 0x7267, 0x725f, + 0x6a21, 0x7a46, 0x66ae, 0x7261, 0x62c7, 0x52df, 0x6155, 0x7766, + 0x59c6, 0x94bc, 0x6bea, 0x5776, 0x6c90, 0x4eeb, 0x82dc}}, + {"na", // L"那拿哪纳钠娜呐衲捺镎肭" + {0x90a3, 0x62ff, 0x54ea, 0x7eb3, 0x94a0, 0x5a1c, 0x5450, 0x8872, 0x637a, 0x954e, 0x80ad}}, + {"nai", // L"乃耐奶奈氖萘艿柰鼐佴" + {0x4e43, 0x8010, 0x5976, 0x5948, 0x6c16, 0x8418, 0x827f, 0x67f0, 0x9f10, 0x4f74}}, + {"nan", // L"难南男赧囡蝻楠喃腩" + {0x96be, 0x5357, 0x7537, 0x8d67, 0x56e1, 0x877b, 0x6960, 0x5583, 0x8169}}, + {"nang", // L"囊馕曩囔攮" + {0x56ca, 0x9995, 0x66e9, 0x56d4, 0x652e}}, + {"nao", // L"闹脑恼挠淖孬铙瑙垴呶蛲猱硇" + {0x95f9, 0x8111, 0x607c, 0x6320, 0x6dd6, 0x5b6c, 0x94d9, 0x7459, 0x57b4, 0x5476, 0x86f2, + 0x7331, 0x7847}}, + {"ne", // L"呢哪讷" + {0x5462, 0x54ea, 0x8bb7}}, + {"nei", // L"内馁" + {0x5185, 0x9981}}, + {"nen", // L"嫩恁" + {0x5ae9, 0x6041}}, + {"neng", // L"能" + {0x80fd}}, + {"ni", // L"你泥拟腻逆呢溺倪尼匿妮霓铌昵坭祢猊伲怩鲵睨旎慝" + {0x4f60, 0x6ce5, 0x62df, 0x817b, 0x9006, 0x5462, 0x6eba, 0x502a, + 0x5c3c, 0x533f, 0x59ae, 0x9713, 0x94cc, 0x6635, 0x576d, 0x7962, + 0x730a, 0x4f32, 0x6029, 0x9cb5, 0x7768, 0x65ce, 0x615d}}, + {"nian", // L"年念捻撵拈碾蔫廿黏辇鲇鲶埝" + {0x5e74, 0x5ff5, 0x637b, 0x64b5, 0x62c8, 0x78be, 0x852b, 0x5eff, 0x9ecf, 0x8f87, 0x9c87, + 0x9cb6, 0x57dd}}, + {"niang", // L"娘酿" + {0x5a18, 0x917f}}, + {"niao", // L"鸟尿袅茑脲嬲" + {0x9e1f, 0x5c3f, 0x8885, 0x8311, 0x8132, 0x5b32}}, + {"nie", // L"捏镍聂孽涅镊啮陧蘖嗫臬蹑颞乜" + {0x634f, 0x954d, 0x8042, 0x5b7d, 0x6d85, 0x954a, 0x556e, 0x9667, 0x8616, 0x55eb, 0x81ec, + 0x8e51, 0x989e, 0x4e5c}}, + {"nin", // L"您" + {0x60a8}}, + {"ning", // L"拧凝宁柠狞泞佞甯咛聍" + {0x62e7, 0x51dd, 0x5b81, 0x67e0, 0x72de, 0x6cde, 0x4f5e, 0x752f, 0x549b, 0x804d}}, + {"niu", // L"牛扭纽钮拗妞狃忸" + {0x725b, 0x626d, 0x7ebd, 0x94ae, 0x62d7, 0x599e, 0x72c3, 0x5ff8}}, + {"nong", // L"弄浓农脓哝侬" + {0x5f04, 0x6d53, 0x519c, 0x8113, 0x54dd, 0x4fac}}, + {"nou", // L"耨" + {0x8028}}, + {"nu", // L"怒努奴孥胬驽弩" + {0x6012, 0x52aa, 0x5974, 0x5b65, 0x80ec, 0x9a7d, 0x5f29}}, + {"nuan", // L"暖" + {0x6696}}, + {"nue", // L"虐疟" + {0x8650, 0x759f}}, + {"nuo", // L"挪诺懦糯娜喏傩锘搦" + {0x632a, 0x8bfa, 0x61e6, 0x7cef, 0x5a1c, 0x558f, 0x50a9, 0x9518, 0x6426}}, + {"nv", // L"女衄钕恧" + {0x5973, 0x8844, 0x9495, 0x6067}}, + {"nve", // L"虐疟" + {0x8650, 0x759f}}, + {"o", // L"哦喔噢" + {0x54e6, 0x5594, 0x5662}}, + {"ou", // L"偶呕欧藕鸥区沤殴怄瓯讴耦" + {0x5076, 0x5455, 0x6b27, 0x85d5, 0x9e25, 0x533a, 0x6ca4, 0x6bb4, 0x6004, 0x74ef, 0x8bb4, + 0x8026}}, + {"pa", // L"怕爬趴啪耙扒帕琶筢杷葩" + {0x6015, 0x722c, 0x8db4, 0x556a, 0x8019, 0x6252, 0x5e15, 0x7436, 0x7b62, 0x6777, 0x8469}}, + {"pai", // L"派排拍牌迫徘湃哌俳蒎" + {0x6d3e, 0x6392, 0x62cd, 0x724c, 0x8feb, 0x5f98, 0x6e43, 0x54cc, 0x4ff3, 0x848e}}, + {"pan", // L"盘盼判攀畔潘叛磐番胖襻蟠袢泮拚爿蹒" + {0x76d8, 0x76fc, 0x5224, 0x6500, 0x7554, 0x6f58, 0x53db, 0x78d0, 0x756a, 0x80d6, 0x897b, + 0x87e0, 0x88a2, 0x6cee, 0x62da, 0x723f, 0x8e52}}, + {"pang", // L"旁胖耪庞乓膀磅滂彷逄螃镑" + {0x65c1, 0x80d6, 0x802a, 0x5e9e, 0x4e53, 0x8180, 0x78c5, 0x6ec2, 0x5f77, 0x9004, 0x8783, + 0x9551}}, + {"pao", // L"跑抛炮泡刨袍咆狍匏庖疱脬" + {0x8dd1, 0x629b, 0x70ae, 0x6ce1, 0x5228, 0x888d, 0x5486, 0x72cd, 0x530f, 0x5e96, 0x75b1, + 0x812c}}, + {"pei", // L"陪配赔呸胚佩培沛裴旆锫帔醅霈辔" + {0x966a, 0x914d, 0x8d54, 0x5478, 0x80da, 0x4f69, 0x57f9, 0x6c9b, 0x88f4, 0x65c6, 0x952b, + 0x5e14, 0x9185, 0x9708, 0x8f94}}, + {"pen", // L"喷盆湓" + {0x55b7, 0x76c6, 0x6e53}}, + {"peng", // L"碰捧棚砰蓬朋彭鹏烹硼膨抨澎篷怦堋蟛嘭" + {0x78b0, 0x6367, 0x68da, 0x7830, 0x84ec, 0x670b, 0x5f6d, 0x9e4f, 0x70f9, 0x787c, 0x81a8, + 0x62a8, 0x6f8e, 0x7bf7, 0x6026, 0x580b, 0x87db, 0x562d}}, + {"pi", // L"批皮披匹劈辟坯屁脾僻疲痞霹琵毗啤譬砒否貔丕圮媲癖仳擗郫甓枇睥蜱鼙邳陂铍庀罴埤纰陴淠噼蚍裨疋芘" + {0x6279, 0x76ae, 0x62ab, 0x5339, 0x5288, 0x8f9f, 0x576f, 0x5c41, 0x813e, 0x50fb, + 0x75b2, 0x75de, 0x9739, 0x7435, 0x6bd7, 0x5564, 0x8b6c, 0x7812, 0x5426, 0x8c94, + 0x4e15, 0x572e, 0x5ab2, 0x7656, 0x4ef3, 0x64d7, 0x90eb, 0x7513, 0x6787, 0x7765, + 0x8731, 0x9f19, 0x90b3, 0x9642, 0x94cd, 0x5e80, 0x7f74, 0x57e4, 0x7eb0, 0x9674, + 0x6de0, 0x567c, 0x868d, 0x88e8, 0x758b, 0x8298}}, + {"pian", // L"片篇骗偏便扁翩缏犏骈胼蹁谝" + {0x7247, 0x7bc7, 0x9a97, 0x504f, 0x4fbf, 0x6241, 0x7fe9, 0x7f0f, 0x728f, 0x9a88, 0x80fc, + 0x8e41, 0x8c1d}}, + {"piao", // L"票飘漂瓢朴螵嫖瞟殍缥嘌骠剽" + {0x7968, 0x98d8, 0x6f02, 0x74e2, 0x6734, 0x87b5, 0x5ad6, 0x779f, 0x6b8d, 0x7f25, 0x560c, + 0x9aa0, 0x527d}}, + {"pie", // L"瞥撇氕苤" + {0x77a5, 0x6487, 0x6c15, 0x82e4}}, + {"pin", // L"品贫聘拼频嫔榀姘牝颦" + {0x54c1, 0x8d2b, 0x8058, 0x62fc, 0x9891, 0x5ad4, 0x6980, 0x59d8, 0x725d, 0x98a6}}, + {"ping", // L"平凭瓶评屏乒萍苹坪冯娉鲆枰俜" + {0x5e73, 0x51ed, 0x74f6, 0x8bc4, 0x5c4f, 0x4e52, 0x840d, 0x82f9, 0x576a, 0x51af, 0x5a09, + 0x9c86, 0x67b0, 0x4fdc}}, + {"po", // L"破坡颇婆泼迫泊魄朴繁粕笸皤钋陂鄱攴叵珀钷" + {0x7834, 0x5761, 0x9887, 0x5a46, 0x6cfc, 0x8feb, 0x6cca, 0x9b44, 0x6734, 0x7e41, + 0x7c95, 0x7b38, 0x76a4, 0x948b, 0x9642, 0x9131, 0x6534, 0x53f5, 0x73c0, 0x94b7}}, + {"pou", // L"剖掊裒" + {0x5256, 0x638a, 0x88d2}}, + {"pu", // L"扑铺谱脯仆蒲葡朴菩曝莆瀑埔圃浦堡普暴镨噗匍溥濮氆蹼璞镤" + {0x6251, 0x94fa, 0x8c31, 0x812f, 0x4ec6, 0x84b2, 0x8461, 0x6734, 0x83e9, + 0x66dd, 0x8386, 0x7011, 0x57d4, 0x5703, 0x6d66, 0x5821, 0x666e, 0x66b4, + 0x9568, 0x5657, 0x530d, 0x6ea5, 0x6fee, 0x6c06, 0x8e7c, 0x749e, 0x9564}}, + {"qi", // L"起其七气期齐器妻骑汽棋奇欺漆启戚柒岂砌弃泣祁凄企乞契歧祈栖畦脐崎稽迄缉沏讫旗祺颀骐屺岐蹊蕲桤憩芪荠萋芑汔亟鳍俟槭嘁蛴綦亓欹琪麒琦蜞圻杞葺碛淇耆绮綮" + {0x8d77, 0x5176, 0x4e03, 0x6c14, 0x671f, 0x9f50, 0x5668, 0x59bb, 0x9a91, 0x6c7d, 0x68cb, + 0x5947, 0x6b3a, 0x6f06, 0x542f, 0x621a, 0x67d2, 0x5c82, 0x780c, 0x5f03, 0x6ce3, 0x7941, + 0x51c4, 0x4f01, 0x4e5e, 0x5951, 0x6b67, 0x7948, 0x6816, 0x7566, 0x8110, 0x5d0e, 0x7a3d, + 0x8fc4, 0x7f09, 0x6c8f, 0x8bab, 0x65d7, 0x797a, 0x9880, 0x9a90, 0x5c7a, 0x5c90, 0x8e4a, + 0x8572, 0x6864, 0x61a9, 0x82aa, 0x8360, 0x840b, 0x8291, 0x6c54, 0x4e9f, 0x9ccd, 0x4fdf, + 0x69ed, 0x5601, 0x86f4, 0x7da6, 0x4e93, 0x6b39, 0x742a, 0x9e92, 0x7426, 0x871e, 0x573b, + 0x675e, 0x847a, 0x789b, 0x6dc7, 0x8006, 0x7eee, 0x7dae}}, + {"qia", // L"恰卡掐洽髂袷葜" + {0x6070, 0x5361, 0x6390, 0x6d3d, 0x9ac2, 0x88b7, 0x845c}}, + {"qian", // L"前钱千牵浅签欠铅嵌钎迁钳乾谴谦潜歉纤扦遣黔堑仟岍钤褰箝掮搴倩慊悭愆虔芡荨缱佥芊阡肷茜椠犍骞羟赶" + {0x524d, 0x94b1, 0x5343, 0x7275, 0x6d45, 0x7b7e, 0x6b20, 0x94c5, 0x5d4c, 0x948e, + 0x8fc1, 0x94b3, 0x4e7e, 0x8c34, 0x8c26, 0x6f5c, 0x6b49, 0x7ea4, 0x6266, 0x9063, + 0x9ed4, 0x5811, 0x4edf, 0x5c8d, 0x94a4, 0x8930, 0x7b9d, 0x63ae, 0x6434, 0x5029, + 0x614a, 0x60ad, 0x6106, 0x8654, 0x82a1, 0x8368, 0x7f31, 0x4f65, 0x828a, 0x9621, + 0x80b7, 0x831c, 0x6920, 0x728d, 0x9a9e, 0x7f9f, 0x8d76}}, + {"qiang", // L"强枪墙抢腔呛羌蔷蜣跄戗襁戕炝镪锵羟樯嫱" + {0x5f3a, 0x67aa, 0x5899, 0x62a2, 0x8154, 0x545b, 0x7f8c, 0x8537, 0x8723, 0x8dc4, 0x6217, + 0x8941, 0x6215, 0x709d, 0x956a, 0x9535, 0x7f9f, 0x6a2f, 0x5af1}}, + {"qiao", // L"桥瞧敲巧翘锹壳鞘撬悄俏窍雀乔侨峭橇樵荞跷硗憔谯鞒愀缲诮劁峤搞铫" + {0x6865, 0x77a7, 0x6572, 0x5de7, 0x7fd8, 0x9539, 0x58f3, 0x9798, 0x64ac, 0x6084, 0x4fcf, + 0x7a8d, 0x96c0, 0x4e54, 0x4fa8, 0x5ced, 0x6a47, 0x6a35, 0x835e, 0x8df7, 0x7857, 0x6194, + 0x8c2f, 0x9792, 0x6100, 0x7f32, 0x8bee, 0x5281, 0x5ce4, 0x641e, 0x94eb}}, + {"qie", // L"切且怯窃茄郄趄惬锲妾箧慊伽挈" + {0x5207, 0x4e14, 0x602f, 0x7a83, 0x8304, 0x90c4, 0x8d84, 0x60ec, 0x9532, 0x59be, 0x7ba7, + 0x614a, 0x4f3d, 0x6308}}, + {"qin", // L"亲琴侵勤擒寝秦芹沁禽钦吣覃矜衾芩廑嗪螓噙揿檎锓" + {0x4eb2, 0x7434, 0x4fb5, 0x52e4, 0x64d2, 0x5bdd, 0x79e6, 0x82b9, + 0x6c81, 0x79bd, 0x94a6, 0x5423, 0x8983, 0x77dc, 0x887e, 0x82a9, + 0x5ed1, 0x55ea, 0x8793, 0x5659, 0x63ff, 0x6a8e, 0x9513}}, + {"qing", // L"请轻清青情晴氢倾庆擎顷亲卿氰圊謦檠箐苘蜻黥罄鲭磬綮" + {0x8bf7, 0x8f7b, 0x6e05, 0x9752, 0x60c5, 0x6674, 0x6c22, 0x503e, 0x5e86, + 0x64ce, 0x9877, 0x4eb2, 0x537f, 0x6c30, 0x570a, 0x8b26, 0x6aa0, 0x7b90, + 0x82d8, 0x873b, 0x9ee5, 0x7f44, 0x9cad, 0x78ec, 0x7dae}}, + {"qiong", // L"穷琼跫穹邛蛩茕銎筇" + {0x7a77, 0x743c, 0x8deb, 0x7a79, 0x909b, 0x86e9, 0x8315, 0x928e, 0x7b47}}, + {"qiu", // L"求球秋丘泅仇邱囚酋龟楸蚯裘糗蝤巯逑俅虬赇鳅犰湫遒" + {0x6c42, 0x7403, 0x79cb, 0x4e18, 0x6cc5, 0x4ec7, 0x90b1, 0x56da, + 0x914b, 0x9f9f, 0x6978, 0x86af, 0x88d8, 0x7cd7, 0x8764, 0x5def, + 0x9011, 0x4fc5, 0x866c, 0x8d47, 0x9cc5, 0x72b0, 0x6e6b, 0x9052}}, + {"qu", // L"去取区娶渠曲趋趣屈驱蛆躯龋戌蠼蘧祛蕖磲劬诎鸲阒麴癯衢黢璩氍觑蛐朐瞿岖苣" + {0x53bb, 0x53d6, 0x533a, 0x5a36, 0x6e20, 0x66f2, 0x8d8b, 0x8da3, 0x5c48, + 0x9a71, 0x86c6, 0x8eaf, 0x9f8b, 0x620c, 0x883c, 0x8627, 0x795b, 0x8556, + 0x78f2, 0x52ac, 0x8bce, 0x9e32, 0x9612, 0x9eb4, 0x766f, 0x8862, 0x9ee2, + 0x74a9, 0x6c0d, 0x89d1, 0x86d0, 0x6710, 0x77bf, 0x5c96, 0x82e3}}, + {"quan", // L"全权劝圈拳犬泉券颧痊醛铨筌绻诠辁畎鬈悛蜷荃犭" + {0x5168, 0x6743, 0x529d, 0x5708, 0x62f3, 0x72ac, 0x6cc9, 0x5238, 0x98a7, 0x75ca, 0x919b, + 0x94e8, 0x7b4c, 0x7efb, 0x8be0, 0x8f81, 0x754e, 0x9b08, 0x609b, 0x8737, 0x8343, 0x72ad}}, + {"que", // L"却缺确雀瘸鹊炔榷阙阕悫" + {0x5374, 0x7f3a, 0x786e, 0x96c0, 0x7638, 0x9e4a, 0x7094, 0x69b7, 0x9619, 0x9615, 0x60ab}}, + {"qun", // L"群裙麇逡" + {0x7fa4, 0x88d9, 0x9e87, 0x9021}}, + {"ran", // L"染燃然冉髯苒蚺" + {0x67d3, 0x71c3, 0x7136, 0x5189, 0x9aef, 0x82d2, 0x86ba}}, + {"rang", // L"让嚷瓤攘壤穰禳" + {0x8ba9, 0x56b7, 0x74e4, 0x6518, 0x58e4, 0x7a70, 0x79b3}}, + {"rao", // L"饶绕扰荛桡娆" + {0x9976, 0x7ed5, 0x6270, 0x835b, 0x6861, 0x5a06}}, + {"re", // L"热惹喏" + {0x70ed, 0x60f9, 0x558f}}, + {"ren", // L"人任忍认刃仁韧妊纫壬饪轫仞荏葚衽稔亻" + {0x4eba, 0x4efb, 0x5fcd, 0x8ba4, 0x5203, 0x4ec1, 0x97e7, 0x598a, 0x7eab, 0x58ec, 0x996a, + 0x8f6b, 0x4ede, 0x834f, 0x845a, 0x887d, 0x7a14, 0x4ebb}}, + {"reng", // L"仍扔" + {0x4ecd, 0x6254}}, + {"ri", // L"日" + {0x65e5}}, + {"rong", // L"容绒融溶熔荣戎蓉冗茸榕狨嵘肜蝾" + {0x5bb9, 0x7ed2, 0x878d, 0x6eb6, 0x7194, 0x8363, 0x620e, 0x84c9, 0x5197, 0x8338, 0x6995, + 0x72e8, 0x5d58, 0x809c, 0x877e}}, + {"rou", // L"肉揉柔糅蹂鞣" + {0x8089, 0x63c9, 0x67d4, 0x7cc5, 0x8e42, 0x97a3}}, + {"ru", // L"如入汝儒茹乳褥辱蠕孺蓐襦铷嚅缛濡薷颥溽洳" + {0x5982, 0x5165, 0x6c5d, 0x5112, 0x8339, 0x4e73, 0x8925, 0x8fb1, 0x8815, 0x5b7a, + 0x84d0, 0x8966, 0x94f7, 0x5685, 0x7f1b, 0x6fe1, 0x85b7, 0x98a5, 0x6ebd, 0x6d33}}, + {"ruan", // L"软阮朊" + {0x8f6f, 0x962e, 0x670a}}, + {"rui", // L"瑞蕊锐睿芮蚋枘蕤" + {0x745e, 0x854a, 0x9510, 0x777f, 0x82ae, 0x868b, 0x6798, 0x8564}}, + {"run", // L"润闰" + {0x6da6, 0x95f0}}, + {"ruo", // L"若弱箬偌" + {0x82e5, 0x5f31, 0x7bac, 0x504c}}, + {"sa", // L"撒洒萨仨卅飒脎" + {0x6492, 0x6d12, 0x8428, 0x4ee8, 0x5345, 0x98d2, 0x810e}}, + {"sai", // L"塞腮鳃赛噻" + {0x585e, 0x816e, 0x9cc3, 0x8d5b, 0x567b}}, + {"san", // L"三散伞叁馓糁毵" + {0x4e09, 0x6563, 0x4f1e, 0x53c1, 0x9993, 0x7cc1, 0x6bf5}}, + {"sang", // L"桑丧嗓颡磉搡" + {0x6851, 0x4e27, 0x55d3, 0x98a1, 0x78c9, 0x6421}}, + {"sao", // L"扫嫂搔骚埽鳋臊缫瘙" + {0x626b, 0x5ac2, 0x6414, 0x9a9a, 0x57fd, 0x9ccb, 0x81ca, 0x7f2b, 0x7619}}, + {"se", // L"色涩瑟塞啬铯穑" + {0x8272, 0x6da9, 0x745f, 0x585e, 0x556c, 0x94ef, 0x7a51}}, + {"sen", // L"森" + {0x68ee}}, + {"seng", // L"僧" + {0x50e7}}, + {"sha", // L"杀沙啥纱傻砂刹莎厦煞杉唼鲨霎铩痧裟歃" + {0x6740, 0x6c99, 0x5565, 0x7eb1, 0x50bb, 0x7802, 0x5239, 0x838e, 0x53a6, 0x715e, 0x6749, + 0x553c, 0x9ca8, 0x970e, 0x94e9, 0x75e7, 0x88df, 0x6b43}}, + {"shai", // L"晒筛色" + {0x6652, 0x7b5b, 0x8272}}, + {"shan", // L"山闪衫善扇杉删煽单珊掺赡栅苫膳陕汕擅缮嬗蟮芟禅跚鄯潸鳝姗剡骟疝膻讪钐舢埏彡髟" + {0x5c71, 0x95ea, 0x886b, 0x5584, 0x6247, 0x6749, 0x5220, 0x717d, 0x5355, 0x73ca, + 0x63ba, 0x8d61, 0x6805, 0x82eb, 0x81b3, 0x9655, 0x6c55, 0x64c5, 0x7f2e, 0x5b17, + 0x87ee, 0x829f, 0x7985, 0x8dda, 0x912f, 0x6f78, 0x9cdd, 0x59d7, 0x5261, 0x9a9f, + 0x759d, 0x81bb, 0x8baa, 0x9490, 0x8222, 0x57cf, 0x5f61, 0x9adf}}, + {"shang", // L"上伤尚商赏晌墒裳熵觞绱殇垧" + {0x4e0a, 0x4f24, 0x5c1a, 0x5546, 0x8d4f, 0x664c, 0x5892, 0x88f3, 0x71b5, 0x89de, 0x7ef1, + 0x6b87, 0x57a7}}, + {"shao", // L"少烧捎哨勺梢稍邵韶绍芍鞘苕劭潲艄蛸筲" + {0x5c11, 0x70e7, 0x634e, 0x54e8, 0x52fa, 0x68a2, 0x7a0d, 0x90b5, 0x97f6, 0x7ecd, 0x828d, + 0x9798, 0x82d5, 0x52ad, 0x6f72, 0x8244, 0x86f8, 0x7b72}}, + {"she", // L"社射蛇设舌摄舍折涉赊赦慑奢歙厍畲猞麝滠佘揲" + {0x793e, 0x5c04, 0x86c7, 0x8bbe, 0x820c, 0x6444, 0x820d, 0x6298, 0x6d89, 0x8d4a, 0x8d66, + 0x6151, 0x5962, 0x6b59, 0x538d, 0x7572, 0x731e, 0x9e9d, 0x6ee0, 0x4f58, 0x63f2}}, + {"shei", // L"谁" + {0x8c01}}, + {"shen", // L"身伸深婶神甚渗肾审申沈绅呻参砷什娠慎葚莘诜谂矧椹渖蜃哂胂" + {0x8eab, 0x4f38, 0x6df1, 0x5a76, 0x795e, 0x751a, 0x6e17, 0x80be, 0x5ba1, 0x7533, + 0x6c88, 0x7ec5, 0x547b, 0x53c2, 0x7837, 0x4ec0, 0x5a20, 0x614e, 0x845a, 0x8398, + 0x8bdc, 0x8c02, 0x77e7, 0x6939, 0x6e16, 0x8703, 0x54c2, 0x80c2}}, + {"sheng", // L"声省剩生升绳胜盛圣甥牲乘晟渑眚笙嵊" + {0x58f0, 0x7701, 0x5269, 0x751f, 0x5347, 0x7ef3, 0x80dc, 0x76db, 0x5723, 0x7525, 0x7272, + 0x4e58, 0x665f, 0x6e11, 0x771a, 0x7b19, 0x5d4a}}, + {"shi", // L"是使十时事室市石师试史式识虱矢拾屎驶始似示士世柿匙拭誓逝势什殖峙嗜噬失适仕侍释饰氏狮食恃蚀视实施湿诗尸豕莳埘铈舐鲥鲺贳轼蓍筮炻谥弑酾螫礻铊饣" + {0x662f, 0x4f7f, 0x5341, 0x65f6, 0x4e8b, 0x5ba4, 0x5e02, 0x77f3, 0x5e08, 0x8bd5, + 0x53f2, 0x5f0f, 0x8bc6, 0x8671, 0x77e2, 0x62fe, 0x5c4e, 0x9a76, 0x59cb, 0x4f3c, + 0x793a, 0x58eb, 0x4e16, 0x67ff, 0x5319, 0x62ed, 0x8a93, 0x901d, 0x52bf, 0x4ec0, + 0x6b96, 0x5cd9, 0x55dc, 0x566c, 0x5931, 0x9002, 0x4ed5, 0x4f8d, 0x91ca, 0x9970, + 0x6c0f, 0x72ee, 0x98df, 0x6043, 0x8680, 0x89c6, 0x5b9e, 0x65bd, 0x6e7f, 0x8bd7, + 0x5c38, 0x8c55, 0x83b3, 0x57d8, 0x94c8, 0x8210, 0x9ca5, 0x9cba, 0x8d33, 0x8f7c, + 0x84cd, 0x7b6e, 0x70bb, 0x8c25, 0x5f11, 0x917e, 0x87ab, 0x793b, 0x94ca, 0x9963}}, + {"shou", // L"手受收首守瘦授兽售寿艏狩绶扌" + {0x624b, 0x53d7, 0x6536, 0x9996, 0x5b88, 0x7626, 0x6388, 0x517d, 0x552e, 0x5bff, 0x824f, + 0x72e9, 0x7ef6, 0x624c}}, + {"shu", // L"书树数熟输梳叔属束术述蜀黍鼠淑赎孰蔬疏戍竖墅庶薯漱恕枢暑殊抒曙署舒姝摅秫纾沭毹腧塾菽殳澍倏疋镯" + {0x4e66, 0x6811, 0x6570, 0x719f, 0x8f93, 0x68b3, 0x53d4, 0x5c5e, 0x675f, 0x672f, + 0x8ff0, 0x8700, 0x9ecd, 0x9f20, 0x6dd1, 0x8d4e, 0x5b70, 0x852c, 0x758f, 0x620d, + 0x7ad6, 0x5885, 0x5eb6, 0x85af, 0x6f31, 0x6055, 0x67a2, 0x6691, 0x6b8a, 0x6292, + 0x66d9, 0x7f72, 0x8212, 0x59dd, 0x6445, 0x79eb, 0x7ebe, 0x6cad, 0x6bf9, 0x8167, + 0x587e, 0x83fd, 0x6bb3, 0x6f8d, 0x500f, 0x758b, 0x956f}}, + {"shua", // L"刷耍唰" + {0x5237, 0x800d, 0x5530}}, + {"shuai", // L"摔甩率帅衰蟀" + {0x6454, 0x7529, 0x7387, 0x5e05, 0x8870, 0x87c0}}, + {"shuan", // L"栓拴闩涮" + {0x6813, 0x62f4, 0x95e9, 0x6dae}}, + {"shuang", // L"双霜爽泷孀" + {0x53cc, 0x971c, 0x723d, 0x6cf7, 0x5b40}}, + {"shui", // L"水睡税说氵" + {0x6c34, 0x7761, 0x7a0e, 0x8bf4, 0x6c35}}, + {"shun", // L"顺吮瞬舜" + {0x987a, 0x542e, 0x77ac, 0x821c}}, + {"shuo", // L"说数硕烁朔搠妁槊蒴铄" + {0x8bf4, 0x6570, 0x7855, 0x70c1, 0x6714, 0x6420, 0x5981, 0x69ca, 0x84b4, 0x94c4}}, + {"si", // L"四死丝撕似私嘶思寺司斯伺肆饲嗣巳耜驷兕蛳厮汜锶泗笥咝鸶姒厶缌祀澌俟徙" + {0x56db, 0x6b7b, 0x4e1d, 0x6495, 0x4f3c, 0x79c1, 0x5636, 0x601d, 0x5bfa, + 0x53f8, 0x65af, 0x4f3a, 0x8086, 0x9972, 0x55e3, 0x5df3, 0x801c, 0x9a77, + 0x5155, 0x86f3, 0x53ae, 0x6c5c, 0x9536, 0x6cd7, 0x7b25, 0x549d, 0x9e36, + 0x59d2, 0x53b6, 0x7f0c, 0x7940, 0x6f8c, 0x4fdf, 0x5f99}}, + {"song", // L"送松耸宋颂诵怂讼竦菘淞悚嵩凇崧忪" + {0x9001, 0x677e, 0x8038, 0x5b8b, 0x9882, 0x8bf5, 0x6002, 0x8bbc, 0x7ae6, 0x83d8, 0x6dde, + 0x609a, 0x5d69, 0x51c7, 0x5d27, 0x5fea}}, + {"sou", // L"艘搜擞嗽嗾嗖飕叟薮锼馊瞍溲螋" + {0x8258, 0x641c, 0x64de, 0x55fd, 0x55fe, 0x55d6, 0x98d5, 0x53df, 0x85ae, 0x953c, 0x998a, + 0x778d, 0x6eb2, 0x878b}}, + {"su", // L"素速诉塑宿俗苏肃粟酥缩溯僳愫簌觫稣夙嗉谡蔌涑" + {0x7d20, 0x901f, 0x8bc9, 0x5851, 0x5bbf, 0x4fd7, 0x82cf, 0x8083, 0x7c9f, 0x9165, 0x7f29, + 0x6eaf, 0x50f3, 0x612b, 0x7c0c, 0x89eb, 0x7a23, 0x5919, 0x55c9, 0x8c21, 0x850c, 0x6d91}}, + {"suan", // L"酸算蒜狻" + {0x9178, 0x7b97, 0x849c, 0x72fb}}, + {"sui", // L"岁随碎虽穗遂尿隋髓绥隧祟眭谇濉邃燧荽睢" + {0x5c81, 0x968f, 0x788e, 0x867d, 0x7a57, 0x9042, 0x5c3f, 0x968b, 0x9ad3, 0x7ee5, 0x96a7, + 0x795f, 0x772d, 0x8c07, 0x6fc9, 0x9083, 0x71e7, 0x837d, 0x7762}}, + {"sun", // L"孙损笋榫荪飧狲隼" + {0x5b59, 0x635f, 0x7b0b, 0x69ab, 0x836a, 0x98e7, 0x72f2, 0x96bc}}, + {"suo", // L"所缩锁琐索梭蓑莎唆挲睃嗍唢桫嗦娑羧" + {0x6240, 0x7f29, 0x9501, 0x7410, 0x7d22, 0x68ad, 0x84d1, 0x838e, 0x5506, 0x6332, 0x7743, + 0x55cd, 0x5522, 0x686b, 0x55e6, 0x5a11, 0x7fa7}}, + {"ta", // L"他她它踏塔塌拓獭挞蹋溻趿鳎沓榻漯遢铊闼" + {0x4ed6, 0x5979, 0x5b83, 0x8e0f, 0x5854, 0x584c, 0x62d3, 0x736d, 0x631e, 0x8e4b, 0x6ebb, + 0x8dbf, 0x9cce, 0x6c93, 0x69bb, 0x6f2f, 0x9062, 0x94ca, 0x95fc}}, + {"tai", // L"太抬台态胎苔泰酞汰炱肽跆鲐钛薹邰骀" + {0x592a, 0x62ac, 0x53f0, 0x6001, 0x80ce, 0x82d4, 0x6cf0, 0x915e, 0x6c70, 0x70b1, 0x80bd, + 0x8dc6, 0x9c90, 0x949b, 0x85b9, 0x90b0, 0x9a80}}, + {"tan", // L"谈叹探滩弹碳摊潭贪坛痰毯坦炭瘫谭坍檀袒钽郯镡锬覃澹昙忐赕" + {0x8c08, 0x53f9, 0x63a2, 0x6ee9, 0x5f39, 0x78b3, 0x644a, 0x6f6d, 0x8d2a, 0x575b, + 0x75f0, 0x6bef, 0x5766, 0x70ad, 0x762b, 0x8c2d, 0x574d, 0x6a80, 0x8892, 0x94bd, + 0x90ef, 0x9561, 0x952c, 0x8983, 0x6fb9, 0x6619, 0x5fd0, 0x8d55}}, + {"tang", // L"躺趟堂糖汤塘烫倘淌唐搪棠膛螳樘羰醣瑭镗傥饧溏耥帑铴螗铛" + {0x8eba, 0x8d9f, 0x5802, 0x7cd6, 0x6c64, 0x5858, 0x70eb, 0x5018, 0x6dcc, + 0x5510, 0x642a, 0x68e0, 0x819b, 0x87b3, 0x6a18, 0x7fb0, 0x91a3, 0x746d, + 0x9557, 0x50a5, 0x9967, 0x6e8f, 0x8025, 0x5e11, 0x94f4, 0x8797, 0x94db}}, + {"tao", // L"套掏逃桃讨淘涛滔陶绦萄鼗洮焘啕饕韬叨" + {0x5957, 0x638f, 0x9003, 0x6843, 0x8ba8, 0x6dd8, 0x6d9b, 0x6ed4, 0x9676, 0x7ee6, 0x8404, + 0x9f17, 0x6d2e, 0x7118, 0x5555, 0x9955, 0x97ec, 0x53e8}}, + {"te", // L"特铽忑忒" + {0x7279, 0x94fd, 0x5fd1, 0x5fd2}}, + {"teng", // L"疼腾藤誊滕" + {0x75bc, 0x817e, 0x85e4, 0x8a8a, 0x6ed5}}, + {"ti", // L"提替体题踢蹄剃剔梯锑啼涕嚏惕屉醍鹈绨缇倜裼逖荑悌" + {0x63d0, 0x66ff, 0x4f53, 0x9898, 0x8e22, 0x8e44, 0x5243, 0x5254, + 0x68af, 0x9511, 0x557c, 0x6d95, 0x568f, 0x60d5, 0x5c49, 0x918d, + 0x9e48, 0x7ee8, 0x7f07, 0x501c, 0x88fc, 0x9016, 0x8351, 0x608c}}, + {"tian", // L"天田添填甜舔恬腆掭钿阗忝殄畋锘" + {0x5929, 0x7530, 0x6dfb, 0x586b, 0x751c, 0x8214, 0x606c, 0x8146, 0x63ad, 0x94bf, 0x9617, + 0x5fdd, 0x6b84, 0x754b, 0x9518}}, + {"tiao", // L"条跳挑调迢眺龆笤祧蜩髫佻窕鲦苕粜铫" + {0x6761, 0x8df3, 0x6311, 0x8c03, 0x8fe2, 0x773a, 0x9f86, 0x7b24, 0x7967, 0x8729, 0x9aeb, + 0x4f7b, 0x7a95, 0x9ca6, 0x82d5, 0x7c9c, 0x94eb}}, + {"tie", // L"铁贴帖萜餮锇" + {0x94c1, 0x8d34, 0x5e16, 0x841c, 0x992e, 0x9507}}, + {"ting", // L"听停挺厅亭艇庭廷烃汀莛铤葶婷蜓梃霆" + {0x542c, 0x505c, 0x633a, 0x5385, 0x4ead, 0x8247, 0x5ead, 0x5ef7, 0x70c3, 0x6c40, 0x839b, + 0x94e4, 0x8476, 0x5a77, 0x8713, 0x6883, 0x9706}}, + {"tong", // L"同通痛铜桶筒捅统童彤桐瞳酮潼茼仝砼峒恸佟嗵垌僮" + {0x540c, 0x901a, 0x75db, 0x94dc, 0x6876, 0x7b52, 0x6345, 0x7edf, + 0x7ae5, 0x5f64, 0x6850, 0x77b3, 0x916e, 0x6f7c, 0x833c, 0x4edd, + 0x783c, 0x5cd2, 0x6078, 0x4f5f, 0x55f5, 0x578c, 0x50ee}}, + {"tou", // L"头偷透投钭骰亠" + {0x5934, 0x5077, 0x900f, 0x6295, 0x94ad, 0x9ab0, 0x4ea0}}, + {"tu", // L"土图兔涂吐秃突徒凸途屠酴荼钍菟堍" + {0x571f, 0x56fe, 0x5154, 0x6d82, 0x5410, 0x79c3, 0x7a81, 0x5f92, 0x51f8, 0x9014, 0x5c60, + 0x9174, 0x837c, 0x948d, 0x83df, 0x580d}}, + {"tuan", // L"团湍疃抟彖" + {0x56e2, 0x6e4d, 0x7583, 0x629f, 0x5f56}}, + {"tui", // L"腿推退褪颓蜕煺" + {0x817f, 0x63a8, 0x9000, 0x892a, 0x9893, 0x8715, 0x717a}}, + {"tun", // L"吞屯褪臀囤氽饨豚暾" + {0x541e, 0x5c6f, 0x892a, 0x81c0, 0x56e4, 0x6c3d, 0x9968, 0x8c5a, 0x66be}}, + {"tuo", // L"拖脱托妥驮拓驼椭唾鸵陀橐柝跎乇坨佗庹酡柁鼍沱箨砣说铊" + {0x62d6, 0x8131, 0x6258, 0x59a5, 0x9a6e, 0x62d3, 0x9a7c, 0x692d, 0x553e, + 0x9e35, 0x9640, 0x6a50, 0x67dd, 0x8dce, 0x4e47, 0x5768, 0x4f57, 0x5eb9, + 0x9161, 0x67c1, 0x9f0d, 0x6cb1, 0x7ba8, 0x7823, 0x8bf4, 0x94ca}}, + {"wa", // L"挖瓦蛙哇娃洼袜佤娲腽" + {0x6316, 0x74e6, 0x86d9, 0x54c7, 0x5a03, 0x6d3c, 0x889c, 0x4f64, 0x5a32, 0x817d}}, + {"wai", // L"外歪崴" + {0x5916, 0x6b6a, 0x5d34}}, + {"wan", // L"完万晚碗玩弯挽湾丸腕宛婉烷顽豌惋皖蔓莞脘蜿绾芄琬纨剜畹菀" + {0x5b8c, 0x4e07, 0x665a, 0x7897, 0x73a9, 0x5f2f, 0x633d, 0x6e7e, 0x4e38, 0x8155, + 0x5b9b, 0x5a49, 0x70f7, 0x987d, 0x8c4c, 0x60cb, 0x7696, 0x8513, 0x839e, 0x8118, + 0x873f, 0x7efe, 0x8284, 0x742c, 0x7ea8, 0x525c, 0x7579, 0x83c0}}, + {"wang", // L"望忘王往网亡枉旺汪妄辋魍惘罔尢" + {0x671b, 0x5fd8, 0x738b, 0x5f80, 0x7f51, 0x4ea1, 0x6789, 0x65fa, 0x6c6a, 0x5984, 0x8f8b, + 0x9b4d, 0x60d8, 0x7f54, 0x5c22}}, + {"wei", // L"为位未围喂胃微味尾伪威伟卫危违委魏唯维畏惟韦巍蔚谓尉潍纬慰桅萎苇渭葳帏艉鲔娓逶闱隈沩玮涠帷崴隗诿洧偎猥猬嵬軎韪炜煨圩薇痿囗" + {0x4e3a, 0x4f4d, 0x672a, 0x56f4, 0x5582, 0x80c3, 0x5fae, 0x5473, 0x5c3e, 0x4f2a, 0x5a01, + 0x4f1f, 0x536b, 0x5371, 0x8fdd, 0x59d4, 0x9b4f, 0x552f, 0x7ef4, 0x754f, 0x60df, 0x97e6, + 0x5dcd, 0x851a, 0x8c13, 0x5c09, 0x6f4d, 0x7eac, 0x6170, 0x6845, 0x840e, 0x82c7, 0x6e2d, + 0x8473, 0x5e0f, 0x8249, 0x9c94, 0x5a13, 0x9036, 0x95f1, 0x9688, 0x6ca9, 0x73ae, 0x6da0, + 0x5e37, 0x5d34, 0x9697, 0x8bff, 0x6d27, 0x504e, 0x7325, 0x732c, 0x5d6c, 0x8ece, 0x97ea, + 0x709c, 0x7168, 0x5729, 0x8587, 0x75ff, 0x56d7}}, + {"wen", // L"问文闻稳温吻蚊纹瘟紊汶阌刎雯璺" + {0x95ee, 0x6587, 0x95fb, 0x7a33, 0x6e29, 0x543b, 0x868a, 0x7eb9, 0x761f, 0x7d0a, 0x6c76, + 0x960c, 0x520e, 0x96ef, 0x74ba}}, + {"weng", // L"翁嗡瓮蕹蓊" + {0x7fc1, 0x55e1, 0x74ee, 0x8579, 0x84ca}}, + {"wo", // L"我握窝卧挝沃蜗涡斡倭幄龌肟莴喔渥硪" + {0x6211, 0x63e1, 0x7a9d, 0x5367, 0x631d, 0x6c83, 0x8717, 0x6da1, 0x65a1, 0x502d, 0x5e44, + 0x9f8c, 0x809f, 0x83b4, 0x5594, 0x6e25, 0x786a}}, + {"wu", // L"无五屋物舞雾误捂污悟勿钨武戊务呜伍吴午吾侮乌毋恶诬芜巫晤梧坞妩蜈牾寤兀怃阢邬唔忤骛於鋈仵杌鹜婺迕痦芴焐庑鹉鼯浯圬" + {0x65e0, 0x4e94, 0x5c4b, 0x7269, 0x821e, 0x96fe, 0x8bef, 0x6342, 0x6c61, 0x609f, + 0x52ff, 0x94a8, 0x6b66, 0x620a, 0x52a1, 0x545c, 0x4f0d, 0x5434, 0x5348, 0x543e, + 0x4fae, 0x4e4c, 0x6bcb, 0x6076, 0x8bec, 0x829c, 0x5deb, 0x6664, 0x68a7, 0x575e, + 0x59a9, 0x8708, 0x727e, 0x5be4, 0x5140, 0x6003, 0x9622, 0x90ac, 0x5514, 0x5fe4, + 0x9a9b, 0x65bc, 0x92c8, 0x4ef5, 0x674c, 0x9e5c, 0x5a7a, 0x8fd5, 0x75e6, 0x82b4, + 0x7110, 0x5e91, 0x9e49, 0x9f2f, 0x6d6f, 0x572c}}, + {"xi", // L"西洗细吸戏系喜席稀溪熄锡膝息袭惜习嘻夕悉矽熙希檄牺晰昔媳硒铣烯析隙汐犀蜥奚浠葸饩屣玺嬉禊兮翕穸禧僖淅蓰舾蹊醯郗欷皙蟋羲茜徙隰唏曦螅歙樨阋粞熹觋菥鼷裼舄" + {0x897f, 0x6d17, 0x7ec6, 0x5438, 0x620f, 0x7cfb, 0x559c, 0x5e2d, 0x7a00, 0x6eaa, 0x7184, + 0x9521, 0x819d, 0x606f, 0x88ad, 0x60dc, 0x4e60, 0x563b, 0x5915, 0x6089, 0x77fd, 0x7199, + 0x5e0c, 0x6a84, 0x727a, 0x6670, 0x6614, 0x5ab3, 0x7852, 0x94e3, 0x70ef, 0x6790, 0x9699, + 0x6c50, 0x7280, 0x8725, 0x595a, 0x6d60, 0x8478, 0x9969, 0x5c63, 0x73ba, 0x5b09, 0x798a, + 0x516e, 0x7fd5, 0x7a78, 0x79a7, 0x50d6, 0x6dc5, 0x84f0, 0x823e, 0x8e4a, 0x91af, 0x90d7, + 0x6b37, 0x7699, 0x87cb, 0x7fb2, 0x831c, 0x5f99, 0x96b0, 0x550f, 0x66e6, 0x8785, 0x6b59, + 0x6a28, 0x960b, 0x7c9e, 0x71b9, 0x89cb, 0x83e5, 0x9f37, 0x88fc, 0x8204}}, + {"xia", // L"下吓夏峡虾瞎霞狭匣侠辖厦暇狎柙呷黠硖罅遐瑕" + {0x4e0b, 0x5413, 0x590f, 0x5ce1, 0x867e, 0x778e, 0x971e, 0x72ed, 0x5323, 0x4fa0, 0x8f96, + 0x53a6, 0x6687, 0x72ce, 0x67d9, 0x5477, 0x9ee0, 0x7856, 0x7f45, 0x9050, 0x7455}}, + {"xian", // L"先线县现显掀闲献嫌陷险鲜弦衔馅限咸锨仙腺贤纤宪舷涎羡铣苋藓岘痫莶籼娴蚬猃祆冼燹跣跹酰暹氙鹇筅霰洗" + {0x5148, 0x7ebf, 0x53bf, 0x73b0, 0x663e, 0x6380, 0x95f2, 0x732e, 0x5acc, 0x9677, + 0x9669, 0x9c9c, 0x5f26, 0x8854, 0x9985, 0x9650, 0x54b8, 0x9528, 0x4ed9, 0x817a, + 0x8d24, 0x7ea4, 0x5baa, 0x8237, 0x6d8e, 0x7fa1, 0x94e3, 0x82cb, 0x85d3, 0x5c98, + 0x75eb, 0x83b6, 0x7c7c, 0x5a34, 0x86ac, 0x7303, 0x7946, 0x51bc, 0x71f9, 0x8de3, + 0x8df9, 0x9170, 0x66b9, 0x6c19, 0x9e47, 0x7b45, 0x9730, 0x6d17}}, + {"xiang", // L"想向象项响香乡相像箱巷享镶厢降翔祥橡详湘襄飨鲞骧蟓庠芗饷缃葙" + {0x60f3, 0x5411, 0x8c61, 0x9879, 0x54cd, 0x9999, 0x4e61, 0x76f8, 0x50cf, 0x7bb1, + 0x5df7, 0x4eab, 0x9576, 0x53a2, 0x964d, 0x7fd4, 0x7965, 0x6a61, 0x8be6, 0x6e58, + 0x8944, 0x98e8, 0x9c9e, 0x9aa7, 0x87d3, 0x5ea0, 0x8297, 0x9977, 0x7f03, 0x8459}}, + {"xiao", // L"小笑消削销萧效宵晓肖孝硝淆啸霄哮嚣校魈蛸骁枵哓筱潇逍枭绡箫" + {0x5c0f, 0x7b11, 0x6d88, 0x524a, 0x9500, 0x8427, 0x6548, 0x5bb5, 0x6653, 0x8096, + 0x5b5d, 0x785d, 0x6dc6, 0x5578, 0x9704, 0x54ee, 0x56a3, 0x6821, 0x9b48, 0x86f8, + 0x9a81, 0x67b5, 0x54d3, 0x7b71, 0x6f47, 0x900d, 0x67ad, 0x7ee1, 0x7bab}}, + {"xie", // L"写些鞋歇斜血谢卸挟屑蟹泻懈泄楔邪协械谐蝎携胁解叶绁颉缬獬榭廨撷偕瀣渫亵榍邂薤躞燮勰骱鲑" + {0x5199, 0x4e9b, 0x978b, 0x6b47, 0x659c, 0x8840, 0x8c22, 0x5378, 0x631f, 0x5c51, 0x87f9, + 0x6cfb, 0x61c8, 0x6cc4, 0x6954, 0x90aa, 0x534f, 0x68b0, 0x8c10, 0x874e, 0x643a, 0x80c1, + 0x89e3, 0x53f6, 0x7ec1, 0x9889, 0x7f2c, 0x736c, 0x69ad, 0x5ee8, 0x64b7, 0x5055, 0x7023, + 0x6e2b, 0x4eb5, 0x698d, 0x9082, 0x85a4, 0x8e9e, 0x71ee, 0x52f0, 0x9ab1, 0x9c91}}, + {"xin", // L"新心欣信芯薪锌辛衅忻歆囟莘镡馨鑫昕忄" + {0x65b0, 0x5fc3, 0x6b23, 0x4fe1, 0x82af, 0x85aa, 0x950c, 0x8f9b, 0x8845, 0x5ffb, 0x6b46, + 0x56df, 0x8398, 0x9561, 0x99a8, 0x946b, 0x6615, 0x5fc4}}, + {"xing", // L"性行型形星醒姓腥刑杏兴幸邢猩惺省硎悻荥陉擤荇研饧" + {0x6027, 0x884c, 0x578b, 0x5f62, 0x661f, 0x9192, 0x59d3, 0x8165, + 0x5211, 0x674f, 0x5174, 0x5e78, 0x90a2, 0x7329, 0x60fa, 0x7701, + 0x784e, 0x60bb, 0x8365, 0x9649, 0x64e4, 0x8347, 0x7814, 0x9967}}, + {"xiong", // L"胸雄凶兄熊汹匈芎" + {0x80f8, 0x96c4, 0x51f6, 0x5144, 0x718a, 0x6c79, 0x5308, 0x828e}}, + {"xiu", // L"修锈绣休羞宿嗅袖秀朽臭溴貅馐髹鸺咻庥岫" + {0x4fee, 0x9508, 0x7ee3, 0x4f11, 0x7f9e, 0x5bbf, 0x55c5, 0x8896, 0x79c0, 0x673d, 0x81ed, + 0x6eb4, 0x8c85, 0x9990, 0x9af9, 0x9e3a, 0x54bb, 0x5ea5, 0x5cab}}, + {"xu", // L"许须需虚嘘蓄续序叙畜絮婿戌徐旭绪吁酗恤墟糈勖栩浒蓿顼圩洫胥醑诩溆煦盱" + {0x8bb8, 0x987b, 0x9700, 0x865a, 0x5618, 0x84c4, 0x7eed, 0x5e8f, 0x53d9, + 0x755c, 0x7d6e, 0x5a7f, 0x620c, 0x5f90, 0x65ed, 0x7eea, 0x5401, 0x9157, + 0x6064, 0x589f, 0x7cc8, 0x52d6, 0x6829, 0x6d52, 0x84ff, 0x987c, 0x5729, + 0x6d2b, 0x80e5, 0x9191, 0x8be9, 0x6e86, 0x7166, 0x76f1}}, + {"xuan", // L"选悬旋玄宣喧轩绚眩癣券暄楦儇渲漩泫铉璇煊碹镟炫揎萱谖" + {0x9009, 0x60ac, 0x65cb, 0x7384, 0x5ba3, 0x55a7, 0x8f69, 0x7eda, 0x7729, + 0x7663, 0x5238, 0x6684, 0x6966, 0x5107, 0x6e32, 0x6f29, 0x6ceb, 0x94c9, + 0x7487, 0x714a, 0x78b9, 0x955f, 0x70ab, 0x63ce, 0x8431, 0x8c16}}, + {"xue", // L"学雪血靴穴削薛踅噱鳕泶谑" + {0x5b66, 0x96ea, 0x8840, 0x9774, 0x7a74, 0x524a, 0x859b, 0x8e05, 0x5671, 0x9cd5, 0x6cf6, + 0x8c11}}, + {"xun", // L"寻讯熏训循殉旬巡迅驯汛逊勋询浚巽鲟浔埙恂獯醺洵郇峋蕈薰荀窨曛徇荨" + {0x5bfb, 0x8baf, 0x718f, 0x8bad, 0x5faa, 0x6b89, 0x65ec, 0x5de1, 0x8fc5, 0x9a6f, 0x6c5b, + 0x900a, 0x52cb, 0x8be2, 0x6d5a, 0x5dfd, 0x9c9f, 0x6d54, 0x57d9, 0x6042, 0x736f, 0x91ba, + 0x6d35, 0x90c7, 0x5ccb, 0x8548, 0x85b0, 0x8340, 0x7aa8, 0x66db, 0x5f87, 0x8368}}, + {"ya", // L"呀压牙押芽鸭轧崖哑亚涯丫雅衙鸦讶蚜垭疋砑琊桠睚娅痖岈氩伢迓揠" + {0x5440, 0x538b, 0x7259, 0x62bc, 0x82bd, 0x9e2d, 0x8f67, 0x5d16, 0x54d1, 0x4e9a, + 0x6daf, 0x4e2b, 0x96c5, 0x8859, 0x9e26, 0x8bb6, 0x869c, 0x57ad, 0x758b, 0x7811, + 0x740a, 0x6860, 0x775a, 0x5a05, 0x75d6, 0x5c88, 0x6c29, 0x4f22, 0x8fd3, 0x63e0}}, + {"yan", // L"眼烟沿盐言演严咽淹炎掩厌宴岩研延堰验艳殷阉砚雁唁彦焰蜒衍谚燕颜阎铅焉奄芫厣阏菸魇琰滟焱赝筵腌兖剡餍恹罨檐湮偃谳胭晏闫俨郾酽鄢妍鼹崦阽嫣涎讠" + {0x773c, 0x70df, 0x6cbf, 0x76d0, 0x8a00, 0x6f14, 0x4e25, 0x54bd, 0x6df9, 0x708e, + 0x63a9, 0x538c, 0x5bb4, 0x5ca9, 0x7814, 0x5ef6, 0x5830, 0x9a8c, 0x8273, 0x6bb7, + 0x9609, 0x781a, 0x96c1, 0x5501, 0x5f66, 0x7130, 0x8712, 0x884d, 0x8c1a, 0x71d5, + 0x989c, 0x960e, 0x94c5, 0x7109, 0x5944, 0x82ab, 0x53a3, 0x960f, 0x83f8, 0x9b47, + 0x7430, 0x6edf, 0x7131, 0x8d5d, 0x7b75, 0x814c, 0x5156, 0x5261, 0x990d, 0x6079, + 0x7f68, 0x6a90, 0x6e6e, 0x5043, 0x8c33, 0x80ed, 0x664f, 0x95eb, 0x4fe8, 0x90fe, + 0x917d, 0x9122, 0x598d, 0x9f39, 0x5d26, 0x963d, 0x5ae3, 0x6d8e, 0x8ba0}}, + {"yang", // L"样养羊洋仰扬秧氧痒杨漾阳殃央鸯佯疡炀恙徉鞅泱蛘烊怏" + {0x6837, 0x517b, 0x7f8a, 0x6d0b, 0x4ef0, 0x626c, 0x79e7, 0x6c27, 0x75d2, + 0x6768, 0x6f3e, 0x9633, 0x6b83, 0x592e, 0x9e2f, 0x4f6f, 0x75a1, 0x7080, + 0x6059, 0x5f89, 0x9785, 0x6cf1, 0x86d8, 0x70ca, 0x600f}}, + {"yao", // L"要摇药咬腰窑舀邀妖谣遥姚瑶耀尧钥侥疟珧夭鳐鹞轺爻吆铫幺崾肴曜徭杳窈啮繇" + {0x8981, 0x6447, 0x836f, 0x54ac, 0x8170, 0x7a91, 0x8200, 0x9080, 0x5996, + 0x8c23, 0x9065, 0x59da, 0x7476, 0x8000, 0x5c27, 0x94a5, 0x4fa5, 0x759f, + 0x73e7, 0x592d, 0x9cd0, 0x9e5e, 0x8f7a, 0x723b, 0x5406, 0x94eb, 0x5e7a, + 0x5d3e, 0x80b4, 0x66dc, 0x5fad, 0x6773, 0x7a88, 0x556e, 0x7e47}}, + {"ye", // L"也夜业野叶爷页液掖腋冶噎耶咽曳椰邪谒邺晔烨揶铘靥" + {0x4e5f, 0x591c, 0x4e1a, 0x91ce, 0x53f6, 0x7237, 0x9875, 0x6db2, + 0x6396, 0x814b, 0x51b6, 0x564e, 0x8036, 0x54bd, 0x66f3, 0x6930, + 0x90aa, 0x8c12, 0x90ba, 0x6654, 0x70e8, 0x63f6, 0x94d8, 0x9765}}, + {"yi", // L"一以已亿衣移依易医乙仪亦椅益倚姨翼译伊遗艾胰疑沂宜异彝壹蚁谊揖铱矣翌艺抑绎邑屹尾役臆逸肄疫颐裔意毅忆义夷溢诣议怿痍镒癔怡驿旖熠酏翊欹峄圯殪咦懿噫劓诒饴漪佚咿瘗猗眙羿弈苡荑佾贻钇缢迤刈悒黟翳弋奕蜴埸挹嶷薏呓轶镱舣奇硪衤铊" + {0x4e00, 0x4ee5, 0x5df2, 0x4ebf, 0x8863, 0x79fb, 0x4f9d, 0x6613, 0x533b, 0x4e59, 0x4eea, + 0x4ea6, 0x6905, 0x76ca, 0x501a, 0x59e8, 0x7ffc, 0x8bd1, 0x4f0a, 0x9057, 0x827e, 0x80f0, + 0x7591, 0x6c82, 0x5b9c, 0x5f02, 0x5f5d, 0x58f9, 0x8681, 0x8c0a, 0x63d6, 0x94f1, 0x77e3, + 0x7fcc, 0x827a, 0x6291, 0x7ece, 0x9091, 0x5c79, 0x5c3e, 0x5f79, 0x81c6, 0x9038, 0x8084, + 0x75ab, 0x9890, 0x88d4, 0x610f, 0x6bc5, 0x5fc6, 0x4e49, 0x5937, 0x6ea2, 0x8be3, 0x8bae, + 0x603f, 0x75cd, 0x9552, 0x7654, 0x6021, 0x9a7f, 0x65d6, 0x71a0, 0x914f, 0x7fca, 0x6b39, + 0x5cc4, 0x572f, 0x6baa, 0x54a6, 0x61ff, 0x566b, 0x5293, 0x8bd2, 0x9974, 0x6f2a, 0x4f5a, + 0x54bf, 0x7617, 0x7317, 0x7719, 0x7fbf, 0x5f08, 0x82e1, 0x8351, 0x4f7e, 0x8d3b, 0x9487, + 0x7f22, 0x8fe4, 0x5208, 0x6092, 0x9edf, 0x7ff3, 0x5f0b, 0x5955, 0x8734, 0x57f8, 0x6339, + 0x5db7, 0x858f, 0x5453, 0x8f76, 0x9571, 0x8223, 0x5947, 0x786a, 0x8864, 0x94ca}}, + {"yin", // L"因引印银音饮阴隐荫吟尹寅茵淫殷姻堙鄞喑夤胤龈吲狺垠霪蚓氤铟窨瘾洇茚廴" + {0x56e0, 0x5f15, 0x5370, 0x94f6, 0x97f3, 0x996e, 0x9634, 0x9690, 0x836b, + 0x541f, 0x5c39, 0x5bc5, 0x8335, 0x6deb, 0x6bb7, 0x59fb, 0x5819, 0x911e, + 0x5591, 0x5924, 0x80e4, 0x9f88, 0x5432, 0x72fa, 0x57a0, 0x972a, 0x8693, + 0x6c24, 0x94df, 0x7aa8, 0x763e, 0x6d07, 0x831a, 0x5ef4}}, + {"ying", // L"应硬影营迎映蝇赢鹰英颖莹盈婴樱缨荧萤萦楹蓥瘿茔鹦媵莺璎郢嘤撄瑛滢潆嬴罂瀛膺荥颍" + {0x5e94, 0x786c, 0x5f71, 0x8425, 0x8fce, 0x6620, 0x8747, 0x8d62, 0x9e70, 0x82f1, + 0x9896, 0x83b9, 0x76c8, 0x5a74, 0x6a31, 0x7f28, 0x8367, 0x8424, 0x8426, 0x6979, + 0x84e5, 0x763f, 0x8314, 0x9e66, 0x5ab5, 0x83ba, 0x748e, 0x90e2, 0x5624, 0x6484, + 0x745b, 0x6ee2, 0x6f46, 0x5b34, 0x7f42, 0x701b, 0x81ba, 0x8365, 0x988d}}, + {"yo", // L"哟育唷" + {0x54df, 0x80b2, 0x5537}}, + {"yong", // L"用涌永拥蛹勇雍咏泳佣踊痈庸臃恿壅慵俑墉鳙邕喁甬饔镛" + {0x7528, 0x6d8c, 0x6c38, 0x62e5, 0x86f9, 0x52c7, 0x96cd, 0x548f, 0x6cf3, + 0x4f63, 0x8e0a, 0x75c8, 0x5eb8, 0x81c3, 0x607f, 0x58c5, 0x6175, 0x4fd1, + 0x5889, 0x9cd9, 0x9095, 0x5581, 0x752c, 0x9954, 0x955b}}, + {"you", // L"有又由右油游幼优友铀忧尤犹诱悠邮酉佑釉幽疣攸蚰莠鱿卣黝莸猷蚴宥牖囿柚蝣莜鼬铕蝤繇呦侑尢" + {0x6709, 0x53c8, 0x7531, 0x53f3, 0x6cb9, 0x6e38, 0x5e7c, 0x4f18, 0x53cb, 0x94c0, 0x5fe7, + 0x5c24, 0x72b9, 0x8bf1, 0x60a0, 0x90ae, 0x9149, 0x4f51, 0x91c9, 0x5e7d, 0x75a3, 0x6538, + 0x86b0, 0x83a0, 0x9c7f, 0x5363, 0x9edd, 0x83b8, 0x7337, 0x86b4, 0x5ba5, 0x7256, 0x56ff, + 0x67da, 0x8763, 0x839c, 0x9f2c, 0x94d5, 0x8764, 0x7e47, 0x5466, 0x4f91, 0x5c22}}, + {"yu", // L"与于欲鱼雨余遇语愈狱玉渔予誉育愚羽虞娱淤舆屿禹宇迂俞逾域芋郁吁盂喻峪御愉渝尉榆隅浴寓裕预豫驭蔚妪嵛雩馀阈窬鹆妤揄窳觎臾舁龉蓣煜钰谀纡於竽瑜禺聿欤俣伛圄鹬庾昱萸瘐谕鬻圉瘀熨饫毓燠腴狳菀蜮蝓吾" + {0x4e0e, 0x4e8e, 0x6b32, 0x9c7c, 0x96e8, 0x4f59, 0x9047, 0x8bed, 0x6108, 0x72f1, 0x7389, + 0x6e14, 0x4e88, 0x8a89, 0x80b2, 0x611a, 0x7fbd, 0x865e, 0x5a31, 0x6de4, 0x8206, 0x5c7f, + 0x79b9, 0x5b87, 0x8fc2, 0x4fde, 0x903e, 0x57df, 0x828b, 0x90c1, 0x5401, 0x76c2, 0x55bb, + 0x5cea, 0x5fa1, 0x6109, 0x6e1d, 0x5c09, 0x6986, 0x9685, 0x6d74, 0x5bd3, 0x88d5, 0x9884, + 0x8c6b, 0x9a6d, 0x851a, 0x59aa, 0x5d5b, 0x96e9, 0x9980, 0x9608, 0x7aac, 0x9e46, 0x59a4, + 0x63c4, 0x7ab3, 0x89ce, 0x81fe, 0x8201, 0x9f89, 0x84e3, 0x715c, 0x94b0, 0x8c00, 0x7ea1, + 0x65bc, 0x7afd, 0x745c, 0x79ba, 0x807f, 0x6b24, 0x4fe3, 0x4f1b, 0x5704, 0x9e6c, 0x5ebe, + 0x6631, 0x8438, 0x7610, 0x8c15, 0x9b3b, 0x5709, 0x7600, 0x71a8, 0x996b, 0x6bd3, 0x71e0, + 0x8174, 0x72f3, 0x83c0, 0x872e, 0x8753, 0x543e}}, + {"yuan", // L"远员元院圆原愿园援猿怨冤源缘袁渊苑垣鸳辕圜鼋橼媛爰眢鸢掾芫沅瑗螈箢塬" + {0x8fdc, 0x5458, 0x5143, 0x9662, 0x5706, 0x539f, 0x613f, 0x56ed, 0x63f4, + 0x733f, 0x6028, 0x51a4, 0x6e90, 0x7f18, 0x8881, 0x6e0a, 0x82d1, 0x57a3, + 0x9e33, 0x8f95, 0x571c, 0x9f0b, 0x6a7c, 0x5a9b, 0x7230, 0x7722, 0x9e22, + 0x63be, 0x82ab, 0x6c85, 0x7457, 0x8788, 0x7ba2, 0x586c}}, + {"yue", // L"月越约跃阅乐岳悦曰说粤钥瀹钺刖龠栎樾哕" + {0x6708, 0x8d8a, 0x7ea6, 0x8dc3, 0x9605, 0x4e50, 0x5cb3, 0x60a6, 0x66f0, 0x8bf4, 0x7ca4, + 0x94a5, 0x7039, 0x94ba, 0x5216, 0x9fa0, 0x680e, 0x6a3e, 0x54d5}}, + {"yun", // L"云运晕允匀韵陨孕耘蕴酝郧员氲恽愠郓芸筠韫昀狁殒纭熨" + {0x4e91, 0x8fd0, 0x6655, 0x5141, 0x5300, 0x97f5, 0x9668, 0x5b55, 0x8018, + 0x8574, 0x915d, 0x90e7, 0x5458, 0x6c32, 0x607d, 0x6120, 0x90d3, 0x82b8, + 0x7b60, 0x97eb, 0x6600, 0x72c1, 0x6b92, 0x7ead, 0x71a8}}, + {"za", // L"杂砸咋匝扎咂拶" + {0x6742, 0x7838, 0x548b, 0x531d, 0x624e, 0x5482, 0x62f6}}, + {"zai", // L"在再灾载栽宰哉甾崽" + {0x5728, 0x518d, 0x707e, 0x8f7d, 0x683d, 0x5bb0, 0x54c9, 0x753e, 0x5d3d}}, + {"zan", // L"咱暂攒赞簪趱糌瓒拶昝錾" + {0x54b1, 0x6682, 0x6512, 0x8d5e, 0x7c2a, 0x8db1, 0x7ccc, 0x74d2, 0x62f6, 0x661d, 0x933e}}, + {"zang", // L"脏葬赃藏臧驵" + {0x810f, 0x846c, 0x8d43, 0x85cf, 0x81e7, 0x9a75}}, + {"zao", // L"早造遭糟灶燥枣凿躁藻皂噪澡蚤唣" + {0x65e9, 0x9020, 0x906d, 0x7cdf, 0x7076, 0x71e5, 0x67a3, 0x51ff, 0x8e81, 0x85fb, 0x7682, + 0x566a, 0x6fa1, 0x86a4, 0x5523}}, + {"ze", // L"则责择泽咋箦舴帻迮啧仄昃笮赜" + {0x5219, 0x8d23, 0x62e9, 0x6cfd, 0x548b, 0x7ba6, 0x8234, 0x5e3b, 0x8fee, 0x5567, 0x4ec4, + 0x6603, 0x7b2e, 0x8d5c}}, + {"zei", // L"贼" + {0x8d3c}}, + {"zen", // L"怎谮" + {0x600e, 0x8c2e}}, + {"zeng", // L"增赠憎曾缯罾甑锃" + {0x589e, 0x8d60, 0x618e, 0x66fe, 0x7f2f, 0x7f7e, 0x7511, 0x9503}}, + {"zha", // L"扎炸渣闸眨榨乍轧诈铡札查栅咋喳砟痄吒哳楂蚱揸喋柞咤齄龃" + {0x624e, 0x70b8, 0x6e23, 0x95f8, 0x7728, 0x69a8, 0x4e4d, 0x8f67, 0x8bc8, + 0x94e1, 0x672d, 0x67e5, 0x6805, 0x548b, 0x55b3, 0x781f, 0x75c4, 0x5412, + 0x54f3, 0x6942, 0x86b1, 0x63f8, 0x558b, 0x67de, 0x54a4, 0x9f44, 0x9f83}}, + {"zhai", // L"摘窄债斋寨择翟宅砦瘵" + {0x6458, 0x7a84, 0x503a, 0x658b, 0x5be8, 0x62e9, 0x7fdf, 0x5b85, 0x7826, 0x7635}}, + {"zhan", // L"站占战盏沾粘毡展栈詹颤蘸湛绽斩辗崭瞻谵搌旃骣" + {0x7ad9, 0x5360, 0x6218, 0x76cf, 0x6cbe, 0x7c98, 0x6be1, 0x5c55, 0x6808, 0x8a79, 0x98a4, + 0x8638, 0x6e5b, 0x7efd, 0x65a9, 0x8f97, 0x5d2d, 0x77bb, 0x8c35, 0x640c, 0x65c3, 0x9aa3}}, + {"zhang", // L"张章长帐仗丈掌涨账樟杖彰漳胀瘴障仉嫜幛鄣璋嶂獐蟑" + {0x5f20, 0x7ae0, 0x957f, 0x5e10, 0x4ed7, 0x4e08, 0x638c, 0x6da8, + 0x8d26, 0x6a1f, 0x6756, 0x5f70, 0x6f33, 0x80c0, 0x7634, 0x969c, + 0x4ec9, 0x5adc, 0x5e5b, 0x9123, 0x748b, 0x5d82, 0x7350, 0x87d1}}, + {"zhao", // L"找着照招罩爪兆朝昭沼肇召赵棹啁钊笊诏" + {0x627e, 0x7740, 0x7167, 0x62db, 0x7f69, 0x722a, 0x5146, 0x671d, 0x662d, 0x6cbc, 0x8087, + 0x53ec, 0x8d75, 0x68f9, 0x5541, 0x948a, 0x7b0a, 0x8bcf}}, + {"zhe", // L"着这者折遮蛰哲蔗锗辙浙柘辄赭摺鹧磔褶蜇谪" + {0x7740, 0x8fd9, 0x8005, 0x6298, 0x906e, 0x86f0, 0x54f2, 0x8517, 0x9517, 0x8f99, + 0x6d59, 0x67d8, 0x8f84, 0x8d6d, 0x647a, 0x9e67, 0x78d4, 0x8936, 0x8707, 0x8c2a}}, + {"zhen", // L"真阵镇针震枕振斟珍疹诊甄砧臻贞侦缜蓁祯箴轸榛稹赈朕鸩胗浈桢畛圳椹溱" + {0x771f, 0x9635, 0x9547, 0x9488, 0x9707, 0x6795, 0x632f, 0x659f, 0x73cd, 0x75b9, 0x8bca, + 0x7504, 0x7827, 0x81fb, 0x8d1e, 0x4fa6, 0x7f1c, 0x84c1, 0x796f, 0x7bb4, 0x8f78, 0x699b, + 0x7a39, 0x8d48, 0x6715, 0x9e29, 0x80d7, 0x6d48, 0x6862, 0x755b, 0x5733, 0x6939, 0x6eb1}}, + {"zheng", // L"正整睁争挣征怔证症郑拯蒸狰政峥钲铮筝诤徵鲭" + {0x6b63, 0x6574, 0x7741, 0x4e89, 0x6323, 0x5f81, 0x6014, 0x8bc1, 0x75c7, 0x90d1, 0x62ef, + 0x84b8, 0x72f0, 0x653f, 0x5ce5, 0x94b2, 0x94ee, 0x7b5d, 0x8be4, 0x5fb5, 0x9cad}}, + {"zhi", // L"只之直知制指纸支芝枝稚吱蜘质肢脂汁炙织职痔植抵殖执值侄址滞止趾治旨窒志挚掷至致置帜识峙智秩帙摭黹桎枳轵忮祉蛭膣觯郅栀彘芷祗咫鸷絷踬胝骘轾痣陟踯雉埴贽卮酯豸跖栉夂徵" + {0x53ea, 0x4e4b, 0x76f4, 0x77e5, 0x5236, 0x6307, 0x7eb8, 0x652f, 0x829d, 0x679d, 0x7a1a, + 0x5431, 0x8718, 0x8d28, 0x80a2, 0x8102, 0x6c41, 0x7099, 0x7ec7, 0x804c, 0x75d4, 0x690d, + 0x62b5, 0x6b96, 0x6267, 0x503c, 0x4f84, 0x5740, 0x6ede, 0x6b62, 0x8dbe, 0x6cbb, 0x65e8, + 0x7a92, 0x5fd7, 0x631a, 0x63b7, 0x81f3, 0x81f4, 0x7f6e, 0x5e1c, 0x8bc6, 0x5cd9, 0x667a, + 0x79e9, 0x5e19, 0x646d, 0x9ef9, 0x684e, 0x67b3, 0x8f75, 0x5fee, 0x7949, 0x86ed, 0x81a3, + 0x89ef, 0x90c5, 0x6800, 0x5f58, 0x82b7, 0x7957, 0x54ab, 0x9e37, 0x7d77, 0x8e2c, 0x80dd, + 0x9a98, 0x8f7e, 0x75e3, 0x965f, 0x8e2f, 0x96c9, 0x57f4, 0x8d3d, 0x536e, 0x916f, 0x8c78, + 0x8dd6, 0x6809, 0x5902, 0x5fb5}}, + {"zhong", // L"中重种钟肿众终盅忠仲衷踵舯螽锺冢忪" + {0x4e2d, 0x91cd, 0x79cd, 0x949f, 0x80bf, 0x4f17, 0x7ec8, 0x76c5, 0x5fe0, 0x4ef2, 0x8877, + 0x8e35, 0x822f, 0x87bd, 0x953a, 0x51a2, 0x5fea}}, + {"zhou", // L"周洲皱粥州轴舟昼骤宙诌肘帚咒繇胄纣荮啁碡绉籀妯酎" + {0x5468, 0x6d32, 0x76b1, 0x7ca5, 0x5dde, 0x8f74, 0x821f, 0x663c, + 0x9aa4, 0x5b99, 0x8bcc, 0x8098, 0x5e1a, 0x5492, 0x7e47, 0x80c4, + 0x7ea3, 0x836e, 0x5541, 0x78a1, 0x7ec9, 0x7c40, 0x59af, 0x914e}}, + {"zhu", // L"住主猪竹株煮筑著贮铸嘱拄注祝驻属术珠瞩蛛朱柱诸诛逐助烛蛀潴洙伫瘃翥茱苎橥舳杼箸炷侏铢疰渚褚躅麈邾槠竺丶" + {0x4f4f, 0x4e3b, 0x732a, 0x7af9, 0x682a, 0x716e, 0x7b51, 0x8457, 0x8d2e, 0x94f8, 0x5631, + 0x62c4, 0x6ce8, 0x795d, 0x9a7b, 0x5c5e, 0x672f, 0x73e0, 0x77a9, 0x86db, 0x6731, 0x67f1, + 0x8bf8, 0x8bdb, 0x9010, 0x52a9, 0x70db, 0x86c0, 0x6f74, 0x6d19, 0x4f2b, 0x7603, 0x7fe5, + 0x8331, 0x82ce, 0x6a65, 0x8233, 0x677c, 0x7bb8, 0x70b7, 0x4f8f, 0x94e2, 0x75b0, 0x6e1a, + 0x891a, 0x8e85, 0x9e88, 0x90be, 0x69e0, 0x7afa, 0x4e36}}, + {"zhua", // L"抓爪挝" + {0x6293, 0x722a, 0x631d}}, + {"zhuai", // L"拽转" + {0x62fd, 0x8f6c}}, + {"zhuan", // L"转专砖赚传撰篆颛馔啭沌" + {0x8f6c, 0x4e13, 0x7816, 0x8d5a, 0x4f20, 0x64b0, 0x7bc6, 0x989b, 0x9994, 0x556d, 0x6c8c}}, + {"zhuang", // L"装撞庄壮桩状幢妆奘戆" + {0x88c5, 0x649e, 0x5e84, 0x58ee, 0x6869, 0x72b6, 0x5e62, 0x5986, 0x5958, 0x6206}}, + {"zhui", // L"追坠缀锥赘椎骓惴缒隹" + {0x8ffd, 0x5760, 0x7f00, 0x9525, 0x8d58, 0x690e, 0x9a93, 0x60f4, 0x7f12, 0x96b9}}, + {"zhun", // L"准谆肫窀饨" + {0x51c6, 0x8c06, 0x80ab, 0x7a80, 0x9968}}, + {"zhuo", // L"捉桌着啄拙灼浊卓琢茁酌擢焯濯诼浞涿倬镯禚斫淖" + {0x6349, 0x684c, 0x7740, 0x5544, 0x62d9, 0x707c, 0x6d4a, 0x5353, 0x7422, 0x8301, 0x914c, + 0x64e2, 0x712f, 0x6fef, 0x8bfc, 0x6d5e, 0x6dbf, 0x502c, 0x956f, 0x799a, 0x65ab, 0x6dd6}}, + {"zi", // L"字自子紫籽资姿吱滓仔兹咨孜渍滋淄笫粢龇秭恣谘趑缁梓鲻锱孳耔觜髭赀茈訾嵫眦姊辎" + {0x5b57, 0x81ea, 0x5b50, 0x7d2b, 0x7c7d, 0x8d44, 0x59ff, 0x5431, 0x6ed3, 0x4ed4, + 0x5179, 0x54a8, 0x5b5c, 0x6e0d, 0x6ecb, 0x6dc4, 0x7b2b, 0x7ca2, 0x9f87, 0x79ed, + 0x6063, 0x8c18, 0x8d91, 0x7f01, 0x6893, 0x9cbb, 0x9531, 0x5b73, 0x8014, 0x89dc, + 0x9aed, 0x8d40, 0x8308, 0x8a3e, 0x5d6b, 0x7726, 0x59ca, 0x8f8e}}, + {"zong", // L"总纵宗棕综踪鬃偬粽枞腙" + {0x603b, 0x7eb5, 0x5b97, 0x68d5, 0x7efc, 0x8e2a, 0x9b03, 0x506c, 0x7cbd, 0x679e, 0x8159}}, + {"zou", // L"走揍奏邹鲰鄹陬驺诹" + {0x8d70, 0x63cd, 0x594f, 0x90b9, 0x9cb0, 0x9139, 0x966c, 0x9a7a, 0x8bf9}}, + {"zu", // L"组族足阻租祖诅菹镞卒俎" + {0x7ec4, 0x65cf, 0x8db3, 0x963b, 0x79df, 0x7956, 0x8bc5, 0x83f9, 0x955e, 0x5352, 0x4fce}}, + {"zuan", // L"钻纂缵躜攥" + {0x94bb, 0x7e82, 0x7f35, 0x8e9c, 0x6525}}, + {"zui", // L"最嘴醉罪觜蕞" + {0x6700, 0x5634, 0x9189, 0x7f6a, 0x89dc, 0x855e}}, + {"zun", // L"尊遵鳟撙樽" + {0x5c0a, 0x9075, 0x9cdf, 0x6499, 0x6a3d}}, + {"zuo", // L"做作坐左座昨琢撮佐嘬酢唑祚胙怍阼柞砟" + {0x505a, 0x4f5c, 0x5750, 0x5de6, 0x5ea7, 0x6628, 0x7422, 0x64ae, 0x4f50, 0x562c, 0x9162, + 0x5511, 0x795a, 0x80d9, 0x600d, 0x963c, 0x67de, 0x781f}}, +}; + +CInputCodingTableBasePY::CInputCodingTableBasePY() +{ + m_codechars = "abcdefghijklmnopqrstuvwxyz"; +} + +std::vector<std::wstring> CInputCodingTableBasePY::GetResponse(int) +{ + return m_words; +} + +bool CInputCodingTableBasePY::GetWordListPage(const std::string& strCode, bool isFirstPage) +{ + if (!isFirstPage) + return false; + + m_words.clear(); + auto finder = codemap.find(strCode); + if (finder != codemap.end()) + { + for (unsigned int i = 0; i < finder->second.size(); i++) + { + m_words.push_back(finder->second.substr(i, 1)); + } + } + CGUIMessage msg(GUI_MSG_CODINGTABLE_LOOKUP_COMPLETED, 0, 0, 0); + msg.SetStringParam(strCode); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage( + msg, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + return true; +} diff --git a/xbmc/input/InputCodingTableBasePY.h b/xbmc/input/InputCodingTableBasePY.h new file mode 100644 index 0000000..243b4de --- /dev/null +++ b/xbmc/input/InputCodingTableBasePY.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#pragma once + +#include "InputCodingTable.h" + +#include <map> +#include <string> +#include <vector> + +class CInputCodingTableBasePY : public IInputCodingTable +{ +public: + CInputCodingTableBasePY(); + ~CInputCodingTableBasePY() override = default; + + bool GetWordListPage(const std::string& strCode, bool isFirstPage) override; + std::vector<std::wstring> GetResponse(int) override; + +private: + std::vector<std::wstring> m_words; +}; diff --git a/xbmc/input/InputCodingTableFactory.cpp b/xbmc/input/InputCodingTableFactory.cpp new file mode 100644 index 0000000..9dbcfd2 --- /dev/null +++ b/xbmc/input/InputCodingTableFactory.cpp @@ -0,0 +1,24 @@ +/* + * 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 "InputCodingTableFactory.h" + +#include "InputCodingTableBasePY.h" +#include "InputCodingTableKorean.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +IInputCodingTable* CInputCodingTableFactory::CreateCodingTable(const std::string& strTableName, + const TiXmlElement* element) +{ + if (strTableName == "BasePY") + return new CInputCodingTableBasePY(); + if (strTableName == "Korean") + return new CInputCodingTableKorean(); + return nullptr; +} diff --git a/xbmc/input/InputCodingTableFactory.h b/xbmc/input/InputCodingTableFactory.h new file mode 100644 index 0000000..a2dc247 --- /dev/null +++ b/xbmc/input/InputCodingTableFactory.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +#include <string> + +class TiXmlElement; +class IInputCodingTable; + +class CInputCodingTableFactory +{ +public: + static IInputCodingTable* CreateCodingTable(const std::string& strTableName, + const TiXmlElement* element); +}; diff --git a/xbmc/input/InputCodingTableKorean.cpp b/xbmc/input/InputCodingTableKorean.cpp new file mode 100644 index 0000000..223f81d --- /dev/null +++ b/xbmc/input/InputCodingTableKorean.cpp @@ -0,0 +1,380 @@ +/* + * 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 "InputCodingTableKorean.h" + +#include "utils/CharsetConverter.h" + +#include <stdlib.h> + +CInputCodingTableKorean::CInputCodingTableKorean() = default; + +std::vector<std::wstring> CInputCodingTableKorean::GetResponse(int) +{ + return m_words; +} + +bool CInputCodingTableKorean::GetWordListPage(const std::string& strCode, bool isFirstPage) +{ + return false; +} + +void CInputCodingTableKorean::SetTextPrev(const std::string& strTextPrev) +{ + m_strTextPrev = strTextPrev; +} + +int CInputCodingTableKorean::MergeCode(int choseong, int jungseong, int jongseong) +{ + return (unsigned short)0xAC00 + choseong * 21 * 28 + jungseong * 28 + jongseong + 1; +} + +// Reference +// https://en.wikipedia.org/wiki/Hangul +// http://www.theyt.net/wiki/%ED%95%9C%EC%98%81%ED%83%80%EB%B3%80%ED%99%98%EA%B8%B0 + +std::wstring CInputCodingTableKorean::InputToKorean(const std::wstring& input) +{ + std::wstring dicEnglish = // L"rRseEfaqQtTdwWczxvgkoiOjpuPhynbml"; + {0x72, 0x52, 0x73, 0x65, 0x45, 0x66, 0x61, 0x71, 0x51, 0x74, 0x54, + 0x64, 0x77, 0x57, 0x63, 0x7A, 0x78, 0x76, 0x67, 0x6B, 0x6F, 0x69, + 0x4F, 0x6A, 0x70, 0x75, 0x50, 0x68, 0x79, 0x6E, 0x62, 0x6D, 0x6C}; + std::wstring dicKorean = // L"ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅐㅑㅒㅓㅔㅕㅖㅗㅛㅜㅠㅡㅣ"; + {0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139, 0x3141, 0x3142, 0x3143, 0x3145, 0x3146, + 0x3147, 0x3148, 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e, 0x314f, 0x3150, 0x3151, + 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x315b, 0x315c, 0x3160, 0x3161, 0x3163}; + std::wstring dicChoseong = // L"ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"; + {0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139, 0x3141, 0x3142, 0x3143, 0x3145, + 0x3146, 0x3147, 0x3148, 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e}; + std::wstring dicJungseong = // L"ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"; + {0x314f, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158, 0x3159, + 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160, 0x3161, 0x3162, 0x3163}; + std::wstring dicJongseong = // L"ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"; + {0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3139, 0x313a, + 0x313b, 0x313c, 0x313d, 0x313e, 0x313f, 0x3140, 0x3141, 0x3142, 0x3144, + 0x3145, 0x3146, 0x3147, 0x3148, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e}; + + std::wstring korean; + + if (input.empty()) + return korean; + + int choseong = -1, jungseong = -1, jongseong = -1; + + for (unsigned int i = 0; i < input.size(); i++) + { + wchar_t ch = input.at(i); + int key = dicKorean.find(ch); + + // H/W Keyboard input with English will be changed to Korean + // because H/W input in Korean is not supported. + if (key == -1) + key = dicEnglish.find(ch); + + if (key == -1) // If not Korean and English + { + // If there is remained Korean, merge code into character + if (choseong != -1) // There is choseong + { + if (jungseong != -1) // choseong+jungseong+(jongseong) + korean += MergeCode(choseong, jungseong, jongseong); + else // Only choseong + korean += dicChoseong.at(choseong); + } + else + { + if (jungseong != -1) // Jungseong + korean += dicJungseong.at(jungseong); + + if (jongseong != -1) // Jongseong + korean += dicJongseong.at(jongseong); + } + choseong = -1; + jungseong = -1; + jongseong = -1; + korean += ch; + } + else if (key < 19) // If key is consonant, key could be choseong or jungseong + { + if (jungseong != -1) + { + // Jungseong without choseong cannot have jongseong. + // So inputted key is jungseong character, new character is begun. + if (choseong == -1) + { + korean += dicJungseong.at(jungseong); + jungseong = -1; + choseong = key; + } + else // Jungseong with choseong can have jongseong. + { + // Chongseong can have two consonant. So this is first consonant of chongseong. + if (jongseong == -1) + { + jongseong = dicJongseong.find(dicKorean.at(key)); + if (jongseong == -1) // This consonant cannot be jongseong. ex) "ㄸ", "ㅃ", "ㅉ" + { + korean += MergeCode(choseong, jungseong, jongseong); + choseong = dicChoseong.find(dicKorean.at(key)); + jungseong = -1; + } + } + else if (jongseong == 0 && key == 9) // "ㄳ" + jongseong = 2; + else if (jongseong == 3 && key == 12) // "ㄵ" + jongseong = 4; + else if (jongseong == 3 && key == 18) // "ㄶ" + jongseong = 5; + else if (jongseong == 7 && key == 0) // "ㄺ" + jongseong = 8; + else if (jongseong == 7 && key == 6) // "ㄻ" + jongseong = 9; + else if (jongseong == 7 && key == 7) // "ㄼ" + jongseong = 10; + else if (jongseong == 7 && key == 9) // "ㄽ" + jongseong = 11; + else if (jongseong == 7 && key == 16) // "ㄾ" + jongseong = 12; + else if (jongseong == 7 && key == 17) // "ㄿ" + jongseong = 13; + else if (jongseong == 7 && key == 18) // "ㅀ" + jongseong = 14; + else if (jongseong == 16 && key == 9) // "ㅄ" + jongseong = 17; + else // Jongseong is completed. So new consonant is choseong. + { + korean += MergeCode(choseong, jungseong, jongseong); + choseong = dicChoseong.find(dicKorean.at(key)); + jungseong = -1; + jongseong = -1; + } + } + } + else // If there is no jungseong, new consonant can be choseong or second part of double + // consonant. + { + // New consonant is choseong. Also it could be first part of double consonant. + if (choseong == -1) + { + // If choseong is already completed, new consonant is another choseong. + // So previous character has only jongseong. + if (jongseong != -1) + { + korean += dicJongseong.at(jongseong); + jongseong = -1; + } + choseong = dicChoseong.find(dicKorean.at(key)); + } + // Find double consonant of chongseong + else if (choseong == 0 && key == 9) // "ㄳ" + { + choseong = -1; + jongseong = 2; + } + else if (choseong == 2 && key == 12) // "ㄵ" + { + choseong = -1; + jongseong = 4; + } + else if (choseong == 2 && key == 18) // "ㄶ" + { + choseong = -1; + jongseong = 5; + } + else if (choseong == 5 && key == 0) // "ㄺ" + { + choseong = -1; + jongseong = 8; + } + else if (choseong == 5 && key == 6) // "ㄻ" + { + choseong = -1; + jongseong = 9; + } + else if (choseong == 5 && key == 7) // "ㄼ" + { + choseong = -1; + jongseong = 10; + } + else if (choseong == 5 && key == 9) // "ㄽ" + { + choseong = -1; + jongseong = 11; + } + else if (choseong == 5 && key == 16) // "ㄾ" + { + choseong = -1; + jongseong = 12; + } + else if (choseong == 5 && key == 17) // "ㄿ" + { + choseong = -1; + jongseong = 13; + } + else if (choseong == 5 && key == 18) // "ㅀ" + { + choseong = -1; + jongseong = 14; + } + else if (choseong == 7 && key == 9) // "ㅄ" + { + choseong = -1; + jongseong = 17; + } + else // In this case, previous character has only choseong. And new consonant is choseong. + { + korean += dicChoseong.at(choseong); + choseong = dicChoseong.find(dicKorean.at(key)); + } + } + } + else // If key is vowel, key is jungseong. + { + // If previous character has jongseong and this key is jungseong, + // actually latest vowel is not jongseong. It's choseong of new character. + if (jongseong != -1) + { + // If jongseong of previous character is double consonant, we will separate it to two vowel + // again. First part of double consonant is jongseong of previous character. Second part of + // double consonant is choseong of current character. + int newCho; + if (jongseong == 2) // "ㄱ, ㅅ" + { + jongseong = 0; + newCho = 9; + } + else if (jongseong == 4) // "ㄴ, ㅈ" + { + jongseong = 3; + newCho = 12; + } + else if (jongseong == 5) // "ㄴ, ㅎ" + { + jongseong = 3; + newCho = 18; + } + else if (jongseong == 8) // "ㄹ, ㄱ" + { + jongseong = 7; + newCho = 0; + } + else if (jongseong == 9) // "ㄹ, ㅁ" + { + jongseong = 7; + newCho = 6; + } + else if (jongseong == 10) // "ㄹ, ㅂ" + { + jongseong = 7; + newCho = 7; + } + else if (jongseong == 11) // "ㄹ, ㅅ" + { + jongseong = 7; + newCho = 9; + } + else if (jongseong == 12) // "ㄹ, ㅌ" + { + jongseong = 7; + newCho = 16; + } + else if (jongseong == 13) // "ㄹ, ㅍ" + { + jongseong = 7; + newCho = 17; + } + else if (jongseong == 14) // "ㄹ, ㅎ" + { + jongseong = 7; + newCho = 18; + } + else if (jongseong == 17) // "ㅂ, ㅅ" + { + jongseong = 16; + newCho = 9; + } + else + { + // If jongseong is single consonant, previous character has no chongseong. + // It's choseong of current character. + newCho = dicChoseong.find(dicJongseong.at(jongseong)); + jongseong = -1; + } + if (choseong != -1) // If previous character has choseong and jungseong. + korean += MergeCode(choseong, jungseong, jongseong); + else // If previous character has Jongseong only. + korean += dicJongseong.at(jongseong); + + choseong = newCho; + jungseong = -1; + jongseong = -1; + } + if (jungseong == -1) // If this key is first vowel, it's first part of jungseong. + { + jungseong = dicJungseong.find(dicKorean.at(key)); + } + // If there is jungseong already, jungseong is double vowel. + else if (jungseong == 8 && key == 19) // "ㅘ" + jungseong = 9; + else if (jungseong == 8 && key == 20) // "ㅙ" + jungseong = 10; + else if (jungseong == 8 && key == 32) // "ㅚ" + jungseong = 11; + else if (jungseong == 13 && key == 23) // "ㅝ" + jungseong = 14; + else if (jungseong == 13 && key == 24) // "ㅞ" + jungseong = 15; + else if (jungseong == 13 && key == 32) // "ㅟ" + jungseong = 16; + else if (jungseong == 18 && key == 32) // "ㅢ" + jungseong = 19; + else // If two vowel cannot be double vowel. + { + // Previous character is completed. + // Current character is begin with jungseong. + if (choseong != -1) + { + korean += MergeCode(choseong, jungseong, jongseong); + choseong = -1; + } + else // Previous character has jungseon only. + korean += dicJungseong.at(jungseong); + jungseong = -1; + korean += dicKorean.at(key); + } + } + } + + // Process last character + if (choseong != -1) + { + if (jungseong != -1) // Current character has choseong and jungseong. + korean += MergeCode(choseong, jungseong, jongseong); + else // Current character has choseong only. + korean += dicChoseong.at(choseong); + } + else + { + if (jungseong != -1) // Current character has jungseong only + korean += dicJungseong.at(jungseong); + else if (jongseong != -1) // Current character has jongseong only + korean += dicJongseong.at(jongseong); + } + + return korean; +} + +std::string CInputCodingTableKorean::ConvertString(const std::string& strCode) +{ + std::wstring input; + std::string result; + g_charsetConverter.utf8ToW(strCode, input); + InputToKorean(input); + g_charsetConverter.wToUTF8(InputToKorean(input), result); + return m_strTextPrev + result; +} diff --git a/xbmc/input/InputCodingTableKorean.h b/xbmc/input/InputCodingTableKorean.h new file mode 100644 index 0000000..55a64d6 --- /dev/null +++ b/xbmc/input/InputCodingTableKorean.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include "InputCodingTable.h" + +#include <map> +#include <string> +#include <vector> + +class CInputCodingTableKorean : public IInputCodingTable +{ +public: + CInputCodingTableKorean(); + ~CInputCodingTableKorean() override = default; + + bool GetWordListPage(const std::string& strCode, bool isFirstPage) override; + std::vector<std::wstring> GetResponse(int) override; + + void SetTextPrev(const std::string& strTextPrev) override; + std::string ConvertString(const std::string& strCode) override; + int GetType() override { return TYPE_CONVERT_STRING; } + +protected: + int MergeCode(int choseong, int jungseong, int jongseong); + std::wstring InputToKorean(const std::wstring& input); + +private: + std::vector<std::wstring> m_words; + std::string m_strTextPrev; +}; diff --git a/xbmc/input/InputManager.cpp b/xbmc/input/InputManager.cpp new file mode 100644 index 0000000..571b259 --- /dev/null +++ b/xbmc/input/InputManager.cpp @@ -0,0 +1,938 @@ +/* + * 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 "InputManager.h" + +#include "ButtonTranslator.h" +#include "CustomControllerTranslator.h" +#include "JoystickMapper.h" +#include "KeymapEnvironment.h" +#include "ServiceBroker.h" +#include "TouchTranslator.h" +#include "XBMC_vkeys.h" +#include "application/AppInboundProtocol.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIControl.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" +#include "input/keyboard/KeyboardEasterEgg.h" +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" +#include "input/mouse/MouseTranslator.h" +#include "input/mouse/interfaces/IMouseDriverHandler.h" +#include "messaging/ApplicationMessenger.h" +#include "network/EventServer.h" +#include "peripherals/Peripherals.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/ExecString.h" +#include "utils/Geometry.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <math.h> +#include <mutex> + +using EVENTSERVER::CEventServer; + +using namespace KODI; + +const std::string CInputManager::SETTING_INPUT_ENABLE_CONTROLLER = "input.enablejoystick"; + +CInputManager::CInputManager() + : m_keymapEnvironment(new CKeymapEnvironment), + m_buttonTranslator(new CButtonTranslator), + m_customControllerTranslator(new CCustomControllerTranslator), + m_touchTranslator(new CTouchTranslator), + m_joystickTranslator(new CJoystickMapper), + m_keyboardEasterEgg(new KEYBOARD::CKeyboardEasterEgg) +{ + m_buttonTranslator->RegisterMapper("touch", m_touchTranslator.get()); + m_buttonTranslator->RegisterMapper("customcontroller", m_customControllerTranslator.get()); + m_buttonTranslator->RegisterMapper("joystick", m_joystickTranslator.get()); + + RegisterKeyboardDriverHandler(m_keyboardEasterEgg.get()); + + // Register settings + std::set<std::string> settingSet; + settingSet.insert(CSettings::SETTING_INPUT_ENABLEMOUSE); + settingSet.insert(SETTING_INPUT_ENABLE_CONTROLLER); + CServiceBroker::GetSettingsComponent()->GetSettings()->RegisterCallback(this, settingSet); +} + +CInputManager::~CInputManager() +{ + Deinitialize(); + + // Unregister settings + CServiceBroker::GetSettingsComponent()->GetSettings()->UnregisterCallback(this); + + UnregisterKeyboardDriverHandler(m_keyboardEasterEgg.get()); + + m_buttonTranslator->UnregisterMapper(m_touchTranslator.get()); + m_buttonTranslator->UnregisterMapper(m_customControllerTranslator.get()); + m_buttonTranslator->UnregisterMapper(m_joystickTranslator.get()); +} + +void CInputManager::InitializeInputs() +{ + m_Keyboard.Initialize(); + + m_Mouse.Initialize(); + m_Mouse.SetEnabled(CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_INPUT_ENABLEMOUSE)); + + m_enableController = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + SETTING_INPUT_ENABLE_CONTROLLER); +} + +void CInputManager::Deinitialize() +{ +} + +bool CInputManager::ProcessPeripherals(float frameTime) +{ + CKey key; + if (CServiceBroker::GetPeripherals().GetNextKeypress(frameTime, key)) + return OnKey(key); + return false; +} + +bool CInputManager::ProcessMouse(int windowId) +{ + if (!m_Mouse.IsActive() || !g_application.IsAppFocused()) + return false; + + // Get the mouse command ID + uint32_t mousekey = m_Mouse.GetKey(); + if (mousekey == KEY_MOUSE_NOOP) + return true; + + // Reset the screensaver and idle timers + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + appPower->ResetScreenSaver(); + + if (appPower->WakeUpScreenSaverAndDPMS()) + return true; + + // Retrieve the corresponding action + CKey key(mousekey, (unsigned int)0); + CAction mouseaction = m_buttonTranslator->GetAction(windowId, key); + + // Deactivate mouse if non-mouse action + if (!mouseaction.IsMouse()) + m_Mouse.SetActive(false); + + // Consume ACTION_NOOP. + // Some views or dialogs gets closed after any ACTION and + // a sensitive mouse might cause problems. + if (mouseaction.GetID() == ACTION_NOOP) + return false; + + // If we couldn't find an action return false to indicate we have not + // handled this mouse action + if (!mouseaction.GetID()) + { + CLog::LogF(LOGDEBUG, "unknown mouse command {}", mousekey); + return false; + } + + // Log mouse actions except for move and noop + if (mouseaction.GetID() != ACTION_MOUSE_MOVE && mouseaction.GetID() != ACTION_NOOP) + CLog::LogF(LOGDEBUG, "trying mouse action {}", mouseaction.GetName()); + + // The action might not be a mouse action. For example wheel moves might + // be mapped to volume up/down in mouse.xml. In this case we do not want + // the mouse position saved in the action. + if (!mouseaction.IsMouse()) + return g_application.OnAction(mouseaction); + + // This is a mouse action so we need to record the mouse position + return g_application.OnAction( + CAction(mouseaction.GetID(), static_cast<uint32_t>(m_Mouse.GetHold(MOUSE_LEFT_BUTTON)), + static_cast<float>(m_Mouse.GetX()), static_cast<float>(m_Mouse.GetY()), + static_cast<float>(m_Mouse.GetDX()), static_cast<float>(m_Mouse.GetDY()), 0.0f, 0.0f, + mouseaction.GetName())); +} + +bool CInputManager::ProcessEventServer(int windowId, float frameTime) +{ + CEventServer* es = CEventServer::GetInstance(); + if (!es || !es->Running() || es->GetNumberOfClients() == 0) + return false; + + // process any queued up actions + if (es->ExecuteNextAction()) + { + // reset idle timers + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + } + + // now handle any buttons or axis + std::string strMapName; + bool isAxis = false; + float fAmount = 0.0; + bool isJoystick = false; + + // es->ExecuteNextAction() invalidates the ref to the CEventServer instance + // when the action exits XBMC + es = CEventServer::GetInstance(); + if (!es || !es->Running() || es->GetNumberOfClients() == 0) + return false; + unsigned int wKeyID = es->GetButtonCode(strMapName, isAxis, fAmount, isJoystick); + + if (wKeyID) + { + if (strMapName.length() > 0) + { + // joysticks are not supported via eventserver + if (isJoystick) + { + return false; + } + else // it is a customcontroller + { + int actionID; + std::string actionName; + + // Translate using custom controller translator. + if (m_customControllerTranslator->TranslateCustomControllerString( + windowId, strMapName, wKeyID, actionID, actionName)) + { + // break screensaver + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + appPower->ResetScreenSaver(); + + // in case we wokeup the screensaver or screen - eat that action... + if (appPower->WakeUpScreenSaverAndDPMS()) + return true; + + m_Mouse.SetActive(false); + + CLog::Log(LOGDEBUG, "EventServer: key {} translated to action {}", wKeyID, actionName); + + return ExecuteInputAction(CAction(actionID, fAmount, 0.0f, actionName)); + } + else + { + CLog::Log(LOGDEBUG, "ERROR mapping customcontroller action. CustomController: {} {}", + strMapName, wKeyID); + } + } + } + else + { + CKey key; + if (wKeyID & ES_FLAG_UNICODE) + { + key = CKey(0u, 0u, static_cast<wchar_t>(wKeyID & ~ES_FLAG_UNICODE), 0, 0, 0, 0); + return OnKey(key); + } + + if (wKeyID == KEY_BUTTON_LEFT_ANALOG_TRIGGER) + key = CKey(wKeyID, static_cast<uint8_t>(255 * fAmount), 0, 0.0, 0.0, 0.0, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_RIGHT_ANALOG_TRIGGER) + key = CKey(wKeyID, 0, static_cast<uint8_t>(255 * fAmount), 0.0, 0.0, 0.0, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_LEFT_THUMB_STICK_LEFT) + key = CKey(wKeyID, 0, 0, -fAmount, 0.0, 0.0, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_LEFT_THUMB_STICK_RIGHT) + key = CKey(wKeyID, 0, 0, fAmount, 0.0, 0.0, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_LEFT_THUMB_STICK_UP) + key = CKey(wKeyID, 0, 0, 0.0, fAmount, 0.0, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_LEFT_THUMB_STICK_DOWN) + key = CKey(wKeyID, 0, 0, 0.0, -fAmount, 0.0, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_RIGHT_THUMB_STICK_LEFT) + key = CKey(wKeyID, 0, 0, 0.0, 0.0, -fAmount, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_RIGHT_THUMB_STICK_RIGHT) + key = CKey(wKeyID, 0, 0, 0.0, 0.0, fAmount, 0.0, frameTime); + else if (wKeyID == KEY_BUTTON_RIGHT_THUMB_STICK_UP) + key = CKey(wKeyID, 0, 0, 0.0, 0.0, 0.0, fAmount, frameTime); + else if (wKeyID == KEY_BUTTON_RIGHT_THUMB_STICK_DOWN) + key = CKey(wKeyID, 0, 0, 0.0, 0.0, 0.0, -fAmount, frameTime); + else + key = CKey(wKeyID); + key.SetFromService(true); + return OnKey(key); + } + } + + { + CPoint pos; + if (es->GetMousePos(pos.x, pos.y) && m_Mouse.IsEnabled()) + { + XBMC_Event newEvent = {}; + newEvent.type = XBMC_MOUSEMOTION; + newEvent.motion.x = (uint16_t)pos.x; + newEvent.motion.y = (uint16_t)pos.y; + CServiceBroker::GetAppPort()->OnEvent( + newEvent); // had to call this to update g_Mouse position + return g_application.OnAction(CAction(ACTION_MOUSE_MOVE, pos.x, pos.y)); + } + } + + return false; +} + +void CInputManager::ProcessQueuedActions() +{ + std::vector<CAction> queuedActions; + { + std::unique_lock<CCriticalSection> lock(m_actionMutex); + queuedActions.swap(m_queuedActions); + } + + for (const CAction& action : queuedActions) + g_application.OnAction(action); +} + +void CInputManager::QueueAction(const CAction& action) +{ + std::unique_lock<CCriticalSection> lock(m_actionMutex); + + // Avoid dispatching multiple analog actions per frame with the same ID + if (action.IsAnalog()) + { + m_queuedActions.erase(std::remove_if(m_queuedActions.begin(), m_queuedActions.end(), + [&action](const CAction& queuedAction) { + return action.GetID() == queuedAction.GetID(); + }), + m_queuedActions.end()); + } + + m_queuedActions.push_back(action); +} + +bool CInputManager::Process(int windowId, float frameTime) +{ + // process input actions + ProcessEventServer(windowId, frameTime); + ProcessPeripherals(frameTime); + ProcessQueuedActions(); + + // Inform the environment of the new active window ID + m_keymapEnvironment->SetWindowID(windowId); + + return true; +} + +bool CInputManager::OnEvent(XBMC_Event& newEvent) +{ + switch (newEvent.type) + { + case XBMC_KEYDOWN: + { + m_Keyboard.ProcessKeyDown(newEvent.key.keysym); + CKey key = m_Keyboard.TranslateKey(newEvent.key.keysym); + OnKey(key); + break; + } + case XBMC_KEYUP: + m_Keyboard.ProcessKeyUp(); + OnKeyUp(m_Keyboard.TranslateKey(newEvent.key.keysym)); + break; + case XBMC_MOUSEBUTTONDOWN: + case XBMC_MOUSEBUTTONUP: + case XBMC_MOUSEMOTION: + { + bool handled = false; + + for (auto driverHandler : m_mouseHandlers) + { + switch (newEvent.type) + { + case XBMC_MOUSEMOTION: + { + if (driverHandler->OnPosition(newEvent.motion.x, newEvent.motion.y)) + handled = true; + break; + } + case XBMC_MOUSEBUTTONDOWN: + { + MOUSE::BUTTON_ID buttonId; + if (CMouseTranslator::TranslateEventID(newEvent.button.button, buttonId)) + { + if (driverHandler->OnButtonPress(buttonId)) + handled = true; + } + break; + } + case XBMC_MOUSEBUTTONUP: + { + MOUSE::BUTTON_ID buttonId; + if (CMouseTranslator::TranslateEventID(newEvent.button.button, buttonId)) + driverHandler->OnButtonRelease(buttonId); + break; + } + default: + break; + } + + if (handled) + break; + } + + if (!handled) + { + m_Mouse.HandleEvent(newEvent); + ProcessMouse(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + } + break; + } + case XBMC_TOUCH: + { + if (newEvent.touch.action == ACTION_TOUCH_TAP) + { // Send a mouse motion event with no dx,dy for getting the current guiitem selected + g_application.OnAction( + CAction(ACTION_MOUSE_MOVE, 0, newEvent.touch.x, newEvent.touch.y, 0, 0)); + } + int actionId = 0; + std::string actionString; + if (newEvent.touch.action == ACTION_GESTURE_BEGIN || + newEvent.touch.action == ACTION_GESTURE_END || + newEvent.touch.action == ACTION_GESTURE_ABORT) + actionId = newEvent.touch.action; + else + { + int iWin = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(); + m_touchTranslator->TranslateTouchAction(iWin, newEvent.touch.action, + newEvent.touch.pointers, actionId, actionString); + } + + if (actionId <= 0) + return false; + + if ((actionId >= ACTION_TOUCH_TAP && actionId <= ACTION_GESTURE_END) || + (actionId >= ACTION_MOUSE_START && actionId <= ACTION_MOUSE_END)) + { + auto action = + new CAction(actionId, 0, newEvent.touch.x, newEvent.touch.y, newEvent.touch.x2, + newEvent.touch.y2, newEvent.touch.x3, newEvent.touch.y3); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(action)); + } + else + { + if (actionId == ACTION_BUILT_IN_FUNCTION && !actionString.empty()) + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionId, actionString))); + else + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionId))); + } + + break; + } // case + case XBMC_BUTTON: + { + HandleKey( + m_buttonStat.TranslateKey(CKey(newEvent.keybutton.button, newEvent.keybutton.holdtime))); + break; + } + } // switch + + return true; +} + +// OnKey() translates the key into a CAction which is sent on to our Window Manager. +// The window manager will return true if the event is processed, false otherwise. +// If not already processed, this routine handles global keypresses. It returns +// true if the key has been processed, false otherwise. + +bool CInputManager::OnKey(const CKey& key) +{ + bool bHandled = false; + + for (auto handler : m_keyboardHandlers) + { + if (handler->OnKeyPress(key)) + { + bHandled = true; + break; + } + } + + if (bHandled) + { + m_LastKey.Reset(); + } + else + { + if (key.GetButtonCode() == m_LastKey.GetButtonCode() && + (m_LastKey.GetButtonCode() & CKey::MODIFIER_LONG)) + { + // Do not repeat long presses + } + else + { + // Event server keyboard doesn't give normal key up and key down, so don't + // process for long press if that is the source + if (key.GetFromService() || + !m_buttonTranslator->HasLongpressMapping( + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), key)) + { + m_LastKey.Reset(); + bHandled = HandleKey(key); + } + else + { + if (key.GetButtonCode() != m_LastKey.GetButtonCode() && + (key.GetButtonCode() & CKey::MODIFIER_LONG)) + { + m_LastKey = key; // OnKey is reentrant; need to do this before entering + bHandled = HandleKey(key); + } + + m_LastKey = key; + } + } + } + + return bHandled; +} + +bool CInputManager::HandleKey(const CKey& key) +{ + // Turn the mouse off, as we've just got a keypress from controller or remote + m_Mouse.SetActive(false); + + // get the current active window + int iWin = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(); + + // this will be checked for certain keycodes that need + // special handling if the screensaver is active + CAction action = m_buttonTranslator->GetAction(iWin, key); + + // a key has been pressed. + // reset Idle Timer + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + bool processKey = AlwaysProcess(action); + + if (StringUtils::StartsWithNoCase(action.GetName(), "CECToggleState") || + StringUtils::StartsWithNoCase(action.GetName(), "CECStandby")) + { + // do not wake up the screensaver right after switching off the playing device + if (StringUtils::StartsWithNoCase(action.GetName(), "CECToggleState")) + { + CLog::LogF(LOGDEBUG, "action {} [{}], toggling state of playing device", action.GetName(), + action.GetID()); + bool result; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_CECTOGGLESTATE, 0, 0, + static_cast<void*>(&result)); + if (!result) + return true; + } + else + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_CECSTANDBY); + return true; + } + } + + appPower->ResetScreenSaver(); + + // allow some keys to be processed while the screensaver is active + if (appPower->WakeUpScreenSaverAndDPMS(processKey) && !processKey) + { + CLog::LogF(LOGDEBUG, "{} pressed, screen saver/dpms woken up", + m_Keyboard.GetKeyName((int)key.GetButtonCode())); + return true; + } + + if (iWin != WINDOW_FULLSCREEN_VIDEO && iWin != WINDOW_FULLSCREEN_GAME) + { + // current active window isnt the fullscreen window + // just use corresponding section from keymap.xml + // to map key->action + + // first determine if we should use keyboard input directly + bool useKeyboard = + key.FromKeyboard() && (iWin == WINDOW_DIALOG_KEYBOARD || iWin == WINDOW_DIALOG_NUMERIC); + CGUIWindow* window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(iWin); + if (window) + { + CGUIControl* control = window->GetFocusedControl(); + if (control) + { + // If this is an edit control set usekeyboard to true. This causes the + // keypress to be processed directly not through the key mappings. + if (control->GetControlType() == CGUIControl::GUICONTROL_EDIT) + useKeyboard = true; + + // If the key pressed is shift-A to shift-Z set usekeyboard to true. + // This causes the keypress to be used for list navigation. + if (control->IsContainer() && key.GetModifiers() == CKey::MODIFIER_SHIFT && + key.GetUnicode()) + useKeyboard = true; + } + } + if (useKeyboard) + { + // use the virtualkeyboard section of the keymap, and send keyboard-specific or navigation + // actions through if that's what they are + CAction action = m_buttonTranslator->GetAction(WINDOW_DIALOG_KEYBOARD, key); + if (!(action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_RIGHT || + action.GetID() == ACTION_MOVE_UP || action.GetID() == ACTION_MOVE_DOWN || + action.GetID() == ACTION_SELECT_ITEM || action.GetID() == ACTION_ENTER || + action.GetID() == ACTION_PREVIOUS_MENU || action.GetID() == ACTION_NAV_BACK || + action.GetID() == ACTION_VOICE_RECOGNIZE)) + { + // the action isn't plain navigation - check for a keyboard-specific keymap + action = m_buttonTranslator->GetAction(WINDOW_DIALOG_KEYBOARD, key, false); + if (!(action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9) || + action.GetID() == ACTION_BACKSPACE || action.GetID() == ACTION_SHIFT || + action.GetID() == ACTION_SYMBOLS || action.GetID() == ACTION_CURSOR_LEFT || + action.GetID() == ACTION_CURSOR_RIGHT) + action = CAction(0); // don't bother with this action + } + // else pass the keys through directly + if (!action.GetID()) + { + if (key.GetFromService()) + action = CAction(key.GetButtonCode() != KEY_INVALID ? key.GetButtonCode() : 0, + key.GetUnicode()); + else + { + // Check for paste keypress +#ifdef TARGET_WINDOWS + // In Windows paste is ctrl-V + if (key.GetVKey() == XBMCVK_V && key.GetModifiers() == CKey::MODIFIER_CTRL) +#elif defined(TARGET_LINUX) + // In Linux paste is ctrl-V + if (key.GetVKey() == XBMCVK_V && key.GetModifiers() == CKey::MODIFIER_CTRL) +#elif defined(TARGET_DARWIN_OSX) + // In OSX paste is cmd-V + if (key.GetVKey() == XBMCVK_V && key.GetModifiers() == CKey::MODIFIER_META) +#else + // Placeholder for other operating systems + if (false) +#endif + action = CAction(ACTION_PASTE); + // If the unicode is non-zero the keypress is a non-printing character + else if (key.GetUnicode()) + action = CAction(KEY_UNICODE, key.GetUnicode()); + // The keypress is a non-printing character + else + action = CAction(key.GetVKey() | KEY_VKEY); + } + } + + CLog::LogF(LOGDEBUG, "{} pressed, trying keyboard action {:x}", + m_Keyboard.GetKeyName((int)key.GetButtonCode()), action.GetID()); + + if (g_application.OnAction(action)) + return true; + // failed to handle the keyboard action, drop down through to standard action + } + if (key.GetFromService()) + { + if (key.GetButtonCode() != KEY_INVALID) + action = m_buttonTranslator->GetAction(iWin, key); + } + else + action = m_buttonTranslator->GetAction(iWin, key); + } + if (!key.IsAnalogButton()) + CLog::LogF(LOGDEBUG, "{} pressed, window {}, action is {}", + m_Keyboard.GetKeyName((int)key.GetButtonCode()), iWin, action.GetName()); + + return ExecuteInputAction(action); +} + +void CInputManager::OnKeyUp(const CKey& key) +{ + for (auto handler : m_keyboardHandlers) + handler->OnKeyRelease(key); + + if (m_LastKey.GetButtonCode() != KEY_INVALID && + !(m_LastKey.GetButtonCode() & CKey::MODIFIER_LONG)) + { + CKey key = m_LastKey; + m_LastKey.Reset(); // OnKey is reentrant; need to do this before entering + HandleKey(key); + } + else + m_LastKey.Reset(); +} + +bool CInputManager::AlwaysProcess(const CAction& action) +{ + // check if this button is mapped to a built-in function + if (!action.GetName().empty()) + { + const CExecString exec(action.GetName()); + if (exec.IsValid()) + { + const std::string builtInFunction = exec.GetFunction(); + + // should this button be handled normally or just cancel the screensaver? + if (builtInFunction == "powerdown" || builtInFunction == "reboot" || + builtInFunction == "restart" || builtInFunction == "restartapp" || + builtInFunction == "suspend" || builtInFunction == "hibernate" || + builtInFunction == "quit" || builtInFunction == "shutdown" || + builtInFunction == "volumeup" || builtInFunction == "volumedown" || + builtInFunction == "mute" || builtInFunction == "RunAppleScript" || + builtInFunction == "RunAddon" || builtInFunction == "RunPlugin" || + builtInFunction == "RunScript" || builtInFunction == "System.Exec" || + builtInFunction == "System.ExecWait") + { + return true; + } + } + } + + return false; +} + +bool CInputManager::ExecuteInputAction(const CAction& action) +{ + bool bResult = false; + CGUIComponent* gui = CServiceBroker::GetGUI(); + + // play sound before the action unless the button is held, + // where we execute after the action as held actions aren't fired every time. + if (action.GetHoldTime()) + { + bResult = g_application.OnAction(action); + if (bResult && gui) + gui->GetAudioManager().PlayActionSound(action); + } + else + { + if (gui) + gui->GetAudioManager().PlayActionSound(action); + + bResult = g_application.OnAction(action); + } + return bResult; +} + +bool CInputManager::HasBuiltin(const std::string& command) +{ + return false; +} + +int CInputManager::ExecuteBuiltin(const std::string& execute, + const std::vector<std::string>& params) +{ + return 0; +} + +void CInputManager::SetMouseActive(bool active /* = true */) +{ + m_Mouse.SetActive(active); +} + +void CInputManager::SetMouseEnabled(bool mouseEnabled /* = true */) +{ + m_Mouse.SetEnabled(mouseEnabled); +} + +bool CInputManager::IsMouseActive() +{ + return m_Mouse.IsActive(); +} + +MOUSE_STATE CInputManager::GetMouseState() +{ + return m_Mouse.GetState(); +} + +MousePosition CInputManager::GetMousePosition() +{ + return m_Mouse.GetPosition(); +} + +void CInputManager::SetMouseResolution(int maxX, int maxY, float speedX, float speedY) +{ + m_Mouse.SetResolution(maxX, maxY, speedX, speedY); +} + +void CInputManager::SetMouseState(MOUSE_STATE mouseState) +{ + m_Mouse.SetState(mouseState); +} + +bool CInputManager::IsControllerEnabled() const +{ + return m_enableController; +} + +void CInputManager::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_INPUT_ENABLEMOUSE) + m_Mouse.SetEnabled(std::dynamic_pointer_cast<const CSettingBool>(setting)->GetValue()); + + else if (settingId == SETTING_INPUT_ENABLE_CONTROLLER) + m_enableController = std::dynamic_pointer_cast<const CSettingBool>(setting)->GetValue(); +} + +bool CInputManager::OnAction(const CAction& action) +{ + if (action.GetID() != ACTION_NONE) + { + if (action.IsAnalog()) + { + QueueAction(action); + } + else + { + // If button was pressed this frame, send action + if (action.GetHoldTime() == 0) + { + QueueAction(action); + } + else + { + // Only send repeated actions for basic navigation commands + bool bIsNavigation = false; + + switch (action.GetID()) + { + case ACTION_MOVE_LEFT: + case ACTION_MOVE_RIGHT: + case ACTION_MOVE_UP: + case ACTION_MOVE_DOWN: + case ACTION_PAGE_UP: + case ACTION_PAGE_DOWN: + bIsNavigation = true; + break; + + default: + break; + } + + if (bIsNavigation) + QueueAction(action); + } + } + + return true; + } + + return false; +} + +bool CInputManager::LoadKeymaps() +{ + bool bSuccess = false; + + if (m_buttonTranslator->Load()) + { + bSuccess = true; + } + + SetChanged(); + NotifyObservers(ObservableMessageButtonMapsChanged); + + return bSuccess; +} + +bool CInputManager::ReloadKeymaps() +{ + return LoadKeymaps(); +} + +void CInputManager::ClearKeymaps() +{ + m_buttonTranslator->Clear(); + + SetChanged(); + NotifyObservers(ObservableMessageButtonMapsChanged); +} + +void CInputManager::AddKeymap(const std::string& keymap) +{ + if (m_buttonTranslator->AddDevice(keymap)) + { + SetChanged(); + NotifyObservers(ObservableMessageButtonMapsChanged); + } +} + +void CInputManager::RemoveKeymap(const std::string& keymap) +{ + if (m_buttonTranslator->RemoveDevice(keymap)) + { + SetChanged(); + NotifyObservers(ObservableMessageButtonMapsChanged); + } +} + +CAction CInputManager::GetAction(int window, const CKey& key, bool fallback /* = true */) +{ + return m_buttonTranslator->GetAction(window, key, fallback); +} + +bool CInputManager::TranslateCustomControllerString(int windowId, + const std::string& controllerName, + int buttonId, + int& action, + std::string& strAction) +{ + return m_customControllerTranslator->TranslateCustomControllerString(windowId, controllerName, + buttonId, action, strAction); +} + +bool CInputManager::TranslateTouchAction( + int windowId, int touchAction, int touchPointers, int& action, std::string& actionString) +{ + return m_touchTranslator->TranslateTouchAction(windowId, touchAction, touchPointers, action, + actionString); +} + +std::vector<std::shared_ptr<const IWindowKeymap>> CInputManager::GetJoystickKeymaps() const +{ + return m_joystickTranslator->GetJoystickKeymaps(); +} + +void CInputManager::RegisterKeyboardDriverHandler(KEYBOARD::IKeyboardDriverHandler* handler) +{ + if (std::find(m_keyboardHandlers.begin(), m_keyboardHandlers.end(), handler) == + m_keyboardHandlers.end()) + m_keyboardHandlers.insert(m_keyboardHandlers.begin(), handler); +} + +void CInputManager::UnregisterKeyboardDriverHandler(KEYBOARD::IKeyboardDriverHandler* handler) +{ + m_keyboardHandlers.erase( + std::remove(m_keyboardHandlers.begin(), m_keyboardHandlers.end(), handler), + m_keyboardHandlers.end()); +} + +void CInputManager::RegisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler) +{ + if (std::find(m_mouseHandlers.begin(), m_mouseHandlers.end(), handler) == m_mouseHandlers.end()) + m_mouseHandlers.insert(m_mouseHandlers.begin(), handler); +} + +void CInputManager::UnregisterMouseDriverHandler(MOUSE::IMouseDriverHandler* handler) +{ + m_mouseHandlers.erase(std::remove(m_mouseHandlers.begin(), m_mouseHandlers.end(), handler), + m_mouseHandlers.end()); +} diff --git a/xbmc/input/InputManager.h b/xbmc/input/InputManager.h new file mode 100644 index 0000000..52ad459 --- /dev/null +++ b/xbmc/input/InputManager.h @@ -0,0 +1,310 @@ +/* + * 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. + */ + +#pragma once + +#include "input/KeyboardStat.h" +#include "input/actions/Action.h" +#include "input/button/ButtonStat.h" +#include "input/mouse/MouseStat.h" +#include "input/mouse/interfaces/IMouseInputProvider.h" +#include "interfaces/IActionListener.h" +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" +#include "utils/Observer.h" +#include "windowing/XBMC_events.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +class CButtonTranslator; +class CCustomControllerTranslator; +class CJoystickMapper; +class CKey; +class CProfileManager; +class CTouchTranslator; +class IKeymapEnvironment; +class IWindowKeymap; + +namespace KODI +{ + +namespace KEYBOARD +{ +class IKeyboardDriverHandler; +} + +namespace MOUSE +{ +class IMouseDriverHandler; +} +} // namespace KODI + +/// \addtogroup input +/// \{ + +/*! + * \ingroup input keyboard mouse touch joystick + * \brief Main input processing class. + * + * This class consolidates all input generated from different sources such as + * mouse, keyboard, joystick or touch (in \ref OnEvent). + * + * \copydoc keyboard + * \copydoc mouse + */ +class CInputManager : public ISettingCallback, public IActionListener, public Observable +{ +public: + CInputManager(); + CInputManager(const CInputManager&) = delete; + CInputManager const& operator=(CInputManager const&) = delete; + ~CInputManager() override; + + /*! \brief decode a mouse event and reset idle timers. + * + * \param windowId Currently active window + * \return true if event is handled, false otherwise + */ + bool ProcessMouse(int windowId); + + /*! \brief decode an event from the event service, this can be mouse, key, joystick, reset idle + * timers. + * + * \param windowId Currently active window + * \param frameTime Time in seconds since last call + * \return true if event is handled, false otherwise + */ + bool ProcessEventServer(int windowId, float frameTime); + + /*! \brief decode an event from peripherals. + * + * \param frameTime Time in seconds since last call + * \return true if event is handled, false otherwise + */ + bool ProcessPeripherals(float frameTime); + + /*! \brief Process all inputs + * + * \param windowId Currently active window + * \param frameTime Time in seconds since last call + * \return true on success, false otherwise + */ + bool Process(int windowId, float frameTime); + + /*! + * \brief Call once during application startup to initialize peripherals that need it + */ + void InitializeInputs(); + + /*! + * \brief Deinitialize input and keymaps + */ + void Deinitialize(); + + /*! \brief Handle an input event + * + * \param newEvent event details + * \return true on successfully handled event + * \sa XBMC_Event + */ + bool OnEvent(XBMC_Event& newEvent); + + /*! \brief Control if the mouse is actively used or not + * + * \param[in] active sets mouse active or inactive + */ + void SetMouseActive(bool active = true); + + /*! \brief Control if we should use a mouse or not + * + * \param[in] mouseEnabled sets mouse enabled or disabled + */ + void SetMouseEnabled(bool mouseEnabled = true); + + /*! \brief Set the current state of the mouse such as click, drag operation + * + * \param[in] mouseState which state the mouse should be set to + * \sa MOUSE_STATE + */ + void SetMouseState(MOUSE_STATE mouseState); + + /*! \brief Check if the mouse is currently active + * + * \return true if active, false otherwise + */ + bool IsMouseActive(); + + /*! \brief Get the current state of the mouse, such as click or drag operation + * + * \return the current state of the mouse as a value from MOUSE_STATE + * \sa MOUSE_STATE + */ + MOUSE_STATE GetMouseState(); + + /*! \brief Get the current mouse positions x and y coordinates + * + * \return a struct containing the x and y coordinates + * \sa MousePosition + */ + MousePosition GetMousePosition(); + + /*! \brief Set the current screen resolution and pointer speed + * + * \param[in] maxX screen width + * \param[in] maxY screen height + * \param[in] speedX mouse speed in x dimension + * \param[in] speedY mouse speed in y dimension + * \return + */ + void SetMouseResolution(int maxX, int maxY, float speedX, float speedY); + + /*! \brief Get the status of the controller-enable setting + * \return True if controller input is enabled for the UI, false otherwise + */ + bool IsControllerEnabled() const; + + /*! \brief Returns whether or not we can handle a given built-in command. + * + */ + bool HasBuiltin(const std::string& command); + + /*! \brief Parse a builtin command and execute any input action + * currently only LIRC commands implemented + * + * \param[in] execute Command to execute + * \param[in] params parameters that was passed to the command + * \return 0 on success, -1 on failure + */ + int ExecuteBuiltin(const std::string& execute, const std::vector<std::string>& params); + + // Button translation + bool LoadKeymaps(); + bool ReloadKeymaps(); + void ClearKeymaps(); + void AddKeymap(const std::string& keymap); + void RemoveKeymap(const std::string& keymap); + + const IKeymapEnvironment* KeymapEnvironment() const { return m_keymapEnvironment.get(); } + + /*! \brief Obtain the action configured for a given window and key + * + * \param window the window id + * \param key the key to query the action for + * \param fallback if no action is directly configured for the given window, obtain the action + * from fallback window, if exists or from global config as last resort + * + * \return the action matching the key + */ + CAction GetAction(int window, const CKey& key, bool fallback = true); + + bool TranslateCustomControllerString(int windowId, + const std::string& controllerName, + int buttonId, + int& action, + std::string& strAction); + + bool TranslateTouchAction( + int windowId, int touchAction, int touchPointers, int& action, std::string& actionString); + + std::vector<std::shared_ptr<const IWindowKeymap>> GetJoystickKeymaps() const; + + /*! + * \brief Queue an action to be processed on the next call to Process() + */ + void QueueAction(const CAction& action); + + // implementation of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + // implementation of IActionListener + bool OnAction(const CAction& action) override; + + void RegisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler); + void UnregisterKeyboardDriverHandler(KODI::KEYBOARD::IKeyboardDriverHandler* handler); + + virtual void RegisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler); + virtual void UnregisterMouseDriverHandler(KODI::MOUSE::IMouseDriverHandler* handler); + +private: + /*! \brief Process keyboard event and translate into an action + * + * \param key keypress details + * \return true on successfully handled event + * \sa CKey + */ + bool OnKey(const CKey& key); + + /*! \brief Process key up event + * + * \param key details of released key + * \sa CKey + */ + void OnKeyUp(const CKey& key); + + /*! \brief Handle keypress + * + * \param key keypress details + * \return true on successfully handled event + */ + bool HandleKey(const CKey& key); + + /*! \brief Determine if an action should be processed or just + * cancel the screensaver + * + * \param action Action that is about to be processed + * \return true on any poweractions such as shutdown/reboot/sleep/suspend, false otherwise + * \sa CAction + */ + bool AlwaysProcess(const CAction& action); + + /*! \brief Send the Action to CApplication for further handling, + * play a sound before or after sending the action. + * + * \param action Action to send to CApplication + * \return result from CApplication::OnAction + * \sa CAction + */ + bool ExecuteInputAction(const CAction& action); + + /*! \brief Dispatch actions queued since the last call to Process() + */ + void ProcessQueuedActions(); + + CKeyboardStat m_Keyboard; + KODI::INPUT::CButtonStat m_buttonStat; + CMouseStat m_Mouse; + CKey m_LastKey; + + std::map<std::string, std::map<int, float>> m_lastAxisMap; + + std::vector<CAction> m_queuedActions; + CCriticalSection m_actionMutex; + + // Button translation + std::unique_ptr<IKeymapEnvironment> m_keymapEnvironment; + std::unique_ptr<CButtonTranslator> m_buttonTranslator; + std::unique_ptr<CCustomControllerTranslator> m_customControllerTranslator; + std::unique_ptr<CTouchTranslator> m_touchTranslator; + std::unique_ptr<CJoystickMapper> m_joystickTranslator; + + std::vector<KODI::KEYBOARD::IKeyboardDriverHandler*> m_keyboardHandlers; + std::vector<KODI::MOUSE::IMouseDriverHandler*> m_mouseHandlers; + + std::unique_ptr<KODI::KEYBOARD::IKeyboardDriverHandler> m_keyboardEasterEgg; + + // Input state + bool m_enableController = true; + + // Settings + static const std::string SETTING_INPUT_ENABLE_CONTROLLER; +}; + +/// \} diff --git a/xbmc/input/InputTranslator.cpp b/xbmc/input/InputTranslator.cpp new file mode 100644 index 0000000..6e3786d --- /dev/null +++ b/xbmc/input/InputTranslator.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 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 "InputTranslator.h" + +using namespace KODI; +using namespace INPUT; + +#define TAN_1_8_PI 0.4142136f // tan(1/8*PI) +#define TAN_3_8_PI 2.4142136f // tan(3/8*PI) + +CARDINAL_DIRECTION CInputTranslator::VectorToCardinalDirection(float x, float y) +{ + if (y >= -x && y > x) + return CARDINAL_DIRECTION::UP; + else if (y <= x && y > -x) + return CARDINAL_DIRECTION::RIGHT; + else if (y <= -x && y < x) + return CARDINAL_DIRECTION::DOWN; + else if (y >= x && y < -x) + return CARDINAL_DIRECTION::LEFT; + + return CARDINAL_DIRECTION::NONE; +} + +INTERCARDINAL_DIRECTION CInputTranslator::VectorToIntercardinalDirection(float x, float y) +{ + if (y >= TAN_3_8_PI * -x && y > TAN_3_8_PI * x) + return INTERCARDINAL_DIRECTION::UP; + else if (y <= TAN_3_8_PI * x && y > TAN_1_8_PI * x) + return INTERCARDINAL_DIRECTION::RIGHTUP; + else if (y <= TAN_1_8_PI * x && y > TAN_1_8_PI * -x) + return INTERCARDINAL_DIRECTION::RIGHT; + else if (y <= TAN_1_8_PI * -x && y > TAN_3_8_PI * -x) + return INTERCARDINAL_DIRECTION::RIGHTDOWN; + else if (y <= TAN_3_8_PI * -x && y < TAN_3_8_PI * x) + return INTERCARDINAL_DIRECTION::DOWN; + else if (y >= TAN_3_8_PI * x && y < TAN_1_8_PI * x) + return INTERCARDINAL_DIRECTION::LEFTDOWN; + else if (y >= TAN_1_8_PI * x && y < TAN_1_8_PI * -x) + return INTERCARDINAL_DIRECTION::LEFT; + else if (y >= TAN_1_8_PI * -x && y < TAN_3_8_PI * -x) + return INTERCARDINAL_DIRECTION::LEFTUP; + + return INTERCARDINAL_DIRECTION::NONE; +} diff --git a/xbmc/input/InputTranslator.h b/xbmc/input/InputTranslator.h new file mode 100644 index 0000000..70047f5 --- /dev/null +++ b/xbmc/input/InputTranslator.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +#include "InputTypes.h" + +namespace KODI +{ +namespace INPUT +{ +class CInputTranslator +{ +public: + /*! + * \brief Get the closest cardinal direction to the given vector + * + * This function assumes a right-handed cartesian coordinate system; positive + * X is right, positive Y is up. + * + * Ties are resolved in the clockwise direction: (0.5, 0.5) will resolve to + * RIGHT. + * + * \param x The x component of the vector + * \param y The y component of the vector + * + * \return The closest cardinal direction (up, down, right or left), or + * CARDINAL_DIRECTION::NONE if x and y are both 0 + */ + static CARDINAL_DIRECTION VectorToCardinalDirection(float x, float y); + + /*! + * \brief Get the closest cardinal or intercardinal direction to the given + * vector + * + * This function assumes a right-handed cartesian coordinate system; positive + * X is right, positive Y is up. + * + * Ties are resolved in the clockwise direction. + * + * \param x The x component of the vector + * \param y The y component of the vector + * + * \return The closest intercardinal direction, or + * INTERCARDINAL_DIRECTION::NONE if x and y are both 0 + */ + static INTERCARDINAL_DIRECTION VectorToIntercardinalDirection(float x, float y); +}; +} // namespace INPUT +} // namespace KODI diff --git a/xbmc/input/InputTypes.h b/xbmc/input/InputTypes.h new file mode 100644 index 0000000..851beb7 --- /dev/null +++ b/xbmc/input/InputTypes.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +namespace KODI +{ +namespace INPUT +{ +/*! + * \brief Cardinal directions, used for input device motions + */ +enum class CARDINAL_DIRECTION +{ + NONE = 0x0, + UP = 0x1, + DOWN = 0x2, + RIGHT = 0x4, + LEFT = 0x8, +}; + +/*! + * \brief Cardinal and intercardinal directions, used for input device motions + */ +enum class INTERCARDINAL_DIRECTION +{ + NONE = static_cast<unsigned int>(CARDINAL_DIRECTION::NONE), + UP = static_cast<unsigned int>(CARDINAL_DIRECTION::UP), + DOWN = static_cast<unsigned int>(CARDINAL_DIRECTION::DOWN), + RIGHT = static_cast<unsigned int>(CARDINAL_DIRECTION::RIGHT), + LEFT = static_cast<unsigned int>(CARDINAL_DIRECTION::LEFT), + RIGHTUP = RIGHT | UP, + RIGHTDOWN = RIGHT | DOWN, + LEFTUP = LEFT | UP, + LEFTDOWN = LEFT | DOWN, +}; + +const unsigned int HOLD_TRESHOLD = 250; + +} // namespace INPUT +} // namespace KODI diff --git a/xbmc/input/JoystickMapper.cpp b/xbmc/input/JoystickMapper.cpp new file mode 100644 index 0000000..233238d --- /dev/null +++ b/xbmc/input/JoystickMapper.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2017-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 "JoystickMapper.h" + +#include "input/WindowKeymap.h" +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "input/joysticks/JoystickTranslator.h" +#include "input/joysticks/JoystickUtils.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" + +#include <algorithm> +#include <sstream> +#include <utility> + +using namespace KODI; + +#define JOYSTICK_XML_NODE_PROFILE "profile" +#define JOYSTICK_XML_ATTR_DIRECTION "direction" +#define JOYSTICK_XML_ATTR_HOLDTIME "holdtime" +#define JOYSTICK_XML_ATTR_HOTKEY "hotkey" + +void CJoystickMapper::MapActions(int windowID, const TiXmlNode* pDevice) +{ + std::string controllerId; + DeserializeJoystickNode(pDevice, controllerId); + if (controllerId.empty()) + return; + + // Update Controller IDs + if (std::find(m_controllerIds.begin(), m_controllerIds.end(), controllerId) == + m_controllerIds.end()) + m_controllerIds.emplace_back(controllerId); + + // Create/overwrite keymap + auto& keymap = m_joystickKeymaps[controllerId]; + if (!keymap) + keymap.reset(new CWindowKeymap(controllerId)); + + const TiXmlElement* pButton = pDevice->FirstChildElement(); + while (pButton != nullptr) + { + std::string feature; + JOYSTICK::ANALOG_STICK_DIRECTION dir; + unsigned int holdtimeMs; + std::set<std::string> hotkeys; + std::string actionString; + if (DeserializeButton(pButton, feature, dir, holdtimeMs, hotkeys, actionString)) + { + // Update keymap + unsigned int actionId = ACTION_NONE; + if (CActionTranslator::TranslateString(actionString, actionId)) + { + JOYSTICK::KeymapAction action = { + actionId, + std::move(actionString), + holdtimeMs, + std::move(hotkeys), + }; + keymap->MapAction(windowID, JOYSTICK::CJoystickUtils::MakeKeyName(feature, dir), + std::move(action)); + } + } + pButton = pButton->NextSiblingElement(); + } +} + +void CJoystickMapper::Clear() +{ + m_joystickKeymaps.clear(); + m_controllerIds.clear(); +} + +std::vector<std::shared_ptr<const IWindowKeymap>> CJoystickMapper::GetJoystickKeymaps() const +{ + std::vector<std::shared_ptr<const IWindowKeymap>> keymaps; + + for (const auto& controllerId : m_controllerIds) + { + auto it = m_joystickKeymaps.find(controllerId); + if (it != m_joystickKeymaps.end()) + keymaps.emplace_back(it->second); + } + + return keymaps; +} + +void CJoystickMapper::DeserializeJoystickNode(const TiXmlNode* pDevice, std::string& controllerId) +{ + const TiXmlElement* deviceElem = pDevice->ToElement(); + if (deviceElem != nullptr) + deviceElem->QueryValueAttribute(JOYSTICK_XML_NODE_PROFILE, &controllerId); +} + +bool CJoystickMapper::DeserializeButton(const TiXmlElement* pButton, + std::string& feature, + JOYSTICK::ANALOG_STICK_DIRECTION& dir, + unsigned int& holdtimeMs, + std::set<std::string>& hotkeys, + std::string& actionStr) +{ + const char* szButton = pButton->Value(); + if (szButton != nullptr) + { + const char* szAction = nullptr; + + const TiXmlNode* actionNode = pButton->FirstChild(); + if (actionNode != nullptr) + szAction = actionNode->Value(); + + if (szAction != nullptr) + { + feature = szButton; + StringUtils::ToLower(feature); + actionStr = szAction; + } + } + + if (!feature.empty() && !actionStr.empty()) + { + // Handle direction + dir = JOYSTICK::ANALOG_STICK_DIRECTION::NONE; + const char* szDirection = pButton->Attribute(JOYSTICK_XML_ATTR_DIRECTION); + if (szDirection != nullptr) + dir = JOYSTICK::CJoystickTranslator::TranslateAnalogStickDirection(szDirection); + + // Process holdtime parameter + holdtimeMs = 0; + std::string strHoldTime; + if (pButton->QueryValueAttribute(JOYSTICK_XML_ATTR_HOLDTIME, &strHoldTime) == TIXML_SUCCESS) + { + std::istringstream ss(strHoldTime); + ss >> holdtimeMs; + } + + // Process hotkeys + hotkeys.clear(); + std::string strHotkeys; + if (pButton->QueryValueAttribute(JOYSTICK_XML_ATTR_HOTKEY, &strHotkeys) == TIXML_SUCCESS) + { + std::vector<std::string> vecHotkeys = StringUtils::Split(strHotkeys, ","); + for (auto& hotkey : vecHotkeys) + hotkeys.insert(std::move(hotkey)); + } + + return true; + } + + return false; +} diff --git a/xbmc/input/JoystickMapper.h b/xbmc/input/JoystickMapper.h new file mode 100644 index 0000000..ed5e61a --- /dev/null +++ b/xbmc/input/JoystickMapper.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "IButtonMapper.h" +#include "input/joysticks/JoystickTypes.h" + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +class IWindowKeymap; +class TiXmlElement; +class TiXmlNode; + +class CJoystickMapper : public IButtonMapper +{ +public: + CJoystickMapper() = default; + ~CJoystickMapper() override = default; + + // implementation of IButtonMapper + void MapActions(int windowID, const TiXmlNode* pDevice) override; + void Clear() override; + + std::vector<std::shared_ptr<const IWindowKeymap>> GetJoystickKeymaps() const; + +private: + void DeserializeJoystickNode(const TiXmlNode* pDevice, std::string& controllerId); + bool DeserializeButton(const TiXmlElement* pButton, + std::string& feature, + KODI::JOYSTICK::ANALOG_STICK_DIRECTION& dir, + unsigned int& holdtimeMs, + std::set<std::string>& hotkeys, + std::string& actionStr); + + using ControllerID = std::string; + std::map<ControllerID, std::shared_ptr<IWindowKeymap>> m_joystickKeymaps; + + std::vector<std::string> m_controllerIds; +}; diff --git a/xbmc/input/Key.cpp b/xbmc/input/Key.cpp new file mode 100644 index 0000000..24f6a0c --- /dev/null +++ b/xbmc/input/Key.cpp @@ -0,0 +1,178 @@ +/* + * 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 "input/Key.h" + +CKey::CKey(void) +{ + Reset(); +} + +CKey::~CKey(void) = default; + +CKey::CKey(uint32_t buttonCode, + uint8_t leftTrigger, + uint8_t rightTrigger, + float leftThumbX, + float leftThumbY, + float rightThumbX, + float rightThumbY, + float repeat) +{ + Reset(); + m_buttonCode = buttonCode; + m_leftTrigger = leftTrigger; + m_rightTrigger = rightTrigger; + m_leftThumbX = leftThumbX; + m_leftThumbY = leftThumbY; + m_rightThumbX = rightThumbX; + m_rightThumbY = rightThumbY; + m_repeat = repeat; +} + +CKey::CKey(uint32_t buttonCode, unsigned int held) +{ + Reset(); + m_buttonCode = buttonCode; + m_held = held; +} + +CKey::CKey(uint32_t keycode, + uint8_t vkey, + wchar_t unicode, + char ascii, + uint32_t modifiers, + uint32_t lockingModifiers, + unsigned int held) +{ + Reset(); + if (vkey) // FIXME: This needs cleaning up - should we always use the unicode key where available? + m_buttonCode = vkey | KEY_VKEY; + else + m_buttonCode = KEY_UNICODE; + m_buttonCode |= modifiers; + m_keycode = keycode; + m_vkey = vkey; + m_unicode = unicode; + m_ascii = ascii; + m_modifiers = modifiers; + m_lockingModifiers = lockingModifiers; + m_held = held; +} + +CKey::CKey(const CKey& key) +{ + *this = key; +} + +void CKey::Reset() +{ + m_leftTrigger = 0; + m_rightTrigger = 0; + m_leftThumbX = 0.0f; + m_leftThumbY = 0.0f; + m_rightThumbX = 0.0f; + m_rightThumbY = 0.0f; + m_repeat = 0.0f; + m_fromService = false; + m_buttonCode = KEY_INVALID; + m_keycode = 0; + m_vkey = 0; + m_unicode = 0; + m_ascii = 0; + m_modifiers = 0; + m_lockingModifiers = 0; + m_held = 0; +} + +CKey& CKey::operator=(const CKey& key) +{ + if (&key == this) + return *this; + m_leftTrigger = key.m_leftTrigger; + m_rightTrigger = key.m_rightTrigger; + m_leftThumbX = key.m_leftThumbX; + m_leftThumbY = key.m_leftThumbY; + m_rightThumbX = key.m_rightThumbX; + m_rightThumbY = key.m_rightThumbY; + m_repeat = key.m_repeat; + m_fromService = key.m_fromService; + m_buttonCode = key.m_buttonCode; + m_keycode = key.m_keycode; + m_vkey = key.m_vkey; + m_unicode = key.m_unicode; + m_ascii = key.m_ascii; + m_modifiers = key.m_modifiers; + m_lockingModifiers = key.m_lockingModifiers; + m_held = key.m_held; + return *this; +} + +uint8_t CKey::GetLeftTrigger() const +{ + return m_leftTrigger; +} + +uint8_t CKey::GetRightTrigger() const +{ + return m_rightTrigger; +} + +float CKey::GetLeftThumbX() const +{ + return m_leftThumbX; +} + +float CKey::GetLeftThumbY() const +{ + return m_leftThumbY; +} + +float CKey::GetRightThumbX() const +{ + return m_rightThumbX; +} + +float CKey::GetRightThumbY() const +{ + return m_rightThumbY; +} + +bool CKey::FromKeyboard() const +{ + return (m_buttonCode >= KEY_VKEY && m_buttonCode != KEY_INVALID); +} + +bool CKey::IsAnalogButton() const +{ + if ((GetButtonCode() > 261 && GetButtonCode() < 270) || + (GetButtonCode() > 279 && GetButtonCode() < 284)) + return true; + + return false; +} + +bool CKey::IsIRRemote() const +{ + if (GetButtonCode() < 256) + return true; + return false; +} + +float CKey::GetRepeat() const +{ + return m_repeat; +} + +void CKey::SetFromService(bool fromService) +{ + if (fromService && (m_buttonCode & KEY_VKEY)) + m_unicode = m_buttonCode - KEY_VKEY; + + m_fromService = fromService; +} diff --git a/xbmc/input/Key.h b/xbmc/input/Key.h new file mode 100644 index 0000000..5e3e819 --- /dev/null +++ b/xbmc/input/Key.h @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#pragma once + +/*! + \file Key.h + \brief + */ + +//! @todo Remove dependence on CAction +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" + +#include <stdint.h> +#include <string> + +// Reserved 0 - 255 +// IRRemote.h +// XINPUT_IR_REMOTE-* + +/* + * EventServer "gamepad" keys based on original Xbox controller + */ +// Analogue - don't change order +#define KEY_BUTTON_A 256 +#define KEY_BUTTON_B 257 +#define KEY_BUTTON_X 258 +#define KEY_BUTTON_Y 259 +#define KEY_BUTTON_BLACK 260 +#define KEY_BUTTON_WHITE 261 +#define KEY_BUTTON_LEFT_TRIGGER 262 +#define KEY_BUTTON_RIGHT_TRIGGER 263 + +#define KEY_BUTTON_LEFT_THUMB_STICK 264 +#define KEY_BUTTON_RIGHT_THUMB_STICK 265 + +#define KEY_BUTTON_RIGHT_THUMB_STICK_UP 266 // right thumb stick directions +#define KEY_BUTTON_RIGHT_THUMB_STICK_DOWN 267 // for defining different actions per direction +#define KEY_BUTTON_RIGHT_THUMB_STICK_LEFT 268 +#define KEY_BUTTON_RIGHT_THUMB_STICK_RIGHT 269 + +// Digital - don't change order +#define KEY_BUTTON_DPAD_UP 270 +#define KEY_BUTTON_DPAD_DOWN 271 +#define KEY_BUTTON_DPAD_LEFT 272 +#define KEY_BUTTON_DPAD_RIGHT 273 + +#define KEY_BUTTON_START 274 +#define KEY_BUTTON_BACK 275 + +#define KEY_BUTTON_LEFT_THUMB_BUTTON 276 +#define KEY_BUTTON_RIGHT_THUMB_BUTTON 277 + +#define KEY_BUTTON_LEFT_ANALOG_TRIGGER 278 +#define KEY_BUTTON_RIGHT_ANALOG_TRIGGER 279 + +#define KEY_BUTTON_LEFT_THUMB_STICK_UP 280 // left thumb stick directions +#define KEY_BUTTON_LEFT_THUMB_STICK_DOWN 281 // for defining different actions per direction +#define KEY_BUTTON_LEFT_THUMB_STICK_LEFT 282 +#define KEY_BUTTON_LEFT_THUMB_STICK_RIGHT 283 + +// 0xF000 -> 0xF200 is reserved for the keyboard; a keyboard press is either +#define KEY_VKEY 0xF000 // a virtual key/functional key e.g. cursor left +#define KEY_UNICODE \ + 0xF200 // another printable character whose range is not included in this KEY code + +// 0xE000 -> 0xEFFF is reserved for mouse actions +#define KEY_VMOUSE 0xEFFF + +#define KEY_MOUSE_START 0xE000 +#define KEY_MOUSE_CLICK 0xE000 +#define KEY_MOUSE_RIGHTCLICK 0xE001 +#define KEY_MOUSE_MIDDLECLICK 0xE002 +#define KEY_MOUSE_DOUBLE_CLICK 0xE010 +#define KEY_MOUSE_LONG_CLICK 0xE020 +#define KEY_MOUSE_WHEEL_UP 0xE101 +#define KEY_MOUSE_WHEEL_DOWN 0xE102 +#define KEY_MOUSE_MOVE 0xE103 +#define KEY_MOUSE_DRAG 0xE104 +#define KEY_MOUSE_DRAG_START 0xE105 +#define KEY_MOUSE_DRAG_END 0xE106 +#define KEY_MOUSE_RDRAG 0xE107 +#define KEY_MOUSE_RDRAG_START 0xE108 +#define KEY_MOUSE_RDRAG_END 0xE109 +#define KEY_MOUSE_NOOP 0xEFFF +#define KEY_MOUSE_END 0xEFFF + +// 0xD000 -> 0xD0FF is reserved for WM_APPCOMMAND messages +#define KEY_APPCOMMAND 0xD000 + +#define KEY_INVALID 0xFFFF + +#define ICON_TYPE_NONE 101 +#define ICON_TYPE_PROGRAMS 102 +#define ICON_TYPE_MUSIC 103 +#define ICON_TYPE_PICTURES 104 +#define ICON_TYPE_VIDEOS 105 +#define ICON_TYPE_FILES 106 +#define ICON_TYPE_WEATHER 107 +#define ICON_TYPE_SETTINGS 109 + +#ifndef SWIG + +/*! + \ingroup actionkeys, mouse + \brief Simple class for mouse events + */ +class CMouseEvent +{ +public: + CMouseEvent(int actionID, int state = 0, float offsetX = 0, float offsetY = 0) + { + m_id = actionID; + m_state = state; + m_offsetX = offsetX; + m_offsetY = offsetY; + }; + + int m_id; + int m_state; + float m_offsetX; + float m_offsetY; +}; + +/*! + \ingroup actionkeys + \brief + */ +class CKey +{ +public: + CKey(void); + CKey(uint32_t buttonCode, + uint8_t leftTrigger = 0, + uint8_t rightTrigger = 0, + float leftThumbX = 0.0f, + float leftThumbY = 0.0f, + float rightThumbX = 0.0f, + float rightThumbY = 0.0f, + float repeat = 0.0f); + CKey(uint32_t buttonCode, unsigned int held); + CKey(uint32_t keycode, + uint8_t vkey, + wchar_t unicode, + char ascii, + uint32_t modifiers, + uint32_t lockingModifiers, + unsigned int held); + CKey(const CKey& key); + void Reset(); + + virtual ~CKey(void); + CKey& operator=(const CKey& key); + uint8_t GetLeftTrigger() const; + uint8_t GetRightTrigger() const; + float GetLeftThumbX() const; + float GetLeftThumbY() const; + float GetRightThumbX() const; + float GetRightThumbY() const; + float GetRepeat() const; + bool FromKeyboard() const; + bool IsAnalogButton() const; + bool IsIRRemote() const; + void SetFromService(bool fromService); + bool GetFromService() const { return m_fromService; } + + inline uint32_t GetButtonCode() const { return m_buttonCode; } + inline uint32_t GetKeycode() const { return m_keycode; } // XBMCKey enum in XBMC_keysym.h + inline uint8_t GetVKey() const { return m_vkey; } + inline wchar_t GetUnicode() const { return m_unicode; } + inline char GetAscii() const { return m_ascii; } + inline uint32_t GetModifiers() const { return m_modifiers; } + inline uint32_t GetLockingModifiers() const { return m_lockingModifiers; } + inline unsigned int GetHeld() const { return m_held; } + + enum Modifier + { + MODIFIER_CTRL = 0x00010000, + MODIFIER_SHIFT = 0x00020000, + MODIFIER_ALT = 0x00040000, + MODIFIER_RALT = 0x00080000, + MODIFIER_SUPER = 0x00100000, + MODIFIER_META = 0X00200000, + MODIFIER_LONG = 0X01000000, + MODIFIER_NUMLOCK = 0X02000000, + MODIFIER_CAPSLOCK = 0X04000000, + MODIFIER_SCROLLLOCK = 0X08000000, + }; + +private: + uint32_t m_buttonCode; + uint32_t m_keycode; + uint8_t m_vkey; + wchar_t m_unicode; + char m_ascii; + uint32_t m_modifiers; + uint32_t m_lockingModifiers; + unsigned int m_held; + + uint8_t m_leftTrigger; + uint8_t m_rightTrigger; + float m_leftThumbX; + float m_leftThumbY; + float m_rightThumbX; + float m_rightThumbY; + float m_repeat; // time since last keypress + bool m_fromService; +}; +#endif // undef SWIG diff --git a/xbmc/input/KeyboardLayout.cpp b/xbmc/input/KeyboardLayout.cpp new file mode 100644 index 0000000..4759627 --- /dev/null +++ b/xbmc/input/KeyboardLayout.cpp @@ -0,0 +1,165 @@ +/* + * 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 "KeyboardLayout.h" + +#include "InputCodingTableFactory.h" +#include "guilib/LocalizeStrings.h" +#include "utils/CharsetConverter.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> +#include <set> + +CKeyboardLayout::~CKeyboardLayout() = default; + +bool CKeyboardLayout::Load(const TiXmlElement* element) +{ + const char* language = element->Attribute("language"); + if (language == NULL) + { + CLog::Log(LOGWARNING, "CKeyboardLayout: invalid \"language\" attribute"); + return false; + } + + m_language = language; + if (m_language.empty()) + { + CLog::Log(LOGWARNING, "CKeyboardLayout: empty \"language\" attribute"); + return false; + } + + const char* layout = element->Attribute("layout"); + if (layout == NULL) + { + CLog::Log(LOGWARNING, "CKeyboardLayout: invalid \"layout\" attribute"); + return false; + } + + m_layout = layout; + if (m_layout.empty()) + { + CLog::Log(LOGWARNING, "CKeyboardLayout: empty \"layout\" attribute"); + return false; + } + + const TiXmlElement* keyboard = element->FirstChildElement("keyboard"); + if (element->Attribute("codingtable")) + m_codingtable = IInputCodingTablePtr( + CInputCodingTableFactory::CreateCodingTable(element->Attribute("codingtable"), element)); + else + m_codingtable = NULL; + while (keyboard != NULL) + { + // parse modifiers keys + std::set<unsigned int> modifierKeysSet; + + const char* strModifiers = keyboard->Attribute("modifiers"); + if (strModifiers != NULL) + { + std::string modifiers = strModifiers; + StringUtils::ToLower(modifiers); + + std::vector<std::string> variants = StringUtils::Split(modifiers, ","); + for (const auto& itv : variants) + { + unsigned int iKeys = ModifierKeyNone; + std::vector<std::string> keys = StringUtils::Split(itv, "+"); + for (const std::string& strKey : keys) + { + if (strKey == "shift") + iKeys |= ModifierKeyShift; + else if (strKey == "symbol") + iKeys |= ModifierKeySymbol; + } + + modifierKeysSet.insert(iKeys); + } + } + + // parse keyboard rows + const TiXmlNode* row = keyboard->FirstChild("row"); + while (row != NULL) + { + if (!row->NoChildren()) + { + std::string strRow = row->FirstChild()->ValueStr(); + std::vector<std::string> chars = BreakCharacters(strRow); + if (!modifierKeysSet.empty()) + { + for (const auto& it : modifierKeysSet) + m_keyboards[it].push_back(chars); + } + else + m_keyboards[ModifierKeyNone].push_back(chars); + } + + row = row->NextSibling(); + } + + keyboard = keyboard->NextSiblingElement(); + } + + if (m_keyboards.empty()) + { + CLog::Log(LOGWARNING, "CKeyboardLayout: no keyboard layout found"); + return false; + } + + return true; +} + +std::string CKeyboardLayout::GetIdentifier() const +{ + return StringUtils::Format("{} {}", m_language, m_layout); +} + +std::string CKeyboardLayout::GetName() const +{ + return StringUtils::Format(g_localizeStrings.Get(311), m_language, m_layout); +} + +std::string CKeyboardLayout::GetCharAt(unsigned int row, + unsigned int column, + unsigned int modifiers) const +{ + Keyboards::const_iterator mod = m_keyboards.find(modifiers); + if (modifiers != ModifierKeyNone && mod != m_keyboards.end() && mod->second.empty()) + { + // fallback to basic keyboard + mod = m_keyboards.find(ModifierKeyNone); + } + + if (mod != m_keyboards.end()) + { + if (row < mod->second.size() && column < mod->second[row].size()) + { + std::string ch = mod->second[row][column]; + if (ch != " ") + return ch; + } + } + + return ""; +} + +std::vector<std::string> CKeyboardLayout::BreakCharacters(const std::string& chars) +{ + std::vector<std::string> result; + // break into utf8 characters + std::u32string chars32 = g_charsetConverter.utf8ToUtf32(chars); + for (const auto& it : chars32) + { + std::u32string char32(1, it); + result.push_back(g_charsetConverter.utf32ToUtf8(char32)); + } + + return result; +} diff --git a/xbmc/input/KeyboardLayout.h b/xbmc/input/KeyboardLayout.h new file mode 100644 index 0000000..653b114 --- /dev/null +++ b/xbmc/input/KeyboardLayout.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#pragma once + +#include "InputCodingTable.h" + +#include <map> +#include <string> +#include <vector> + +class TiXmlElement; + +class CKeyboardLayout +{ +public: + CKeyboardLayout() = default; + virtual ~CKeyboardLayout(); + IInputCodingTablePtr GetCodingTable() { return m_codingtable; } + + bool Load(const TiXmlElement* element); + + std::string GetIdentifier() const; + std::string GetName() const; + const std::string& GetLanguage() const { return m_language; } + const std::string& GetLayout() const { return m_layout; } + + enum ModifierKey + { + ModifierKeyNone = 0x00, + ModifierKeyShift = 0x01, + ModifierKeySymbol = 0x02 + }; + + std::string GetCharAt(unsigned int row, unsigned int column, unsigned int modifiers = 0) const; + +private: + static std::vector<std::string> BreakCharacters(const std::string& chars); + + typedef std::vector<std::vector<std::string>> KeyboardRows; + typedef std::map<unsigned int, KeyboardRows> Keyboards; + + std::string m_language; + std::string m_layout; + Keyboards m_keyboards; + IInputCodingTablePtr m_codingtable; +}; diff --git a/xbmc/input/KeyboardLayoutManager.cpp b/xbmc/input/KeyboardLayoutManager.cpp new file mode 100644 index 0000000..66ed817 --- /dev/null +++ b/xbmc/input/KeyboardLayoutManager.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015-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 "KeyboardLayoutManager.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "filesystem/Directory.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> + +#define KEYBOARD_LAYOUTS_PATH "special://xbmc/system/keyboardlayouts" + +CKeyboardLayoutManager::~CKeyboardLayoutManager() +{ + Unload(); +} + +bool CKeyboardLayoutManager::Load(const std::string& path /* = "" */) +{ + std::string layoutDirectory = path; + if (layoutDirectory.empty()) + layoutDirectory = KEYBOARD_LAYOUTS_PATH; + + if (!XFILE::CDirectory::Exists(layoutDirectory)) + { + CLog::Log(LOGWARNING, + "CKeyboardLayoutManager: unable to load keyboard layouts from non-existing directory " + "\"{}\"", + layoutDirectory); + return false; + } + + CFileItemList layouts; + if (!XFILE::CDirectory::GetDirectory(CURL(layoutDirectory), layouts, ".xml", + XFILE::DIR_FLAG_DEFAULTS) || + layouts.IsEmpty()) + { + CLog::Log(LOGWARNING, "CKeyboardLayoutManager: no keyboard layouts found in {}", + layoutDirectory); + return false; + } + + CLog::Log(LOGINFO, "CKeyboardLayoutManager: loading keyboard layouts from {}...", + layoutDirectory); + size_t oldLayoutCount = m_layouts.size(); + for (int i = 0; i < layouts.Size(); i++) + { + std::string layoutPath = layouts[i]->GetPath(); + if (layoutPath.empty()) + continue; + + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(layoutPath)) + { + CLog::Log(LOGWARNING, "CKeyboardLayoutManager: unable to open {}", layoutPath); + continue; + } + + const TiXmlElement* rootElement = xmlDoc.RootElement(); + if (rootElement == NULL) + { + CLog::Log(LOGWARNING, "CKeyboardLayoutManager: missing or invalid XML root element in {}", + layoutPath); + continue; + } + + if (rootElement->ValueStr() != "keyboardlayouts") + { + CLog::Log(LOGWARNING, "CKeyboardLayoutManager: unexpected XML root element \"{}\" in {}", + rootElement->Value(), layoutPath); + continue; + } + + const TiXmlElement* layoutElement = rootElement->FirstChildElement("layout"); + while (layoutElement != NULL) + { + CKeyboardLayout layout; + if (!layout.Load(layoutElement)) + CLog::Log(LOGWARNING, "CKeyboardLayoutManager: failed to load {}", layoutPath); + else if (m_layouts.find(layout.GetIdentifier()) != m_layouts.end()) + CLog::Log(LOGWARNING, + "CKeyboardLayoutManager: duplicate layout with identifier \"{}\" in {}", + layout.GetIdentifier(), layoutPath); + else + { + CLog::Log(LOGDEBUG, "CKeyboardLayoutManager: keyboard layout \"{}\" successfully loaded", + layout.GetIdentifier()); + m_layouts.insert(std::make_pair(layout.GetIdentifier(), layout)); + } + + layoutElement = layoutElement->NextSiblingElement(); + } + } + + return m_layouts.size() > oldLayoutCount; +} + +void CKeyboardLayoutManager::Unload() +{ + m_layouts.clear(); +} + +bool CKeyboardLayoutManager::GetLayout(const std::string& name, CKeyboardLayout& layout) const +{ + if (name.empty()) + return false; + + KeyboardLayouts::const_iterator it = m_layouts.find(name); + if (it == m_layouts.end()) + return false; + + layout = it->second; + return true; +} + +namespace +{ +inline bool LayoutSort(const StringSettingOption& i, const StringSettingOption& j) +{ + return (i.value < j.value); +} +} // namespace + +void CKeyboardLayoutManager::SettingOptionsKeyboardLayoutsFiller( + const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + for (const auto& it : CServiceBroker::GetKeyboardLayoutManager()->m_layouts) + { + std::string name = it.second.GetName(); + list.emplace_back(name, name); + } + + std::sort(list.begin(), list.end(), LayoutSort); +} diff --git a/xbmc/input/KeyboardLayoutManager.h b/xbmc/input/KeyboardLayoutManager.h new file mode 100644 index 0000000..49c6473 --- /dev/null +++ b/xbmc/input/KeyboardLayoutManager.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015-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. + */ + +#pragma once + +#include "input/KeyboardLayout.h" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +class CSetting; +struct StringSettingOption; + +typedef std::map<std::string, CKeyboardLayout> KeyboardLayouts; + +class CKeyboardLayoutManager +{ +public: + CKeyboardLayoutManager() = default; + virtual ~CKeyboardLayoutManager(); + + bool Load(const std::string& path = ""); + void Unload(); + + const KeyboardLayouts& GetLayouts() const { return m_layouts; } + bool GetLayout(const std::string& name, CKeyboardLayout& layout) const; + + static void SettingOptionsKeyboardLayoutsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + +private: + CKeyboardLayoutManager(const CKeyboardLayoutManager&) = delete; + CKeyboardLayoutManager const& operator=(CKeyboardLayoutManager const&) = delete; + + KeyboardLayouts m_layouts; +}; diff --git a/xbmc/input/KeyboardStat.cpp b/xbmc/input/KeyboardStat.cpp new file mode 100644 index 0000000..ac36fe4 --- /dev/null +++ b/xbmc/input/KeyboardStat.cpp @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2007-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. + */ + +// C++ Implementation: CKeyboard + +// Comment OUT, if not really debugging!!!: +//#define DEBUG_KEYBOARD_GETCHAR + +#include "KeyboardStat.h" + +#include "ServiceBroker.h" +#include "input/InputTypes.h" +#include "input/XBMC_keytable.h" +#include "input/XBMC_vkeys.h" +#include "peripherals/Peripherals.h" +#include "peripherals/devices/PeripheralHID.h" +#include "utils/log.h" +#include "windowing/XBMC_events.h" + +using namespace KODI; +using namespace INPUT; + +bool operator==(const XBMC_keysym& lhs, const XBMC_keysym& rhs) +{ + return lhs.mod == rhs.mod && lhs.scancode == rhs.scancode && lhs.sym == rhs.sym && + lhs.unicode == rhs.unicode; +} + +CKeyboardStat::CKeyboardStat() +{ + memset(&m_lastKeysym, 0, sizeof(m_lastKeysym)); + m_lastKeyTime = {}; +} + +void CKeyboardStat::Initialize() +{ +} + +bool CKeyboardStat::LookupSymAndUnicodePeripherals(XBMC_keysym& keysym, uint8_t* key, char* unicode) +{ + using namespace PERIPHERALS; + + PeripheralVector hidDevices; + if (CServiceBroker::GetPeripherals().GetPeripheralsWithFeature(hidDevices, FEATURE_HID)) + { + for (auto& peripheral : hidDevices) + { + std::shared_ptr<CPeripheralHID> hidDevice = + std::static_pointer_cast<CPeripheralHID>(peripheral); + if (hidDevice->LookupSymAndUnicode(keysym, key, unicode)) + return true; + } + } + return false; +} + +CKey CKeyboardStat::TranslateKey(XBMC_keysym& keysym) const +{ + uint32_t keycode; + uint8_t vkey; + wchar_t unicode; + char ascii; + uint32_t modifiers; + uint32_t lockingModifiers; + std::chrono::milliseconds held; + XBMCKEYTABLE keytable; + + modifiers = 0; + if (keysym.mod & XBMCKMOD_CTRL) + modifiers |= CKey::MODIFIER_CTRL; + if (keysym.mod & XBMCKMOD_SHIFT) + modifiers |= CKey::MODIFIER_SHIFT; + if (keysym.mod & XBMCKMOD_ALT) + modifiers |= CKey::MODIFIER_ALT; + if (keysym.mod & XBMCKMOD_SUPER) + modifiers |= CKey::MODIFIER_SUPER; + if (keysym.mod & XBMCKMOD_META) + modifiers |= CKey::MODIFIER_META; + + lockingModifiers = 0; + if (keysym.mod & XBMCKMOD_NUM) + lockingModifiers |= CKey::MODIFIER_NUMLOCK; + if (keysym.mod & XBMCKMOD_CAPS) + lockingModifiers |= CKey::MODIFIER_CAPSLOCK; + if (keysym.mod & XBMCKMOD_MODE) + lockingModifiers |= CKey::MODIFIER_SCROLLLOCK; + + CLog::Log(LOGDEBUG, + "Keyboard: scancode: {:#02x}, sym: {:#04x}, unicode: {:#04x}, modifier: 0x{:x}", + keysym.scancode, keysym.sym, keysym.unicode, keysym.mod); + + // The keysym.unicode is usually valid, even if it is zero. A zero + // unicode just means this is a non-printing keypress. The ascii and + // vkey will be set below. + keycode = keysym.sym; + unicode = keysym.unicode; + ascii = 0; + vkey = 0; + held = std::chrono::milliseconds(0); + + // Start by check whether any of the HID peripherals wants to translate this keypress + if (LookupSymAndUnicodePeripherals(keysym, &vkey, &ascii)) + { + CLog::Log(LOGDEBUG, "{} - keypress translated by a HID peripheral", __FUNCTION__); + } + + // Continue by trying to match both the sym and unicode. This will identify + // the majority of keypresses + else if (KeyTableLookupSymAndUnicode(keysym.sym, keysym.unicode, &keytable)) + { + vkey = keytable.vkey; + ascii = keytable.ascii; + } + + // If we failed to match the sym and unicode try just the unicode. This + // will match keys like \ that are on different keys on regional keyboards. + else if (KeyTableLookupUnicode(keysym.unicode, &keytable)) + { + if (keycode == 0) + keycode = keytable.sym; + vkey = keytable.vkey; + ascii = keytable.ascii; + } + + // If there is still no match try the sym + else if (KeyTableLookupSym(keysym.sym, &keytable)) + { + vkey = keytable.vkey; + + // Occasionally we get non-printing keys that have a non-zero value in + // the keysym.unicode. Check for this here and replace any rogue unicode + // values. + if (keytable.unicode == 0 && unicode != 0) + unicode = 0; + else if (keysym.unicode > 32 && keysym.unicode < 128) + ascii = unicode & 0x7f; + } + + // The keysym.sym is unknown ... + else + { + if (!vkey && !ascii) + { + if (keysym.mod & XBMCKMOD_LSHIFT) + vkey = 0xa0; + else if (keysym.mod & XBMCKMOD_RSHIFT) + vkey = 0xa1; + else if (keysym.mod & XBMCKMOD_LALT) + vkey = 0xa4; + else if (keysym.mod & XBMCKMOD_RALT) + vkey = 0xa5; + else if (keysym.mod & XBMCKMOD_LCTRL) + vkey = 0xa2; + else if (keysym.mod & XBMCKMOD_RCTRL) + vkey = 0xa3; + else if (keysym.unicode > 32 && keysym.unicode < 128) + // only TRUE ASCII! (Otherwise XBMC crashes! No unicode not even latin 1!) + ascii = (char)(keysym.unicode & 0xff); + } + } + + if (keysym == m_lastKeysym) + { + auto now = std::chrono::steady_clock::now(); + + held = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastKeyTime); + if (held.count() > HOLD_TRESHOLD) + modifiers |= CKey::MODIFIER_LONG; + } + + // For all shift-X keys except shift-A to shift-Z and shift-F1 to shift-F24 the + // shift modifier is ignored. This so that, for example, the * keypress (shift-8) + // is seen as <asterisk> not <asterisk mod="shift">. + // The A-Z keys are exempted because shift-A-Z is used for navigation in lists. + // The function keys are exempted because function keys have no shifted value and + // the Nyxboard remote uses keys like Shift-F3 for some buttons. + if (modifiers == CKey::MODIFIER_SHIFT) + if ((unicode < 'A' || unicode > 'Z') && (unicode < 'a' || unicode > 'z') && + (vkey < XBMCVK_F1 || vkey > XBMCVK_F24)) + modifiers = 0; + + // Create and return a CKey + + CKey key(keycode, vkey, unicode, ascii, modifiers, lockingModifiers, held.count()); + + return key; +} + +void CKeyboardStat::ProcessKeyDown(XBMC_keysym& keysym) +{ + if (!(m_lastKeysym == keysym)) + { + m_lastKeysym = keysym; + m_lastKeyTime = std::chrono::steady_clock::now(); + } +} + +void CKeyboardStat::ProcessKeyUp(void) +{ + memset(&m_lastKeysym, 0, sizeof(m_lastKeysym)); + m_lastKeyTime = {}; +} + +// Return the key name given a key ID +// Used to make the debug log more intelligible +// The KeyID includes the flags for ctrl, alt etc + +std::string CKeyboardStat::GetKeyName(int KeyID) +{ + int keyid; + std::string keyname; + XBMCKEYTABLE keytable; + + keyname.clear(); + + // Get modifiers + + if (KeyID & CKey::MODIFIER_CTRL) + keyname.append("ctrl-"); + if (KeyID & CKey::MODIFIER_SHIFT) + keyname.append("shift-"); + if (KeyID & CKey::MODIFIER_ALT) + keyname.append("alt-"); + if (KeyID & CKey::MODIFIER_SUPER) + keyname.append("win-"); + if (KeyID & CKey::MODIFIER_META) + keyname.append("meta-"); + if (KeyID & CKey::MODIFIER_LONG) + keyname.append("long-"); + + // Now get the key name + + keyid = KeyID & 0xFF; + bool VKeyFound = KeyTableLookupVKeyName(keyid, &keytable); + if (VKeyFound) + keyname.append(keytable.keyname); + else + keyname += std::to_string(keyid); + + // in case this might be an universalremote keyid + // we also print the possible corresponding obc code + // so users can easily find it in their universalremote + // map xml + if (VKeyFound || keyid > 255) + keyname += StringUtils::Format(" ({:#02x})", KeyID); + else // obc keys are 255 -rawid + keyname += StringUtils::Format(" ({:#02x}, obc{})", KeyID, 255 - KeyID); + + return keyname; +} diff --git a/xbmc/input/KeyboardStat.h b/xbmc/input/KeyboardStat.h new file mode 100644 index 0000000..77d59a9 --- /dev/null +++ b/xbmc/input/KeyboardStat.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007-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. + */ + +#pragma once + +// +// C++ Interface: CKeyboard +// +// Description: Adds features like international keyboard layout mapping on top of the +// platform specific low level keyboard classes. +// Here it must be done only once. Within the other mentioned classes it would have to be done +// several times. +// +// Keyboards always deliver printable characters, logical keys for functional behaviour, modifiers +// ... alongside Based on the same hardware with the same scancodes (also alongside) but delivered +// with different labels to customers the software must solve the mapping to the real labels. This +// is done here. The mapping must be specified by an xml configuration that should be able to access +// everything available, but this allows for double/redundant or ambiguous mapping definition, e.g. +// ASCII/unicode could be derived from scancodes, virtual keys, modifiers and/or other +// ASCII/unicode. + +#include "input/Key.h" +#include "input/XBMC_keyboard.h" + +#include <chrono> +#include <string> + +class CKeyboardStat +{ +public: + CKeyboardStat(); + ~CKeyboardStat() = default; + + void Initialize(); + + CKey TranslateKey(XBMC_keysym& keysym) const; + + void ProcessKeyDown(XBMC_keysym& keysym); + void ProcessKeyUp(void); + + std::string GetKeyName(int KeyID); + +private: + static bool LookupSymAndUnicodePeripherals(XBMC_keysym& keysym, uint8_t* key, char* unicode); + + XBMC_keysym m_lastKeysym; + std::chrono::time_point<std::chrono::steady_clock> m_lastKeyTime; +}; diff --git a/xbmc/input/KeyboardTranslator.cpp b/xbmc/input/KeyboardTranslator.cpp new file mode 100644 index 0000000..1b50151 --- /dev/null +++ b/xbmc/input/KeyboardTranslator.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017-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 "KeyboardTranslator.h" + +#include "Key.h" +#include "XBMC_keytable.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <string> +#include <vector> + +uint32_t CKeyboardTranslator::TranslateButton(const TiXmlElement* pButton) +{ + uint32_t button_id = 0; + const char* szButton = pButton->Value(); + + if (szButton == nullptr) + return 0; + + const std::string strKey = szButton; + if (strKey == "key") + { + std::string strID; + if (pButton->QueryValueAttribute("id", &strID) == TIXML_SUCCESS) + { + const char* str = strID.c_str(); + char* endptr; + long int id = strtol(str, &endptr, 0); + if (endptr - str != (int)strlen(str) || id <= 0 || id > 0x00FFFFFF) + CLog::Log(LOGDEBUG, "{} - invalid key id {}", __FUNCTION__, strID); + else + button_id = (uint32_t)id; + } + else + CLog::Log(LOGERROR, "Keyboard Translator: `key' button has no id"); + } + else + button_id = TranslateString(szButton); + + // Process the ctrl/shift/alt modifiers + std::string strMod; + if (pButton->QueryValueAttribute("mod", &strMod) == TIXML_SUCCESS) + { + StringUtils::ToLower(strMod); + + std::vector<std::string> modArray = StringUtils::Split(strMod, ","); + for (auto substr : modArray) + { + StringUtils::Trim(substr); + + if (substr == "ctrl" || substr == "control") + button_id |= CKey::MODIFIER_CTRL; + else if (substr == "shift") + button_id |= CKey::MODIFIER_SHIFT; + else if (substr == "alt") + button_id |= CKey::MODIFIER_ALT; + else if (substr == "super" || substr == "win") + button_id |= CKey::MODIFIER_SUPER; + else if (substr == "meta" || substr == "cmd") + button_id |= CKey::MODIFIER_META; + else if (substr == "longpress") + button_id |= CKey::MODIFIER_LONG; + else + CLog::Log(LOGERROR, "Keyboard Translator: Unknown key modifier {} in {}", substr, strMod); + } + } + + return button_id; +} + +uint32_t CKeyboardTranslator::TranslateString(const std::string& szButton) +{ + uint32_t buttonCode = 0; + XBMCKEYTABLE keytable; + + // Look up the key name + if (KeyTableLookupName(szButton, &keytable)) + { + buttonCode = keytable.vkey; + } + else + { + // The lookup failed i.e. the key name wasn't found + CLog::Log(LOGERROR, "Keyboard Translator: Can't find button {}", szButton); + } + + buttonCode |= KEY_VKEY; + + return buttonCode; +} diff --git a/xbmc/input/KeyboardTranslator.h b/xbmc/input/KeyboardTranslator.h new file mode 100644 index 0000000..7432037 --- /dev/null +++ b/xbmc/input/KeyboardTranslator.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <stdint.h> +#include <string> + +class TiXmlElement; + +class CKeyboardTranslator +{ +public: + static uint32_t TranslateButton(const TiXmlElement* pButton); + static uint32_t TranslateString(const std::string& szButton); +}; diff --git a/xbmc/input/Keymap.cpp b/xbmc/input/Keymap.cpp new file mode 100644 index 0000000..a2b431d --- /dev/null +++ b/xbmc/input/Keymap.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-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 "Keymap.h" + +#include "IKeymapEnvironment.h" + +using namespace KODI; + +CKeymap::CKeymap(std::shared_ptr<const IWindowKeymap> keymap, const IKeymapEnvironment* environment) + : m_keymap(std::move(keymap)), m_environment(environment) +{ +} + +std::string CKeymap::ControllerID() const +{ + return m_keymap->ControllerID(); +} + +const JOYSTICK::KeymapActionGroup& CKeymap::GetActions(const std::string& keyName) const +{ + const int windowId = m_environment->GetWindowID(); + const auto& actions = m_keymap->GetActions(windowId, keyName); + if (!actions.actions.empty()) + return actions; + + const int fallbackWindowId = m_environment->GetFallthrough(windowId); + if (fallbackWindowId >= 0) + { + const auto& fallbackActions = m_keymap->GetActions(fallbackWindowId, keyName); + if (!fallbackActions.actions.empty()) + return fallbackActions; + } + + if (m_environment->UseGlobalFallthrough()) + { + const auto& globalActions = m_keymap->GetActions(-1, keyName); + if (!globalActions.actions.empty()) + return globalActions; + } + + static const JOYSTICK::KeymapActionGroup empty{}; + return empty; +} diff --git a/xbmc/input/Keymap.h b/xbmc/input/Keymap.h new file mode 100644 index 0000000..822ed67 --- /dev/null +++ b/xbmc/input/Keymap.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "IKeymap.h" + +#include <memory> + +class IKeymapEnvironment; + +class CKeymap : public IKeymap +{ +public: + CKeymap(std::shared_ptr<const IWindowKeymap> keymap, const IKeymapEnvironment* environment); + + // implementation of IKeymap + std::string ControllerID() const override; + const IKeymapEnvironment* Environment() const override { return m_environment; } + const KODI::JOYSTICK::KeymapActionGroup& GetActions(const std::string& keyName) const override; + +private: + // Construction parameters + const std::shared_ptr<const IWindowKeymap> m_keymap; + const IKeymapEnvironment* const m_environment; +}; diff --git a/xbmc/input/KeymapEnvironment.cpp b/xbmc/input/KeymapEnvironment.cpp new file mode 100644 index 0000000..dbfe9f7 --- /dev/null +++ b/xbmc/input/KeymapEnvironment.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2017-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 "KeymapEnvironment.h" + +#include "WindowTranslator.h" + +int CKeymapEnvironment::GetFallthrough(int windowId) const +{ + return CWindowTranslator::GetFallbackWindow(windowId); +} diff --git a/xbmc/input/KeymapEnvironment.h b/xbmc/input/KeymapEnvironment.h new file mode 100644 index 0000000..64ee67a --- /dev/null +++ b/xbmc/input/KeymapEnvironment.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "IKeymapEnvironment.h" + +class CKeymapEnvironment : public IKeymapEnvironment +{ +public: + ~CKeymapEnvironment() override = default; + + // implementation of IKeymapEnvironment + int GetWindowID() const override { return m_windowId; } + void SetWindowID(int windowId) override { m_windowId = windowId; } + int GetFallthrough(int windowId) const override; + bool UseGlobalFallthrough() const override { return true; } + bool UseEasterEgg() const override { return true; } + +private: + int m_windowId = -1; +}; diff --git a/xbmc/input/TouchTranslator.cpp b/xbmc/input/TouchTranslator.cpp new file mode 100644 index 0000000..e1eb843 --- /dev/null +++ b/xbmc/input/TouchTranslator.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2017-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 "TouchTranslator.h" + +#include "WindowTranslator.h" //! @todo +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <map> + +using ActionName = std::string; +using TouchCommandID = unsigned int; + +#define TOUCH_COMMAND_NONE 0 + +static const std::map<ActionName, TouchCommandID> TouchCommands = { + {"tap", ACTION_TOUCH_TAP}, + {"longpress", ACTION_TOUCH_LONGPRESS}, + {"pan", ACTION_GESTURE_PAN}, + {"zoom", ACTION_GESTURE_ZOOM}, + {"rotate", ACTION_GESTURE_ROTATE}, + {"swipeleft", ACTION_GESTURE_SWIPE_LEFT}, + {"swiperight", ACTION_GESTURE_SWIPE_RIGHT}, + {"swipeup", ACTION_GESTURE_SWIPE_UP}, + {"swipedown", ACTION_GESTURE_SWIPE_DOWN}}; + +void CTouchTranslator::MapActions(int windowID, const TiXmlNode* pTouch) +{ + if (pTouch == nullptr) + return; + + TouchActionMap map; + + // Check if there already is a touch map for the window ID + auto it = m_touchMap.find(windowID); + if (it != m_touchMap.end()) + { + // Get the existing touch map and remove it from the window mapping as it + // will be inserted later on + map = std::move(it->second); + m_touchMap.erase(it); + } + + const TiXmlElement* pTouchElem = pTouch->ToElement(); + if (pTouchElem == nullptr) + return; + + const TiXmlElement* pButton = pTouchElem->FirstChildElement(); + while (pButton != nullptr) + { + CTouchAction action; + unsigned int touchActionKey = TranslateTouchCommand(pButton, action); + if (touchActionKey != ACTION_NONE) + { + // check if there already is a mapping for the parsed action + // and remove it if necessary + TouchActionMap::iterator actionIt = map.find(touchActionKey); + if (actionIt != map.end()) + map.erase(actionIt); + + map.insert(std::make_pair(touchActionKey, std::move(action))); + } + + pButton = pButton->NextSiblingElement(); + } + + // add the modified touch map with the window ID + if (!map.empty()) + m_touchMap.insert(std::make_pair(windowID, std::move(map))); +} + +void CTouchTranslator::Clear() +{ + m_touchMap.clear(); +} + +bool CTouchTranslator::TranslateTouchAction( + int window, int touchAction, int touchPointers, int& action, std::string& actionString) +{ + if (touchAction < 0) + return false; + + unsigned int actionId = ACTION_NONE; + + // handle virtual windows + window = CWindowTranslator::GetVirtualWindow(window); + + if (!TranslateAction(window, touchAction, touchPointers, actionId, actionString)) + { + // 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); + TranslateAction(window, touchAction, touchPointers, actionId, actionString); + } + } + + action = actionId; + return actionId != ACTION_NONE; +} + +bool CTouchTranslator::TranslateAction(int window, + unsigned int touchCommand, + int touchPointers, + unsigned int& actionId, + std::string& actionString) +{ + unsigned int touchActionKey = GetTouchActionKey(touchCommand, touchPointers); + + actionId = GetActionID(window, touchActionKey, actionString); + + return actionId != ACTION_NONE; +} + +unsigned int CTouchTranslator::GetActionID(WindowID window, + TouchActionKey touchActionKey, + std::string& actionString) +{ + auto windowIt = m_touchMap.find(window); + if (windowIt == m_touchMap.end()) + return ACTION_NONE; + + auto touchIt = windowIt->second.find(touchActionKey); + if (touchIt == windowIt->second.end()) + return ACTION_NONE; + + actionString = touchIt->second.strAction; + return touchIt->second.actionId; +} + +unsigned int CTouchTranslator::TranslateTouchCommand(const TiXmlElement* pButton, + CTouchAction& action) +{ + const char* szButton = pButton->Value(); + if (szButton == nullptr || pButton->FirstChild() == nullptr) + return ACTION_NONE; + + const char* szAction = pButton->FirstChild()->Value(); + if (szAction == nullptr) + return ACTION_NONE; + + std::string strTouchCommand = szButton; + StringUtils::ToLower(strTouchCommand); + + // Handle direction + const char* attrVal = pButton->Attribute("direction"); + if (attrVal != nullptr) + strTouchCommand += attrVal; + + // Lookup command + unsigned int touchCommandId = TOUCH_COMMAND_NONE; + auto it = TouchCommands.find(strTouchCommand); + if (it != TouchCommands.end()) + touchCommandId = it->second; + + if (touchCommandId == TOUCH_COMMAND_NONE) + { + CLog::Log(LOGERROR, "{}: Can't find touch command {}", __FUNCTION__, szButton); + return ACTION_NONE; + } + + // Handle pointers + int pointers = 1; + attrVal = pButton->Attribute("pointers"); + if (attrVal != nullptr) + pointers = (int)strtol(attrVal, nullptr, 0); + + unsigned int touchActionKey = GetTouchActionKey(touchCommandId, pointers); + + action.strAction = szAction; + if (!CActionTranslator::TranslateString(action.strAction, action.actionId) || + action.actionId == ACTION_NONE) + return ACTION_NONE; + + return touchActionKey; +} + +unsigned int CTouchTranslator::GetTouchActionKey(unsigned int touchCommandId, int touchPointers) +{ + if (touchPointers <= 0) + touchPointers = 1; + + return touchCommandId + touchPointers - 1; +} diff --git a/xbmc/input/TouchTranslator.h b/xbmc/input/TouchTranslator.h new file mode 100644 index 0000000..989857e --- /dev/null +++ b/xbmc/input/TouchTranslator.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "IButtonMapper.h" + +#include <map> +#include <string> + +class TiXmlElement; + +class CTouchTranslator : public IButtonMapper +{ +public: + CTouchTranslator() = default; + + // implementation of IButtonMapper + void MapActions(int windowID, const TiXmlNode* bDevice) override; + void Clear() override; + + bool TranslateTouchAction( + int window, int touchAction, int touchPointers, int& action, std::string& actionString); + +private: + bool TranslateAction(int window, + unsigned int touchCommand, + int touchPointers, + unsigned int& actionId, + std::string& actionString); + + struct CTouchAction + { + unsigned int actionId; + std::string strAction; // Needed for "ActivateWindow()" type actions + }; + + using TouchActionKey = unsigned int; + using TouchActionMap = std::map<TouchActionKey, CTouchAction>; + + using WindowID = int; + using TouchMap = std::map<WindowID, TouchActionMap>; + + unsigned int GetActionID(WindowID window, + TouchActionKey touchActionKey, + std::string& actionString); + + static unsigned int TranslateTouchCommand(const TiXmlElement* pButton, CTouchAction& action); + + static unsigned int GetTouchActionKey(unsigned int touchCommandId, int touchPointers); + + TouchMap m_touchMap; +}; diff --git a/xbmc/input/WindowKeymap.cpp b/xbmc/input/WindowKeymap.cpp new file mode 100644 index 0000000..4fd2ae0 --- /dev/null +++ b/xbmc/input/WindowKeymap.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-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 "WindowKeymap.h" + +#include "WindowTranslator.h" + +using namespace KODI; + +CWindowKeymap::CWindowKeymap(const std::string& controllerId) : m_controllerId(controllerId) +{ +} + +void CWindowKeymap::MapAction(int windowId, + const std::string& keyName, + JOYSTICK::KeymapAction action) +{ + auto& actionGroup = m_windowKeymap[windowId][keyName]; + + actionGroup.windowId = windowId; + auto it = actionGroup.actions.begin(); + while (it != actionGroup.actions.end()) + { + if (it->holdTimeMs == action.holdTimeMs && it->hotkeys == action.hotkeys) + it = actionGroup.actions.erase(it); + else + it++; + } + actionGroup.actions.insert(std::move(action)); +} + +const JOYSTICK::KeymapActionGroup& CWindowKeymap::GetActions(int windowId, + const std::string& keyName) const +{ + // handle virtual windows + windowId = CWindowTranslator::GetVirtualWindow(windowId); + + auto it = m_windowKeymap.find(windowId); + if (it != m_windowKeymap.end()) + { + auto& keymap = it->second; + auto it2 = keymap.find(keyName); + if (it2 != keymap.end()) + return it2->second; + } + + static const JOYSTICK::KeymapActionGroup empty{}; + return empty; +} diff --git a/xbmc/input/WindowKeymap.h b/xbmc/input/WindowKeymap.h new file mode 100644 index 0000000..f6c0612 --- /dev/null +++ b/xbmc/input/WindowKeymap.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "IKeymap.h" +#include "input/joysticks/JoystickTypes.h" + +#include <map> +#include <string> + +class CWindowKeymap : public IWindowKeymap +{ +public: + explicit CWindowKeymap(const std::string& controllerId); + + // implementation of IWindowKeymap + std::string ControllerID() const override { return m_controllerId; } + void MapAction(int windowId, + const std::string& keyName, + KODI::JOYSTICK::KeymapAction action) override; + const KODI::JOYSTICK::KeymapActionGroup& GetActions(int windowId, + const std::string& keyName) const override; + +private: + // Construction parameter + const std::string m_controllerId; + + using KeyName = std::string; + using Keymap = std::map<KeyName, KODI::JOYSTICK::KeymapActionGroup>; + + using WindowID = int; + using WindowMap = std::map<WindowID, Keymap>; + + WindowMap m_windowKeymap; +}; diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp new file mode 100644 index 0000000..cceb3f0 --- /dev/null +++ b/xbmc/input/WindowTranslator.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2017-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 "WindowTranslator.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "guilib/WindowIDs.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <cstring> +#include <stdlib.h> + +const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName = { + {"home", WINDOW_HOME}, + {"programs", WINDOW_PROGRAMS}, + {"pictures", WINDOW_PICTURES}, + {"filemanager", WINDOW_FILES}, + {"settings", WINDOW_SETTINGS_MENU}, + {"music", WINDOW_MUSIC_NAV}, + {"videos", WINDOW_VIDEO_NAV}, + {"tvchannels", WINDOW_TV_CHANNELS}, + {"tvrecordings", WINDOW_TV_RECORDINGS}, + {"tvguide", WINDOW_TV_GUIDE}, + {"tvtimers", WINDOW_TV_TIMERS}, + {"tvsearch", WINDOW_TV_SEARCH}, + {"radiochannels", WINDOW_RADIO_CHANNELS}, + {"radiorecordings", WINDOW_RADIO_RECORDINGS}, + {"radioguide", WINDOW_RADIO_GUIDE}, + {"radiotimers", WINDOW_RADIO_TIMERS}, + {"radiosearch", WINDOW_RADIO_SEARCH}, + {"gamecontrollers", WINDOW_DIALOG_GAME_CONTROLLERS}, + {"gameports", WINDOW_DIALOG_GAME_PORTS}, + {"games", WINDOW_GAMES}, + {"pvrguidecontrols", WINDOW_DIALOG_PVR_GUIDE_CONTROLS}, + {"pvrguideinfo", WINDOW_DIALOG_PVR_GUIDE_INFO}, + {"pvrrecordinginfo", WINDOW_DIALOG_PVR_RECORDING_INFO}, + {"pvrradiordsinfo", WINDOW_DIALOG_PVR_RADIO_RDS_INFO}, + {"pvrtimersetting", WINDOW_DIALOG_PVR_TIMER_SETTING}, + {"pvrgroupmanager", WINDOW_DIALOG_PVR_GROUP_MANAGER}, + {"pvrchannelmanager", WINDOW_DIALOG_PVR_CHANNEL_MANAGER}, + {"pvrguidesearch", WINDOW_DIALOG_PVR_GUIDE_SEARCH}, + {"pvrchannelscan", WINDOW_DIALOG_PVR_CHANNEL_SCAN}, + {"pvrupdateprogress", WINDOW_DIALOG_PVR_UPDATE_PROGRESS}, + {"pvrosdchannels", WINDOW_DIALOG_PVR_OSD_CHANNELS}, + {"pvrchannelguide", WINDOW_DIALOG_PVR_CHANNEL_GUIDE}, + {"pvrosdguide", WINDOW_DIALOG_PVR_CHANNEL_GUIDE}, // backward compatibility to v17 + {"pvrosdteletext", WINDOW_DIALOG_OSD_TELETEXT}, + {"systeminfo", WINDOW_SYSTEM_INFORMATION}, + {"screencalibration", WINDOW_SCREEN_CALIBRATION}, + {"systemsettings", WINDOW_SETTINGS_SYSTEM}, + {"servicesettings", WINDOW_SETTINGS_SERVICE}, + {"pvrsettings", WINDOW_SETTINGS_MYPVR}, + {"playersettings", WINDOW_SETTINGS_PLAYER}, + {"mediasettings", WINDOW_SETTINGS_MEDIA}, + {"interfacesettings", WINDOW_SETTINGS_INTERFACE}, + {"appearancesettings", WINDOW_SETTINGS_INTERFACE}, // backward compatibility to v16 + {"gamesettings", WINDOW_SETTINGS_MYGAMES}, + {"videoplaylist", WINDOW_VIDEO_PLAYLIST}, + {"loginscreen", WINDOW_LOGIN_SCREEN}, + {"profiles", WINDOW_SETTINGS_PROFILES}, + {"skinsettings", WINDOW_SKIN_SETTINGS}, + {"addonbrowser", WINDOW_ADDON_BROWSER}, + {"yesnodialog", WINDOW_DIALOG_YES_NO}, + {"progressdialog", WINDOW_DIALOG_PROGRESS}, + {"virtualkeyboard", WINDOW_DIALOG_KEYBOARD}, + {"volumebar", WINDOW_DIALOG_VOLUME_BAR}, + {"submenu", WINDOW_DIALOG_SUB_MENU}, + {"favourites", WINDOW_DIALOG_FAVOURITES}, + {"contextmenu", WINDOW_DIALOG_CONTEXT_MENU}, + {"notification", WINDOW_DIALOG_KAI_TOAST}, + {"numericinput", WINDOW_DIALOG_NUMERIC}, + {"gamepadinput", WINDOW_DIALOG_GAMEPAD}, + {"shutdownmenu", WINDOW_DIALOG_BUTTON_MENU}, + {"playercontrols", WINDOW_DIALOG_PLAYER_CONTROLS}, + {"playerprocessinfo", WINDOW_DIALOG_PLAYER_PROCESS_INFO}, + {"seekbar", WINDOW_DIALOG_SEEK_BAR}, + {"musicosd", WINDOW_DIALOG_MUSIC_OSD}, + {"addonsettings", WINDOW_DIALOG_ADDON_SETTINGS}, + {"visualisationpresetlist", WINDOW_DIALOG_VIS_PRESET_LIST}, + {"osdcmssettings", WINDOW_DIALOG_CMS_OSD_SETTINGS}, + {"osdvideosettings", WINDOW_DIALOG_VIDEO_OSD_SETTINGS}, + {"osdaudiosettings", WINDOW_DIALOG_AUDIO_OSD_SETTINGS}, + {"osdsubtitlesettings", WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS}, + {"videobookmarks", WINDOW_DIALOG_VIDEO_BOOKMARKS}, + {"filebrowser", WINDOW_DIALOG_FILE_BROWSER}, + {"networksetup", WINDOW_DIALOG_NETWORK_SETUP}, + {"mediasource", WINDOW_DIALOG_MEDIA_SOURCE}, + {"profilesettings", WINDOW_DIALOG_PROFILE_SETTINGS}, + {"locksettings", WINDOW_DIALOG_LOCK_SETTINGS}, + {"contentsettings", WINDOW_DIALOG_CONTENT_SETTINGS}, + {"libexportsettings", WINDOW_DIALOG_LIBEXPORT_SETTINGS}, + {"songinformation", WINDOW_DIALOG_SONG_INFO}, + {"smartplaylisteditor", WINDOW_DIALOG_SMART_PLAYLIST_EDITOR}, + {"smartplaylistrule", WINDOW_DIALOG_SMART_PLAYLIST_RULE}, + {"busydialog", WINDOW_DIALOG_BUSY}, + {"busydialognocancel", WINDOW_DIALOG_BUSY_NOCANCEL}, + {"pictureinfo", WINDOW_DIALOG_PICTURE_INFO}, + {"fullscreeninfo", WINDOW_DIALOG_FULLSCREEN_INFO}, + {"sliderdialog", WINDOW_DIALOG_SLIDER}, + {"addoninformation", WINDOW_DIALOG_ADDON_INFO}, + {"subtitlesearch", WINDOW_DIALOG_SUBTITLES}, + {"musicplaylist", WINDOW_MUSIC_PLAYLIST}, + {"musicplaylisteditor", WINDOW_MUSIC_PLAYLIST_EDITOR}, + {"infoprovidersettings", WINDOW_DIALOG_INFOPROVIDER_SETTINGS}, + {"teletext", WINDOW_DIALOG_OSD_TELETEXT}, + {"selectdialog", WINDOW_DIALOG_SELECT}, + {"musicinformation", WINDOW_DIALOG_MUSIC_INFO}, + {"okdialog", WINDOW_DIALOG_OK}, + {"movieinformation", WINDOW_DIALOG_VIDEO_INFO}, + {"textviewer", WINDOW_DIALOG_TEXT_VIEWER}, + {"fullscreenvideo", WINDOW_FULLSCREEN_VIDEO}, + {"dialogcolorpicker", WINDOW_DIALOG_COLOR_PICKER}, + + // Virtual window for fullscreen radio, uses WINDOW_FULLSCREEN_VIDEO as + // fallback + {"fullscreenlivetv", WINDOW_FULLSCREEN_LIVETV}, + + // Live TV channel preview + {"fullscreenlivetvpreview", WINDOW_FULLSCREEN_LIVETV_PREVIEW}, + + // Live TV direct channel number input + {"fullscreenlivetvinput", WINDOW_FULLSCREEN_LIVETV_INPUT}, + + // Virtual window for fullscreen radio, uses WINDOW_VISUALISATION as fallback + {"fullscreenradio", WINDOW_FULLSCREEN_RADIO}, + + // PVR Radio channel preview + {"fullscreenradiopreview", WINDOW_FULLSCREEN_RADIO_PREVIEW}, + + // PVR radio direct channel number input + {"fullscreenradioinput", WINDOW_FULLSCREEN_RADIO_INPUT}, + + {"fullscreengame", WINDOW_FULLSCREEN_GAME}, + {"visualisation", WINDOW_VISUALISATION}, + {"slideshow", WINDOW_SLIDESHOW}, + {"weather", WINDOW_WEATHER}, + {"screensaver", WINDOW_SCREENSAVER}, + {"videoosd", WINDOW_DIALOG_VIDEO_OSD}, + {"videomenu", WINDOW_VIDEO_MENU}, + {"videotimeseek", WINDOW_VIDEO_TIME_SEEK}, + {"splash", WINDOW_SPLASH}, + {"startwindow", WINDOW_START}, + {"startup", WINDOW_STARTUP_ANIM}, + {"peripheralsettings", WINDOW_DIALOG_PERIPHERAL_SETTINGS}, + {"extendedprogressdialog", WINDOW_DIALOG_EXT_PROGRESS}, + {"mediafilter", WINDOW_DIALOG_MEDIA_FILTER}, + {"addon", WINDOW_ADDON_START}, + {"eventlog", WINDOW_EVENT_LOG}, + {"favouritesbrowser", WINDOW_FAVOURITES}, + {"tvtimerrules", WINDOW_TV_TIMER_RULES}, + {"radiotimerrules", WINDOW_RADIO_TIMER_RULES}, + {"gameosd", WINDOW_DIALOG_GAME_OSD}, + {"gamevideofilter", WINDOW_DIALOG_GAME_VIDEO_FILTER}, + {"gamestretchmode", WINDOW_DIALOG_GAME_STRETCH_MODE}, + {"gamevolume", WINDOW_DIALOG_GAME_VOLUME}, + {"gameadvancedsettings", WINDOW_DIALOG_GAME_ADVANCED_SETTINGS}, + {"gamevideorotation", WINDOW_DIALOG_GAME_VIDEO_ROTATION}, + {"ingamesaves", WINDOW_DIALOG_IN_GAME_SAVES}, + {"gamesaves", WINDOW_DIALOG_GAME_SAVES}}; + +namespace +{ +struct FallbackWindowMapping +{ + int origin; + int target; +}; + +static const std::vector<FallbackWindowMapping> FallbackWindows = { + {WINDOW_FULLSCREEN_LIVETV, WINDOW_FULLSCREEN_VIDEO}, + {WINDOW_FULLSCREEN_LIVETV_INPUT, WINDOW_FULLSCREEN_LIVETV}, + {WINDOW_FULLSCREEN_LIVETV_PREVIEW, WINDOW_FULLSCREEN_LIVETV}, + {WINDOW_FULLSCREEN_RADIO, WINDOW_VISUALISATION}, + {WINDOW_FULLSCREEN_RADIO_INPUT, WINDOW_FULLSCREEN_RADIO}, + {WINDOW_FULLSCREEN_RADIO_PREVIEW, WINDOW_FULLSCREEN_RADIO}, +}; +} // anonymous namespace + +bool CWindowTranslator::WindowNameCompare::operator()(const WindowMapItem& lhs, + const WindowMapItem& rhs) const +{ + return std::strcmp(lhs.windowName, rhs.windowName) < 0; +} + +bool CWindowTranslator::WindowIDCompare::operator()(const WindowMapItem& lhs, + const WindowMapItem& rhs) const +{ + return lhs.windowId < rhs.windowId; +} + +void CWindowTranslator::GetWindows(std::vector<std::string>& windowList) +{ + windowList.clear(); + windowList.reserve(WindowMappingByName.size()); + for (auto itMapping : WindowMappingByName) + windowList.emplace_back(itMapping.windowName); +} + +int CWindowTranslator::TranslateWindow(const std::string& window) +{ + std::string strWindow(window); + if (strWindow.empty()) + return WINDOW_INVALID; + + StringUtils::ToLower(strWindow); + + // Eliminate .xml + if (StringUtils::EndsWith(strWindow, ".xml")) + strWindow = strWindow.substr(0, strWindow.size() - 4); + + // window12345, for custom window to be keymapped + if (strWindow.length() > 6 && StringUtils::StartsWith(strWindow, "window")) + strWindow = strWindow.substr(6); + + // Drop "my" prefix + if (StringUtils::StartsWith(strWindow, "my")) + strWindow = strWindow.substr(2); + + if (StringUtils::IsNaturalNumber(strWindow)) + { + // Allow a full window ID or a delta ID + int iWindow = atoi(strWindow.c_str()); + if (iWindow > WINDOW_INVALID) + return iWindow; + + return WINDOW_HOME + iWindow; + } + + // Run through the window structure + auto it = WindowMappingByName.find({strWindow.c_str(), {}}); + if (it != WindowMappingByName.end()) + return it->windowId; + + CLog::Log(LOGERROR, "Window Translator: Can't find window {}", window); + + return WINDOW_INVALID; +} + +std::string CWindowTranslator::TranslateWindow(int windowId) +{ + static auto reverseWindowMapping = CreateWindowMappingByID(); + + windowId = GetVirtualWindow(windowId); + + auto it = reverseWindowMapping.find(WindowMapItem{"", windowId}); + if (it != reverseWindowMapping.end()) + return it->windowName; + + return ""; +} + +int CWindowTranslator::GetFallbackWindow(int windowId) +{ + auto it = std::find_if( + FallbackWindows.begin(), FallbackWindows.end(), + [windowId](const FallbackWindowMapping& mapping) { return mapping.origin == windowId; }); + + if (it != FallbackWindows.end()) + return it->target; + + // For add-on windows use WINDOW_ADDON_START because ID is dynamic + if (WINDOW_ADDON_START < windowId && windowId <= WINDOW_ADDON_END) + return WINDOW_ADDON_START; + + return -1; +} + +CWindowTranslator::WindowMapByID CWindowTranslator::CreateWindowMappingByID() +{ + WindowMapByID reverseWindowMapping; + + reverseWindowMapping.insert(WindowMappingByName.begin(), WindowMappingByName.end()); + + return reverseWindowMapping; +} + +int CWindowTranslator::GetVirtualWindow(int windowId) +{ + if (windowId == WINDOW_FULLSCREEN_VIDEO) + { + if (g_application.CurrentFileItem().HasPVRChannelInfoTag()) + { + // special casing for Live TV + if (CServiceBroker::GetPVRManager() + .Get<PVR::GUI::Channels>() + .GetChannelNumberInputHandler() + .HasChannelNumber()) + return WINDOW_FULLSCREEN_LIVETV_INPUT; + else if (CServiceBroker::GetPVRManager() + .Get<PVR::GUI::Channels>() + .GetChannelNavigator() + .IsPreview()) + return WINDOW_FULLSCREEN_LIVETV_PREVIEW; + else + return WINDOW_FULLSCREEN_LIVETV; + } + else + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + // check if we're in a DVD menu + if (appPlayer->IsInMenu()) + return WINDOW_VIDEO_MENU; + // special casing for numeric seek + else if (appPlayer->GetSeekHandler().HasTimeCode()) + return WINDOW_VIDEO_TIME_SEEK; + } + } + else if (windowId == WINDOW_VISUALISATION) + { + if (g_application.CurrentFileItem().HasPVRChannelInfoTag()) + { + // special casing for PVR radio + if (CServiceBroker::GetPVRManager() + .Get<PVR::GUI::Channels>() + .GetChannelNumberInputHandler() + .HasChannelNumber()) + return WINDOW_FULLSCREEN_RADIO_INPUT; + else if (CServiceBroker::GetPVRManager() + .Get<PVR::GUI::Channels>() + .GetChannelNavigator() + .IsPreview()) + return WINDOW_FULLSCREEN_RADIO_PREVIEW; + else + return WINDOW_FULLSCREEN_RADIO; + } + else + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + // special casing for numeric seek + if (appPlayer->GetSeekHandler().HasTimeCode()) + return WINDOW_VIDEO_TIME_SEEK; + } + } + + return windowId; +} diff --git a/xbmc/input/WindowTranslator.h b/xbmc/input/WindowTranslator.h new file mode 100644 index 0000000..9d99c15 --- /dev/null +++ b/xbmc/input/WindowTranslator.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <set> +#include <string> +#include <vector> + +class CWindowTranslator +{ +public: + /*! + * \brief Get a list of all known window names + */ + static void GetWindows(std::vector<std::string>& windowList); + + /*! + * \brief Translate between a window name and its ID + * \param window The name of the window + * \return ID of the window, or WINDOW_INVALID if not found + */ + static int TranslateWindow(const std::string& window); + + /*! + * \brief Translate between a window id and it's name + * \param window id of the window + * \return name of the window, or an empty string if not found + */ + static std::string TranslateWindow(int windowId); + + /*! + * \brief Get the window ID that should be used as fallback for keymap input + * \return The fallback window, or -1 for no fallback window + */ + static int GetFallbackWindow(int windowId); + + /*! + * \brief Get the special window ID if conditions met + * \return The special window ID or the given window ID + */ + static int GetVirtualWindow(int windowId); + +private: + struct WindowMapItem + { + const char* windowName; + int windowId; + }; + + struct WindowNameCompare + { + bool operator()(const WindowMapItem& lhs, const WindowMapItem& rhs) const; + }; + + struct WindowIDCompare + { + bool operator()(const WindowMapItem& lhs, const WindowMapItem& rhs) const; + }; + + using WindowMapByName = std::set<WindowMapItem, WindowNameCompare>; + using WindowMapByID = std::set<WindowMapItem, WindowIDCompare>; + + static WindowMapByID CreateWindowMappingByID(); + + static const WindowMapByName WindowMappingByName; +}; diff --git a/xbmc/input/XBMC_keyboard.h b/xbmc/input/XBMC_keyboard.h new file mode 100644 index 0000000..09f2af7 --- /dev/null +++ b/xbmc/input/XBMC_keyboard.h @@ -0,0 +1,45 @@ +/* + * SDL - Simple DirectMedia Layer + * Copyright (C) 1997-2009 Sam Lantinga + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + * + * Sam Lantinga + * slouken@libsdl.org + */ + +#pragma once + +/* Include file for SDL keyboard event handling */ + +#include "XBMC_keysym.h" + +#include <stdint.h> + +/* Keysym structure + - The scancode is hardware dependent, and should not be used by general + applications. If no hardware scancode is available, it will be 0. + + - The 'unicode' translated character is only available when character + translation is enabled by the XBMC_EnableUNICODE() API. If non-zero, + this is a UNICODE character corresponding to the keypress. If the + high 9 bits of the character are 0, then this maps to the equivalent + ASCII character: + char ch; + if ( (keysym.unicode & 0xFF80) == 0 ) { + ch = keysym.unicode & 0x7F; + } else { + An international character.. + } + */ +typedef struct XBMC_keysym +{ + unsigned char scancode; /* hardware specific scancode */ + XBMCKey sym; /* SDL virtual keysym */ + XBMCMod mod; /* current key modifiers */ + uint16_t unicode; /* translated character */ +} XBMC_keysym; + +/* This is the mask which refers to all hotkey bindings */ +#define XBMC_ALL_HOTKEYS 0xFFFFFFFF diff --git a/xbmc/input/XBMC_keysym.h b/xbmc/input/XBMC_keysym.h new file mode 100644 index 0000000..a417c0c --- /dev/null +++ b/xbmc/input/XBMC_keysym.h @@ -0,0 +1,263 @@ +/* + * SDL - Simple DirectMedia Layer + * Copyright (C) 1997-2009 Sam Lantinga + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + * + * Sam Lantinga + * slouken@libsdl.org + */ + +#pragma once + +// The XBMC_keysym identifies a physical key on the keyboard i.e. it is +// analogous to a scan code but is hardware independent. +// These values are bazsed on the SDL_keysym standards, see: +// +// http://www.libsdl.org/tmp/SDL-1.3-docs/SDL__keysym_8h.html +// +// On SDL_KEYDOWN messages the keysym.sym will be one of these values. +// +// On OSs that don't support SDL (i.e. Windows) the OS dependant key +// handling code converts keypresses to an XBMC_keysym value. + +typedef enum +{ + // The keyboard syms have been cleverly chosen to map to ASCII + XBMCK_UNKNOWN = 0x00, + XBMCK_FIRST = 0x00, + XBMCK_CTRLF = 0x06, + XBMCK_BACKSPACE = 0x08, + XBMCK_TAB = 0x09, + XBMCK_CLEAR = 0x0C, + XBMCK_RETURN = 0x0D, + XBMCK_PAUSE = 0x13, + XBMCK_ESCAPE = 0x1B, + XBMCK_SPACE = 0x20, + XBMCK_EXCLAIM = 0x21, + XBMCK_QUOTEDBL = 0x22, + XBMCK_HASH = 0x23, + XBMCK_DOLLAR = 0x24, + XBMCK_PERCENT = 0x25, + XBMCK_AMPERSAND = 0x26, + XBMCK_QUOTE = 0x27, + XBMCK_LEFTPAREN = 0x28, + XBMCK_RIGHTPAREN = 0x29, + XBMCK_ASTERISK = 0x2A, + XBMCK_PLUS = 0x2B, + XBMCK_COMMA = 0x2C, + XBMCK_MINUS = 0x2D, + XBMCK_PERIOD = 0x2E, + XBMCK_SLASH = 0x2F, + XBMCK_0 = 0x30, + XBMCK_1 = 0x31, + XBMCK_2 = 0x32, + XBMCK_3 = 0x33, + XBMCK_4 = 0x34, + XBMCK_5 = 0x35, + XBMCK_6 = 0x36, + XBMCK_7 = 0x37, + XBMCK_8 = 0x38, + XBMCK_9 = 0x39, + XBMCK_COLON = 0x3A, + XBMCK_SEMICOLON = 0x3B, + XBMCK_LESS = 0x3C, + XBMCK_EQUALS = 0x3D, + XBMCK_GREATER = 0x3E, + XBMCK_QUESTION = 0x3F, + XBMCK_AT = 0x40, + // Skip uppercase letters + XBMCK_LEFTBRACKET = 0x5B, + XBMCK_BACKSLASH = 0x5C, + XBMCK_RIGHTBRACKET = 0x5D, + XBMCK_CARET = 0x5E, + XBMCK_UNDERSCORE = 0x5F, + XBMCK_BACKQUOTE = 0x60, + XBMCK_a = 0x61, + XBMCK_b = 0x62, + XBMCK_c = 0x63, + XBMCK_d = 0x64, + XBMCK_e = 0x65, + XBMCK_f = 0x66, + XBMCK_g = 0x67, + XBMCK_h = 0x68, + XBMCK_i = 0x69, + XBMCK_j = 0x6A, + XBMCK_k = 0x6B, + XBMCK_l = 0x6C, + XBMCK_m = 0x6D, + XBMCK_n = 0x6E, + XBMCK_o = 0x6F, + XBMCK_p = 0x70, + XBMCK_q = 0x71, + XBMCK_r = 0x72, + XBMCK_s = 0x73, + XBMCK_t = 0x74, + XBMCK_u = 0x75, + XBMCK_v = 0x76, + XBMCK_w = 0x77, + XBMCK_x = 0x78, + XBMCK_y = 0x79, + XBMCK_z = 0x7A, + XBMCK_LEFTBRACE = 0x7b, + XBMCK_PIPE = 0x7C, + XBMCK_RIGHTBRACE = 0x7D, + XBMCK_TILDE = 0x7E, + XBMCK_DELETE = 0x7F, + // End of ASCII mapped keysyms + + // Multimedia keys + // These are the Windows VK_ codes. SDL doesn't define codes for + // these keys. + XBMCK_BROWSER_BACK = 0xA6, + XBMCK_BROWSER_FORWARD = 0xA7, + XBMCK_BROWSER_REFRESH = 0xA8, + XBMCK_BROWSER_STOP = 0xA9, + XBMCK_BROWSER_SEARCH = 0xAA, + XBMCK_BROWSER_FAVORITES = 0xAB, + XBMCK_BROWSER_HOME = 0xAC, + XBMCK_VOLUME_MUTE = 0xAD, + XBMCK_VOLUME_DOWN = 0xAE, + XBMCK_VOLUME_UP = 0xAF, + XBMCK_MEDIA_NEXT_TRACK = 0xB0, + XBMCK_MEDIA_PREV_TRACK = 0xB1, + XBMCK_MEDIA_STOP = 0xB2, + XBMCK_MEDIA_PLAY_PAUSE = 0xB3, + XBMCK_LAUNCH_MAIL = 0xB4, + XBMCK_LAUNCH_MEDIA_SELECT = 0xB5, + XBMCK_LAUNCH_APP1 = 0xB6, + XBMCK_LAUNCH_APP2 = 0xB7, + XBMCK_LAUNCH_FILE_BROWSER = 0xB8, + XBMCK_LAUNCH_MEDIA_CENTER = 0xB9, + XBMCK_MEDIA_REWIND = 0xBA, + XBMCK_MEDIA_FASTFORWARD = 0xBB, + + // Numeric keypad + XBMCK_KP0 = 0x100, + XBMCK_KP1 = 0x101, + XBMCK_KP2 = 0x102, + XBMCK_KP3 = 0x103, + XBMCK_KP4 = 0x104, + XBMCK_KP5 = 0x105, + XBMCK_KP6 = 0x106, + XBMCK_KP7 = 0x107, + XBMCK_KP8 = 0x108, + XBMCK_KP9 = 0x109, + XBMCK_KP_PERIOD = 0x10A, + XBMCK_KP_DIVIDE = 0x10B, + XBMCK_KP_MULTIPLY = 0x10C, + XBMCK_KP_MINUS = 0x10D, + XBMCK_KP_PLUS = 0x10E, + XBMCK_KP_ENTER = 0x10F, + XBMCK_KP_EQUALS = 0x110, + + // Arrows + Home/End pad + XBMCK_UP = 0x111, + XBMCK_DOWN = 0x112, + XBMCK_RIGHT = 0x113, + XBMCK_LEFT = 0x114, + XBMCK_INSERT = 0x115, + XBMCK_HOME = 0x116, + XBMCK_END = 0x117, + XBMCK_PAGEUP = 0x118, + XBMCK_PAGEDOWN = 0x119, + + // Function keys + XBMCK_F1 = 0x11A, + XBMCK_F2 = 0x11B, + XBMCK_F3 = 0x11C, + XBMCK_F4 = 0x11D, + XBMCK_F5 = 0x11E, + XBMCK_F6 = 0x11F, + XBMCK_F7 = 0x120, + XBMCK_F8 = 0x121, + XBMCK_F9 = 0x122, + XBMCK_F10 = 0x123, + XBMCK_F11 = 0x124, + XBMCK_F12 = 0x125, + XBMCK_F13 = 0x126, + XBMCK_F14 = 0x127, + XBMCK_F15 = 0x128, + + // Key state modifier keys + XBMCK_NUMLOCK = 0x12C, + XBMCK_CAPSLOCK = 0x12D, + XBMCK_SCROLLOCK = 0x12E, + XBMCK_RSHIFT = 0x12F, + XBMCK_LSHIFT = 0x130, + XBMCK_RCTRL = 0x131, + XBMCK_LCTRL = 0x132, + XBMCK_RALT = 0x133, + XBMCK_LALT = 0x134, + XBMCK_RMETA = 0x135, + XBMCK_LMETA = 0x136, + XBMCK_LSUPER = 0x137, // Left "Windows" key + XBMCK_RSUPER = 0x138, // Right "Windows" key + XBMCK_MODE = 0x139, // "Alt Gr" key + XBMCK_COMPOSE = 0x13A, // Multi-key compose key + + // Miscellaneous function keys + XBMCK_HELP = 0x13B, + XBMCK_PRINT = 0x13C, + XBMCK_SYSREQ = 0x13D, + XBMCK_BREAK = 0x13E, + XBMCK_MENU = 0x13F, + XBMCK_POWER = 0x140, // Power Macintosh power key + XBMCK_EURO = 0x141, // Some european keyboards + XBMCK_UNDO = 0x142, // Atari keyboard has Undo + XBMCK_SLEEP = 0x143, // Sleep button on Nyxboard remote (and others?) + XBMCK_GUIDE = 0x144, + XBMCK_SETTINGS = 0x145, + XBMCK_INFO = 0x146, + XBMCK_RED = 0x147, + XBMCK_GREEN = 0x148, + XBMCK_YELLOW = 0x149, + XBMCK_BLUE = 0x14a, + XBMCK_ZOOM = 0x14b, + XBMCK_TEXT = 0x14c, + XBMCK_FAVORITES = 0x14d, + XBMCK_HOMEPAGE = 0x14e, + XBMCK_CONFIG = 0x14f, + XBMCK_EPG = 0x150, + + // Add any other keys here + + /* Media keys */ + XBMCK_STOP = 337, + XBMCK_RECORD = 338, + XBMCK_REWIND = 339, + XBMCK_PHONE = 340, + XBMCK_PLAY = 341, + XBMCK_SHUFFLE = 342, + XBMCK_FASTFORWARD = 343, + XBMCK_EJECT = 344, + + XBMCK_LAST +} XBMCKey; + +// Enumeration of valid key mods (possibly OR'd together) +typedef enum +{ + XBMCKMOD_NONE = 0x0000, + XBMCKMOD_LSHIFT = 0x0001, + XBMCKMOD_RSHIFT = 0x0002, + XBMCKMOD_LSUPER = 0x0010, + XBMCKMOD_RSUPER = 0x0020, + XBMCKMOD_LCTRL = 0x0040, + XBMCKMOD_RCTRL = 0x0080, + XBMCKMOD_LALT = 0x0100, + XBMCKMOD_RALT = 0x0200, + XBMCKMOD_LMETA = 0x0400, + XBMCKMOD_RMETA = 0x0800, + XBMCKMOD_NUM = 0x1000, + XBMCKMOD_CAPS = 0x2000, + XBMCKMOD_MODE = 0x4000, + XBMCKMOD_RESERVED = 0x8000 +} XBMCMod; + +#define XBMCKMOD_CTRL (XBMCKMOD_LCTRL | XBMCKMOD_RCTRL) +#define XBMCKMOD_SHIFT (XBMCKMOD_LSHIFT | XBMCKMOD_RSHIFT) +#define XBMCKMOD_ALT (XBMCKMOD_LALT | XBMCKMOD_RALT) +#define XBMCKMOD_META (XBMCKMOD_LMETA | XBMCKMOD_RMETA) +#define XBMCKMOD_SUPER (XBMCKMOD_LSUPER | XBMCKMOD_RSUPER) diff --git a/xbmc/input/XBMC_keytable.cpp b/xbmc/input/XBMC_keytable.cpp new file mode 100644 index 0000000..95ddb4c --- /dev/null +++ b/xbmc/input/XBMC_keytable.cpp @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2007-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 "input/XBMC_keytable.h" + +#include "input/XBMC_keysym.h" +#include "input/XBMC_vkeys.h" +#include "utils/StringUtils.h" + +// The array of XBMCKEYTABLEs used in XBMC. +// scancode, sym, unicode, ascii, vkey, keyname +static const XBMCKEYTABLE XBMCKeyTable[] = { + {XBMCK_BACKSPACE, 0, 0, XBMCVK_BACK, "backspace"}, + {XBMCK_TAB, 0, 0, XBMCVK_TAB, "tab"}, + {XBMCK_RETURN, 0, 0, XBMCVK_RETURN, "return"}, + {XBMCK_ESCAPE, 0, 0, XBMCVK_ESCAPE, "escape"}, + {0, 0, 0, XBMCVK_ESCAPE, "esc"} // Allowed abbreviation for "escape" + + // Number keys on the main keyboard + , + {XBMCK_0, '0', '0', XBMCVK_0, "zero"}, + {XBMCK_1, '1', '1', XBMCVK_1, "one"}, + {XBMCK_2, '2', '2', XBMCVK_2, "two"}, + {XBMCK_3, '3', '3', XBMCVK_3, "three"}, + {XBMCK_4, '4', '4', XBMCVK_4, "four"}, + {XBMCK_5, '5', '5', XBMCVK_5, "five"}, + {XBMCK_6, '6', '6', XBMCVK_6, "six"}, + {XBMCK_7, '7', '7', XBMCVK_7, "seven"}, + {XBMCK_8, '8', '8', XBMCVK_8, "eight"}, + {XBMCK_9, '9', '9', XBMCVK_9, "nine"} + + // A to Z - note that upper case A-Z don't have a matching name or + // vkey. Only the lower case a-z are used in key mappings. + , + {XBMCK_a, 'A', 'A', XBMCVK_A, NULL}, + {XBMCK_b, 'B', 'B', XBMCVK_B, NULL}, + {XBMCK_c, 'C', 'C', XBMCVK_C, NULL}, + {XBMCK_d, 'D', 'D', XBMCVK_D, NULL}, + {XBMCK_e, 'E', 'E', XBMCVK_E, NULL}, + {XBMCK_f, 'F', 'F', XBMCVK_F, NULL}, + {XBMCK_g, 'G', 'G', XBMCVK_G, NULL}, + {XBMCK_h, 'H', 'H', XBMCVK_H, NULL}, + {XBMCK_i, 'I', 'I', XBMCVK_I, NULL}, + {XBMCK_j, 'J', 'J', XBMCVK_J, NULL}, + {XBMCK_k, 'K', 'K', XBMCVK_K, NULL}, + {XBMCK_l, 'L', 'L', XBMCVK_L, NULL}, + {XBMCK_m, 'M', 'M', XBMCVK_M, NULL}, + {XBMCK_n, 'N', 'N', XBMCVK_N, NULL}, + {XBMCK_o, 'O', 'O', XBMCVK_O, NULL}, + {XBMCK_p, 'P', 'P', XBMCVK_P, NULL}, + {XBMCK_q, 'Q', 'Q', XBMCVK_Q, NULL}, + {XBMCK_r, 'R', 'R', XBMCVK_R, NULL}, + {XBMCK_s, 'S', 'S', XBMCVK_S, NULL}, + {XBMCK_t, 'T', 'T', XBMCVK_T, NULL}, + {XBMCK_u, 'U', 'U', XBMCVK_U, NULL}, + {XBMCK_v, 'V', 'V', XBMCVK_V, NULL}, + {XBMCK_w, 'W', 'W', XBMCVK_W, NULL}, + {XBMCK_x, 'X', 'X', XBMCVK_X, NULL}, + {XBMCK_y, 'Y', 'Y', XBMCVK_Y, NULL}, + {XBMCK_z, 'Z', 'Z', XBMCVK_Z, NULL} + + , + {XBMCK_a, 'a', 'a', XBMCVK_A, "a"}, + {XBMCK_b, 'b', 'b', XBMCVK_B, "b"}, + {XBMCK_c, 'c', 'c', XBMCVK_C, "c"}, + {XBMCK_d, 'd', 'd', XBMCVK_D, "d"}, + {XBMCK_e, 'e', 'e', XBMCVK_E, "e"}, + {XBMCK_f, 'f', 'f', XBMCVK_F, "f"}, + {XBMCK_g, 'g', 'g', XBMCVK_G, "g"}, + {XBMCK_h, 'h', 'h', XBMCVK_H, "h"}, + {XBMCK_i, 'i', 'i', XBMCVK_I, "i"}, + {XBMCK_j, 'j', 'j', XBMCVK_J, "j"}, + {XBMCK_k, 'k', 'k', XBMCVK_K, "k"}, + {XBMCK_l, 'l', 'l', XBMCVK_L, "l"}, + {XBMCK_m, 'm', 'm', XBMCVK_M, "m"}, + {XBMCK_n, 'n', 'n', XBMCVK_N, "n"}, + {XBMCK_o, 'o', 'o', XBMCVK_O, "o"}, + {XBMCK_p, 'p', 'p', XBMCVK_P, "p"}, + {XBMCK_q, 'q', 'q', XBMCVK_Q, "q"}, + {XBMCK_r, 'r', 'r', XBMCVK_R, "r"}, + {XBMCK_s, 's', 's', XBMCVK_S, "s"}, + {XBMCK_t, 't', 't', XBMCVK_T, "t"}, + {XBMCK_u, 'u', 'u', XBMCVK_U, "u"}, + {XBMCK_v, 'v', 'v', XBMCVK_V, "v"}, + {XBMCK_w, 'w', 'w', XBMCVK_W, "w"}, + {XBMCK_x, 'x', 'x', XBMCVK_X, "x"}, + {XBMCK_y, 'y', 'y', XBMCVK_Y, "y"}, + {XBMCK_z, 'z', 'z', XBMCVK_Z, "z"} + + // Misc printing characters + , + {XBMCK_SPACE, ' ', ' ', XBMCVK_SPACE, "space"}, + {XBMCK_EXCLAIM, '!', '!', XBMCVK_EXCLAIM, "exclaim"}, + {XBMCK_QUOTEDBL, '"', '"', XBMCVK_QUOTEDBL, "doublequote"}, + {XBMCK_HASH, '#', '#', XBMCVK_HASH, "hash"}, + {XBMCK_DOLLAR, '$', '$', XBMCVK_DOLLAR, "dollar"}, + {XBMCK_PERCENT, '%', '%', XBMCVK_PERCENT, "percent"}, + {XBMCK_AMPERSAND, '&', '&', XBMCVK_AMPERSAND, "ampersand"}, + {XBMCK_QUOTE, '\'', '\'', XBMCVK_QUOTE, "quote"}, + {XBMCK_LEFTPAREN, '(', '(', XBMCVK_LEFTPAREN, "leftbracket"}, + {XBMCK_RIGHTPAREN, ')', ')', XBMCVK_RIGHTPAREN, "rightbracket"}, + {XBMCK_ASTERISK, '*', '*', XBMCVK_ASTERISK, "asterisk"}, + {XBMCK_PLUS, '+', '+', XBMCVK_PLUS, "plus"}, + {XBMCK_COMMA, ',', ',', XBMCVK_COMMA, "comma"}, + {XBMCK_MINUS, '-', '-', XBMCVK_MINUS, "minus"}, + {XBMCK_PERIOD, '.', '.', XBMCVK_PERIOD, "period"}, + {XBMCK_SLASH, '/', '/', XBMCVK_SLASH, "forwardslash"} + + , + {XBMCK_COLON, ':', ':', XBMCVK_COLON, "colon"}, + {XBMCK_SEMICOLON, ';', ';', XBMCVK_SEMICOLON, "semicolon"}, + {XBMCK_LESS, '<', '<', XBMCVK_LESS, "lessthan"}, + {XBMCK_EQUALS, '=', '=', XBMCVK_EQUALS, "equals"}, + {XBMCK_GREATER, '>', '>', XBMCVK_GREATER, "greaterthan"}, + {XBMCK_QUESTION, '?', '?', XBMCVK_QUESTION, "questionmark"}, + {XBMCK_AT, '@', '@', XBMCVK_AT, "at"} + + , + {XBMCK_LEFTBRACKET, '[', '[', XBMCVK_LEFTBRACKET, "opensquarebracket"}, + {XBMCK_BACKSLASH, '\\', '\\', XBMCVK_BACKSLASH, "backslash"}, + {XBMCK_RIGHTBRACKET, ']', ']', XBMCVK_RIGHTBRACKET, "closesquarebracket"}, + {XBMCK_CARET, '^', '^', XBMCVK_CARET, "caret"}, + {XBMCK_UNDERSCORE, '_', '_', XBMCVK_UNDERSCORE, "underline"}, + {XBMCK_BACKQUOTE, '`', '`', XBMCVK_BACKQUOTE, "leftquote"} + + , + {XBMCK_LEFTBRACE, '{', '{', XBMCVK_LEFTBRACE, "openbrace"}, + {XBMCK_PIPE, '|', '|', XBMCVK_PIPE, "pipe"}, + {XBMCK_RIGHTBRACE, '}', '}', XBMCVK_RIGHTBRACE, "closebrace"}, + {XBMCK_TILDE, '~', '~', XBMCVK_TILDE, "tilde"} + + // Numeric keypad + , + {XBMCK_KP0, '0', '0', XBMCVK_NUMPAD0, "numpadzero"}, + {XBMCK_KP1, '1', '1', XBMCVK_NUMPAD1, "numpadone"}, + {XBMCK_KP2, '2', '2', XBMCVK_NUMPAD2, "numpadtwo"}, + {XBMCK_KP3, '3', '3', XBMCVK_NUMPAD3, "numpadthree"}, + {XBMCK_KP4, '4', '4', XBMCVK_NUMPAD4, "numpadfour"}, + {XBMCK_KP5, '5', '5', XBMCVK_NUMPAD5, "numpadfive"}, + {XBMCK_KP6, '6', '6', XBMCVK_NUMPAD6, "numpadsix"}, + {XBMCK_KP7, '7', '7', XBMCVK_NUMPAD7, "numpadseven"}, + {XBMCK_KP8, '8', '8', XBMCVK_NUMPAD8, "numpadeight"}, + {XBMCK_KP9, '9', '9', XBMCVK_NUMPAD9, "numpadnine"} + + , + {XBMCK_KP_DIVIDE, '/', '/', XBMCVK_NUMPADDIVIDE, "numpaddivide"}, + {XBMCK_KP_MULTIPLY, '*', '*', XBMCVK_NUMPADTIMES, "numpadtimes"}, + {XBMCK_KP_MINUS, '-', '-', XBMCVK_NUMPADMINUS, "numpadminus"}, + {XBMCK_KP_PLUS, '+', '+', XBMCVK_NUMPADPLUS, "numpadplus"}, + {XBMCK_KP_ENTER, 0, 0, XBMCVK_NUMPADENTER, "enter"}, + {XBMCK_KP_PERIOD, '.', '.', XBMCVK_NUMPADPERIOD, "numpadperiod"} + + // Multimedia keys + , + {XBMCK_BROWSER_BACK, 0, 0, XBMCVK_BROWSER_BACK, "browser_back"}, + {XBMCK_BROWSER_FORWARD, 0, 0, XBMCVK_BROWSER_FORWARD, "browser_forward"}, + {XBMCK_BROWSER_REFRESH, 0, 0, XBMCVK_BROWSER_REFRESH, "browser_refresh"}, + {XBMCK_BROWSER_STOP, 0, 0, XBMCVK_BROWSER_STOP, "browser_stop"}, + {XBMCK_BROWSER_SEARCH, 0, 0, XBMCVK_BROWSER_SEARCH, "browser_search"}, + {XBMCK_BROWSER_FAVORITES, 0, 0, XBMCVK_BROWSER_FAVORITES, "browser_favorites"}, + {XBMCK_BROWSER_HOME, 0, 0, XBMCVK_BROWSER_HOME, "browser_home"}, + {XBMCK_VOLUME_MUTE, 0, 0, XBMCVK_VOLUME_MUTE, "volume_mute"}, + {XBMCK_VOLUME_DOWN, 0, 0, XBMCVK_VOLUME_DOWN, "volume_down"}, + {XBMCK_VOLUME_UP, 0, 0, XBMCVK_VOLUME_UP, "volume_up"}, + {XBMCK_MEDIA_NEXT_TRACK, 0, 0, XBMCVK_MEDIA_NEXT_TRACK, "next_track"}, + {XBMCK_MEDIA_PREV_TRACK, 0, 0, XBMCVK_MEDIA_PREV_TRACK, "prev_track"}, + {XBMCK_MEDIA_STOP, 0, 0, XBMCVK_MEDIA_STOP, "stop"}, + {XBMCK_MEDIA_PLAY_PAUSE, 0, 0, XBMCVK_MEDIA_PLAY_PAUSE, "play_pause"}, + {XBMCK_MEDIA_REWIND, 0, 0, XBMCVK_MEDIA_REWIND, "rewind"}, + {XBMCK_MEDIA_FASTFORWARD, 0, 0, XBMCVK_MEDIA_FASTFORWARD, "fastforward"}, + {XBMCK_LAUNCH_MAIL, 0, 0, XBMCVK_LAUNCH_MAIL, "launch_mail"}, + {XBMCK_LAUNCH_MEDIA_SELECT, 0, 0, XBMCVK_LAUNCH_MEDIA_SELECT, "launch_media_select"}, + {XBMCK_LAUNCH_APP1, 0, 0, XBMCVK_LAUNCH_APP1, "launch_app1_pc_icon"}, + {XBMCK_LAUNCH_APP2, 0, 0, XBMCVK_LAUNCH_APP2, "launch_app2_pc_icon"}, + {XBMCK_LAUNCH_FILE_BROWSER, 0, 0, XBMCVK_LAUNCH_FILE_BROWSER, "launch_file_browser"}, + {XBMCK_LAUNCH_MEDIA_CENTER, 0, 0, XBMCVK_LAUNCH_MEDIA_CENTER, "launch_media_center"}, + {XBMCK_PLAY, 0, 0, XBMCVK_MEDIA_PLAY_PAUSE, "play_pause"}, + {XBMCK_STOP, 0, 0, XBMCVK_MEDIA_STOP, "stop"}, + {XBMCK_REWIND, 0, 0, XBMCVK_MEDIA_REWIND, "rewind"}, + {XBMCK_FASTFORWARD, 0, 0, XBMCVK_MEDIA_FASTFORWARD, "fastforward"}, + {XBMCK_RECORD, 0, 0, XBMCVK_MEDIA_RECORD, "record"} + + // Function keys + , + {XBMCK_F1, 0, 0, XBMCVK_F1, "f1"}, + {XBMCK_F2, 0, 0, XBMCVK_F2, "f2"}, + {XBMCK_F3, 0, 0, XBMCVK_F3, "f3"}, + {XBMCK_F4, 0, 0, XBMCVK_F4, "f4"}, + {XBMCK_F5, 0, 0, XBMCVK_F5, "f5"}, + {XBMCK_F6, 0, 0, XBMCVK_F6, "f6"}, + {XBMCK_F7, 0, 0, XBMCVK_F7, "f7"}, + {XBMCK_F8, 0, 0, XBMCVK_F8, "f8"}, + {XBMCK_F9, 0, 0, XBMCVK_F9, "f9"}, + {XBMCK_F10, 0, 0, XBMCVK_F10, "f10"}, + {XBMCK_F11, 0, 0, XBMCVK_F11, "f11"}, + {XBMCK_F12, 0, 0, XBMCVK_F12, "f12"}, + {XBMCK_F13, 0, 0, XBMCVK_F13, "f13"}, + {XBMCK_F14, 0, 0, XBMCVK_F14, "f14"}, + {XBMCK_F15, 0, 0, XBMCVK_F15, "f15"} + + // Misc non-printing keys + , + {XBMCK_UP, 0, 0, XBMCVK_UP, "up"}, + {XBMCK_DOWN, 0, 0, XBMCVK_DOWN, "down"}, + {XBMCK_RIGHT, 0, 0, XBMCVK_RIGHT, "right"}, + {XBMCK_LEFT, 0, 0, XBMCVK_LEFT, "left"}, + {XBMCK_INSERT, 0, 0, XBMCVK_INSERT, "insert"}, + {XBMCK_DELETE, 0, 0, XBMCVK_DELETE, "delete"}, + {XBMCK_HOME, 0, 0, XBMCVK_HOME, "home"}, + {XBMCK_END, 0, 0, XBMCVK_END, "end"}, + {XBMCK_PAGEUP, 0, 0, XBMCVK_PAGEUP, "pageup"}, + {XBMCK_PAGEDOWN, 0, 0, XBMCVK_PAGEDOWN, "pagedown"}, + {XBMCK_NUMLOCK, 0, 0, XBMCVK_NUMLOCK, "numlock"}, + {XBMCK_CAPSLOCK, 0, 0, XBMCVK_CAPSLOCK, "capslock"}, + {XBMCK_RSHIFT, 0, 0, XBMCVK_RSHIFT, "rightshift"}, + {XBMCK_LSHIFT, 0, 0, XBMCVK_LSHIFT, "leftshift"}, + {XBMCK_RCTRL, 0, 0, XBMCVK_RCONTROL, "rightctrl"}, + {XBMCK_LCTRL, 0, 0, XBMCVK_LCONTROL, "leftctrl"}, + {XBMCK_LALT, 0, 0, XBMCVK_LMENU, "leftalt"}, + {XBMCK_LSUPER, 0, 0, XBMCVK_LWIN, "leftwindows"}, + {XBMCK_RSUPER, 0, 0, XBMCVK_RWIN, "rightwindows"}, + {XBMCK_MENU, 0, 0, XBMCVK_MENU, "menu"}, + {XBMCK_PAUSE, 0, 0, XBMCVK_PAUSE, "pause"}, + {XBMCK_SCROLLOCK, 0, 0, XBMCVK_SCROLLLOCK, "scrolllock"}, + {XBMCK_PRINT, 0, 0, XBMCVK_PRINTSCREEN, "printscreen"}, + {XBMCK_POWER, 0, 0, XBMCVK_POWER, "power"}, + {XBMCK_SLEEP, 0, 0, XBMCVK_SLEEP, "sleep"}, + {XBMCK_GUIDE, 0, 0, XBMCVK_GUIDE, "guide"}, + {XBMCK_SETTINGS, 0, 0, XBMCVK_SETTINGS, "settings"}, + {XBMCK_INFO, 0, 0, XBMCVK_INFO, "info"}, + {XBMCK_RED, 0, 0, XBMCVK_RED, "red"}, + {XBMCK_GREEN, 0, 0, XBMCVK_GREEN, "green"}, + {XBMCK_YELLOW, 0, 0, XBMCVK_YELLOW, "yellow"}, + {XBMCK_BLUE, 0, 0, XBMCVK_BLUE, "blue"}, + {XBMCK_ZOOM, 0, 0, XBMCVK_ZOOM, "zoom"}, + {XBMCK_TEXT, 0, 0, XBMCVK_TEXT, "text"}, + {XBMCK_FAVORITES, 0, 0, XBMCVK_FAVORITES, "favorites"}, + {XBMCK_HOMEPAGE, 0, 0, XBMCVK_HOMEPAGE, "homepage"}, + {XBMCK_CONFIG, 0, 0, XBMCVK_CONFIG, "config"}, + {XBMCK_EPG, 0, 0, XBMCVK_EPG, "epg"}}; + +static int XBMCKeyTableSize = sizeof(XBMCKeyTable) / sizeof(XBMCKEYTABLE); + +bool KeyTableLookupName(std::string keyname, XBMCKEYTABLE* keytable) +{ + // If the name being searched for is empty there will be no match + if (keyname.empty()) + return false; + + // We need the button name to be in lowercase + StringUtils::ToLower(keyname); + + // Look up the key name in XBMCKeyTable + for (int i = 0; i < XBMCKeyTableSize; i++) + { + if (XBMCKeyTable[i].keyname) + { + if (strcmp(keyname.c_str(), XBMCKeyTable[i].keyname) == 0) + { + *keytable = XBMCKeyTable[i]; + return true; + } + } + } + + // The name wasn't found + return false; +} + +bool KeyTableLookupSym(uint16_t sym, XBMCKEYTABLE* keytable) +{ + // If the sym being searched for is zero there will be no match + if (sym == 0) + return false; + + // Look up the sym in XBMCKeyTable + for (int i = 0; i < XBMCKeyTableSize; i++) + { + if (sym == XBMCKeyTable[i].sym) + { + *keytable = XBMCKeyTable[i]; + return true; + } + } + + // The name wasn't found + return false; +} + +bool KeyTableLookupUnicode(uint16_t unicode, XBMCKEYTABLE* keytable) +{ + // If the unicode being searched for is zero there will be no match + if (unicode == 0) + return false; + + // Look up the unicode in XBMCKeyTable + for (int i = 0; i < XBMCKeyTableSize; i++) + { + if (unicode == XBMCKeyTable[i].unicode) + { + *keytable = XBMCKeyTable[i]; + return true; + } + } + + // The name wasn't found + return false; +} + +bool KeyTableLookupSymAndUnicode(uint16_t sym, uint16_t unicode, XBMCKEYTABLE* keytable) +{ + // If the sym being searched for is zero there will be no match (the + // unicode can be zero if the sym is non-zero) + if (sym == 0) + return false; + + // Look up the sym and unicode in XBMCKeyTable + for (int i = 0; i < XBMCKeyTableSize; i++) + { + if (sym == XBMCKeyTable[i].sym && unicode == XBMCKeyTable[i].unicode) + { + *keytable = XBMCKeyTable[i]; + return true; + } + } + + // The sym and unicode weren't found + return false; +} + +bool KeyTableLookupVKeyName(uint32_t vkey, XBMCKEYTABLE* keytable) +{ + // If the vkey being searched for is zero there will be no match + if (vkey == 0) + return false; + + // Look up the vkey in XBMCKeyTable + for (int i = 0; i < XBMCKeyTableSize; i++) + { + if (vkey == XBMCKeyTable[i].vkey && XBMCKeyTable[i].keyname) + { + *keytable = XBMCKeyTable[i]; + return true; + } + } + + // The name wasn't found + return false; +} diff --git a/xbmc/input/XBMC_keytable.h b/xbmc/input/XBMC_keytable.h new file mode 100644 index 0000000..1a4990e --- /dev/null +++ b/xbmc/input/XBMC_keytable.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007-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. + */ + +#pragma once + +#include <stdint.h> +#include <string> + +typedef struct struct_XBMCKEYTABLE +{ + + // The sym is a value that identifies which key was pressed. Note + // that it specifies the key not the character so it is unaffected by + // shift, control, etc. + uint16_t sym; + + // If the keypress generates a printing character the unicode and + // ascii member variables contain the character generated. If the + // key is a non-printing character, e.g. a function or arrow key, + // the unicode and ascii member variables are zero. + uint16_t unicode; + char ascii; + + // The following two member variables are used to specify the + // action/function assigned to a key. + // The keynames are used as tags in keyboard.xml. When reading keyboard.xml + // TranslateKeyboardString uses the keyname to look up the vkey, and + // this is used in the mapping table. + uint32_t vkey; + const char* keyname; + +} XBMCKEYTABLE; + +bool KeyTableLookupName(std::string keyname, XBMCKEYTABLE* keytable); +bool KeyTableLookupSym(uint16_t sym, XBMCKEYTABLE* keytable); +bool KeyTableLookupUnicode(uint16_t unicode, XBMCKEYTABLE* keytable); +bool KeyTableLookupSymAndUnicode(uint16_t sym, uint16_t unicode, XBMCKEYTABLE* keytable); +bool KeyTableLookupVKeyName(uint32_t vkey, XBMCKEYTABLE* keytable); diff --git a/xbmc/input/XBMC_vkeys.h b/xbmc/input/XBMC_vkeys.h new file mode 100644 index 0000000..833d2da --- /dev/null +++ b/xbmc/input/XBMC_vkeys.h @@ -0,0 +1,273 @@ +/* + * SDL - Simple DirectMedia Layer + * Copyright (C) 1997-2009 Sam Lantinga + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * See LICENSES/README.md for more information. + * + * Sam Lantinga + * slouken@libsdl.org + */ + +#pragma once + +// The XBMC_vkey identifies a key that is mapped to an action or function. +// The keysym.sym generated by SDL_KEYDOWN is mapped to a vkey and the vkey +// is used to lookup an action in the global mapping table. +// The vkey values are the ASCII code of the character where this is possible. +// Non-printing keypresses get a value in the range 0x80 - 0xFF. +// Note that the vkey is a byte value so it cannot be greater than 0xFF. + +typedef enum +{ + XBMCVK_BACK = 0x08, + XBMCVK_TAB = 0x09, + XBMCVK_RETURN = 0x0D, + XBMCVK_ESCAPE = 0x1B, + + XBMCVK_SPACE = 0x20, + XBMCVK_EXCLAIM = 0x21, + XBMCVK_QUOTEDBL = 0x22, + XBMCVK_HASH = 0x23, + XBMCVK_DOLLAR = 0x24, + XBMCVK_PERCENT = 0x25, + XBMCVK_AMPERSAND = 0x26, + XBMCVK_QUOTE = 0x27, + XBMCVK_LEFTPAREN = 0x28, + XBMCVK_RIGHTPAREN = 0x29, + XBMCVK_ASTERISK = 0x2A, + XBMCVK_PLUS = 0x2B, + XBMCVK_COMMA = 0x2C, + XBMCVK_MINUS = 0x2D, + XBMCVK_PERIOD = 0x2E, + XBMCVK_SLASH = 0x2F, + + XBMCVK_0 = 0x30, + XBMCVK_1 = 0x31, + XBMCVK_2 = 0x32, + XBMCVK_3 = 0x33, + XBMCVK_4 = 0x34, + XBMCVK_5 = 0x35, + XBMCVK_6 = 0x36, + XBMCVK_7 = 0x37, + XBMCVK_8 = 0x38, + XBMCVK_9 = 0x39, + + XBMCVK_COLON = 0x3A, + XBMCVK_SEMICOLON = 0x3B, + XBMCVK_LESS = 0x3C, + XBMCVK_EQUALS = 0x3D, + XBMCVK_GREATER = 0x3E, + XBMCVK_QUESTION = 0x3F, + XBMCVK_AT = 0x40, + + XBMCVK_A = 0x41, + XBMCVK_B = 0x42, + XBMCVK_C = 0x43, + XBMCVK_D = 0x44, + XBMCVK_E = 0x45, + XBMCVK_F = 0x46, + XBMCVK_G = 0x47, + XBMCVK_H = 0x48, + XBMCVK_I = 0x49, + XBMCVK_J = 0x4A, + XBMCVK_K = 0x4B, + XBMCVK_L = 0x4C, + XBMCVK_M = 0x4D, + XBMCVK_N = 0x4E, + XBMCVK_O = 0x4F, + XBMCVK_P = 0x50, + XBMCVK_Q = 0x51, + XBMCVK_R = 0x52, + XBMCVK_S = 0x53, + XBMCVK_T = 0x54, + XBMCVK_U = 0x55, + XBMCVK_V = 0x56, + XBMCVK_W = 0x57, + XBMCVK_X = 0x58, + XBMCVK_Y = 0x59, + XBMCVK_Z = 0x5A, + + XBMCVK_LEFTBRACKET = 0x5B, + XBMCVK_BACKSLASH = 0x5C, + XBMCVK_RIGHTBRACKET = 0x5D, + XBMCVK_CARET = 0x5E, + XBMCVK_UNDERSCORE = 0x5F, + XBMCVK_BACKQUOTE = 0x60, + + // Lowercase letters 0x61 - 0x7a have the same vkey as uppercase, so + // use this block for the numpad keys + XBMCVK_NUMPADDIVIDE = 0x61, + XBMCVK_NUMPADTIMES = 0x62, + XBMCVK_NUMPADMINUS = 0x63, + XBMCVK_NUMPADPLUS = 0x64, + XBMCVK_NUMPADENTER = 0x65, + XBMCVK_NUMPADPERIOD = 0x66, + XBMCVK_NUMPAD0 = 0x70, + XBMCVK_NUMPAD1 = 0x71, + XBMCVK_NUMPAD2 = 0x72, + XBMCVK_NUMPAD3 = 0x73, + XBMCVK_NUMPAD4 = 0x74, + XBMCVK_NUMPAD5 = 0x75, + XBMCVK_NUMPAD6 = 0x76, + XBMCVK_NUMPAD7 = 0x77, + XBMCVK_NUMPAD8 = 0x78, + XBMCVK_NUMPAD9 = 0x79, + + XBMCVK_LEFTBRACE = 0x7B, + XBMCVK_PIPE = 0x7C, + XBMCVK_RIGHTBRACE = 0x7D, + XBMCVK_TILDE = 0x7E, + + // Non-printing characters + + XBMCVK_UP = 0x80, + XBMCVK_DOWN = 0x81, + XBMCVK_LEFT = 0x82, + XBMCVK_RIGHT = 0x83, + XBMCVK_PAGEUP = 0x84, + XBMCVK_PAGEDOWN = 0x85, + XBMCVK_INSERT = 0x86, + XBMCVK_DELETE = 0x87, + XBMCVK_HOME = 0x88, + XBMCVK_END = 0x89, + + XBMCVK_F1 = 0x90, + XBMCVK_F2 = 0x91, + XBMCVK_F3 = 0x92, + XBMCVK_F4 = 0x93, + XBMCVK_F5 = 0x94, + XBMCVK_F6 = 0x95, + XBMCVK_F7 = 0x96, + XBMCVK_F8 = 0x97, + XBMCVK_F9 = 0x98, + XBMCVK_F10 = 0x99, + XBMCVK_F11 = 0x9A, + XBMCVK_F12 = 0x9B, + XBMCVK_F13 = 0x9C, + XBMCVK_F14 = 0x9D, + XBMCVK_F15 = 0x9E, + XBMCVK_F16 = 0x9F, + XBMCVK_F17 = 0xA0, + XBMCVK_F18 = 0xA1, + XBMCVK_F19 = 0xA2, + XBMCVK_F20 = 0xA3, + XBMCVK_F21 = 0xA4, + XBMCVK_F22 = 0xA5, + XBMCVK_F23 = 0xA6, + XBMCVK_F24 = 0xA7, + + XBMCVK_BROWSER_BACK = 0xB0, + XBMCVK_BROWSER_FORWARD = 0xB1, + XBMCVK_BROWSER_REFRESH = 0xB2, + XBMCVK_BROWSER_STOP = 0xB3, + XBMCVK_BROWSER_SEARCH = 0xB4, + XBMCVK_BROWSER_FAVORITES = 0xB5, + XBMCVK_BROWSER_HOME = 0xB6, + XBMCVK_VOLUME_MUTE = 0xB7, + XBMCVK_VOLUME_DOWN = 0xB8, + XBMCVK_VOLUME_UP = 0xB9, + XBMCVK_MEDIA_NEXT_TRACK = 0xBA, + XBMCVK_MEDIA_PREV_TRACK = 0xBB, + XBMCVK_MEDIA_STOP = 0xBC, + XBMCVK_MEDIA_PLAY_PAUSE = 0xBD, + XBMCVK_LAUNCH_MAIL = 0xBE, + XBMCVK_LAUNCH_MEDIA_SELECT = 0xBF, + XBMCVK_LAUNCH_APP1 = 0xC0, + XBMCVK_LAUNCH_APP2 = 0xC1, + XBMCVK_LAUNCH_FILE_BROWSER = 0xC2, + XBMCVK_LAUNCH_MEDIA_CENTER = 0xC3, + XBMCVK_MEDIA_REWIND = 0xC4, + XBMCVK_MEDIA_FASTFORWARD = 0xC5, + XBMCVK_MEDIA_RECORD = 0xC6, + + XBMCVK_LCONTROL = 0xD0, + XBMCVK_RCONTROL = 0xD1, + XBMCVK_LSHIFT = 0xD2, + XBMCVK_RSHIFT = 0xD3, + XBMCVK_LMENU = 0xD4, + XBMCVK_RMENU = 0xD5, + XBMCVK_LWIN = 0xD6, + XBMCVK_RWIN = 0xD7, + XBMCVK_MENU = 0xD8, + XBMCVK_CAPSLOCK = 0xD9, + XBMCVK_NUMLOCK = 0xDA, + + XBMCVK_PRINTSCREEN = 0xDB, + XBMCVK_SCROLLLOCK = 0xDC, + XBMCVK_PAUSE = 0XDD, + XBMCVK_POWER = 0XDE, + XBMCVK_SLEEP = 0XDF, + XBMCVK_GUIDE = 0xE0, + XBMCVK_SETTINGS = 0xE1, + XBMCVK_INFO = 0xE2, + XBMCVK_RED = 0xE3, + XBMCVK_GREEN = 0xE4, + XBMCVK_YELLOW = 0xE5, + XBMCVK_BLUE = 0xE6, + XBMCVK_ZOOM = 0xE7, + XBMCVK_TEXT = 0xE8, + XBMCVK_FAVORITES = 0xE9, + XBMCVK_HOMEPAGE = 0xEA, + XBMCVK_CONFIG = 0xEB, + XBMCVK_EPG = 0xEC, + + XBMCVK_LAST = 0xFF +} XBMCVKey; + +// These should be in winuser.h. Not sure why they have been defined here +#ifndef VK_0 +#define VK_0 '0' +#define VK_1 '1' +#define VK_2 '2' +#define VK_3 '3' +#define VK_4 '4' +#define VK_5 '5' +#define VK_6 '6' +#define VK_7 '7' +#define VK_8 '8' +#define VK_9 '9' +#define VK_A 'A' +#define VK_B 'B' +#define VK_C 'C' +#define VK_D 'D' +#define VK_E 'E' +#define VK_F 'F' +#define VK_G 'G' +#define VK_H 'H' +#define VK_I 'I' +#define VK_J 'J' +#define VK_K 'K' +#define VK_L 'L' +#define VK_M 'M' +#define VK_N 'N' +#define VK_O 'O' +#define VK_P 'P' +#define VK_Q 'Q' +#define VK_R 'R' +#define VK_S 'S' +#define VK_T 'T' +#define VK_U 'U' +#define VK_V 'V' +#define VK_W 'W' +#define VK_X 'X' +#define VK_Y 'Y' +#define VK_Z 'Z' +#endif /* VK_0 */ + +/* These keys haven't been defined, but were experimentally determined */ +#ifndef VK_SEMICOLON +#define VK_SEMICOLON 0xBA +#define VK_EQUALS 0xBB +#define VK_COMMA 0xBC +#define VK_MINUS 0xBD +#define VK_PERIOD 0xBE +#define VK_SLASH 0xBF +#define VK_GRAVE 0xC0 +#define VK_LBRACKET 0xDB +#define VK_BACKSLASH 0xDC +#define VK_RBRACKET 0xDD +#define VK_APOSTROPHE 0xDE +#define VK_BACKTICK 0xDF +#define VK_OEM_102 0xE2 +#endif diff --git a/xbmc/input/actions/Action.cpp b/xbmc/input/actions/Action.cpp new file mode 100644 index 0000000..8449e42 --- /dev/null +++ b/xbmc/input/actions/Action.cpp @@ -0,0 +1,154 @@ +/* + * 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 "Action.h" + +#include "ActionIDs.h" +#include "ActionTranslator.h" +#include "input/Key.h" + +CAction::CAction() : m_id(ACTION_NONE) +{ +} + +CAction::CAction(int actionID, + float amount1 /* = 1.0f */, + float amount2 /* = 0.0f */, + const std::string& name /* = "" */, + unsigned int holdTime /*= 0*/) + : m_name(name) +{ + m_id = actionID; + m_amount[0] = amount1; + m_amount[1] = amount2; + m_repeat = 0; + m_buttonCode = 0; + m_unicode = 0; + m_holdTime = holdTime; +} + +CAction::CAction(int actionID, + unsigned int state, + float posX, + float posY, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + const std::string& name) + : m_name(name) +{ + m_id = actionID; + m_amount[0] = posX; + m_amount[1] = posY; + m_amount[2] = offsetX; + m_amount[3] = offsetY; + m_amount[4] = velocityX; + m_amount[5] = velocityY; + m_repeat = 0; + m_buttonCode = 0; + m_unicode = 0; + m_holdTime = state; +} + +CAction::CAction(int actionID, wchar_t unicode) +{ + m_id = actionID; + m_repeat = 0; + m_buttonCode = 0; + m_unicode = unicode; + m_holdTime = 0; +} + +CAction::CAction(int actionID, const std::string& name, const CKey& key) : m_name(name) +{ + m_id = actionID; + m_amount[0] = 1; // digital button (could change this for repeat acceleration) + m_repeat = key.GetRepeat(); + m_buttonCode = key.GetButtonCode(); + m_unicode = key.GetUnicode(); + m_holdTime = key.GetHeld(); + // get the action amounts of the analog buttons + if (key.GetButtonCode() == KEY_BUTTON_LEFT_ANALOG_TRIGGER) + m_amount[0] = (float)key.GetLeftTrigger() / 255.0f; + else if (key.GetButtonCode() == KEY_BUTTON_RIGHT_ANALOG_TRIGGER) + m_amount[0] = (float)key.GetRightTrigger() / 255.0f; + else if (key.GetButtonCode() == KEY_BUTTON_LEFT_THUMB_STICK) + { + m_amount[0] = key.GetLeftThumbX(); + m_amount[1] = key.GetLeftThumbY(); + } + else if (key.GetButtonCode() == KEY_BUTTON_RIGHT_THUMB_STICK) + { + m_amount[0] = key.GetRightThumbX(); + m_amount[1] = key.GetRightThumbY(); + } + else if (key.GetButtonCode() == KEY_BUTTON_LEFT_THUMB_STICK_UP) + m_amount[0] = key.GetLeftThumbY(); + else if (key.GetButtonCode() == KEY_BUTTON_LEFT_THUMB_STICK_DOWN) + m_amount[0] = -key.GetLeftThumbY(); + else if (key.GetButtonCode() == KEY_BUTTON_LEFT_THUMB_STICK_LEFT) + m_amount[0] = -key.GetLeftThumbX(); + else if (key.GetButtonCode() == KEY_BUTTON_LEFT_THUMB_STICK_RIGHT) + m_amount[0] = key.GetLeftThumbX(); + else if (key.GetButtonCode() == KEY_BUTTON_RIGHT_THUMB_STICK_UP) + m_amount[0] = key.GetRightThumbY(); + else if (key.GetButtonCode() == KEY_BUTTON_RIGHT_THUMB_STICK_DOWN) + m_amount[0] = -key.GetRightThumbY(); + else if (key.GetButtonCode() == KEY_BUTTON_RIGHT_THUMB_STICK_LEFT) + m_amount[0] = -key.GetRightThumbX(); + else if (key.GetButtonCode() == KEY_BUTTON_RIGHT_THUMB_STICK_RIGHT) + m_amount[0] = key.GetRightThumbX(); +} + +CAction::CAction(int actionID, const std::string& name) : m_name(name) +{ + m_id = actionID; + m_repeat = 0; + m_buttonCode = 0; + m_unicode = 0; + m_holdTime = 0; +} + +CAction& CAction::operator=(const CAction& rhs) +{ + if (this != &rhs) + { + m_id = rhs.m_id; + for (unsigned int i = 0; i < max_amounts; i++) + m_amount[i] = rhs.m_amount[i]; + m_name = rhs.m_name; + m_repeat = rhs.m_repeat; + m_buttonCode = rhs.m_buttonCode; + m_unicode = rhs.m_unicode; + m_holdTime = rhs.m_holdTime; + m_text = rhs.m_text; + } + return *this; +} + +void CAction::ClearAmount() +{ + for (float& amount : m_amount) + amount = 0; +} + +bool CAction::IsMouse() const +{ + return (m_id >= ACTION_MOUSE_START && m_id <= ACTION_MOUSE_END); +} + +bool CAction::IsGesture() const +{ + return (m_id >= ACTION_GESTURE_NOTIFY && m_id <= ACTION_GESTURE_END); +} + +bool CAction::IsAnalog() const +{ + return CActionTranslator::IsAnalog(m_id); +} diff --git a/xbmc/input/actions/Action.h b/xbmc/input/actions/Action.h new file mode 100644 index 0000000..f1d4173 --- /dev/null +++ b/xbmc/input/actions/Action.h @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#pragma once + +#include <string> + +#ifndef SWIG + +class CKey; + +/*! + \ingroup actionkeys + \brief class encapsulating information regarding a particular user action to be sent to windows + and controls + */ +class CAction +{ +public: + CAction(); + CAction(int actionID, + float amount1 = 1.0f, + float amount2 = 0.0f, + const std::string& name = "", + unsigned int holdTime = 0); + CAction(int actionID, wchar_t unicode); + CAction(int actionID, + unsigned int state, + float posX, + float posY, + float offsetX, + float offsetY, + float velocityX = 0.0f, + float velocityY = 0.0f, + const std::string& name = ""); + CAction(int actionID, const std::string& name, const CKey& key); + CAction(int actionID, const std::string& name); + + CAction(const CAction& other) { *this = other; } + CAction& operator=(const CAction& rhs); + + /*! \brief Identifier of the action + \return id of the action + */ + int GetID() const { return m_id; } + + /*! \brief Is this an action from the mouse + \return true if this is a mouse action, false otherwise + */ + bool IsMouse() const; + + bool IsGesture() const; + + /*! \brief Human-readable name of the action + \return name of the action + */ + const std::string& GetName() const { return m_name; } + + /*! \brief Text of the action if any + \return text payload of this action. + */ + const std::string& GetText() const { return m_text; } + + /*! \brief Set the text payload of the action + \param text to be set + */ + void SetText(const std::string& text) { m_text = text; } + + /*! \brief Get an amount associated with this action + \param zero-based index of amount to retrieve, defaults to 0 + \return an amount associated with this action + */ + float GetAmount(unsigned int index = 0) const + { + return (index < max_amounts) ? m_amount[index] : 0; + }; + + /*! \brief Reset all amount values to zero + */ + void ClearAmount(); + + /*! \brief Unicode value associated with this action + \return unicode value associated with this action, for keyboard input. + */ + wchar_t GetUnicode() const { return m_unicode; } + + /*! \brief Time in ms that the key has been held + \return time that the key has been held down in ms. + */ + unsigned int GetHoldTime() const { return m_holdTime; } + + /*! \brief Time since last repeat in ms + \return time since last repeat in ms. Returns 0 if unknown. + */ + float GetRepeat() const { return m_repeat; } + + /*! \brief Button code that triggered this action + \return button code + */ + unsigned int GetButtonCode() const { return m_buttonCode; } + + bool IsAnalog() const; + +private: + int m_id; + std::string m_name; + + static const unsigned int max_amounts = 6; // Must be at least 6 + float m_amount[max_amounts] = {}; + + float m_repeat = 0.0f; + unsigned int m_holdTime = 0; + unsigned int m_buttonCode = 0; + wchar_t m_unicode = 0; + std::string m_text; +}; + +#endif diff --git a/xbmc/input/actions/ActionIDs.h b/xbmc/input/actions/ActionIDs.h new file mode 100644 index 0000000..50fa662 --- /dev/null +++ b/xbmc/input/actions/ActionIDs.h @@ -0,0 +1,493 @@ +/* + * 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. + */ + +#pragma once + +/** + * \defgroup kodi_key_action_ids Action Id's + * \ingroup python_xbmcgui_window_cb + * \ingroup python_xbmcgui_action + * @{ + * @brief Actions that we have defined. + */ + +constexpr const int ACTION_NONE = 0; +constexpr const int ACTION_MOVE_LEFT = 1; +constexpr const int ACTION_MOVE_RIGHT = 2; +constexpr const int ACTION_MOVE_UP = 3; +constexpr const int ACTION_MOVE_DOWN = 4; +constexpr const int ACTION_PAGE_UP = 5; +constexpr const int ACTION_PAGE_DOWN = 6; +constexpr const int ACTION_SELECT_ITEM = 7; +constexpr const int ACTION_HIGHLIGHT_ITEM = 8; +constexpr const int ACTION_PARENT_DIR = 9; +constexpr const int ACTION_PREVIOUS_MENU = 10; +constexpr const int ACTION_SHOW_INFO = 11; + +constexpr const int ACTION_PAUSE = 12; +constexpr const int ACTION_STOP = 13; +constexpr const int ACTION_NEXT_ITEM = 14; +constexpr const int ACTION_PREV_ITEM = 15; + +//! Can be used to specify specific action in a window, Playback control is +//! handled in ACTION_PLAYER_* +constexpr const int ACTION_FORWARD = 16; + +//! Can be used to specify specific action in a window, Playback control is +//! handled in ACTION_PLAYER_* +constexpr const int ACTION_REWIND = 17; + +//! Toggle between GUI and movie or GUI and visualisation. +constexpr const int ACTION_SHOW_GUI = 18; + +//! Toggle quick-access zoom modes. Can be used in videoFullScreen.zml window id=2005 +constexpr const int ACTION_ASPECT_RATIO = 19; + +//! Seek +1% in the movie. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_STEP_FORWARD = 20; + +//! Seek -1% in the movie. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_STEP_BACK = 21; + +//! Seek +10% in the movie. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_BIG_STEP_FORWARD = 22; + +//! Seek -10% in the movie. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_BIG_STEP_BACK = 23; + +//! Show/hide OSD. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_SHOW_OSD = 24; + +//! Turn subtitles on/off. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_SHOW_SUBTITLES = 25; + +//! Switch to next subtitle of movie. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_NEXT_SUBTITLE = 26; + +//! Show debug info for VideoPlayer +constexpr const int ACTION_PLAYER_DEBUG = 27; + +//! Show next picture of slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_NEXT_PICTURE = 28; + +//! Show previous picture of slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_PREV_PICTURE = 29; + +//! Zoom in picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_OUT = 30; + +//! Zoom out picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_IN = 31; + +//! Used to toggle between source view and destination view. Can be used in +//! myfiles.xml window < id=3 +constexpr const int ACTION_TOGGLE_SOURCE_DEST = 32; + +//! Used to toggle between current view and playlist view. Can be used in all mymusic xml files +constexpr const int ACTION_SHOW_PLAYLIST = 33; + +//! Used to queue a item to the playlist. Can be used in all mymusic xml files +constexpr const int ACTION_QUEUE_ITEM = 34; + +//! Not used anymore +constexpr const int ACTION_REMOVE_ITEM = 35; + +//! Not used anymore +constexpr const int ACTION_SHOW_FULLSCREEN = 36; + +//! Zoom 1x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_NORMAL = 37; + +//! Zoom 2x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_1 = 38; + +//! Zoom 3x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_2 = 39; + +//! Zoom 4x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_3 = 40; + +//! Zoom 5x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_4 = 41; + +//! Zoom 6x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_5 = 42; + +//! Zoom 7x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_6 = 43; + +//! Zoom 8x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_7 = 44; + +//! Zoom 9x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_8 = 45; + +//< Zoom 10x picture during slideshow. Can be used in slideshow.xml window id=2007 +constexpr const int ACTION_ZOOM_LEVEL_9 = 46; + +//< Select next arrow. Can be used in: settingsScreenCalibration.xml windowid=11 +constexpr const int ACTION_CALIBRATE_SWAP_ARROWS = 47; + +//! Reset calibration to defaults. Can be used in: `settingsScreenCalibration.xml` +//! windowid=11/settingsUICalibration.xml windowid=10 +constexpr const int ACTION_CALIBRATE_RESET = 48; + +//! Analog thumbstick move. Can be used in: `slideshow.xml` +//! windowid=2007/settingsScreenCalibration.xml windowid=11/settingsUICalibration.xml +//! windowid=10 +//! @note see also ACTION_ANALOG_MOVE_X_LEFT, ACTION_ANALOG_MOVE_X_RIGHT, +//! ACTION_ANALOG_MOVE_Y_UP, ACTION_ANALOG_MOVE_Y_DOWN +constexpr const int ACTION_ANALOG_MOVE = 49; + +//! Rotate current picture clockwise during slideshow. Can be used in +//! slideshow.xml window < id=2007 +constexpr const int ACTION_ROTATE_PICTURE_CW = 50; + +//! Rotate current picture counterclockwise during slideshow. Can be used in +//! slideshow.xml window id=2007 +constexpr const int ACTION_ROTATE_PICTURE_CCW = 51; + +//! Decrease subtitle/movie Delay. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_SUBTITLE_DELAY_MIN = 52; + +//! Increase subtitle/movie Delay. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_SUBTITLE_DELAY_PLUS = 53; + +//! Increase avsync delay. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_AUDIO_DELAY_MIN = 54; + +//! Decrease avsync delay. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_AUDIO_DELAY_PLUS = 55; + +//! Select next language in movie. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_AUDIO_NEXT_LANGUAGE = 56; + +//! Switch 2 next resolution. Can b used during screen calibration +//! settingsScreenCalibration.xml windowid=11 +constexpr const int ACTION_CHANGE_RESOLUTION = 57; + +//! Remote keys 0-9 are used by multiple windows. +//! +//! For example, in videoFullScreen.xml window id=2005 you can enter +//! time (mmss) to jump to particular point in the movie. +//! +//! With spincontrols you can enter a 3-digit number to quickly set the +//! spincontrol to desired value. +//!@{ +constexpr const int REMOTE_0 = 58; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_1 = 59; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_2 = 60; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_3 = 61; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_4 = 62; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_5 = 63; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_6 = 64; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_7 = 65; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_8 = 66; + +//! @see REMOTE_0 about details. +constexpr const int REMOTE_9 = 67; +//!@} + +//! Show player process info (video decoder, pixel format, pvr signal strength +//! and the like +constexpr const int ACTION_PLAYER_PROCESS_INFO = 69; + +constexpr const int ACTION_PLAYER_PROGRAM_SELECT = 70; + +constexpr const int ACTION_PLAYER_RESOLUTION_SELECT = 71; + +//! Jumps a few seconds back during playback of movie. Can be used in videoFullScreen.xml +//! window id=2005 +constexpr const int ACTION_SMALL_STEP_BACK = 76; + +//! FF in current file played. global action, can be used anywhere +constexpr const int ACTION_PLAYER_FORWARD = 77; + +//! RW in current file played. global action, can be used anywhere +constexpr const int ACTION_PLAYER_REWIND = 78; + +//! Play current song. Unpauses song and sets playspeed to 1x. global action, +//! can be used anywhere +constexpr const int ACTION_PLAYER_PLAY = 79; + +//! Delete current selected item. Can be used in myfiles.xml window id=3 and in +//! myvideoTitle.xml window id=25 +constexpr const int ACTION_DELETE_ITEM = 80; + +//! Copy current selected item. Can be used in myfiles.xml window id=3 +constexpr const int ACTION_COPY_ITEM = 81; + +//! Move current selected item. Can be used in myfiles.xml window id=3 +constexpr const int ACTION_MOVE_ITEM = 82; + +//! Take a screenshot +constexpr const int ACTION_TAKE_SCREENSHOT = 85; + +//! Rename item +constexpr const int ACTION_RENAME_ITEM = 87; + +constexpr const int ACTION_VOLUME_UP = 88; +constexpr const int ACTION_VOLUME_DOWN = 89; +constexpr const int ACTION_VOLAMP = 90; +constexpr const int ACTION_MUTE = 91; +constexpr const int ACTION_NAV_BACK = 92; +constexpr const int ACTION_VOLAMP_UP = 93; +constexpr const int ACTION_VOLAMP_DOWN = 94; + +//! Creates an episode bookmark on the currently playing video file containing +//! more than one episode +constexpr const int ACTION_CREATE_EPISODE_BOOKMARK = 95; + +//! Creates a bookmark of the currently playing video file +constexpr const int ACTION_CREATE_BOOKMARK = 96; + +//! Goto the next chapter, if not available perform a big step forward +constexpr const int ACTION_CHAPTER_OR_BIG_STEP_FORWARD = 97; + +//! Goto the previous chapter, if not available perform a big step back +constexpr const int ACTION_CHAPTER_OR_BIG_STEP_BACK = 98; + +//! Switch to next subtitle of movie, but will not enable/disable the subtitles. +//! Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_CYCLE_SUBTITLE = 99; + +constexpr const int ACTION_MOUSE_START = 100; +constexpr const int ACTION_MOUSE_LEFT_CLICK = 100; +constexpr const int ACTION_MOUSE_RIGHT_CLICK = 101; +constexpr const int ACTION_MOUSE_MIDDLE_CLICK = 102; +constexpr const int ACTION_MOUSE_DOUBLE_CLICK = 103; +constexpr const int ACTION_MOUSE_WHEEL_UP = 104; +constexpr const int ACTION_MOUSE_WHEEL_DOWN = 105; +constexpr const int ACTION_MOUSE_DRAG = 106; +constexpr const int ACTION_MOUSE_MOVE = 107; +constexpr const int ACTION_MOUSE_LONG_CLICK = 108; +constexpr const int ACTION_MOUSE_DRAG_END = 109; +constexpr const int ACTION_MOUSE_END = 109; + +constexpr const int ACTION_BACKSPACE = 110; +constexpr const int ACTION_SCROLL_UP = 111; +constexpr const int ACTION_SCROLL_DOWN = 112; +constexpr const int ACTION_ANALOG_FORWARD = 113; +constexpr const int ACTION_ANALOG_REWIND = 114; + +constexpr const int ACTION_MOVE_ITEM_UP = 115; //!< move item up in playlist +constexpr const int ACTION_MOVE_ITEM_DOWN = 116; //!< move item down in playlist +constexpr const int ACTION_CONTEXT_MENU = 117; //!< pops up the context menu + +// stuff for virtual keyboard shortcuts +constexpr const int ACTION_SHIFT = 118; //!< stuff for virtual keyboard shortcuts +constexpr const int ACTION_SYMBOLS = 119; //!< stuff for virtual keyboard shortcuts +constexpr const int ACTION_CURSOR_LEFT = 120; //!< stuff for virtual keyboard shortcuts +constexpr const int ACTION_CURSOR_RIGHT = 121; //!< stuff for virtual keyboard shortcuts + +constexpr const int ACTION_BUILT_IN_FUNCTION = 122; + +//! Displays current time, can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_SHOW_OSD_TIME = 123; + +constexpr const int ACTION_ANALOG_SEEK_FORWARD = 124; //!< seeks forward, and displays the seek bar. +constexpr const int ACTION_ANALOG_SEEK_BACK = 125; //!< seeks backward, and displays the seek bar. + +constexpr const int ACTION_VIS_PRESET_SHOW = 126; +constexpr const int ACTION_VIS_PRESET_NEXT = 128; +constexpr const int ACTION_VIS_PRESET_PREV = 129; +constexpr const int ACTION_VIS_PRESET_LOCK = 130; +constexpr const int ACTION_VIS_PRESET_RANDOM = 131; +constexpr const int ACTION_VIS_RATE_PRESET_PLUS = 132; +constexpr const int ACTION_VIS_RATE_PRESET_MINUS = 133; + +constexpr const int ACTION_SHOW_VIDEOMENU = 134; +constexpr const int ACTION_ENTER = 135; + +constexpr const int ACTION_INCREASE_RATING = 136; +constexpr const int ACTION_DECREASE_RATING = 137; + +constexpr const int ACTION_NEXT_SCENE = 138; //!< switch to next scene/cutpoint in movie +constexpr const int ACTION_PREV_SCENE = 139; //!< switch to previous scene/cutpoint in movie + +constexpr const int ACTION_NEXT_LETTER = 140; //!< jump through a list or container by letter +constexpr const int ACTION_PREV_LETTER = 141; + +constexpr const int ACTION_JUMP_SMS2 = 142; //!< jump direct to a particular letter using SMS-style input +constexpr const int ACTION_JUMP_SMS3 = 143; +constexpr const int ACTION_JUMP_SMS4 = 144; +constexpr const int ACTION_JUMP_SMS5 = 145; +constexpr const int ACTION_JUMP_SMS6 = 146; +constexpr const int ACTION_JUMP_SMS7 = 147; +constexpr const int ACTION_JUMP_SMS8 = 148; +constexpr const int ACTION_JUMP_SMS9 = 149; + +constexpr const int ACTION_FILTER_CLEAR = 150; +constexpr const int ACTION_FILTER_SMS2 = 151; +constexpr const int ACTION_FILTER_SMS3 = 152; +constexpr const int ACTION_FILTER_SMS4 = 153; +constexpr const int ACTION_FILTER_SMS5 = 154; +constexpr const int ACTION_FILTER_SMS6 = 155; +constexpr const int ACTION_FILTER_SMS7 = 156; +constexpr const int ACTION_FILTER_SMS8 = 157; +constexpr const int ACTION_FILTER_SMS9 = 158; + +constexpr const int ACTION_FIRST_PAGE = 159; +constexpr const int ACTION_LAST_PAGE = 160; + +constexpr const int ACTION_AUDIO_DELAY = 161; +constexpr const int ACTION_SUBTITLE_DELAY = 162; +constexpr const int ACTION_MENU = 163; + +constexpr const int ACTION_SET_RATING = 164; + +constexpr const int ACTION_RECORD = 170; + +constexpr const int ACTION_PASTE = 180; +constexpr const int ACTION_NEXT_CONTROL = 181; +constexpr const int ACTION_PREV_CONTROL = 182; +constexpr const int ACTION_CHANNEL_SWITCH = 183; +constexpr const int ACTION_CHANNEL_UP = 184; +constexpr const int ACTION_CHANNEL_DOWN = 185; +constexpr const int ACTION_NEXT_CHANNELGROUP = 186; +constexpr const int ACTION_PREVIOUS_CHANNELGROUP = 187; +constexpr const int ACTION_PVR_PLAY = 188; +constexpr const int ACTION_PVR_PLAY_TV = 189; +constexpr const int ACTION_PVR_PLAY_RADIO = 190; +constexpr const int ACTION_PVR_SHOW_TIMER_RULE = 191; +constexpr const int ACTION_CHANNEL_NUMBER_SEP = 192; +constexpr const int ACTION_PVR_ANNOUNCE_REMINDERS = 193; + +constexpr const int ACTION_TOGGLE_FULLSCREEN = 199; //!< switch 2 desktop resolution +constexpr const int ACTION_TOGGLE_WATCHED = 200; //!< Toggle watched status (videos) +constexpr const int ACTION_SCAN_ITEM = 201; //!< scan item +constexpr const int ACTION_TOGGLE_DIGITAL_ANALOG = 202; //!< switch digital <-> analog +constexpr const int ACTION_RELOAD_KEYMAPS = 203; //!< reloads CButtonTranslator's keymaps +constexpr const int ACTION_GUIPROFILE_BEGIN = 204; //!< start the GUIControlProfiler running + +constexpr const int ACTION_TELETEXT_RED = 215; //!< Teletext Color button <b>Red</b> to control TopText +constexpr const int ACTION_TELETEXT_GREEN = 216; //!< Teletext Color button <b>Green</b> to control TopText +constexpr const int ACTION_TELETEXT_YELLOW = 217; //!< Teletext Color button <b>Yellow</b> to control TopText +constexpr const int ACTION_TELETEXT_BLUE = 218; //!< Teletext Color button <b>Blue</b> to control TopText + +constexpr const int ACTION_INCREASE_PAR = 219; +constexpr const int ACTION_DECREASE_PAR = 220; + +constexpr const int ACTION_VSHIFT_UP = 227; //!< shift up video image in VideoPlayer +constexpr const int ACTION_VSHIFT_DOWN = 228; //!< shift down video image in VideoPlayer + +constexpr const int ACTION_PLAYER_PLAYPAUSE = 229; //!< Play/pause. If playing it pauses, if paused it plays. + +constexpr const int ACTION_SUBTITLE_VSHIFT_UP = 230; //!< shift up subtitles in VideoPlayer +constexpr const int ACTION_SUBTITLE_VSHIFT_DOWN = 231; //!< shift down subtitles in VideoPlayer +constexpr const int ACTION_SUBTITLE_ALIGN = 232; //!< toggle vertical alignment of subtitles + +constexpr const int ACTION_FILTER = 233; + +constexpr const int ACTION_SWITCH_PLAYER = 234; + +constexpr const int ACTION_STEREOMODE_NEXT = 235; +constexpr const int ACTION_STEREOMODE_PREVIOUS = 236; +constexpr const int ACTION_STEREOMODE_TOGGLE = 237; //!< turns 3d mode on/off +constexpr const int ACTION_STEREOMODE_SELECT = 238; +constexpr const int ACTION_STEREOMODE_TOMONO = 239; +constexpr const int ACTION_STEREOMODE_SET = 240; + +constexpr const int ACTION_SETTINGS_RESET = 241; +constexpr const int ACTION_SETTINGS_LEVEL_CHANGE = 242; + +//! Show autoclosing OSD. Can be used in videoFullScreen.xml window id=2005 +constexpr const int ACTION_TRIGGER_OSD = 243; +constexpr const int ACTION_INPUT_TEXT = 244; +constexpr const int ACTION_VOLUME_SET = 245; +constexpr const int ACTION_TOGGLE_COMMSKIP = 246; + +constexpr const int ACTION_BROWSE_SUBTITLE = 247; //!< Browse for subtitle. Can be used in videofullscreen + +constexpr const int ACTION_PLAYER_RESET = 248; //!< Send a reset command to the active game + +constexpr const int ACTION_TOGGLE_FONT = 249; //!< Toggle font. Used in TextViewer dialog + +constexpr const int ACTION_VIDEO_NEXT_STREAM = 250; //!< Cycle video streams. Used in videofullscreen. + +//! Used to queue an item to the next position in the playlist +constexpr const int ACTION_QUEUE_ITEM_NEXT = 251; + +constexpr const int ACTION_HDR_TOGGLE = 260; //!< Toggle display HDR on/off + +constexpr const int ACTION_CYCLE_TONEMAP_METHOD = 261; //!< Switch to next tonemap method + +//! Show debug info for video (source format, metadata, shaders, render flags and output format) +constexpr const int ACTION_PLAYER_DEBUG_VIDEO = 262; + +// Voice actions +constexpr const int ACTION_VOICE_RECOGNIZE = 300; + +// Touch actions +constexpr const int ACTION_TOUCH_TAP = 401; //!< touch actions +constexpr const int ACTION_TOUCH_TAP_TEN = 410; //!< touch actions +constexpr const int ACTION_TOUCH_LONGPRESS = 411; //!< touch actions +constexpr const int ACTION_TOUCH_LONGPRESS_TEN = 420; //!< touch actions + +constexpr const int ACTION_GESTURE_NOTIFY = 500; +constexpr const int ACTION_GESTURE_BEGIN = 501; + +//! sendaction with point and currentPinchScale (fingers together < 1.0 -> +//! fingers apart > 1.0) +constexpr const int ACTION_GESTURE_ZOOM = 502; +constexpr const int ACTION_GESTURE_ROTATE = 503; +constexpr const int ACTION_GESTURE_PAN = 504; +constexpr const int ACTION_GESTURE_ABORT = 505; //!< gesture was interrupted in unspecified state + +constexpr const int ACTION_GESTURE_SWIPE_LEFT = 511; +constexpr const int ACTION_GESTURE_SWIPE_LEFT_TEN = 520; +constexpr const int ACTION_GESTURE_SWIPE_RIGHT = 521; +constexpr const int ACTION_GESTURE_SWIPE_RIGHT_TEN = 530; +constexpr const int ACTION_GESTURE_SWIPE_UP = 531; +constexpr const int ACTION_GESTURE_SWIPE_UP_TEN = 540; +constexpr const int ACTION_GESTURE_SWIPE_DOWN = 541; +constexpr const int ACTION_GESTURE_SWIPE_DOWN_TEN = 550; + +//! 5xx is reserved for additional gesture actions +constexpr const int ACTION_GESTURE_END = 599; + +/*! + * @brief Other, non-gesture actions + */ +///@{ + +//!< analog thumbstick move, horizontal axis, left; see ACTION_ANALOG_MOVE +constexpr const int ACTION_ANALOG_MOVE_X_LEFT = 601; + +//!< analog thumbstick move, horizontal axis, right; see ACTION_ANALOG_MOVE +constexpr const int ACTION_ANALOG_MOVE_X_RIGHT = 602; + +//!< analog thumbstick move, vertical axis, up; see ACTION_ANALOG_MOVE +constexpr const int ACTION_ANALOG_MOVE_Y_UP = 603; + +//!< analog thumbstick move, vertical axis, down; see ACTION_ANALOG_MOVE +constexpr const int ACTION_ANALOG_MOVE_Y_DOWN = 604; + +///@} + +// The NOOP action can be specified to disable an input event. This is +// useful in user keyboard.xml etc to disable actions specified in the +// system mappings. ERROR action is used to play an error sound +constexpr const int ACTION_ERROR = 998; +constexpr const int ACTION_NOOP = 999; diff --git a/xbmc/input/actions/ActionTranslator.cpp b/xbmc/input/actions/ActionTranslator.cpp new file mode 100644 index 0000000..51d531c --- /dev/null +++ b/xbmc/input/actions/ActionTranslator.cpp @@ -0,0 +1,311 @@ +/* + * 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 "ActionTranslator.h" + +#include "ActionIDs.h" +#include "interfaces/builtins/Builtins.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <map> + +namespace +{ +using ActionName = std::string; +using ActionID = unsigned int; + +static const std::map<ActionName, ActionID> ActionMappings = { + {"left", ACTION_MOVE_LEFT}, + {"right", ACTION_MOVE_RIGHT}, + {"up", ACTION_MOVE_UP}, + {"down", ACTION_MOVE_DOWN}, + {"pageup", ACTION_PAGE_UP}, + {"pagedown", ACTION_PAGE_DOWN}, + {"select", ACTION_SELECT_ITEM}, + {"highlight", ACTION_HIGHLIGHT_ITEM}, + {"parentdir", ACTION_NAV_BACK}, // backward compatibility + {"parentfolder", ACTION_PARENT_DIR}, + {"back", ACTION_NAV_BACK}, + {"menu", ACTION_MENU}, + {"previousmenu", ACTION_PREVIOUS_MENU}, + {"info", ACTION_SHOW_INFO}, + {"pause", ACTION_PAUSE}, + {"stop", ACTION_STOP}, + {"skipnext", ACTION_NEXT_ITEM}, + {"skipprevious", ACTION_PREV_ITEM}, + {"fullscreen", ACTION_SHOW_GUI}, + {"aspectratio", ACTION_ASPECT_RATIO}, + {"stepforward", ACTION_STEP_FORWARD}, + {"stepback", ACTION_STEP_BACK}, + {"bigstepforward", ACTION_BIG_STEP_FORWARD}, + {"bigstepback", ACTION_BIG_STEP_BACK}, + {"chapterorbigstepforward", ACTION_CHAPTER_OR_BIG_STEP_FORWARD}, + {"chapterorbigstepback", ACTION_CHAPTER_OR_BIG_STEP_BACK}, + {"osd", ACTION_SHOW_OSD}, + {"showsubtitles", ACTION_SHOW_SUBTITLES}, + {"nextsubtitle", ACTION_NEXT_SUBTITLE}, + {"browsesubtitle", ACTION_BROWSE_SUBTITLE}, + {"cyclesubtitle", ACTION_CYCLE_SUBTITLE}, + {"playerdebug", ACTION_PLAYER_DEBUG}, + {"playerdebugvideo", ACTION_PLAYER_DEBUG_VIDEO}, + {"codecinfo", ACTION_PLAYER_PROCESS_INFO}, + {"playerprocessinfo", ACTION_PLAYER_PROCESS_INFO}, + {"playerprogramselect", ACTION_PLAYER_PROGRAM_SELECT}, + {"playerresolutionselect", ACTION_PLAYER_RESOLUTION_SELECT}, + {"nextpicture", ACTION_NEXT_PICTURE}, + {"previouspicture", ACTION_PREV_PICTURE}, + {"zoomout", ACTION_ZOOM_OUT}, + {"zoomin", ACTION_ZOOM_IN}, + {"playlist", ACTION_SHOW_PLAYLIST}, + {"queue", ACTION_QUEUE_ITEM}, + {"playnext", ACTION_QUEUE_ITEM_NEXT}, + {"zoomnormal", ACTION_ZOOM_LEVEL_NORMAL}, + {"zoomlevel1", ACTION_ZOOM_LEVEL_1}, + {"zoomlevel2", ACTION_ZOOM_LEVEL_2}, + {"zoomlevel3", ACTION_ZOOM_LEVEL_3}, + {"zoomlevel4", ACTION_ZOOM_LEVEL_4}, + {"zoomlevel5", ACTION_ZOOM_LEVEL_5}, + {"zoomlevel6", ACTION_ZOOM_LEVEL_6}, + {"zoomlevel7", ACTION_ZOOM_LEVEL_7}, + {"zoomlevel8", ACTION_ZOOM_LEVEL_8}, + {"zoomlevel9", ACTION_ZOOM_LEVEL_9}, + {"nextcalibration", ACTION_CALIBRATE_SWAP_ARROWS}, + {"resetcalibration", ACTION_CALIBRATE_RESET}, + {"analogmove", ACTION_ANALOG_MOVE}, + {"analogmovexleft", ACTION_ANALOG_MOVE_X_LEFT}, + {"analogmovexright", ACTION_ANALOG_MOVE_X_RIGHT}, + {"analogmoveyup", ACTION_ANALOG_MOVE_Y_UP}, + {"analogmoveydown", ACTION_ANALOG_MOVE_Y_DOWN}, + {"rotate", ACTION_ROTATE_PICTURE_CW}, + {"rotateccw", ACTION_ROTATE_PICTURE_CCW}, + {"close", ACTION_NAV_BACK}, // backwards compatibility + {"subtitledelayminus", ACTION_SUBTITLE_DELAY_MIN}, + {"subtitledelay", ACTION_SUBTITLE_DELAY}, + {"subtitledelayplus", ACTION_SUBTITLE_DELAY_PLUS}, + {"audiodelayminus", ACTION_AUDIO_DELAY_MIN}, + {"audiodelay", ACTION_AUDIO_DELAY}, + {"audiodelayplus", ACTION_AUDIO_DELAY_PLUS}, + {"subtitleshiftup", ACTION_SUBTITLE_VSHIFT_UP}, + {"subtitleshiftdown", ACTION_SUBTITLE_VSHIFT_DOWN}, + {"subtitlealign", ACTION_SUBTITLE_ALIGN}, + {"audionextlanguage", ACTION_AUDIO_NEXT_LANGUAGE}, + {"verticalshiftup", ACTION_VSHIFT_UP}, + {"verticalshiftdown", ACTION_VSHIFT_DOWN}, + {"nextresolution", ACTION_CHANGE_RESOLUTION}, + {"audiotoggledigital", ACTION_TOGGLE_DIGITAL_ANALOG}, + {"number0", REMOTE_0}, + {"number1", REMOTE_1}, + {"number2", REMOTE_2}, + {"number3", REMOTE_3}, + {"number4", REMOTE_4}, + {"number5", REMOTE_5}, + {"number6", REMOTE_6}, + {"number7", REMOTE_7}, + {"number8", REMOTE_8}, + {"number9", REMOTE_9}, + {"smallstepback", ACTION_SMALL_STEP_BACK}, + {"fastforward", ACTION_PLAYER_FORWARD}, + {"rewind", ACTION_PLAYER_REWIND}, + {"play", ACTION_PLAYER_PLAY}, + {"playpause", ACTION_PLAYER_PLAYPAUSE}, + {"switchplayer", ACTION_SWITCH_PLAYER}, + {"delete", ACTION_DELETE_ITEM}, + {"copy", ACTION_COPY_ITEM}, + {"move", ACTION_MOVE_ITEM}, + {"screenshot", ACTION_TAKE_SCREENSHOT}, + {"rename", ACTION_RENAME_ITEM}, + {"togglewatched", ACTION_TOGGLE_WATCHED}, + {"scanitem", ACTION_SCAN_ITEM}, + {"reloadkeymaps", ACTION_RELOAD_KEYMAPS}, + {"volumeup", ACTION_VOLUME_UP}, + {"volumedown", ACTION_VOLUME_DOWN}, + {"mute", ACTION_MUTE}, + {"backspace", ACTION_BACKSPACE}, + {"scrollup", ACTION_SCROLL_UP}, + {"scrolldown", ACTION_SCROLL_DOWN}, + {"analogfastforward", ACTION_ANALOG_FORWARD}, + {"analogrewind", ACTION_ANALOG_REWIND}, + {"moveitemup", ACTION_MOVE_ITEM_UP}, + {"moveitemdown", ACTION_MOVE_ITEM_DOWN}, + {"contextmenu", ACTION_CONTEXT_MENU}, + {"shift", ACTION_SHIFT}, + {"symbols", ACTION_SYMBOLS}, + {"cursorleft", ACTION_CURSOR_LEFT}, + {"cursorright", ACTION_CURSOR_RIGHT}, + {"showtime", ACTION_SHOW_OSD_TIME}, + {"analogseekforward", ACTION_ANALOG_SEEK_FORWARD}, + {"analogseekback", ACTION_ANALOG_SEEK_BACK}, + {"showpreset", ACTION_VIS_PRESET_SHOW}, + {"nextpreset", ACTION_VIS_PRESET_NEXT}, + {"previouspreset", ACTION_VIS_PRESET_PREV}, + {"lockpreset", ACTION_VIS_PRESET_LOCK}, + {"randompreset", ACTION_VIS_PRESET_RANDOM}, + {"increasevisrating", ACTION_VIS_RATE_PRESET_PLUS}, + {"decreasevisrating", ACTION_VIS_RATE_PRESET_MINUS}, + {"showvideomenu", ACTION_SHOW_VIDEOMENU}, + {"enter", ACTION_ENTER}, + {"increaserating", ACTION_INCREASE_RATING}, + {"decreaserating", ACTION_DECREASE_RATING}, + {"setrating", ACTION_SET_RATING}, + {"togglefullscreen", ACTION_TOGGLE_FULLSCREEN}, + {"nextscene", ACTION_NEXT_SCENE}, + {"previousscene", ACTION_PREV_SCENE}, + {"nextletter", ACTION_NEXT_LETTER}, + {"prevletter", ACTION_PREV_LETTER}, + {"jumpsms2", ACTION_JUMP_SMS2}, + {"jumpsms3", ACTION_JUMP_SMS3}, + {"jumpsms4", ACTION_JUMP_SMS4}, + {"jumpsms5", ACTION_JUMP_SMS5}, + {"jumpsms6", ACTION_JUMP_SMS6}, + {"jumpsms7", ACTION_JUMP_SMS7}, + {"jumpsms8", ACTION_JUMP_SMS8}, + {"jumpsms9", ACTION_JUMP_SMS9}, + {"filter", ACTION_FILTER}, + {"filterclear", ACTION_FILTER_CLEAR}, + {"filtersms2", ACTION_FILTER_SMS2}, + {"filtersms3", ACTION_FILTER_SMS3}, + {"filtersms4", ACTION_FILTER_SMS4}, + {"filtersms5", ACTION_FILTER_SMS5}, + {"filtersms6", ACTION_FILTER_SMS6}, + {"filtersms7", ACTION_FILTER_SMS7}, + {"filtersms8", ACTION_FILTER_SMS8}, + {"filtersms9", ACTION_FILTER_SMS9}, + {"firstpage", ACTION_FIRST_PAGE}, + {"lastpage", ACTION_LAST_PAGE}, + {"guiprofile", ACTION_GUIPROFILE_BEGIN}, + {"red", ACTION_TELETEXT_RED}, + {"green", ACTION_TELETEXT_GREEN}, + {"yellow", ACTION_TELETEXT_YELLOW}, + {"blue", ACTION_TELETEXT_BLUE}, + {"increasepar", ACTION_INCREASE_PAR}, + {"decreasepar", ACTION_DECREASE_PAR}, + {"volampup", ACTION_VOLAMP_UP}, + {"volampdown", ACTION_VOLAMP_DOWN}, + {"volumeamplification", ACTION_VOLAMP}, + {"createbookmark", ACTION_CREATE_BOOKMARK}, + {"createepisodebookmark", ACTION_CREATE_EPISODE_BOOKMARK}, + {"settingsreset", ACTION_SETTINGS_RESET}, + {"settingslevelchange", ACTION_SETTINGS_LEVEL_CHANGE}, + {"togglefont", ACTION_TOGGLE_FONT}, + {"videonextstream", ACTION_VIDEO_NEXT_STREAM}, + + // 3D movie playback/GUI + {"stereomode", ACTION_STEREOMODE_SELECT}, // cycle 3D modes, for now an alias for next + {"nextstereomode", ACTION_STEREOMODE_NEXT}, + {"previousstereomode", ACTION_STEREOMODE_PREVIOUS}, + {"togglestereomode", ACTION_STEREOMODE_TOGGLE}, + {"stereomodetomono", ACTION_STEREOMODE_TOMONO}, + + // HDR display support + {"hdrtoggle", ACTION_HDR_TOGGLE}, + + // Tone mapping + {"cycletonemapmethod", ACTION_CYCLE_TONEMAP_METHOD}, + + // PVR actions + {"channelup", ACTION_CHANNEL_UP}, + {"channeldown", ACTION_CHANNEL_DOWN}, + {"previouschannelgroup", ACTION_PREVIOUS_CHANNELGROUP}, + {"nextchannelgroup", ACTION_NEXT_CHANNELGROUP}, + {"playpvr", ACTION_PVR_PLAY}, + {"playpvrtv", ACTION_PVR_PLAY_TV}, + {"playpvrradio", ACTION_PVR_PLAY_RADIO}, + {"record", ACTION_RECORD}, + {"togglecommskip", ACTION_TOGGLE_COMMSKIP}, + {"showtimerrule", ACTION_PVR_SHOW_TIMER_RULE}, + {"channelnumberseparator", ACTION_CHANNEL_NUMBER_SEP}, + + // Mouse actions + {"leftclick", ACTION_MOUSE_LEFT_CLICK}, + {"rightclick", ACTION_MOUSE_RIGHT_CLICK}, + {"middleclick", ACTION_MOUSE_MIDDLE_CLICK}, + {"doubleclick", ACTION_MOUSE_DOUBLE_CLICK}, + {"longclick", ACTION_MOUSE_LONG_CLICK}, + {"wheelup", ACTION_MOUSE_WHEEL_UP}, + {"wheeldown", ACTION_MOUSE_WHEEL_DOWN}, + {"mousedrag", ACTION_MOUSE_DRAG}, + {"mousedragend", ACTION_MOUSE_DRAG_END}, + {"mousemove", ACTION_MOUSE_MOVE}, + + // Touch + {"tap", ACTION_TOUCH_TAP}, + {"longpress", ACTION_TOUCH_LONGPRESS}, + {"pangesture", ACTION_GESTURE_PAN}, + {"zoomgesture", ACTION_GESTURE_ZOOM}, + {"rotategesture", ACTION_GESTURE_ROTATE}, + {"swipeleft", ACTION_GESTURE_SWIPE_LEFT}, + {"swiperight", ACTION_GESTURE_SWIPE_RIGHT}, + {"swipeup", ACTION_GESTURE_SWIPE_UP}, + {"swipedown", ACTION_GESTURE_SWIPE_DOWN}, + + // Voice + {"voicerecognizer", ACTION_VOICE_RECOGNIZE}, + + // Do nothing / error action + {"error", ACTION_ERROR}, + {"noop", ACTION_NOOP}}; +} // namespace + +void CActionTranslator::GetActions(std::vector<std::string>& actionList) +{ + actionList.reserve(ActionMappings.size()); + for (auto& actionMapping : ActionMappings) + actionList.push_back(actionMapping.first); +} + +bool CActionTranslator::IsAnalog(unsigned int actionID) +{ + switch (actionID) + { + case ACTION_ANALOG_SEEK_FORWARD: + case ACTION_ANALOG_SEEK_BACK: + case ACTION_SCROLL_UP: + case ACTION_SCROLL_DOWN: + case ACTION_ANALOG_FORWARD: + case ACTION_ANALOG_REWIND: + case ACTION_ANALOG_MOVE: + case ACTION_ANALOG_MOVE_X_LEFT: + case ACTION_ANALOG_MOVE_X_RIGHT: + case ACTION_ANALOG_MOVE_Y_UP: + case ACTION_ANALOG_MOVE_Y_DOWN: + case ACTION_CURSOR_LEFT: + case ACTION_CURSOR_RIGHT: + case ACTION_VOLUME_UP: + case ACTION_VOLUME_DOWN: + case ACTION_ZOOM_IN: + case ACTION_ZOOM_OUT: + return true; + default: + return false; + } +} + +bool CActionTranslator::TranslateString(std::string strAction, unsigned int& actionId) +{ + actionId = ACTION_NONE; + + if (strAction.empty()) + return false; + + StringUtils::ToLower(strAction); + + auto it = ActionMappings.find(strAction); + if (it != ActionMappings.end()) + actionId = it->second; + else if (CBuiltins::GetInstance().HasCommand(strAction)) + actionId = ACTION_BUILT_IN_FUNCTION; + + if (actionId == ACTION_NONE) + { + CLog::Log(LOGERROR, "Keymapping error: no such action '{}' defined", strAction); + return false; + } + + return true; +} diff --git a/xbmc/input/actions/ActionTranslator.h b/xbmc/input/actions/ActionTranslator.h new file mode 100644 index 0000000..91c146a --- /dev/null +++ b/xbmc/input/actions/ActionTranslator.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <string> +#include <vector> + +class CActionTranslator +{ +public: + static void GetActions(std::vector<std::string>& actionList); + static bool IsAnalog(unsigned int actionId); + static bool TranslateString(std::string strAction, unsigned int& actionId); +}; diff --git a/xbmc/input/actions/CMakeLists.txt b/xbmc/input/actions/CMakeLists.txt new file mode 100644 index 0000000..a7a4d87 --- /dev/null +++ b/xbmc/input/actions/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SOURCES Action.cpp + ActionTranslator.cpp +) + +set(HEADERS Action.h + ActionIDs.h + ActionTranslator.h +) + +core_add_library(input_actions) diff --git a/xbmc/input/button/ButtonStat.cpp b/xbmc/input/button/ButtonStat.cpp new file mode 100644 index 0000000..679947f --- /dev/null +++ b/xbmc/input/button/ButtonStat.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 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 "ButtonStat.h" + +#include "xbmc/input/InputTypes.h" + +using namespace KODI; +using namespace INPUT; + +CButtonStat::CButtonStat() = default; + +CKey CButtonStat::TranslateKey(const CKey& key) const +{ + uint32_t buttonCode = key.GetButtonCode(); + if (key.GetHeld() > HOLD_TRESHOLD) + buttonCode |= CKey::MODIFIER_LONG; + + CKey translatedKey(buttonCode, key.GetHeld()); + return translatedKey; +} diff --git a/xbmc/input/button/ButtonStat.h b/xbmc/input/button/ButtonStat.h new file mode 100644 index 0000000..04ee51a --- /dev/null +++ b/xbmc/input/button/ButtonStat.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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. + */ + +#pragma once + +#include "input/Key.h" +#include "input/XBMC_keyboard.h" + +#include <string> + +namespace KODI +{ +namespace INPUT +{ + +class CButtonStat +{ +public: + CButtonStat(); + ~CButtonStat() = default; + + CKey TranslateKey(const CKey& key) const; +}; +} // namespace INPUT +} // namespace KODI diff --git a/xbmc/input/button/CMakeLists.txt b/xbmc/input/button/CMakeLists.txt new file mode 100644 index 0000000..489f901 --- /dev/null +++ b/xbmc/input/button/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES ButtonStat.cpp) + +set(HEADERS ButtonStat.h) + +core_add_library(input_button)
\ No newline at end of file diff --git a/xbmc/input/hardware/IHardwareInput.h b/xbmc/input/hardware/IHardwareInput.h new file mode 100644 index 0000000..6bfa757 --- /dev/null +++ b/xbmc/input/hardware/IHardwareInput.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <string> + +namespace KODI +{ +namespace HARDWARE +{ +/*! + * \ingroup hardware + * \brief Handles events for hardware such as reset buttons on a game console + */ +class IHardwareInput +{ +public: + virtual ~IHardwareInput() = default; + + /*! + * \brief A hardware reset button has been pressed + */ + virtual void OnResetButton() = 0; +}; +} // namespace HARDWARE +} // namespace KODI diff --git a/xbmc/input/joysticks/CMakeLists.txt b/xbmc/input/joysticks/CMakeLists.txt new file mode 100644 index 0000000..0155e5e --- /dev/null +++ b/xbmc/input/joysticks/CMakeLists.txt @@ -0,0 +1,30 @@ +set(SOURCES DeadzoneFilter.cpp + DriverPrimitive.cpp + JoystickEasterEgg.cpp + JoystickMonitor.cpp + JoystickTranslator.cpp + JoystickUtils.cpp + RumbleGenerator.cpp) + +set(HEADERS interfaces/IButtonMap.h + interfaces/IButtonMapCallback.h + interfaces/IButtonMapper.h + interfaces/IButtonSequence.h + interfaces/IDriverHandler.h + interfaces/IDriverReceiver.h + interfaces/IInputHandler.h + interfaces/IInputProvider.h + interfaces/IInputReceiver.h + interfaces/IKeyHandler.h + interfaces/IKeymapHandler.h + DeadzoneFilter.h + DriverPrimitive.h + JoystickEasterEgg.h + JoystickIDs.h + JoystickMonitor.h + JoystickTranslator.h + JoystickTypes.h + JoystickUtils.h + RumbleGenerator.h) + +core_add_library(input_joystick) diff --git a/xbmc/input/joysticks/DeadzoneFilter.cpp b/xbmc/input/joysticks/DeadzoneFilter.cpp new file mode 100644 index 0000000..c437f30 --- /dev/null +++ b/xbmc/input/joysticks/DeadzoneFilter.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016-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 "DeadzoneFilter.h" + +#include "JoystickIDs.h" +#include "games/controllers/ControllerIDs.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "peripherals/devices/Peripheral.h" +#include "utils/log.h" + +#include <cmath> +#include <vector> + +using namespace KODI; +using namespace JOYSTICK; + +//! Allowed noise for detecting discrete D-pads (value of 0.007 when centered +//! has been observed) +#define AXIS_EPSILON 0.01f + +// Settings for analog sticks +#define SETTING_LEFT_STICK_DEADZONE "left_stick_deadzone" +#define SETTING_RIGHT_STICK_DEADZONE "right_stick_deadzone" + +CDeadzoneFilter::CDeadzoneFilter(IButtonMap* buttonMap, PERIPHERALS::CPeripheral* peripheral) + : m_buttonMap(buttonMap), m_peripheral(peripheral) +{ + if (m_buttonMap->ControllerID() != DEFAULT_CONTROLLER_ID) + CLog::Log(LOGERROR, "ERROR: Must use default controller profile instead of {}", + m_buttonMap->ControllerID()); +} + +float CDeadzoneFilter::FilterAxis(unsigned int axisIndex, float axisValue) +{ + float deadzone = 0.0f; + + bool bSuccess = + GetDeadzone(axisIndex, deadzone, DEFAULT_LEFT_STICK_NAME, SETTING_LEFT_STICK_DEADZONE) || + GetDeadzone(axisIndex, deadzone, DEFAULT_RIGHT_STICK_NAME, SETTING_RIGHT_STICK_DEADZONE); + + if (bSuccess) + return ApplyDeadzone(axisValue, deadzone); + + // Always filter noise about the center + if (std::abs(axisValue) < AXIS_EPSILON) + axisValue = 0.0f; + + return axisValue; +} + +bool CDeadzoneFilter::GetDeadzone(unsigned int axisIndex, + float& deadzone, + const char* featureName, + const char* settingName) +{ + std::vector<ANALOG_STICK_DIRECTION> dirs = { + ANALOG_STICK_DIRECTION::UP, + ANALOG_STICK_DIRECTION::RIGHT, + ANALOG_STICK_DIRECTION::DOWN, + ANALOG_STICK_DIRECTION::LEFT, + }; + + CDriverPrimitive primitive; + for (auto dir : dirs) + { + if (m_buttonMap->GetAnalogStick(featureName, dir, primitive)) + { + if (primitive.Type() == PRIMITIVE_TYPE::SEMIAXIS && primitive.Index() == axisIndex) + { + deadzone = m_peripheral->GetSettingFloat(settingName); + return true; + } + } + } + + return false; +} + +float CDeadzoneFilter::ApplyDeadzone(float value, float deadzone) +{ + if (deadzone < 0.0f || deadzone >= 1.0f) + return 0.0f; + + if (value > deadzone) + return (value - deadzone) / (1.0f - deadzone); + else if (value < -deadzone) + return (value + deadzone) / (1.0f - deadzone); + + return 0.0f; +} diff --git a/xbmc/input/joysticks/DeadzoneFilter.h b/xbmc/input/joysticks/DeadzoneFilter.h new file mode 100644 index 0000000..4547050 --- /dev/null +++ b/xbmc/input/joysticks/DeadzoneFilter.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +namespace PERIPHERALS +{ +class CPeripheral; +} + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; + +/*! + * \ingroup joystick + * \brief Analog axis deadzone filtering + * + * Axis is scaled appropriately, so position is continuous + * from -1.0 to 1.0: + * + * | / 1.0 + * | / + * __|__/ + * / | + * / |--| Deadzone + * -1.0 / | + * + * After deadzone filtering, the value will be: + * + * - Negative in the interval [-1.0, -deadzone) + * - Zero in the interval [-deadzone, deadzone] + * - Positive in the interval (deadzone, 1.0] + */ +class CDeadzoneFilter +{ +public: + CDeadzoneFilter(IButtonMap* buttonMap, PERIPHERALS::CPeripheral* peripheral); + + /*! + * \brief Apply deadzone filtering to an axis + * \param axisIndex The axis index + * \param axisValue The axis value + * \return The value after applying deadzone filtering + */ + float FilterAxis(unsigned int axisIndex, float axisValue); + +private: + /*! + * \brief Get the deadzone value from the peripheral's settings + * \param axisIndex The axis index + * \param[out] result The deadzone value + * \param featureName The feature that axisIndex is mapped to + * \param settingName The setting corresponding to the given feature + * \return True if the feature is an analog stick and the peripheral has the setting + */ + bool GetDeadzone(unsigned int axisIndex, + float& result, + const char* featureName, + const char* settingName); + + /*! + * \brief Utility function to calculate the deadzone + * \param value The value + * \param deadzone The deadzone + * \return The scaled deadzone + */ + static float ApplyDeadzone(float value, float deadzone); + + // Construction parameters + IButtonMap* const m_buttonMap; + PERIPHERALS::CPeripheral* const m_peripheral; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/DriverPrimitive.cpp b/xbmc/input/joysticks/DriverPrimitive.cpp new file mode 100644 index 0000000..166e926 --- /dev/null +++ b/xbmc/input/joysticks/DriverPrimitive.cpp @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2014-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 "DriverPrimitive.h" + +#include "games/controllers/ControllerTranslator.h" +#include "utils/StringUtils.h" + +#include <utility> + +using namespace KODI; +using namespace JOYSTICK; + +CDriverPrimitive::CDriverPrimitive(void) = default; + +CDriverPrimitive::CDriverPrimitive(PRIMITIVE_TYPE type, unsigned int index) + : m_type(type), m_driverIndex(index) +{ +} + +CDriverPrimitive::CDriverPrimitive(unsigned int hatIndex, HAT_DIRECTION direction) + : m_type(PRIMITIVE_TYPE::HAT), m_driverIndex(hatIndex), m_hatDirection(direction) +{ +} + +CDriverPrimitive::CDriverPrimitive(unsigned int axisIndex, + int center, + SEMIAXIS_DIRECTION direction, + unsigned int range) + : m_type(PRIMITIVE_TYPE::SEMIAXIS), + m_driverIndex(axisIndex), + m_center(center), + m_semiAxisDirection(direction), + m_range(range) +{ +} + +CDriverPrimitive::CDriverPrimitive(XBMCKey keycode) + : m_type(PRIMITIVE_TYPE::KEY), m_keycode(keycode) +{ +} + +CDriverPrimitive::CDriverPrimitive(MOUSE::BUTTON_ID index) + : m_type(PRIMITIVE_TYPE::MOUSE_BUTTON), m_driverIndex(static_cast<unsigned int>(index)) +{ +} + +CDriverPrimitive::CDriverPrimitive(RELATIVE_POINTER_DIRECTION direction) + : m_type(PRIMITIVE_TYPE::RELATIVE_POINTER), m_pointerDirection(direction) +{ +} + +bool CDriverPrimitive::operator==(const CDriverPrimitive& rhs) const +{ + if (m_type == rhs.m_type) + { + switch (m_type) + { + case PRIMITIVE_TYPE::BUTTON: + case PRIMITIVE_TYPE::MOTOR: + case PRIMITIVE_TYPE::MOUSE_BUTTON: + return m_driverIndex == rhs.m_driverIndex; + case PRIMITIVE_TYPE::HAT: + return m_driverIndex == rhs.m_driverIndex && m_hatDirection == rhs.m_hatDirection; + case PRIMITIVE_TYPE::SEMIAXIS: + return m_driverIndex == rhs.m_driverIndex && m_center == rhs.m_center && + m_semiAxisDirection == rhs.m_semiAxisDirection && m_range == rhs.m_range; + case PRIMITIVE_TYPE::KEY: + return m_keycode == rhs.m_keycode; + case PRIMITIVE_TYPE::RELATIVE_POINTER: + return m_pointerDirection == rhs.m_pointerDirection; + default: + return true; + } + } + return false; +} + +bool CDriverPrimitive::operator<(const CDriverPrimitive& rhs) const +{ + if (m_type < rhs.m_type) + return true; + if (m_type > rhs.m_type) + return false; + + if (m_type == PRIMITIVE_TYPE::BUTTON || m_type == PRIMITIVE_TYPE::HAT || + m_type == PRIMITIVE_TYPE::SEMIAXIS || m_type == PRIMITIVE_TYPE::MOTOR || + m_type == PRIMITIVE_TYPE::MOUSE_BUTTON) + { + if (m_driverIndex < rhs.m_driverIndex) + return true; + if (m_driverIndex > rhs.m_driverIndex) + return false; + } + + if (m_type == PRIMITIVE_TYPE::HAT) + { + if (m_hatDirection < rhs.m_hatDirection) + return true; + if (m_hatDirection > rhs.m_hatDirection) + return false; + } + + if (m_type == PRIMITIVE_TYPE::SEMIAXIS) + { + if (m_center < rhs.m_center) + return true; + if (m_center > rhs.m_center) + return false; + + if (m_semiAxisDirection < rhs.m_semiAxisDirection) + return true; + if (m_semiAxisDirection > rhs.m_semiAxisDirection) + return false; + + if (m_range < rhs.m_range) + return true; + if (m_range > rhs.m_range) + return false; + } + + if (m_type == PRIMITIVE_TYPE::KEY) + { + if (m_keycode < rhs.m_keycode) + return true; + if (m_keycode > rhs.m_keycode) + return false; + } + + if (m_type == PRIMITIVE_TYPE::RELATIVE_POINTER) + { + if (m_pointerDirection < rhs.m_pointerDirection) + return true; + if (m_pointerDirection > rhs.m_pointerDirection) + return false; + } + + return false; +} + +bool CDriverPrimitive::IsValid(void) const +{ + if (m_type == PRIMITIVE_TYPE::BUTTON || m_type == PRIMITIVE_TYPE::MOTOR || + m_type == PRIMITIVE_TYPE::MOUSE_BUTTON) + return true; + + if (m_type == PRIMITIVE_TYPE::HAT) + { + return m_hatDirection == HAT_DIRECTION::UP || m_hatDirection == HAT_DIRECTION::DOWN || + m_hatDirection == HAT_DIRECTION::RIGHT || m_hatDirection == HAT_DIRECTION::LEFT; + } + + if (m_type == PRIMITIVE_TYPE::SEMIAXIS) + { + unsigned int maxRange = 1; + + switch (m_center) + { + case -1: + { + if (m_semiAxisDirection != SEMIAXIS_DIRECTION::POSITIVE) + return false; + maxRange = 2; + break; + } + case 0: + { + if (m_semiAxisDirection != SEMIAXIS_DIRECTION::POSITIVE && + m_semiAxisDirection != SEMIAXIS_DIRECTION::NEGATIVE) + return false; + break; + } + case 1: + { + if (m_semiAxisDirection != SEMIAXIS_DIRECTION::POSITIVE) + return false; + maxRange = 2; + break; + } + default: + break; + } + + return 1 <= m_range && m_range <= maxRange; + } + + if (m_type == PRIMITIVE_TYPE::KEY) + return m_keycode != XBMCK_UNKNOWN; + + if (m_type == PRIMITIVE_TYPE::RELATIVE_POINTER) + { + return m_pointerDirection == RELATIVE_POINTER_DIRECTION::UP || + m_pointerDirection == RELATIVE_POINTER_DIRECTION::DOWN || + m_pointerDirection == RELATIVE_POINTER_DIRECTION::RIGHT || + m_pointerDirection == RELATIVE_POINTER_DIRECTION::LEFT; + } + + return false; +} + +std::string CDriverPrimitive::ToString() const +{ + switch (m_type) + { + case PRIMITIVE_TYPE::BUTTON: + return StringUtils::Format("button {}", m_driverIndex); + case PRIMITIVE_TYPE::MOTOR: + return StringUtils::Format("motor {}", m_driverIndex); + case PRIMITIVE_TYPE::MOUSE_BUTTON: + return StringUtils::Format("mouse button {}", m_driverIndex); + case PRIMITIVE_TYPE::HAT: + { + switch (m_hatDirection) + { + case HAT_DIRECTION::UP: + return StringUtils::Format("hat {} up", m_driverIndex); + case HAT_DIRECTION::DOWN: + return StringUtils::Format("hat {} down", m_driverIndex); + case HAT_DIRECTION::RIGHT: + return StringUtils::Format("hat {} right", m_driverIndex); + case HAT_DIRECTION::LEFT: + return StringUtils::Format("hat {} left", m_driverIndex); + default: + break; + } + break; + } + case PRIMITIVE_TYPE::SEMIAXIS: + { + switch (m_semiAxisDirection) + { + case SEMIAXIS_DIRECTION::POSITIVE: + return StringUtils::Format("semiaxis +{}", m_driverIndex); + case SEMIAXIS_DIRECTION::NEGATIVE: + return StringUtils::Format("semiaxis -{}", m_driverIndex); + default: + break; + } + break; + } + case PRIMITIVE_TYPE::KEY: + return StringUtils::Format("key {}", + GAME::CControllerTranslator::TranslateKeycode(m_keycode)); + case PRIMITIVE_TYPE::RELATIVE_POINTER: + { + switch (m_pointerDirection) + { + case RELATIVE_POINTER_DIRECTION::UP: + return StringUtils::Format("pointer {} up", m_driverIndex); + case RELATIVE_POINTER_DIRECTION::DOWN: + return StringUtils::Format("pointer {} down", m_driverIndex); + case RELATIVE_POINTER_DIRECTION::RIGHT: + return StringUtils::Format("pointer {} right", m_driverIndex); + case RELATIVE_POINTER_DIRECTION::LEFT: + return StringUtils::Format("pointer {} left", m_driverIndex); + default: + break; + } + break; + } + default: + break; + } + + return ""; +} diff --git a/xbmc/input/joysticks/DriverPrimitive.h b/xbmc/input/joysticks/DriverPrimitive.h new file mode 100644 index 0000000..11d391b --- /dev/null +++ b/xbmc/input/joysticks/DriverPrimitive.h @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "JoystickTypes.h" +#include "input/keyboard/KeyboardTypes.h" +#include "input/mouse/MouseTypes.h" + +#include <stdint.h> + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Basic driver element associated with input events + * + * Driver input (bools, floats and enums) is split into primitives that better + * map to the physical features on a joystick. + * + * A bool obviously only maps to a single feature, so it is a driver + * primitive. Here, these are called "buttons". + * + * A hat enum encodes the state of the four hat directions. Each direction + * can map to a different feature, so a hat enum consists of four driver + * primitives called "hat directions". + * + * A float is a little trickier. Trivially, it can map to an analog stick or + * trigger. However, DirectInput combines two triggers onto a single axis. + * Therefore, the axis is split into two primitives called "semiaxes". + * + * The type determines the fields in use: + * + * Button: + * - driver index + * + * Hat direction: + * - driver index + * - hat direction (up/right/down/left) + * + * Semiaxis: + * - driver index + * - center (-1, 0 or 1) + * - semiaxis direction (positive/negative) + * - range (1 or 2) + * + * Motor: + * - driver index + * + * Key: + * - keycode + * + * Mouse button: + * - driver index + * + * Relative pointer: + * - pointer direction + * + * For more info, see "Chapter 2. Joystick drivers" in the documentation + * thread: http://forum.kodi.tv/showthread.php?tid=257764 + */ +class CDriverPrimitive +{ +public: + /*! + * \brief Construct an invalid driver primitive + */ + CDriverPrimitive(void); + + /*! + * \brief Construct a driver primitive representing a button or motor + */ + CDriverPrimitive(PRIMITIVE_TYPE type, unsigned int index); + + /*! + * \brief Construct a driver primitive representing one of the four + * direction arrows on a dpad + */ + CDriverPrimitive(unsigned int hatIndex, HAT_DIRECTION direction); + + /*! + * \brief Construct a driver primitive representing the positive or negative + * half of an axis + */ + CDriverPrimitive(unsigned int axisIndex, + int center, + SEMIAXIS_DIRECTION direction, + unsigned int range); + + /*! + * \brief Construct a driver primitive representing a key on a keyboard + */ + CDriverPrimitive(KEYBOARD::KeySymbol keycode); + + /*! + * \brief Construct a driver primitive representing a mouse button + */ + CDriverPrimitive(MOUSE::BUTTON_ID index); + + /*! + * \brief Construct a driver primitive representing a relative pointer + */ + CDriverPrimitive(RELATIVE_POINTER_DIRECTION direction); + + bool operator==(const CDriverPrimitive& rhs) const; + bool operator<(const CDriverPrimitive& rhs) const; + + bool operator!=(const CDriverPrimitive& rhs) const { return !operator==(rhs); } + bool operator>(const CDriverPrimitive& rhs) const { return !(operator<(rhs) || operator==(rhs)); } + bool operator<=(const CDriverPrimitive& rhs) const { return operator<(rhs) || operator==(rhs); } + bool operator>=(const CDriverPrimitive& rhs) const { return !operator<(rhs); } + + /*! + * \brief The type of driver primitive + */ + PRIMITIVE_TYPE Type(void) const { return m_type; } + + /*! + * \brief The index used by the joystick driver + * + * Valid for: + * - buttons + * - hats + * - semiaxes + * - motors + */ + unsigned int Index(void) const { return m_driverIndex; } + + /*! + * \brief The direction arrow (valid for hat directions) + */ + HAT_DIRECTION HatDirection(void) const { return m_hatDirection; } + + /*! + * \brief The location of the zero point of the semiaxis + */ + int Center() const { return m_center; } + + /*! + * \brief The semiaxis direction (valid for semiaxes) + */ + SEMIAXIS_DIRECTION SemiAxisDirection(void) const { return m_semiAxisDirection; } + + /*! + * \brief The distance between the center and the farthest valid value (valid for semiaxes) + */ + unsigned int Range() const { return m_range; } + + /*! + * \brief The keyboard symbol (valid for keys) + */ + KEYBOARD::KeySymbol Keycode() const { return m_keycode; } + + /*! + * \brief The mouse button ID (valid for mouse buttons) + */ + MOUSE::BUTTON_ID MouseButton() const { return static_cast<MOUSE::BUTTON_ID>(m_driverIndex); } + + /*! + * \brief The relative pointer direction (valid for relative pointers) + */ + RELATIVE_POINTER_DIRECTION PointerDirection() const { return m_pointerDirection; } + + /*! + * \brief Test if an driver primitive is valid + * + * A driver primitive is valid if it has a known type and: + * + * 1) for hats, it is a cardinal direction + * 2) for semi-axes, it is a positive or negative direction + * 3) for keys, the keycode is non-empty + */ + bool IsValid(void) const; + + /*! + * \brief Convert primitive to a string suitable for logging + * + * \return The primitive as described by a short string, or empty if invalid + */ + std::string ToString() const; + +private: + PRIMITIVE_TYPE m_type = PRIMITIVE_TYPE::UNKNOWN; + unsigned int m_driverIndex = 0; + HAT_DIRECTION m_hatDirection = HAT_DIRECTION::NONE; + int m_center = 0; + SEMIAXIS_DIRECTION m_semiAxisDirection = SEMIAXIS_DIRECTION::ZERO; + unsigned int m_range = 1; + KEYBOARD::KeySymbol m_keycode = XBMCK_UNKNOWN; + RELATIVE_POINTER_DIRECTION m_pointerDirection = RELATIVE_POINTER_DIRECTION::NONE; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickEasterEgg.cpp b/xbmc/input/joysticks/JoystickEasterEgg.cpp new file mode 100644 index 0000000..b0259a6 --- /dev/null +++ b/xbmc/input/joysticks/JoystickEasterEgg.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016-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 "JoystickEasterEgg.h" + +#include "ServiceBroker.h" +#include "games/GameServices.h" +#include "games/GameSettings.h" +#include "games/controllers/ControllerIDs.h" +#include "games/controllers/DefaultController.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/WindowIDs.h" + +using namespace KODI; +using namespace JOYSTICK; + +const std::map<std::string, std::vector<FeatureName>> CJoystickEasterEgg::m_sequence = { + { + DEFAULT_CONTROLLER_ID, + { + GAME::CDefaultController::FEATURE_UP, + GAME::CDefaultController::FEATURE_UP, + GAME::CDefaultController::FEATURE_DOWN, + GAME::CDefaultController::FEATURE_DOWN, + GAME::CDefaultController::FEATURE_LEFT, + GAME::CDefaultController::FEATURE_RIGHT, + GAME::CDefaultController::FEATURE_LEFT, + GAME::CDefaultController::FEATURE_RIGHT, + GAME::CDefaultController::FEATURE_B, + GAME::CDefaultController::FEATURE_A, + }, + }, + { + DEFAULT_REMOTE_ID, + { + "up", + "up", + "down", + "down", + "left", + "right", + "left", + "right", + "back", + "ok", + }, + }, +}; + +CJoystickEasterEgg::CJoystickEasterEgg(const std::string& controllerId) + : m_controllerId(controllerId), m_state(0) +{ +} + +bool CJoystickEasterEgg::OnButtonPress(const FeatureName& feature) +{ + bool bHandled = false; + + auto it = m_sequence.find(m_controllerId); + if (it != m_sequence.end()) + { + const auto& sequence = it->second; + + // Reset state if it previously finished + if (m_state >= sequence.size()) + m_state = 0; + + if (feature == sequence[m_state]) + m_state++; + else + m_state = 0; + + if (IsCapturing()) + { + bHandled = true; + + if (m_state >= sequence.size()) + OnFinish(); + } + } + + return bHandled; +} + +bool CJoystickEasterEgg::IsCapturing() +{ + // Capture input when finished with arrows (2 x up/down/left/right) + return m_state > 8; +} + +void CJoystickEasterEgg::OnFinish(void) +{ + GAME::CGameSettings& gameSettings = CServiceBroker::GetGameServices().GameSettings(); + gameSettings.ToggleGames(); + + WINDOW_SOUND sound = gameSettings.GamesEnabled() ? SOUND_INIT : SOUND_DEINIT; + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().PlayWindowSound(WINDOW_DIALOG_KAI_TOAST, sound); + + //! @todo Shake screen +} diff --git a/xbmc/input/joysticks/JoystickEasterEgg.h b/xbmc/input/joysticks/JoystickEasterEgg.h new file mode 100644 index 0000000..7059d13 --- /dev/null +++ b/xbmc/input/joysticks/JoystickEasterEgg.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/joysticks/interfaces/IButtonSequence.h" + +#include <map> +#include <string> +#include <vector> + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \brief Hush!!! + */ +class CJoystickEasterEgg : public IButtonSequence +{ +public: + explicit CJoystickEasterEgg(const std::string& controllerId); + ~CJoystickEasterEgg() override = default; + + // implementation of IButtonSequence + bool OnButtonPress(const FeatureName& feature) override; + bool IsCapturing() override; + + static void OnFinish(void); + +private: + // Construction parameters + const std::string m_controllerId; + + static const std::map<std::string, std::vector<FeatureName>> m_sequence; + + unsigned int m_state; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickIDs.h b/xbmc/input/joysticks/JoystickIDs.h new file mode 100644 index 0000000..52ffcf4 --- /dev/null +++ b/xbmc/input/joysticks/JoystickIDs.h @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +// Analog sticks on the default controller +#define DEFAULT_LEFT_STICK_NAME "leftstick" +#define DEFAULT_RIGHT_STICK_NAME "rightstick" diff --git a/xbmc/input/joysticks/JoystickMonitor.cpp b/xbmc/input/joysticks/JoystickMonitor.cpp new file mode 100644 index 0000000..6375a41 --- /dev/null +++ b/xbmc/input/joysticks/JoystickMonitor.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015-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 "JoystickMonitor.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "games/controllers/ControllerIDs.h" +#include "input/InputManager.h" + +#include <cmath> + +using namespace KODI; +using namespace JOYSTICK; + +#define AXIS_DEADZONE 0.05f + +std::string CJoystickMonitor::ControllerID() const +{ + return DEFAULT_CONTROLLER_ID; +} + +bool CJoystickMonitor::AcceptsInput(const FeatureName& feature) const +{ + // Only accept input when screen saver is active + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + return appPower->IsInScreenSaver(); +} + +bool CJoystickMonitor::OnButtonPress(const FeatureName& feature, bool bPressed) +{ + if (bPressed) + { + CServiceBroker::GetInputManager().SetMouseActive(false); + return ResetTimers(); + } + + return false; +} + +bool CJoystickMonitor::OnButtonMotion(const FeatureName& feature, + float magnitude, + unsigned int motionTimeMs) +{ + if (std::fabs(magnitude) > AXIS_DEADZONE) + { + CServiceBroker::GetInputManager().SetMouseActive(false); + return ResetTimers(); + } + + return false; +} + +bool CJoystickMonitor::OnAnalogStickMotion(const FeatureName& feature, + float x, + float y, + unsigned int motionTimeMs) +{ + // Analog stick deadzone already processed + if (x != 0.0f || y != 0.0f) + { + CServiceBroker::GetInputManager().SetMouseActive(false); + return ResetTimers(); + } + + return false; +} + +bool CJoystickMonitor::OnWheelMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) +{ + if (std::fabs(position) > AXIS_DEADZONE) + { + CServiceBroker::GetInputManager().SetMouseActive(false); + return ResetTimers(); + } + + return false; +} + +bool CJoystickMonitor::OnThrottleMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) +{ + if (std::fabs(position) > AXIS_DEADZONE) + { + CServiceBroker::GetInputManager().SetMouseActive(false); + return ResetTimers(); + } + + return false; +} + +bool CJoystickMonitor::ResetTimers(void) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + appPower->ResetScreenSaver(); + return appPower->WakeUpScreenSaverAndDPMS(); + + return true; +} diff --git a/xbmc/input/joysticks/JoystickMonitor.h b/xbmc/input/joysticks/JoystickMonitor.h new file mode 100644 index 0000000..783fff1 --- /dev/null +++ b/xbmc/input/joysticks/JoystickMonitor.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015-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. + */ + +#pragma once + +#include "input/joysticks/interfaces/IInputHandler.h" + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Monitors joystick input and resets screensaver/shutdown timers + * whenever motion occurs. + */ +class CJoystickMonitor : public IInputHandler +{ +public: + // implementation of IInputHandler + std::string ControllerID() const override; + bool HasFeature(const FeatureName& feature) const override { return true; } + bool AcceptsInput(const FeatureName& feature) const override; + bool OnButtonPress(const FeatureName& feature, bool bPressed) override; + void OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) override {} + bool OnButtonMotion(const FeatureName& feature, + float magnitude, + unsigned int motionTimeMs) override; + bool OnAnalogStickMotion(const FeatureName& feature, + float x, + float y, + unsigned int motionTimeMs) override; + bool OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z) override + { + return false; + } + bool OnWheelMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) override; + bool OnThrottleMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) override; + void OnInputFrame() override {} + +private: + /*! + * \brief Reset screensaver and shutdown timers + * \return True if the application was woken from screensaver + */ + bool ResetTimers(void); +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickTranslator.cpp b/xbmc/input/joysticks/JoystickTranslator.cpp new file mode 100644 index 0000000..7edfeaf --- /dev/null +++ b/xbmc/input/joysticks/JoystickTranslator.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015-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 "JoystickTranslator.h" + +#include "guilib/LocalizeStrings.h" +#include "input/joysticks/DriverPrimitive.h" +#include "utils/StringUtils.h" + +using namespace KODI; +using namespace JOYSTICK; + +const char* CJoystickTranslator::HatStateToString(HAT_STATE state) +{ + switch (state) + { + case HAT_STATE::UP: + return "UP"; + case HAT_STATE::DOWN: + return "DOWN"; + case HAT_STATE::RIGHT: + return "RIGHT"; + case HAT_STATE::LEFT: + return "LEFT"; + case HAT_STATE::RIGHTUP: + return "UP RIGHT"; + case HAT_STATE::RIGHTDOWN: + return "DOWN RIGHT"; + case HAT_STATE::LEFTUP: + return "UP LEFT"; + case HAT_STATE::LEFTDOWN: + return "DOWN LEFT"; + default: + break; + } + + return "RELEASED"; +} + +const char* CJoystickTranslator::TranslateAnalogStickDirection(ANALOG_STICK_DIRECTION dir) +{ + switch (dir) + { + case ANALOG_STICK_DIRECTION::UP: + return "up"; + case ANALOG_STICK_DIRECTION::DOWN: + return "down"; + case ANALOG_STICK_DIRECTION::RIGHT: + return "right"; + case ANALOG_STICK_DIRECTION::LEFT: + return "left"; + default: + break; + } + + return ""; +} + +ANALOG_STICK_DIRECTION CJoystickTranslator::TranslateAnalogStickDirection(const std::string& dir) +{ + if (dir == "up") + return ANALOG_STICK_DIRECTION::UP; + if (dir == "down") + return ANALOG_STICK_DIRECTION::DOWN; + if (dir == "right") + return ANALOG_STICK_DIRECTION::RIGHT; + if (dir == "left") + return ANALOG_STICK_DIRECTION::LEFT; + + return ANALOG_STICK_DIRECTION::NONE; +} + +const char* CJoystickTranslator::TranslateWheelDirection(WHEEL_DIRECTION dir) +{ + switch (dir) + { + case WHEEL_DIRECTION::RIGHT: + return "right"; + case WHEEL_DIRECTION::LEFT: + return "left"; + default: + break; + } + + return ""; +} + +WHEEL_DIRECTION CJoystickTranslator::TranslateWheelDirection(const std::string& dir) +{ + if (dir == "right") + return WHEEL_DIRECTION::RIGHT; + if (dir == "left") + return WHEEL_DIRECTION::LEFT; + + return WHEEL_DIRECTION::NONE; +} + +const char* CJoystickTranslator::TranslateThrottleDirection(THROTTLE_DIRECTION dir) +{ + switch (dir) + { + case THROTTLE_DIRECTION::UP: + return "up"; + case THROTTLE_DIRECTION::DOWN: + return "down"; + default: + break; + } + + return ""; +} + +THROTTLE_DIRECTION CJoystickTranslator::TranslateThrottleDirection(const std::string& dir) +{ + if (dir == "up") + return THROTTLE_DIRECTION::UP; + if (dir == "down") + return THROTTLE_DIRECTION::DOWN; + + return THROTTLE_DIRECTION::NONE; +} + +SEMIAXIS_DIRECTION CJoystickTranslator::PositionToSemiAxisDirection(float position) +{ + if (position > 0) + return SEMIAXIS_DIRECTION::POSITIVE; + else if (position < 0) + return SEMIAXIS_DIRECTION::NEGATIVE; + + return SEMIAXIS_DIRECTION::ZERO; +} + +WHEEL_DIRECTION CJoystickTranslator::PositionToWheelDirection(float position) +{ + if (position > 0.0f) + return WHEEL_DIRECTION::RIGHT; + else if (position < 0.0f) + return WHEEL_DIRECTION::LEFT; + + return WHEEL_DIRECTION::NONE; +} + +THROTTLE_DIRECTION CJoystickTranslator::PositionToThrottleDirection(float position) +{ + if (position > 0.0f) + return THROTTLE_DIRECTION::UP; + else if (position < 0.0f) + return THROTTLE_DIRECTION::DOWN; + + return THROTTLE_DIRECTION::NONE; +} + +std::string CJoystickTranslator::GetPrimitiveName(const CDriverPrimitive& primitive) +{ + std::string primitiveTemplate; + + switch (primitive.Type()) + { + case PRIMITIVE_TYPE::BUTTON: + primitiveTemplate = g_localizeStrings.Get(35015); // "Button %d" + break; + case PRIMITIVE_TYPE::SEMIAXIS: + primitiveTemplate = g_localizeStrings.Get(35016); // "Axis %d" + break; + default: + break; + } + + return StringUtils::Format(primitiveTemplate, primitive.Index()); +} diff --git a/xbmc/input/joysticks/JoystickTranslator.h b/xbmc/input/joysticks/JoystickTranslator.h new file mode 100644 index 0000000..2efeacc --- /dev/null +++ b/xbmc/input/joysticks/JoystickTranslator.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015-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. + */ + +#pragma once + +#include "JoystickTypes.h" + +namespace KODI +{ +namespace JOYSTICK +{ +class CDriverPrimitive; + +/*! + * \brief Joystick translation utilities + */ +class CJoystickTranslator +{ +public: + /*! + * \brief Translate a hat state to a string representation + * + * \param state The hat state + * + * \return A capitalized string representation, or "RELEASED" if the hat is centered. + */ + static const char* HatStateToString(HAT_STATE state); + + /*! + * \brief Translate an analog stick direction to a lower-case string + * + * \param dir The analog stick direction + * + * \return A lower-case string representation, or "" if the direction is invalid + */ + static const char* TranslateAnalogStickDirection(ANALOG_STICK_DIRECTION dir); + + /*! + * \brief Translate an analog stick direction string to an enum value + * + * \param dir The analog stick direction + * + * \return The translated direction, or ANALOG_STICK_DIRECTION::UNKNOWN if unknown + */ + static ANALOG_STICK_DIRECTION TranslateAnalogStickDirection(const std::string& dir); + + /*! + * \brief Translate a wheel direction to a lower-case string + * + * \param dir The wheel direction + * + * \return A lower-case string representation, or "" if the direction is invalid + */ + static const char* TranslateWheelDirection(WHEEL_DIRECTION dir); + + /*! + * \brief Translate a wheel direction string to an enum value + * + * \param dir The wheel direction + * + * \return The translated direction, or WHEEL_DIRECTION::UNKNOWN if unknown + */ + static WHEEL_DIRECTION TranslateWheelDirection(const std::string& dir); + + /*! + * \brief Translate a throttle direction to a lower-case string + * + * \param dir The analog stick direction + * + * \return A lower-case string representation, or "" if the direction is invalid + */ + static const char* TranslateThrottleDirection(THROTTLE_DIRECTION dir); + + /*! + * \brief Translate a throttle direction string to an enum value + * + * \param dir The throttle direction + * + * \return The translated direction, or THROTTLE_DIRECTION::UNKNOWN if unknown + */ + static THROTTLE_DIRECTION TranslateThrottleDirection(const std::string& dir); + + /*! + * \brief Get the semi-axis direction containing the specified position + * + * \param position The position of the axis + * + * \return POSITIVE, NEGATIVE, or UNKNOWN if position is 0 + */ + static SEMIAXIS_DIRECTION PositionToSemiAxisDirection(float position); + + /*! + * \brief Get the wheel direction containing the specified position + * + * \param position The position of the axis + * + * \return LEFT, RIGHT, or UNKNOWN if position is 0 + */ + static WHEEL_DIRECTION PositionToWheelDirection(float position); + + /*! + * \brief Get the throttle direction containing the specified position + * + * \param position The position of the axis + * + * \return UP, DOWN, or UNKNOWN if position is 0 + */ + static THROTTLE_DIRECTION PositionToThrottleDirection(float position); + + /*! + * \brief Get the localized name of the primitive + * + * \param primitive The primitive, currently only buttons and axes are supported + * + * \return A title for the primitive, e.g. "Button 0" or "Axis 1" + */ + static std::string GetPrimitiveName(const CDriverPrimitive& primitive); +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickTypes.h b/xbmc/input/joysticks/JoystickTypes.h new file mode 100644 index 0000000..5925793 --- /dev/null +++ b/xbmc/input/joysticks/JoystickTypes.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +/*! + \file + \ingroup joystick + */ + +#include "input/InputTypes.h" + +#include <set> +#include <string> + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \brief Name of a physical feature belonging to the joystick + */ +using FeatureName = std::string; + +/*! + * \brief Types of features used in the joystick library + * + * Available types: + * + * 1) scalar[*] + * 2) analog stick + * 3) accelerometer + * 4) rumble motor + * 5) relative pointer + * 6) absolute pointer + * 7) wheel + * 8) throttle + * 9) keyboard key + * + * [*] All three driver primitives (buttons, hats and axes) have a state that + * can be represented using a single scalar value. For this reason, + * features that map to a single primitive are called "scalar features". + */ +enum class FEATURE_TYPE +{ + UNKNOWN, + SCALAR, + ANALOG_STICK, + ACCELEROMETER, + MOTOR, + RELPOINTER, + ABSPOINTER, + WHEEL, + THROTTLE, + KEY, +}; + +/*! + * \brief Categories of features used in the joystick library + */ +enum class FEATURE_CATEGORY +{ + UNKNOWN, + FACE, + SHOULDER, + TRIGGER, + ANALOG_STICK, + ACCELEROMETER, + HAPTICS, + MOUSE_BUTTON, + POINTER, + LIGHTGUN, + OFFSCREEN, // Virtual button to shoot light gun offscreen + KEY, // A keyboard key + KEYPAD, // A key on a numeric keymap, including star and pound + HARDWARE, // A button or functionality on the console + WHEEL, + JOYSTICK, + PADDLE, +}; + +/*! + * \brief Direction arrows on the hat (directional pad) + */ +using HAT_DIRECTION = INPUT::CARDINAL_DIRECTION; + +/*! + * \brief States in which a hat can be + */ +using HAT_STATE = INPUT::INTERCARDINAL_DIRECTION; + +/*! + * \brief Typedef for analog stick directions + */ +using ANALOG_STICK_DIRECTION = INPUT::CARDINAL_DIRECTION; + +/*! + * \brief Directions of motion for a relative pointer + */ +using RELATIVE_POINTER_DIRECTION = INPUT::CARDINAL_DIRECTION; + +/*! + * \brief Directions in which a semiaxis can point + */ +enum class SEMIAXIS_DIRECTION +{ + NEGATIVE = -1, // semiaxis lies in the interval [-1.0, 0.0] + ZERO = 0, // semiaxis is unknown or invalid + POSITIVE = 1, // semiaxis lies in the interval [0.0, 1.0] +}; + +/*! + * \brief Directions on a wheel + */ +enum class WHEEL_DIRECTION +{ + NONE, + RIGHT, + LEFT, +}; + +/*! + * \brief Directions on a throttle + */ +enum class THROTTLE_DIRECTION +{ + NONE, + UP, + DOWN, +}; + +/*! + * \brief Types of input available for scalar features + */ +enum class INPUT_TYPE +{ + UNKNOWN, + DIGITAL, + ANALOG, +}; + +/*! + * \brief Type of driver primitive + */ +enum class PRIMITIVE_TYPE +{ + UNKNOWN = 0, // primitive has no type (invalid) + BUTTON, // a digital button + HAT, // one of the four direction arrows on a D-pad + SEMIAXIS, // the positive or negative half of an axis + MOTOR, // a rumble motor + KEY, // a keyboard key + MOUSE_BUTTON, // a mouse button + RELATIVE_POINTER, // a relative pointer, such as on a mouse +}; + +/*! + * \ingroup joystick + * \brief Action entry in joystick.xml + */ +struct KeymapAction +{ + unsigned int actionId; + std::string actionString; + unsigned int holdTimeMs; + std::set<std::string> hotkeys; + + bool operator<(const KeymapAction& rhs) const { return holdTimeMs < rhs.holdTimeMs; } +}; + +/*! + * \ingroup joystick + * \brief Container that sorts action entries by their holdtime + */ +struct KeymapActionGroup +{ + int windowId = -1; + std::set<KeymapAction> actions; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/JoystickUtils.cpp b/xbmc/input/joysticks/JoystickUtils.cpp new file mode 100644 index 0000000..3c25121 --- /dev/null +++ b/xbmc/input/joysticks/JoystickUtils.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017-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 "JoystickUtils.h" + +#include "JoystickTranslator.h" +#include "utils/StringUtils.h" + +using namespace KODI; +using namespace JOYSTICK; + +std::string CJoystickUtils::MakeKeyName(const FeatureName& feature) +{ + return feature; +} + +std::string CJoystickUtils::MakeKeyName(const FeatureName& feature, ANALOG_STICK_DIRECTION dir) +{ + std::string keyName = feature; + + if (dir != ANALOG_STICK_DIRECTION::NONE) + keyName += CJoystickTranslator::TranslateAnalogStickDirection(dir); + + return keyName; +} + +std::string CJoystickUtils::MakeKeyName(const FeatureName& feature, WHEEL_DIRECTION dir) +{ + ANALOG_STICK_DIRECTION stickDir = ANALOG_STICK_DIRECTION::NONE; + + switch (dir) + { + case WHEEL_DIRECTION::LEFT: + stickDir = ANALOG_STICK_DIRECTION::LEFT; + break; + case WHEEL_DIRECTION::RIGHT: + stickDir = ANALOG_STICK_DIRECTION::RIGHT; + break; + default: + break; + } + + return MakeKeyName(feature, stickDir); +} + +std::string CJoystickUtils::MakeKeyName(const FeatureName& feature, THROTTLE_DIRECTION dir) +{ + ANALOG_STICK_DIRECTION stickDir = ANALOG_STICK_DIRECTION::NONE; + + switch (dir) + { + case THROTTLE_DIRECTION::UP: + stickDir = ANALOG_STICK_DIRECTION::UP; + break; + case THROTTLE_DIRECTION::DOWN: + stickDir = ANALOG_STICK_DIRECTION::DOWN; + break; + default: + break; + } + + return MakeKeyName(feature, stickDir); +} + +const std::vector<ANALOG_STICK_DIRECTION>& CJoystickUtils::GetAnalogStickDirections() +{ + static std::vector<ANALOG_STICK_DIRECTION> directions; + if (directions.empty()) + { + directions.push_back(ANALOG_STICK_DIRECTION::UP); + directions.push_back(ANALOG_STICK_DIRECTION::DOWN); + directions.push_back(ANALOG_STICK_DIRECTION::RIGHT); + directions.push_back(ANALOG_STICK_DIRECTION::LEFT); + } + return directions; +} + +const std::vector<WHEEL_DIRECTION>& CJoystickUtils::GetWheelDirections() +{ + static std::vector<WHEEL_DIRECTION> directions; + if (directions.empty()) + { + directions.push_back(WHEEL_DIRECTION::RIGHT); + directions.push_back(WHEEL_DIRECTION::LEFT); + } + return directions; +} + +const std::vector<THROTTLE_DIRECTION>& CJoystickUtils::GetThrottleDirections() +{ + static std::vector<THROTTLE_DIRECTION> directions; + if (directions.empty()) + { + directions.push_back(THROTTLE_DIRECTION::UP); + directions.push_back(THROTTLE_DIRECTION::DOWN); + } + return directions; +} diff --git a/xbmc/input/joysticks/JoystickUtils.h b/xbmc/input/joysticks/JoystickUtils.h new file mode 100644 index 0000000..49095d2 --- /dev/null +++ b/xbmc/input/joysticks/JoystickUtils.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "JoystickTypes.h" + +#include <string> +#include <vector> + +/// \ingroup joystick +/// \{ + +namespace KODI +{ +namespace JOYSTICK +{ + +inline HAT_DIRECTION& operator|=(HAT_DIRECTION& lhs, HAT_DIRECTION rhs) +{ + return lhs = static_cast<HAT_DIRECTION>(static_cast<int>(lhs) | static_cast<int>(rhs)); +} + +inline HAT_STATE& operator|=(HAT_STATE& lhs, HAT_STATE rhs) +{ + return lhs = static_cast<HAT_STATE>(static_cast<int>(lhs) | static_cast<int>(rhs)); +} + +inline bool operator&(HAT_STATE lhs, HAT_DIRECTION rhs) +{ + return (static_cast<int>(lhs) & static_cast<int>(rhs)) ? true : false; +} + +inline SEMIAXIS_DIRECTION operator*(SEMIAXIS_DIRECTION lhs, int rhs) +{ + return static_cast<SEMIAXIS_DIRECTION>(static_cast<int>(lhs) * rhs); +} + +inline float operator*(float lhs, SEMIAXIS_DIRECTION rhs) +{ + return lhs * static_cast<int>(rhs); +} + +class CJoystickUtils +{ +public: + /*! + * \brief Create a key name used to index an action in the keymap + * + * \param feature The feature name + * + * \return A valid name for a key in the joystick keymap + */ + static std::string MakeKeyName(const FeatureName& feature); + + /*! + * \brief Create a key name used to index an action in the keymap + * + * \param feature The feature name + * \param dir The direction for analog sticks + * + * \return A valid name for a key in the joystick keymap + */ + static std::string MakeKeyName(const FeatureName& feature, ANALOG_STICK_DIRECTION dir); + + /*! + * \brief Create a key name used to index an action in the keymap + * + * \param feature The feature name + * \param dir The direction for a wheel to turn + * + * \return A valid name for a key in the joystick keymap + */ + static std::string MakeKeyName(const FeatureName& feature, WHEEL_DIRECTION dir); + + /*! + * \brief Create a key name used to index an action in the keymap + * + * \param feature The feature name + * \param dir The direction for a throttle to move + * + * \return A valid name for a key in the joystick keymap + */ + static std::string MakeKeyName(const FeatureName& feature, THROTTLE_DIRECTION dir); + + /*! + * \brief Return a vector of the four cardinal directions + */ + static const std::vector<ANALOG_STICK_DIRECTION>& GetAnalogStickDirections(); + + /*! + * \brief Return a vector of the two wheel directions + */ + static const std::vector<WHEEL_DIRECTION>& GetWheelDirections(); + + /*! + * \brief Return a vector of the two throttle directions + */ + static const std::vector<THROTTLE_DIRECTION>& GetThrottleDirections(); +}; + +} // namespace JOYSTICK +} // namespace KODI + +/// \} diff --git a/xbmc/input/joysticks/RumbleGenerator.cpp b/xbmc/input/joysticks/RumbleGenerator.cpp new file mode 100644 index 0000000..db60cfa --- /dev/null +++ b/xbmc/input/joysticks/RumbleGenerator.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016-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 "RumbleGenerator.h" + +#include "ServiceBroker.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerIDs.h" +#include "games/controllers/ControllerManager.h" +#include "input/joysticks/interfaces/IInputReceiver.h" + +#include <algorithm> + +using namespace std::chrono_literals; + +namespace +{ +constexpr auto RUMBLE_TEST_DURATION_MS = 1000ms; // Per motor +constexpr auto RUMBLE_NOTIFICATION_DURATION_MS = 300ms; + +// From game.controller.default profile +#define WEAK_MOTOR_NAME "rightmotor" +} // namespace + +using namespace KODI; +using namespace JOYSTICK; + +CRumbleGenerator::CRumbleGenerator() + : CThread("RumbleGenerator"), m_motors(GetMotors(ControllerID())) +{ +} + +std::string CRumbleGenerator::ControllerID() const +{ + return DEFAULT_CONTROLLER_ID; +} + +void CRumbleGenerator::NotifyUser(IInputReceiver* receiver) +{ + if (receiver && !m_motors.empty()) + { + if (IsRunning()) + StopThread(true); + + m_receiver = receiver; + m_type = RUMBLE_NOTIFICATION; + Create(); + } +} + +bool CRumbleGenerator::DoTest(IInputReceiver* receiver) +{ + if (receiver && !m_motors.empty()) + { + if (IsRunning()) + StopThread(true); + + m_receiver = receiver; + m_type = RUMBLE_TEST; + Create(); + + return true; + } + return false; +} + +void CRumbleGenerator::Process(void) +{ + switch (m_type) + { + case RUMBLE_NOTIFICATION: + { + std::vector<std::string> motors; + + if (std::find(m_motors.begin(), m_motors.end(), WEAK_MOTOR_NAME) != m_motors.end()) + motors.emplace_back(WEAK_MOTOR_NAME); + else + motors = m_motors; // Not using default profile? Just rumble all motors + + for (const std::string& motor : motors) + m_receiver->SetRumbleState(motor, 1.0f); + + CThread::Sleep(RUMBLE_NOTIFICATION_DURATION_MS); + + if (m_bStop) + break; + + for (const std::string& motor : motors) + m_receiver->SetRumbleState(motor, 0.0f); + + break; + } + case RUMBLE_TEST: + { + for (const std::string& motor : m_motors) + { + m_receiver->SetRumbleState(motor, 1.0f); + + CThread::Sleep(RUMBLE_TEST_DURATION_MS); + + if (m_bStop) + break; + + m_receiver->SetRumbleState(motor, 0.0f); + } + break; + } + default: + break; + } +} + +std::vector<std::string> CRumbleGenerator::GetMotors(const std::string& controllerId) +{ + using namespace GAME; + + std::vector<std::string> motors; + + CControllerManager& controllerManager = CServiceBroker::GetGameControllerManager(); + ControllerPtr controller = controllerManager.GetController(controllerId); + if (controller) + controller->GetFeatures(motors, FEATURE_TYPE::MOTOR); + + return motors; +} diff --git a/xbmc/input/joysticks/RumbleGenerator.h b/xbmc/input/joysticks/RumbleGenerator.h new file mode 100644 index 0000000..a6e9853 --- /dev/null +++ b/xbmc/input/joysticks/RumbleGenerator.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "threads/Thread.h" + +#include <string> +#include <vector> + +namespace KODI +{ +namespace JOYSTICK +{ +class IInputReceiver; + +class CRumbleGenerator : public CThread +{ +public: + CRumbleGenerator(); + + ~CRumbleGenerator() override { AbortRumble(); } + + std::string ControllerID() const; + + void NotifyUser(IInputReceiver* receiver); + bool DoTest(IInputReceiver* receiver); + + void AbortRumble(void) { StopThread(); } + +protected: + // implementation of CThread + void Process() override; + +private: + enum RUMBLE_TYPE + { + RUMBLE_UNKNOWN, + RUMBLE_NOTIFICATION, + RUMBLE_TEST, + }; + + static std::vector<std::string> GetMotors(const std::string& controllerId); + + // Construction param + const std::vector<std::string> m_motors; + + // Test param + IInputReceiver* m_receiver = nullptr; + RUMBLE_TYPE m_type = RUMBLE_UNKNOWN; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/dialogs/CMakeLists.txt b/xbmc/input/joysticks/dialogs/CMakeLists.txt new file mode 100644 index 0000000..73adcec --- /dev/null +++ b/xbmc/input/joysticks/dialogs/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES GUIDialogNewJoystick.cpp) + +set(HEADERS GUIDialogNewJoystick.h) + +core_add_library(input_joystick_dialogs) diff --git a/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp new file mode 100644 index 0000000..b66ce89 --- /dev/null +++ b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016-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 "GUIDialogNewJoystick.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "messaging/helpers/DialogHelper.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" + +using namespace KODI; +using namespace JOYSTICK; + +CGUIDialogNewJoystick::CGUIDialogNewJoystick() : CThread("NewJoystickDlg") +{ +} + +void CGUIDialogNewJoystick::ShowAsync() +{ + bool bShow = true; + + if (IsRunning()) + bShow = false; + else if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_INPUT_ASKNEWCONTROLLERS)) + bShow = false; + else if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive( + WINDOW_DIALOG_GAME_CONTROLLERS, false)) + bShow = false; + + if (bShow) + Create(); +} + +void CGUIDialogNewJoystick::Process() +{ + using namespace MESSAGING::HELPERS; + + // "New controller detected" + // "A new controller has been detected. Configuration can be done at any time in "Settings -> + // System Settings -> Input". Would you like to configure it now?" + if (ShowYesNoDialogText(CVariant{35011}, CVariant{35012}) == DialogResponse::CHOICE_YES) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_DIALOG_GAME_CONTROLLERS); + } + else + { + CServiceBroker::GetSettingsComponent()->GetSettings()->SetBool( + CSettings::SETTING_INPUT_ASKNEWCONTROLLERS, false); + } +} diff --git a/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h new file mode 100644 index 0000000..334191f --- /dev/null +++ b/xbmc/input/joysticks/dialogs/GUIDialogNewJoystick.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "threads/Thread.h" + +namespace KODI +{ +namespace JOYSTICK +{ +class CGUIDialogNewJoystick : protected CThread +{ +public: + CGUIDialogNewJoystick(); + ~CGUIDialogNewJoystick() override = default; + + void ShowAsync(); + +protected: + // implementation of CThread + void Process() override; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/generic/ButtonMapping.cpp b/xbmc/input/joysticks/generic/ButtonMapping.cpp new file mode 100644 index 0000000..665a233 --- /dev/null +++ b/xbmc/input/joysticks/generic/ButtonMapping.cpp @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2014-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 "ButtonMapping.h" + +#include "ServiceBroker.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerManager.h" +#include "games/controllers/input/PhysicalFeature.h" +#include "input/IKeymap.h" +#include "input/InputTranslator.h" +#include "input/Key.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickTranslator.h" +#include "input/joysticks/JoystickUtils.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "input/joysticks/interfaces/IButtonMapper.h" +#include "utils/log.h" + +#include <algorithm> +#include <assert.h> +#include <cmath> + +using namespace KODI; +using namespace JOYSTICK; + +#define MAPPING_COOLDOWN_MS 50 // Guard against repeated input +#define AXIS_THRESHOLD 0.75f // Axis must exceed this value to be mapped +#define TRIGGER_DELAY_MS \ + 200 // Delay trigger detection to handle anomalous triggers with non-zero center + +// --- CPrimitiveDetector ------------------------------------------------------ + +CPrimitiveDetector::CPrimitiveDetector(CButtonMapping* buttonMapping) + : m_buttonMapping(buttonMapping) +{ +} + +bool CPrimitiveDetector::MapPrimitive(const CDriverPrimitive& primitive) +{ + if (primitive.IsValid()) + return m_buttonMapping->MapPrimitive(primitive); + + return false; +} + +// --- CButtonDetector --------------------------------------------------------- + +CButtonDetector::CButtonDetector(CButtonMapping* buttonMapping, unsigned int buttonIndex) + : CPrimitiveDetector(buttonMapping), m_buttonIndex(buttonIndex) +{ +} + +bool CButtonDetector::OnMotion(bool bPressed) +{ + if (bPressed) + return MapPrimitive(CDriverPrimitive(PRIMITIVE_TYPE::BUTTON, m_buttonIndex)); + + return false; +} + +// --- CHatDetector ------------------------------------------------------------ + +CHatDetector::CHatDetector(CButtonMapping* buttonMapping, unsigned int hatIndex) + : CPrimitiveDetector(buttonMapping), m_hatIndex(hatIndex) +{ +} + +bool CHatDetector::OnMotion(HAT_STATE state) +{ + return MapPrimitive(CDriverPrimitive(m_hatIndex, static_cast<HAT_DIRECTION>(state))); +} + +// --- CAxisDetector ----------------------------------------------------------- + +CAxisDetector::CAxisDetector(CButtonMapping* buttonMapping, + unsigned int axisIndex, + const AxisConfiguration& config) + : CPrimitiveDetector(buttonMapping), + m_axisIndex(axisIndex), + m_config(config), + m_state(AXIS_STATE::INACTIVE), + m_type(AXIS_TYPE::UNKNOWN), + m_initialPositionKnown(false), + m_initialPosition(0.0f), + m_initialPositionChanged(false) +{ +} + +bool CAxisDetector::OnMotion(float position) +{ + DetectType(position); + + if (m_type != AXIS_TYPE::UNKNOWN) + { + // Update position if this axis is an anomalous trigger + if (m_type == AXIS_TYPE::OFFSET) + position = (position - m_config.center) / m_config.range; + + // Reset state if position crosses zero + if (m_state == AXIS_STATE::MAPPED) + { + SEMIAXIS_DIRECTION activatedDir = m_activatedPrimitive.SemiAxisDirection(); + SEMIAXIS_DIRECTION newDir = CJoystickTranslator::PositionToSemiAxisDirection(position); + + if (activatedDir != newDir) + m_state = AXIS_STATE::INACTIVE; + } + + // Check if axis has become activated + if (m_state == AXIS_STATE::INACTIVE) + { + if (std::abs(position) >= AXIS_THRESHOLD) + m_state = AXIS_STATE::ACTIVATED; + + if (m_state == AXIS_STATE::ACTIVATED) + { + // Range is set later for anomalous triggers + m_activatedPrimitive = + CDriverPrimitive(m_axisIndex, m_config.center, + CJoystickTranslator::PositionToSemiAxisDirection(position), 1); + m_activationTimeMs = std::chrono::steady_clock::now(); + } + } + } + + return true; +} + +void CAxisDetector::ProcessMotion() +{ + // Process newly-activated axis + if (m_state == AXIS_STATE::ACTIVATED) + { + // Ignore anomalous triggers for a bit so we can detect the full range + bool bIgnore = false; + if (m_type == AXIS_TYPE::OFFSET) + { + auto now = std::chrono::steady_clock::now(); + auto duration = + std::chrono::duration_cast<std::chrono::milliseconds>(now - m_activationTimeMs); + + if (duration.count() < TRIGGER_DELAY_MS) + bIgnore = true; + } + + if (!bIgnore) + { + // Update driver primitive's range if we're mapping an anomalous trigger + if (m_type == AXIS_TYPE::OFFSET) + { + m_activatedPrimitive = + CDriverPrimitive(m_activatedPrimitive.Index(), m_activatedPrimitive.Center(), + m_activatedPrimitive.SemiAxisDirection(), m_config.range); + } + + // Map primitive + if (!MapPrimitive(m_activatedPrimitive)) + { + if (m_type == AXIS_TYPE::OFFSET) + CLog::Log(LOGDEBUG, "Mapping offset axis {} failed", m_axisIndex); + else + CLog::Log(LOGDEBUG, "Mapping normal axis {} failed", m_axisIndex); + } + + m_state = AXIS_STATE::MAPPED; + } + } +} + +void CAxisDetector::SetEmitted(const CDriverPrimitive& activePrimitive) +{ + m_state = AXIS_STATE::MAPPED; + m_activatedPrimitive = activePrimitive; +} + +void CAxisDetector::DetectType(float position) +{ + // Some platforms don't report a value until the axis is first changed. + // Detection relies on an initial value, so this axis will be disabled until + // the user begins button mapping again. + if (m_config.bLateDiscovery) + return; + + // Update range if a range of > 1 is observed + if (std::abs(position - m_config.center) > 1.0f) + m_config.range = 2; + + if (m_type != AXIS_TYPE::UNKNOWN) + return; + + if (m_config.bKnown) + { + if (m_config.center == 0) + m_type = AXIS_TYPE::NORMAL; + else + m_type = AXIS_TYPE::OFFSET; + } + + if (m_type != AXIS_TYPE::UNKNOWN) + return; + + if (!m_initialPositionKnown) + { + m_initialPositionKnown = true; + m_initialPosition = position; + } + + if (position != m_initialPosition) + m_initialPositionChanged = true; + + if (m_initialPositionChanged) + { + // Calculate center based on initial position. + if (m_initialPosition < -0.5f) + { + m_config.center = -1; + m_type = AXIS_TYPE::OFFSET; + CLog::Log(LOGDEBUG, "Anomalous trigger detected on axis {} with center {}", m_axisIndex, + m_config.center); + } + else if (m_initialPosition > 0.5f) + { + m_config.center = 1; + m_type = AXIS_TYPE::OFFSET; + CLog::Log(LOGDEBUG, "Anomalous trigger detected on axis {} with center {}", m_axisIndex, + m_config.center); + } + else + { + m_type = AXIS_TYPE::NORMAL; + CLog::Log(LOGDEBUG, "Normal axis detected on axis {}", m_axisIndex); + } + } +} + +// --- CKeyDetector --------------------------------------------------------- + +CKeyDetector::CKeyDetector(CButtonMapping* buttonMapping, XBMCKey keycode) + : CPrimitiveDetector(buttonMapping), m_keycode(keycode) +{ +} + +bool CKeyDetector::OnMotion(bool bPressed) +{ + if (bPressed) + return MapPrimitive(CDriverPrimitive(m_keycode)); + + return false; +} + +// --- CMouseButtonDetector ---------------------------------------------------- + +CMouseButtonDetector::CMouseButtonDetector(CButtonMapping* buttonMapping, + MOUSE::BUTTON_ID buttonIndex) + : CPrimitiveDetector(buttonMapping), m_buttonIndex(buttonIndex) +{ +} + +bool CMouseButtonDetector::OnMotion(bool bPressed) +{ + if (bPressed) + return MapPrimitive(CDriverPrimitive(m_buttonIndex)); + + return false; +} + +// --- CPointerDetector -------------------------------------------------------- + +CPointerDetector::CPointerDetector(CButtonMapping* buttonMapping) + : CPrimitiveDetector(buttonMapping) +{ +} + +bool CPointerDetector::OnMotion(int x, int y) +{ + if (!m_bStarted) + { + m_bStarted = true; + m_startX = x; + m_startY = y; + m_frameCount = 0; + } + + if (m_frameCount++ >= MIN_FRAME_COUNT) + { + int dx = x - m_startX; + int dy = y - m_startY; + + INPUT::INTERCARDINAL_DIRECTION dir = GetPointerDirection(dx, dy); + + CDriverPrimitive primitive(static_cast<RELATIVE_POINTER_DIRECTION>(dir)); + if (primitive.IsValid()) + { + if (MapPrimitive(primitive)) + m_bStarted = false; + } + } + + return true; +} + +KODI::INPUT::INTERCARDINAL_DIRECTION CPointerDetector::GetPointerDirection(int x, int y) +{ + using namespace INPUT; + + // Translate from left-handed coordinate system to right-handed coordinate system + y *= -1; + + return CInputTranslator::VectorToIntercardinalDirection(static_cast<float>(x), + static_cast<float>(y)); +} + +// --- CButtonMapping ---------------------------------------------------------- + +CButtonMapping::CButtonMapping(IButtonMapper* buttonMapper, IButtonMap* buttonMap, IKeymap* keymap) + : m_buttonMapper(buttonMapper), m_buttonMap(buttonMap), m_keymap(keymap), m_frameCount(0) +{ + assert(m_buttonMapper != nullptr); + assert(m_buttonMap != nullptr); + + // Make sure axes mapped to Select are centered before they can be mapped. + // This ensures that they are not immediately mapped to the first button. + if (m_keymap) + { + using namespace GAME; + + CControllerManager& controllerManager = CServiceBroker::GetGameControllerManager(); + ControllerPtr controller = controllerManager.GetController(m_keymap->ControllerID()); + + const auto& features = controller->Features(); + for (const auto& feature : features) + { + bool bIsSelectAction = false; + + const auto& actions = + m_keymap->GetActions(CJoystickUtils::MakeKeyName(feature.Name())).actions; + if (!actions.empty() && actions.begin()->actionId == ACTION_SELECT_ITEM) + bIsSelectAction = true; + + if (!bIsSelectAction) + continue; + + CDriverPrimitive primitive; + if (!m_buttonMap->GetScalar(feature.Name(), primitive)) + continue; + + if (primitive.Type() != PRIMITIVE_TYPE::SEMIAXIS) + continue; + + // Set initial config, as detection will fail because axis is already activated + AxisConfiguration axisConfig; + axisConfig.bKnown = true; + axisConfig.center = primitive.Center(); + axisConfig.range = primitive.Range(); + + GetAxis(primitive.Index(), static_cast<float>(primitive.Center()), axisConfig) + .SetEmitted(primitive); + } + } +} + +bool CButtonMapping::OnButtonMotion(unsigned int buttonIndex, bool bPressed) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::BUTTON)) + return false; + + return GetButton(buttonIndex).OnMotion(bPressed); +} + +bool CButtonMapping::OnHatMotion(unsigned int hatIndex, HAT_STATE state) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::HAT)) + return false; + + return GetHat(hatIndex).OnMotion(state); +} + +bool CButtonMapping::OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::SEMIAXIS)) + return false; + + return GetAxis(axisIndex, position).OnMotion(position); +} + +void CButtonMapping::OnInputFrame(void) +{ + for (auto& axis : m_axes) + axis.second.ProcessMotion(); + + m_buttonMapper->OnEventFrame(m_buttonMap, IsMapping()); + + m_frameCount++; +} + +bool CButtonMapping::OnKeyPress(const CKey& key) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::KEY)) + return false; + + return GetKey(static_cast<XBMCKey>(key.GetKeycode())).OnMotion(true); +} + +bool CButtonMapping::OnPosition(int x, int y) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::RELATIVE_POINTER)) + return false; + + return GetPointer().OnMotion(x, y); +} + +bool CButtonMapping::OnButtonPress(MOUSE::BUTTON_ID button) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::MOUSE_BUTTON)) + return false; + + return GetMouseButton(button).OnMotion(true); +} + +void CButtonMapping::OnButtonRelease(MOUSE::BUTTON_ID button) +{ + if (!m_buttonMapper->AcceptsPrimitive(PRIMITIVE_TYPE::MOUSE_BUTTON)) + return; + + GetMouseButton(button).OnMotion(false); +} + +void CButtonMapping::SaveButtonMap() +{ + m_buttonMap->SaveButtonMap(); +} + +void CButtonMapping::ResetIgnoredPrimitives() +{ + std::vector<CDriverPrimitive> empty; + m_buttonMap->SetIgnoredPrimitives(empty); +} + +void CButtonMapping::RevertButtonMap() +{ + m_buttonMap->RevertButtonMap(); +} + +bool CButtonMapping::MapPrimitive(const CDriverPrimitive& primitive) +{ + bool bHandled = false; + + if (m_buttonMap->IsIgnored(primitive)) + { + bHandled = true; + } + else + { + auto now = std::chrono::steady_clock::now(); + + bool bTimeoutElapsed = true; + + if (m_buttonMapper->NeedsCooldown()) + bTimeoutElapsed = (now >= m_lastAction + std::chrono::milliseconds(MAPPING_COOLDOWN_MS)); + + if (bTimeoutElapsed) + { + bHandled = m_buttonMapper->MapPrimitive(m_buttonMap, m_keymap, primitive); + + if (bHandled) + m_lastAction = std::chrono::steady_clock::now(); + } + else + { + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastAction); + + CLog::Log(LOGDEBUG, "Button mapping: rapid input after {}ms dropped for profile \"{}\"", + duration.count(), m_buttonMapper->ControllerID()); + bHandled = true; + } + } + + return bHandled; +} + +bool CButtonMapping::IsMapping() const +{ + for (auto itAxis : m_axes) + { + if (itAxis.second.IsMapping()) + return true; + } + + return false; +} + +CButtonDetector& CButtonMapping::GetButton(unsigned int buttonIndex) +{ + auto itButton = m_buttons.find(buttonIndex); + + if (itButton == m_buttons.end()) + { + m_buttons.insert(std::make_pair(buttonIndex, CButtonDetector(this, buttonIndex))); + itButton = m_buttons.find(buttonIndex); + } + + return itButton->second; +} + +CHatDetector& CButtonMapping::GetHat(unsigned int hatIndex) +{ + auto itHat = m_hats.find(hatIndex); + + if (itHat == m_hats.end()) + { + m_hats.insert(std::make_pair(hatIndex, CHatDetector(this, hatIndex))); + itHat = m_hats.find(hatIndex); + } + + return itHat->second; +} + +CAxisDetector& CButtonMapping::GetAxis( + unsigned int axisIndex, + float position, + const AxisConfiguration& initialConfig /* = AxisConfiguration() */) +{ + auto itAxis = m_axes.find(axisIndex); + + if (itAxis == m_axes.end()) + { + AxisConfiguration config(initialConfig); + + if (m_frameCount >= 2) + { + config.bLateDiscovery = true; + OnLateDiscovery(axisIndex); + } + + // Report axis + CLog::Log(LOGDEBUG, "Axis {} discovered at position {:.4f} after {} frames", axisIndex, + position, static_cast<unsigned long>(m_frameCount)); + + m_axes.insert(std::make_pair(axisIndex, CAxisDetector(this, axisIndex, config))); + itAxis = m_axes.find(axisIndex); + } + + return itAxis->second; +} + +CKeyDetector& CButtonMapping::GetKey(XBMCKey keycode) +{ + auto itKey = m_keys.find(keycode); + + if (itKey == m_keys.end()) + { + m_keys.insert(std::make_pair(keycode, CKeyDetector(this, keycode))); + itKey = m_keys.find(keycode); + } + + return itKey->second; +} + +CMouseButtonDetector& CButtonMapping::GetMouseButton(MOUSE::BUTTON_ID buttonIndex) +{ + auto itButton = m_mouseButtons.find(buttonIndex); + + if (itButton == m_mouseButtons.end()) + { + m_mouseButtons.insert(std::make_pair(buttonIndex, CMouseButtonDetector(this, buttonIndex))); + itButton = m_mouseButtons.find(buttonIndex); + } + + return itButton->second; +} + +CPointerDetector& CButtonMapping::GetPointer() +{ + if (!m_pointer) + m_pointer.reset(new CPointerDetector(this)); + + return *m_pointer; +} + +void CButtonMapping::OnLateDiscovery(unsigned int axisIndex) +{ + m_buttonMapper->OnLateAxis(m_buttonMap, axisIndex); +} diff --git a/xbmc/input/joysticks/generic/ButtonMapping.h b/xbmc/input/joysticks/generic/ButtonMapping.h new file mode 100644 index 0000000..3f76767 --- /dev/null +++ b/xbmc/input/joysticks/generic/ButtonMapping.h @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/interfaces/IButtonMapCallback.h" +#include "input/joysticks/interfaces/IDriverHandler.h" +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" +#include "input/mouse/MouseTypes.h" +#include "input/mouse/interfaces/IMouseDriverHandler.h" + +#include <chrono> +#include <map> +#include <memory> +#include <stdint.h> + +class IKeymap; + +namespace KODI +{ +namespace JOYSTICK +{ +class CButtonMapping; +class IButtonMap; +class IButtonMapper; + +/*! + * \brief Detects and dispatches mapping events + * + * A mapping event usually occurs when a driver primitive is pressed or + * exceeds a certain threshold. + * + * Detection can be quite complicated due to driver bugs, so each type of + * driver primitive is given its own detector class inheriting from this one. + */ +class CPrimitiveDetector +{ +protected: + CPrimitiveDetector(CButtonMapping* buttonMapping); + + /*! + * \brief Dispatch a mapping event + * + * \return True if the primitive was mapped, false otherwise + */ + bool MapPrimitive(const CDriverPrimitive& primitive); + +private: + CButtonMapping* const m_buttonMapping; +}; + +/*! + * \brief Detects when a button should be mapped + */ +class CButtonDetector : public CPrimitiveDetector +{ +public: + CButtonDetector(CButtonMapping* buttonMapping, unsigned int buttonIndex); + + /*! + * \brief Button state has been updated + * + * \param bPressed The new state + * + * \return True if this press was handled, false if it should fall through + * to the next driver handler + */ + bool OnMotion(bool bPressed); + +private: + // Construction parameters + const unsigned int m_buttonIndex; +}; + +/*! + * \brief Detects when a D-pad direction should be mapped + */ +class CHatDetector : public CPrimitiveDetector +{ +public: + CHatDetector(CButtonMapping* buttonMapping, unsigned int hatIndex); + + /*! + * \brief Hat state has been updated + * + * \param state The new state + * + * \return True if state is a cardinal direction, false otherwise + */ + bool OnMotion(HAT_STATE state); + +private: + // Construction parameters + const unsigned int m_hatIndex; +}; + +struct AxisConfiguration +{ + bool bKnown = false; + int center = 0; + unsigned int range = 1; + bool bLateDiscovery = false; +}; + +/*! + * \brief Detects when an axis should be mapped + */ +class CAxisDetector : public CPrimitiveDetector +{ +public: + CAxisDetector(CButtonMapping* buttonMapping, + unsigned int axisIndex, + const AxisConfiguration& config); + + /*! + * \brief Axis state has been updated + * + * \param position The new state + * + * \return Always true - axis motion events are always absorbed while button mapping + */ + bool OnMotion(float position); + + /*! + * \brief Called once per frame + * + * If an axis was activated, the button mapping command will be emitted + * here. + */ + void ProcessMotion(); + + /*! + * \brief Check if the axis was mapped and is still in motion + * + * \return True between when the axis is mapped and when it crosses zero + */ + bool IsMapping() const { return m_state == AXIS_STATE::MAPPED; } + + /*! + * \brief Set the state such that this axis has generated a mapping event + * + * If an axis is mapped to the Select action, it may be pressed when button + * mapping begins. This function is used to indicate that the axis shouldn't + * be mapped until after it crosses zero again. + */ + void SetEmitted(const CDriverPrimitive& activePrimitive); + +private: + enum class AXIS_STATE + { + /*! + * \brief Axis is inactive (position is less than threshold) + */ + INACTIVE, + + /*! + * \brief Axis is activated (position has exceeded threshold) + */ + ACTIVATED, + + /*! + * \brief Axis has generated a mapping event, but has not been centered yet + */ + MAPPED, + }; + + enum class AXIS_TYPE + { + /*! + * \brief Axis type is initially unknown + */ + UNKNOWN, + + /*! + * \brief Axis is centered about 0 + * + * - If the axis is an analog stick, it can travel to -1 or +1. + * - If the axis is a pressure-sensitive button or a normal trigger, + * it can travel to +1. + * - If the axis is a DirectInput trigger, then it is possible that two + * triggers can be on the same axis in opposite directions. + * - Normally, D-pads appear as a hat or four buttons. However, some + * D-pads are reported as two axes that can have the discrete values + * -1, 0 or 1. This is called a "discrete D-pad". + */ + NORMAL, + + /*! + * \brief Axis is centered about -1 or 1 + * + * - On OSX, with the cocoa driver, triggers are centered about -1 and + * travel to +1. In this case, the range is 2 and the direction is + * positive. + * - The author of SDL has observed triggers centered at +1 and travel + * to 0. In this case, the range is 1 and the direction is negative. + */ + OFFSET, + }; + + void DetectType(float position); + + // Construction parameters + const unsigned int m_axisIndex; + AxisConfiguration m_config; // mutable + + // State variables + AXIS_STATE m_state; + CDriverPrimitive m_activatedPrimitive; + AXIS_TYPE m_type; + bool m_initialPositionKnown; // set to true on first motion + float m_initialPosition; // set to position of first motion + bool m_initialPositionChanged; // set to true when position differs from the initial position + std::chrono::time_point<std::chrono::steady_clock> + m_activationTimeMs; // only used to delay anomalous trigger mapping to detect full range +}; + +/*! + * \brief Detects when a keyboard key should be mapped + */ +class CKeyDetector : public CPrimitiveDetector +{ +public: + CKeyDetector(CButtonMapping* buttonMapping, XBMCKey keycode); + + /*! + * \brief Key state has been updated + * + * \param bPressed The new state + * + * \return True if this press was handled, false if it should fall through + * to the next driver handler + */ + bool OnMotion(bool bPressed); + +private: + // Construction parameters + const XBMCKey m_keycode; +}; + +/*! + * \brief Detects when a mouse button should be mapped + */ +class CMouseButtonDetector : public CPrimitiveDetector +{ +public: + CMouseButtonDetector(CButtonMapping* buttonMapping, MOUSE::BUTTON_ID buttonIndex); + + /*! + * \brief Button state has been updated + * + * \param bPressed The new state + * + * \return True if this press was handled, false if it should fall through + * to the next driver handler + */ + bool OnMotion(bool bPressed); + +private: + // Construction parameters + const MOUSE::BUTTON_ID m_buttonIndex; +}; + +/*! + * \brief Detects when a mouse button should be mapped + */ +class CPointerDetector : public CPrimitiveDetector +{ +public: + CPointerDetector(CButtonMapping* buttonMapping); + + /*! + * \brief Pointer position has been updated + * + * \param x The new x coordinate + * \param y The new y coordinate + * + * \return Always true - pointer motion events are always absorbed while + * button mapping + */ + bool OnMotion(int x, int y); + +private: + // Utility function + static INPUT::INTERCARDINAL_DIRECTION GetPointerDirection(int x, int y); + + static const unsigned int MIN_FRAME_COUNT = 10; + + // State variables + bool m_bStarted = false; + int m_startX = 0; + int m_startY = 0; + unsigned int m_frameCount = 0; +}; + +/*! + * \ingroup joystick + * \brief Generic implementation of a class that provides button mapping by + * translating driver events to button mapping commands + * + * Button mapping commands are invoked instantly for buttons and hats. + * + * Button mapping commands are deferred for a short while after an axis is + * activated, and only one button mapping command will be invoked per + * activation. + */ +class CButtonMapping : public IDriverHandler, + public KEYBOARD::IKeyboardDriverHandler, + public MOUSE::IMouseDriverHandler, + public IButtonMapCallback +{ +public: + /*! + * \brief Constructor for CButtonMapping + * + * \param buttonMapper Carries out button-mapping commands using <buttonMap> + * \param buttonMap The button map given to <buttonMapper> on each command + */ + CButtonMapping(IButtonMapper* buttonMapper, IButtonMap* buttonMap, IKeymap* keymap); + + ~CButtonMapping() override = default; + + // implementation of IDriverHandler + bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override; + bool OnHatMotion(unsigned int hatIndex, HAT_STATE state) override; + bool OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) override; + void OnInputFrame() override; + + // implementation of IKeyboardDriverHandler + bool OnKeyPress(const CKey& key) override; + void OnKeyRelease(const CKey& key) override {} + + // implementation of IMouseDriverHandler + bool OnPosition(int x, int y) override; + bool OnButtonPress(MOUSE::BUTTON_ID button) override; + void OnButtonRelease(MOUSE::BUTTON_ID button) override; + + // implementation of IButtonMapCallback + void SaveButtonMap() override; + void ResetIgnoredPrimitives() override; + void RevertButtonMap() override; + + /*! + * \brief Process the primitive mapping command + * + * First, this function checks if the input should be dropped. This can + * happen if the input is ignored or the cooldown period is active. If the + * input is dropped, this returns true with no effect, effectively absorbing + * the input. Otherwise, the mapping command is sent to m_buttonMapper. + * + * \param primitive The primitive being mapped + * \return True if the mapping command was handled, false otherwise + */ + bool MapPrimitive(const CDriverPrimitive& primitive); + +private: + bool IsMapping() const; + + void OnLateDiscovery(unsigned int axisIndex); + + CButtonDetector& GetButton(unsigned int buttonIndex); + CHatDetector& GetHat(unsigned int hatIndex); + CAxisDetector& GetAxis(unsigned int axisIndex, + float position, + const AxisConfiguration& initialConfig = AxisConfiguration()); + CKeyDetector& GetKey(XBMCKey keycode); + CMouseButtonDetector& GetMouseButton(MOUSE::BUTTON_ID buttonIndex); + CPointerDetector& GetPointer(); + + // Construction parameters + IButtonMapper* const m_buttonMapper; + IButtonMap* const m_buttonMap; + IKeymap* const m_keymap; + + std::map<unsigned int, CButtonDetector> m_buttons; + std::map<unsigned int, CHatDetector> m_hats; + std::map<unsigned int, CAxisDetector> m_axes; + std::map<XBMCKey, CKeyDetector> m_keys; + std::map<MOUSE::BUTTON_ID, CMouseButtonDetector> m_mouseButtons; + std::unique_ptr<CPointerDetector> m_pointer; + std::chrono::time_point<std::chrono::steady_clock> m_lastAction; + uint64_t m_frameCount; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/generic/CMakeLists.txt b/xbmc/input/joysticks/generic/CMakeLists.txt new file mode 100644 index 0000000..f44258a --- /dev/null +++ b/xbmc/input/joysticks/generic/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES ButtonMapping.cpp + DriverReceiving.cpp + FeatureHandling.cpp + InputHandling.cpp) + +set(HEADERS ButtonMapping.h + DriverReceiving.h + FeatureHandling.h + InputHandling.h) + +core_add_library(input_joystick_generic) diff --git a/xbmc/input/joysticks/generic/DriverReceiving.cpp b/xbmc/input/joysticks/generic/DriverReceiving.cpp new file mode 100644 index 0000000..bf52f4f --- /dev/null +++ b/xbmc/input/joysticks/generic/DriverReceiving.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016-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 "DriverReceiving.h" + +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "input/joysticks/interfaces/IDriverReceiver.h" + +using namespace KODI; +using namespace JOYSTICK; + +CDriverReceiving::CDriverReceiving(IDriverReceiver* receiver, IButtonMap* buttonMap) + : m_receiver(receiver), m_buttonMap(buttonMap) +{ +} + +bool CDriverReceiving::SetRumbleState(const FeatureName& feature, float magnitude) +{ + bool bHandled = false; + + if (m_receiver != nullptr && m_buttonMap != nullptr) + { + CDriverPrimitive primitive; + if (m_buttonMap->GetScalar(feature, primitive)) + { + if (primitive.Type() == PRIMITIVE_TYPE::MOTOR) + bHandled = m_receiver->SetMotorState(primitive.Index(), magnitude); + } + } + + return bHandled; +} diff --git a/xbmc/input/joysticks/generic/DriverReceiving.h b/xbmc/input/joysticks/generic/DriverReceiving.h new file mode 100644 index 0000000..758b560 --- /dev/null +++ b/xbmc/input/joysticks/generic/DriverReceiving.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IInputReceiver.h" + +#include <map> + +namespace KODI +{ +namespace JOYSTICK +{ +class IDriverReceiver; +class IButtonMap; + +/*! + * \ingroup joystick + * \brief Class to translate input events from higher-level features to driver primitives + * + * A button map is used to translate controller features to driver primitives. + * The button map has been abstracted away behind the IButtonMap interface + * so that it can be provided by an add-on. + */ +class CDriverReceiving : public IInputReceiver +{ +public: + CDriverReceiving(IDriverReceiver* receiver, IButtonMap* buttonMap); + + ~CDriverReceiving() override = default; + + // implementation of IInputReceiver + bool SetRumbleState(const FeatureName& feature, float magnitude) override; + +private: + IDriverReceiver* const m_receiver; + IButtonMap* const m_buttonMap; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/generic/FeatureHandling.cpp b/xbmc/input/joysticks/generic/FeatureHandling.cpp new file mode 100644 index 0000000..639ae5b --- /dev/null +++ b/xbmc/input/joysticks/generic/FeatureHandling.cpp @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2014-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 "FeatureHandling.h" + +#include "ServiceBroker.h" +#include "games/controllers/Controller.h" +#include "games/controllers/ControllerManager.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "input/joysticks/interfaces/IInputHandler.h" +#include "utils/log.h" + +#include <vector> + +using namespace KODI; +using namespace JOYSTICK; + +#define ANALOG_DIGITAL_THRESHOLD 0.5f +#define DISCRETE_ANALOG_RAMPUP_TIME_MS 1500 +#define DISCRETE_ANALOG_START_VALUE 0.3f + +// --- CJoystickFeature -------------------------------------------------------- + +CJoystickFeature::CJoystickFeature(const FeatureName& name, + IInputHandler* handler, + IButtonMap* buttonMap) + : m_name(name), + m_handler(handler), + m_buttonMap(buttonMap), + m_bEnabled(m_handler->HasFeature(name)) +{ +} + +bool CJoystickFeature::AcceptsInput(bool bActivation) +{ + bool bAcceptsInput = false; + + if (m_bEnabled) + { + if (m_handler->AcceptsInput(m_name)) + bAcceptsInput = true; + } + + return bAcceptsInput; +} + +void CJoystickFeature::ResetMotion() +{ + m_motionStartTimeMs = {}; +} + +void CJoystickFeature::StartMotion() +{ + m_motionStartTimeMs = std::chrono::steady_clock::now(); +} + +bool CJoystickFeature::InMotion() const +{ + return m_motionStartTimeMs.time_since_epoch().count() > 0; +} + +unsigned int CJoystickFeature::MotionTimeMs() const +{ + if (!InMotion()) + return 0; + + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_motionStartTimeMs); + + return duration.count(); +} + +// --- CScalarFeature ---------------------------------------------------------- + +CScalarFeature::CScalarFeature(const FeatureName& name, + IInputHandler* handler, + IButtonMap* buttonMap) + : CJoystickFeature(name, handler, buttonMap), + m_bDigitalState(false), + m_analogState(0.0f), + m_bActivated(false), + m_bDiscrete(true) +{ + GAME::ControllerPtr controller = + CServiceBroker::GetGameControllerManager().GetController(handler->ControllerID()); + if (controller) + m_inputType = controller->GetInputType(name); +} + +bool CScalarFeature::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) +{ + // Feature must accept input to be considered handled + bool bHandled = AcceptsInput(bPressed); + + if (m_inputType == INPUT_TYPE::DIGITAL) + bHandled &= OnDigitalMotion(bPressed); + else if (m_inputType == INPUT_TYPE::ANALOG) + bHandled &= OnAnalogMotion(bPressed ? 1.0f : 0.0f); + + return bHandled; +} + +bool CScalarFeature::OnAnalogMotion(const CDriverPrimitive& source, float magnitude) +{ + // Update activated status + if (magnitude > 0.0f) + m_bActivated = true; + + // Update discrete status + if (magnitude != 0.0f && magnitude != 1.0f) + m_bDiscrete = false; + + // Feature must accept input to be considered handled + bool bHandled = AcceptsInput(magnitude > 0.0f); + + if (m_inputType == INPUT_TYPE::DIGITAL) + bHandled &= OnDigitalMotion(magnitude >= ANALOG_DIGITAL_THRESHOLD); + else if (m_inputType == INPUT_TYPE::ANALOG) + bHandled &= OnAnalogMotion(magnitude); + + return bHandled; +} + +void CScalarFeature::ProcessMotions(void) +{ + if (m_inputType == INPUT_TYPE::DIGITAL && m_bDigitalState) + ProcessDigitalMotion(); + else if (m_inputType == INPUT_TYPE::ANALOG) + ProcessAnalogMotion(); +} + +bool CScalarFeature::OnDigitalMotion(bool bPressed) +{ + bool bHandled = false; + + if (m_bDigitalState != bPressed) + { + m_bDigitalState = bPressed; + + // Motion is initiated in ProcessMotions() + ResetMotion(); + + bHandled = m_bInitialPressHandled = m_handler->OnButtonPress(m_name, bPressed); + + if (m_bDigitalState) + CLog::Log(LOGDEBUG, "FEATURE [ {} ] on {} pressed ({})", m_name, m_handler->ControllerID(), + bHandled ? "handled" : "ignored"); + else + CLog::Log(LOGDEBUG, "FEATURE [ {} ] on {} released", m_name, m_handler->ControllerID()); + } + else if (m_bDigitalState) + { + bHandled = m_bInitialPressHandled; + } + + return bHandled; +} + +bool CScalarFeature::OnAnalogMotion(float magnitude) +{ + const bool bActivated = (magnitude != 0.0f); + + // Update analog state + m_analogState = magnitude; + + // Update motion time + if (!bActivated) + ResetMotion(); + else if (!InMotion()) + StartMotion(); + + // Log activation/deactivation + if (m_bDigitalState != bActivated) + { + m_bDigitalState = bActivated; + CLog::Log(LOGDEBUG, "FEATURE [ {} ] on {} {}", m_name, m_handler->ControllerID(), + bActivated ? "activated" : "deactivated"); + } + + return true; +} + +void CScalarFeature::ProcessDigitalMotion() +{ + if (!InMotion()) + { + // Button was just pressed, record start time and exit (button press event + // was already sent this frame) + StartMotion(); + } + else + { + // Button has been pressed more than one event frame + const unsigned int elapsed = MotionTimeMs(); + m_handler->OnButtonHold(m_name, elapsed); + } +} + +void CScalarFeature::ProcessAnalogMotion() +{ + float magnitude = m_analogState; + + // Calculate time elapsed since motion began + unsigned int elapsed = MotionTimeMs(); + + // If analog value is discrete, ramp up magnitude + if (m_bActivated && m_bDiscrete) + { + if (elapsed < DISCRETE_ANALOG_RAMPUP_TIME_MS) + { + magnitude *= static_cast<float>(elapsed) / DISCRETE_ANALOG_RAMPUP_TIME_MS; + if (magnitude < DISCRETE_ANALOG_START_VALUE) + magnitude = DISCRETE_ANALOG_START_VALUE; + } + } + + m_handler->OnButtonMotion(m_name, magnitude, elapsed); +} + +// --- CAxisFeature ------------------------------------------------------------ + +CAxisFeature::CAxisFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap) + : CJoystickFeature(name, handler, buttonMap), m_state(0.0f) +{ +} + +bool CAxisFeature::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) +{ + return OnAnalogMotion(source, bPressed ? 1.0f : 0.0f); +} + +void CAxisFeature::ProcessMotions(void) +{ + const float newState = m_axis.GetPosition(); + + const bool bActivated = (newState != 0.0f); + + if (!AcceptsInput(bActivated)) + return; + + const bool bWasActivated = (m_state != 0.0f); + + if (!bActivated && bWasActivated) + CLog::Log(LOGDEBUG, "Feature [ {} ] on {} deactivated", m_name, m_handler->ControllerID()); + else if (bActivated && !bWasActivated) + { + CLog::Log(LOGDEBUG, "Feature [ {} ] on {} activated {}", m_name, m_handler->ControllerID(), + newState > 0.0f ? "positive" : "negative"); + } + + if (bActivated || bWasActivated) + { + m_state = newState; + + unsigned int motionTimeMs = 0; + + if (bActivated) + { + if (!InMotion()) + StartMotion(); + else + motionTimeMs = MotionTimeMs(); + } + else + ResetMotion(); + + switch (m_buttonMap->GetFeatureType(m_name)) + { + case FEATURE_TYPE::WHEEL: + m_handler->OnWheelMotion(m_name, newState, motionTimeMs); + break; + case FEATURE_TYPE::THROTTLE: + m_handler->OnThrottleMotion(m_name, newState, motionTimeMs); + break; + default: + break; + } + } +} + +// --- CWheel ------------------------------------------------------------------ + +CWheel::CWheel(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap) + : CAxisFeature(name, handler, buttonMap) +{ +} + +bool CWheel::OnAnalogMotion(const CDriverPrimitive& source, float magnitude) +{ + WHEEL_DIRECTION direction = WHEEL_DIRECTION::NONE; + + std::vector<WHEEL_DIRECTION> dirs = { + WHEEL_DIRECTION::RIGHT, + WHEEL_DIRECTION::LEFT, + }; + + CDriverPrimitive primitive; + for (auto dir : dirs) + { + if (m_buttonMap->GetWheel(m_name, dir, primitive) && primitive == source) + { + direction = dir; + break; + } + } + + // Feature must accept input to be considered handled + bool bHandled = AcceptsInput(magnitude > 0.0f); + + switch (direction) + { + case WHEEL_DIRECTION::RIGHT: + m_axis.SetPositiveDistance(magnitude); + break; + case WHEEL_DIRECTION::LEFT: + m_axis.SetNegativeDistance(magnitude); + break; + default: + // Just in case, avoid sticking + m_axis.Reset(); + break; + } + + return bHandled; +} + +// --- CThrottle --------------------------------------------------------------- + +CThrottle::CThrottle(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap) + : CAxisFeature(name, handler, buttonMap) +{ +} + +bool CThrottle::OnAnalogMotion(const CDriverPrimitive& source, float magnitude) +{ + THROTTLE_DIRECTION direction = THROTTLE_DIRECTION::NONE; + + std::vector<THROTTLE_DIRECTION> dirs = { + THROTTLE_DIRECTION::UP, + THROTTLE_DIRECTION::DOWN, + }; + + CDriverPrimitive primitive; + for (auto dir : dirs) + { + if (m_buttonMap->GetThrottle(m_name, dir, primitive) && primitive == source) + { + direction = dir; + break; + } + } + + // Feature must accept input to be considered handled + bool bHandled = AcceptsInput(magnitude > 0.0f); + + switch (direction) + { + case THROTTLE_DIRECTION::UP: + m_axis.SetPositiveDistance(magnitude); + break; + case THROTTLE_DIRECTION::DOWN: + m_axis.SetNegativeDistance(magnitude); + break; + default: + // Just in case, avoid sticking + m_axis.Reset(); + break; + } + + return bHandled; +} + +// --- CAnalogStick ------------------------------------------------------------ + +CAnalogStick::CAnalogStick(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap) + : CJoystickFeature(name, handler, buttonMap), m_vertState(0.0f), m_horizState(0.0f) +{ +} + +bool CAnalogStick::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) +{ + return OnAnalogMotion(source, bPressed ? 1.0f : 0.0f); +} + +bool CAnalogStick::OnAnalogMotion(const CDriverPrimitive& source, float magnitude) +{ + ANALOG_STICK_DIRECTION direction = ANALOG_STICK_DIRECTION::NONE; + + std::vector<ANALOG_STICK_DIRECTION> dirs = { + ANALOG_STICK_DIRECTION::UP, + ANALOG_STICK_DIRECTION::DOWN, + ANALOG_STICK_DIRECTION::RIGHT, + ANALOG_STICK_DIRECTION::LEFT, + }; + + CDriverPrimitive primitive; + for (auto dir : dirs) + { + if (m_buttonMap->GetAnalogStick(m_name, dir, primitive) && primitive == source) + { + direction = dir; + break; + } + } + + // Feature must accept input to be considered handled + bool bHandled = AcceptsInput(magnitude > 0.0f); + + switch (direction) + { + case ANALOG_STICK_DIRECTION::UP: + m_vertAxis.SetPositiveDistance(magnitude); + break; + case ANALOG_STICK_DIRECTION::DOWN: + m_vertAxis.SetNegativeDistance(magnitude); + break; + case ANALOG_STICK_DIRECTION::RIGHT: + m_horizAxis.SetPositiveDistance(magnitude); + break; + case ANALOG_STICK_DIRECTION::LEFT: + m_horizAxis.SetNegativeDistance(magnitude); + break; + default: + // Just in case, avoid sticking + m_vertAxis.Reset(); + m_horizAxis.Reset(); + break; + } + + return bHandled; +} + +void CAnalogStick::ProcessMotions(void) +{ + const float newVertState = m_vertAxis.GetPosition(); + const float newHorizState = m_horizAxis.GetPosition(); + + const bool bActivated = (newVertState != 0.0f || newHorizState != 0.0f); + + if (!AcceptsInput(bActivated)) + return; + + const bool bWasActivated = (m_vertState != 0.0f || m_horizState != 0.0f); + + if (bActivated ^ bWasActivated) + { + CLog::Log(LOGDEBUG, "Feature [ {} ] on {} {}", m_name, m_handler->ControllerID(), + bActivated ? "activated" : "deactivated"); + } + + if (bActivated || bWasActivated) + { + m_vertState = newVertState; + m_horizState = newHorizState; + + unsigned int motionTimeMs = 0; + + if (bActivated) + { + if (!InMotion()) + StartMotion(); + else + motionTimeMs = MotionTimeMs(); + } + else + { + ResetMotion(); + } + + m_handler->OnAnalogStickMotion(m_name, newHorizState, newVertState, motionTimeMs); + } +} + +// --- CAccelerometer ---------------------------------------------------------- + +CAccelerometer::CAccelerometer(const FeatureName& name, + IInputHandler* handler, + IButtonMap* buttonMap) + : CJoystickFeature(name, handler, buttonMap), + m_xAxisState(0.0f), + m_yAxisState(0.0f), + m_zAxisState(0.0f) +{ +} + +bool CAccelerometer::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) +{ + return OnAnalogMotion(source, bPressed ? 1.0f : 0.0f); +} + +bool CAccelerometer::OnAnalogMotion(const CDriverPrimitive& source, float magnitude) +{ + // Feature must accept input to be considered handled + bool bHandled = AcceptsInput(true); + + CDriverPrimitive positiveX; + CDriverPrimitive positiveY; + CDriverPrimitive positiveZ; + + m_buttonMap->GetAccelerometer(m_name, positiveX, positiveY, positiveZ); + + if (source == positiveX) + { + m_xAxis.SetPositiveDistance(magnitude); + } + else if (source == positiveY) + { + m_yAxis.SetPositiveDistance(magnitude); + } + else if (source == positiveZ) + { + m_zAxis.SetPositiveDistance(magnitude); + } + else + { + // Just in case, avoid sticking + m_xAxis.Reset(); + m_xAxis.Reset(); + m_yAxis.Reset(); + } + + return bHandled; +} + +void CAccelerometer::ProcessMotions(void) +{ + const float newXAxis = m_xAxis.GetPosition(); + const float newYAxis = m_yAxis.GetPosition(); + const float newZAxis = m_zAxis.GetPosition(); + + const bool bActivated = (newXAxis != 0.0f || newYAxis != 0.0f || newZAxis != 0.0f); + + if (!AcceptsInput(bActivated)) + return; + + const bool bWasActivated = (m_xAxisState != 0.0f || m_yAxisState != 0.0f || m_zAxisState != 0.0f); + + if (bActivated || bWasActivated) + { + m_xAxisState = newXAxis; + m_yAxisState = newYAxis; + m_zAxisState = newZAxis; + m_handler->OnAccelerometerMotion(m_name, newXAxis, newYAxis, newZAxis); + } +} diff --git a/xbmc/input/joysticks/generic/FeatureHandling.h b/xbmc/input/joysticks/generic/FeatureHandling.h new file mode 100644 index 0000000..1a42d5c --- /dev/null +++ b/xbmc/input/joysticks/generic/FeatureHandling.h @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +#include <chrono> +#include <memory> + +namespace KODI +{ +namespace JOYSTICK +{ +class CDriverPrimitive; +class IInputHandler; +class IButtonMap; + +class CJoystickFeature; +using FeaturePtr = std::shared_ptr<CJoystickFeature>; + +/*! + * \ingroup joystick + * \brief Base class for joystick features + * + * See list of feature types in JoystickTypes.h. + */ +class CJoystickFeature +{ +public: + CJoystickFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + virtual ~CJoystickFeature() = default; + + /*! + * \brief A digital motion has occurred + * + * \param source The source of the motion. Must be digital (button or hat) + * \param bPressed True for press motion, false for release motion + * + * \return true if the motion was handled, false otherwise + */ + virtual bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) = 0; + + /*! + * \brief An analog motion has occurred + * + * \param source The source of the motion. Must be a semiaxis + * \param magnitude The magnitude of the press or motion in the interval [0.0, 1.0] + * + * For semiaxes, the magnitude is the force or travel distance in the + * direction of the semiaxis. If the value is in the opposite direction, + * the magnitude is 0.0. + * + * For example, if the analog stick goes left, the negative semiaxis will + * have a value of 1.0 and the positive semiaxis will have a value of 0.0. + */ + virtual bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) = 0; + + /*! + * \brief Process the motions that have occurred since the last invocation + * + * This allows features with motion on multiple driver primitives to call + * their handler once all driver primitives are accounted for. + */ + virtual void ProcessMotions(void) = 0; + + /*! + * \brief Check if the input handler is accepting input + * + * \param bActivation True if the motion is activating (true or positive), + * false if the motion is deactivating (false or zero) + * + * \return True if input should be sent to the input handler, false otherwise + */ + bool AcceptsInput(bool bActivation); + +protected: + /*! + * \brief Reset motion timer + */ + void ResetMotion(); + + /*! + * \brief Start the motion timer + */ + void StartMotion(); + + /*! + * \brief Check if the feature is in motion + */ + bool InMotion() const; + + /*! + * \brief Get the time for which the feature has been in motion + */ + unsigned int MotionTimeMs() const; + + const FeatureName m_name; + IInputHandler* const m_handler; + IButtonMap* const m_buttonMap; + const bool m_bEnabled; + +private: + std::chrono::time_point<std::chrono::steady_clock> m_motionStartTimeMs; +}; + +class CScalarFeature : public CJoystickFeature +{ +public: + CScalarFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + ~CScalarFeature() override = default; + + // implementation of CJoystickFeature + bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override; + bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override; + void ProcessMotions() override; + +private: + bool OnDigitalMotion(bool bPressed); + bool OnAnalogMotion(float magnitude); + + void ProcessDigitalMotion(); + void ProcessAnalogMotion(); + + // State variables + INPUT_TYPE m_inputType = INPUT_TYPE::UNKNOWN; + bool m_bDigitalState; + bool m_bInitialPressHandled = false; + + // Analog state variables + float m_analogState; // The current magnitude + float m_bActivated; // Set to true when first activated (magnitude > 0.0) + bool m_bDiscrete; // Set to false when a non-discrete axis is detected +}; + +/*! + * \ingroup joystick + * \brief Axis of a feature (analog stick, accelerometer, etc) + * + * Axes are composed of two driver primitives, one for the positive semiaxis + * and one for the negative semiaxis. + * + * This effectively means that an axis is two-dimensional, with each dimension + * either: + * + * - a digital value (0.0 or 1.0) + * - an analog value (continuous in the interval [0.0, 1.0]) + */ +class CFeatureAxis +{ +public: + CFeatureAxis(void) { Reset(); } + + /*! + * \brief Set value of positive axis + */ + void SetPositiveDistance(float distance) { m_positiveDistance = distance; } + + /*! + * \brief Set value of negative axis + */ + void SetNegativeDistance(float distance) { m_negativeDistance = distance; } + + /*! + * \brief Get the final value of this axis. + * + * This axis is two-dimensional, so we need to compress these into a single + * dimension. This is done by subtracting the negative from the positive. + * Some examples: + * + * Positive axis: 1.0 (User presses right or analog stick moves right) + * Negative axis: 0.0 + * ------------------- + * Pos - Neg: 1.0 (Emulated analog stick moves right) + * + * + * Positive axis: 0.0 + * Negative axis: 1.0 (User presses left or analog stick moves left) + * ------------------- + * Pos - Neg: -1.0 (Emulated analog stick moves left) + * + * + * Positive axis: 1.0 (User presses both buttons) + * Negative axis: 1.0 + * ------------------- + * Pos - Neg: 0.0 (Emulated analog stick is centered) + * + */ + float GetPosition(void) const { return m_positiveDistance - m_negativeDistance; } + + /*! + * \brief Reset both positive and negative values to zero + */ + void Reset(void) { m_positiveDistance = m_negativeDistance = 0.0f; } + +protected: + float m_positiveDistance; + float m_negativeDistance; +}; + +class CAxisFeature : public CJoystickFeature +{ +public: + CAxisFeature(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + ~CAxisFeature() override = default; + + // partial implementation of CJoystickFeature + bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override; + void ProcessMotions() override; + +protected: + CFeatureAxis m_axis; + + float m_state; +}; + +class CWheel : public CAxisFeature +{ +public: + CWheel(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + ~CWheel() override = default; + + // partial implementation of CJoystickFeature + bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override; +}; + +class CThrottle : public CAxisFeature +{ +public: + CThrottle(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + ~CThrottle() override = default; + + // partial implementation of CJoystickFeature + bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override; +}; + +class CAnalogStick : public CJoystickFeature +{ +public: + CAnalogStick(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + ~CAnalogStick() override = default; + + // implementation of CJoystickFeature + bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override; + bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override; + void ProcessMotions() override; + +protected: + CFeatureAxis m_vertAxis; + CFeatureAxis m_horizAxis; + + float m_vertState; + float m_horizState; +}; + +class CAccelerometer : public CJoystickFeature +{ +public: + CAccelerometer(const FeatureName& name, IInputHandler* handler, IButtonMap* buttonMap); + ~CAccelerometer() override = default; + + // implementation of CJoystickFeature + bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) override; + bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude) override; + void ProcessMotions() override; + +protected: + CFeatureAxis m_xAxis; + CFeatureAxis m_yAxis; + CFeatureAxis m_zAxis; + + float m_xAxisState; + float m_yAxisState; + float m_zAxisState; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/generic/InputHandling.cpp b/xbmc/input/joysticks/generic/InputHandling.cpp new file mode 100644 index 0000000..f6cae6d --- /dev/null +++ b/xbmc/input/joysticks/generic/InputHandling.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2014-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 "InputHandling.h" + +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickUtils.h" +#include "input/joysticks/dialogs/GUIDialogNewJoystick.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "input/joysticks/interfaces/IInputHandler.h" +#include "utils/log.h" + +#include <array> +#include <cmath> +#include <tuple> + +using namespace KODI; +using namespace JOYSTICK; + +CGUIDialogNewJoystick* const CInputHandling::m_dialog = new CGUIDialogNewJoystick; + +CInputHandling::CInputHandling(IInputHandler* handler, IButtonMap* buttonMap) + : m_handler(handler), m_buttonMap(buttonMap) +{ +} + +CInputHandling::~CInputHandling(void) = default; + +bool CInputHandling::OnButtonMotion(unsigned int buttonIndex, bool bPressed) +{ + return OnDigitalMotion(CDriverPrimitive(PRIMITIVE_TYPE::BUTTON, buttonIndex), bPressed); +} + +bool CInputHandling::OnHatMotion(unsigned int hatIndex, HAT_STATE state) +{ + bool bHandled = false; + + bHandled |= + OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::UP), state & HAT_DIRECTION::UP); + bHandled |= OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::RIGHT), + state & HAT_DIRECTION::RIGHT); + bHandled |= + OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::DOWN), state & HAT_DIRECTION::DOWN); + bHandled |= + OnDigitalMotion(CDriverPrimitive(hatIndex, HAT_DIRECTION::LEFT), state & HAT_DIRECTION::LEFT); + + return bHandled; +} + +bool CInputHandling::OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) +{ + bool bHandled = false; + + if (center != 0) + { + float translatedPostion = std::min((position - center) / range, 1.0f); + + // Calculate the direction the trigger travels from the center point + SEMIAXIS_DIRECTION dir; + if (center > 0) + dir = SEMIAXIS_DIRECTION::NEGATIVE; + else + dir = SEMIAXIS_DIRECTION::POSITIVE; + + CDriverPrimitive offsetSemiaxis(axisIndex, center, dir, range); + + bHandled = OnAnalogMotion(offsetSemiaxis, translatedPostion); + } + else + { + CDriverPrimitive positiveSemiaxis(axisIndex, 0, SEMIAXIS_DIRECTION::POSITIVE, 1); + CDriverPrimitive negativeSemiaxis(axisIndex, 0, SEMIAXIS_DIRECTION::NEGATIVE, 1); + + bHandled |= OnAnalogMotion(positiveSemiaxis, position > 0.0f ? position : 0.0f); + bHandled |= OnAnalogMotion(negativeSemiaxis, position < 0.0f ? -position : 0.0f); + } + + return bHandled; +} + +void CInputHandling::OnInputFrame(void) +{ + // Handle driver input + for (auto& it : m_features) + it.second->ProcessMotions(); + + // Handle higher-level controller input + m_handler->OnInputFrame(); +} + +bool CInputHandling::OnDigitalMotion(const CDriverPrimitive& source, bool bPressed) +{ + bool bHandled = false; + + FeatureName featureName; + if (m_buttonMap->GetFeature(source, featureName)) + { + auto it = m_features.find(featureName); + if (it == m_features.end()) + { + FeaturePtr feature(CreateFeature(featureName)); + if (feature) + std::tie(it, std::ignore) = m_features.insert({featureName, std::move(feature)}); + } + + if (it != m_features.end()) + bHandled = it->second->OnDigitalMotion(source, bPressed); + } + else if (bPressed) + { + // If button didn't resolve to a feature, check if the button map is empty + // and ask the user if they would like to start mapping the controller + if (m_buttonMap->IsEmpty()) + { + CLog::Log(LOGDEBUG, "Empty button map detected for {}", m_buttonMap->ControllerID()); + m_dialog->ShowAsync(); + } + } + + return bHandled; +} + +bool CInputHandling::OnAnalogMotion(const CDriverPrimitive& source, float magnitude) +{ + bool bHandled = false; + + FeatureName featureName; + if (m_buttonMap->GetFeature(source, featureName)) + { + auto it = m_features.find(featureName); + if (it == m_features.end()) + { + FeaturePtr feature(CreateFeature(featureName)); + if (feature) + std::tie(it, std::ignore) = m_features.insert({featureName, std::move(feature)}); + } + + if (it != m_features.end()) + bHandled = it->second->OnAnalogMotion(source, magnitude); + } + + return bHandled; +} + +CJoystickFeature* CInputHandling::CreateFeature(const FeatureName& featureName) +{ + CJoystickFeature* feature = nullptr; + + switch (m_buttonMap->GetFeatureType(featureName)) + { + case FEATURE_TYPE::SCALAR: + { + feature = new CScalarFeature(featureName, m_handler, m_buttonMap); + break; + } + case FEATURE_TYPE::ANALOG_STICK: + { + feature = new CAnalogStick(featureName, m_handler, m_buttonMap); + break; + } + case FEATURE_TYPE::ACCELEROMETER: + { + feature = new CAccelerometer(featureName, m_handler, m_buttonMap); + break; + } + default: + break; + } + + return feature; +} diff --git a/xbmc/input/joysticks/generic/InputHandling.h b/xbmc/input/joysticks/generic/InputHandling.h new file mode 100644 index 0000000..772d9d1 --- /dev/null +++ b/xbmc/input/joysticks/generic/InputHandling.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "FeatureHandling.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IDriverHandler.h" + +#include <map> + +namespace KODI +{ +namespace JOYSTICK +{ +class CDriverPrimitive; +class CGUIDialogNewJoystick; +class IInputHandler; +class IButtonMap; + +/*! + * \ingroup joystick + * \brief Class to translate input from the driver into higher-level features + * + * Raw driver input arrives for three elements: buttons, hats and axes. When + * driver input is handled by this class, it translates the raw driver + * elements into physical joystick features, such as buttons, analog sticks, + * etc. + * + * A button map is used to translate driver primitives to controller features. + * The button map has been abstracted away behind the IButtonMap + * interface so that it can be provided by an add-on. + */ +class CInputHandling : public IDriverHandler +{ +public: + CInputHandling(IInputHandler* handler, IButtonMap* buttonMap); + + ~CInputHandling() override; + + // implementation of IDriverHandler + bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) override; + bool OnHatMotion(unsigned int hatIndex, HAT_STATE state) override; + bool OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) override; + void OnInputFrame() override; + +private: + bool OnDigitalMotion(const CDriverPrimitive& source, bool bPressed); + bool OnAnalogMotion(const CDriverPrimitive& source, float magnitude); + + CJoystickFeature* CreateFeature(const FeatureName& featureName); + + IInputHandler* const m_handler; + IButtonMap* const m_buttonMap; + + std::map<FeatureName, FeaturePtr> m_features; + + static CGUIDialogNewJoystick* const m_dialog; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IButtonMap.h b/xbmc/input/joysticks/interfaces/IButtonMap.h new file mode 100644 index 0000000..0b4b7d5 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IButtonMap.h @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/JoystickTypes.h" + +#include <string> +#include <vector> + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Button map interface to translate between the driver's raw + * button/hat/axis elements and physical joystick features. + * + * \sa IButtonMapper + */ +class IButtonMap +{ +public: + virtual ~IButtonMap() = default; + + /*! + * \brief The add-on ID of the game controller associated with this button map + * + * The controller ID provided by the implementation serves as the context + * for the feature names below. + * + * \return The ID of this button map's game controller add-on + */ + virtual std::string ControllerID(void) const = 0; + + /*! + * \brief The Location of the peripheral associated with this button map + * + * \return The peripheral's location + */ + virtual std::string Location(void) const = 0; + + /*! + * \brief Load the button map into memory + * + * \return True if button map is ready to start translating buttons, false otherwise + */ + virtual bool Load(void) = 0; + + /*! + * \brief Reset the button map to its defaults, or clear button map if no defaults + */ + virtual void Reset(void) = 0; + + /*! + * \brief Check if the button map is empty + * + * \return True if the button map is empty, false if it has features + */ + virtual bool IsEmpty(void) const = 0; + + /*! + * \brief Get the ID of the controller profile that best represents the + * appearance of the peripheral + * + * \return The controller ID, or empty if the appearance is unknown + */ + virtual std::string GetAppearance() const = 0; + + /*! + * \brief Set the ID of the controller that best represents the appearance + * of the peripheral + * + * \param controllerId The controller ID, or empty to unset the appearance + * + * \return True if the appearance was set, false on error + */ + virtual bool SetAppearance(const std::string& controllerId) const = 0; + + /*! + * \brief Get the feature associated with a driver primitive + * + * Multiple primitives can be mapped to the same feature. For example, + * analog sticks use one primitive for each direction. + * + * \param primitive The driver primitive + * \param feature The name of the resolved joystick feature, or + * invalid if false is returned + * + * \return True if the driver primitive is associated with a feature, false otherwise + */ + virtual bool GetFeature(const CDriverPrimitive& primitive, FeatureName& feature) = 0; + + /*! + * \brief Get the type of the feature for the given name + * + * \param feature The feature to look up + * + * \return The feature's type + */ + virtual FEATURE_TYPE GetFeatureType(const FeatureName& feature) = 0; + + /*! + * \brief Get the driver primitive for a scalar feature + * + * When a feature can be represented by a single driver primitive, it is + * called a scalar feature. + * + * - This includes buttons and triggers, because they can be mapped to a + * single button/hat/semiaxis + * + * - This does not include analog sticks, because they require two axes + * and four driver primitives (one for each semiaxis) + * + * \param feature Must be a scalar feature (a feature that only + * requires a single driver primitive) + * \param primitive The resolved driver primitive + * + * \return True if the feature resolved to a driver primitive, false if the + * feature didn't resolve or isn't a scalar feature + */ + virtual bool GetScalar(const FeatureName& feature, CDriverPrimitive& primitive) = 0; + + /*! + * \brief Add or update a scalar feature + * + * \param feature Must be a scalar feature + * \param primitive The feature's driver primitive + * + * \return True if the feature was updated, false if the feature is + * unchanged or failure occurs + */ + virtual void AddScalar(const FeatureName& feature, const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Get an analog stick direction from the button map + * + * \param feature Must be an analog stick or this will return false + * \param direction The direction whose primitive is to be retrieved + * \param[out] primitive The primitive mapped to the specified direction + * + * \return True if the feature and direction resolved to a driver primitive + */ + virtual bool GetAnalogStick(const FeatureName& feature, + ANALOG_STICK_DIRECTION direction, + CDriverPrimitive& primitive) = 0; + + /*! + * \brief Add or update an analog stick direction + * + * \param feature Must be an analog stick or this will return false + * \param direction The direction being mapped + * \param primitive The driver primitive for the specified analog stick and direction + * + * \return True if the analog stick was updated, false otherwise + */ + virtual void AddAnalogStick(const FeatureName& feature, + ANALOG_STICK_DIRECTION direction, + const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Get a relative pointer direction from the button map + * + * \param feature Must be a relative pointer stick or this will return false + * \param direction The direction whose primitive is to be retrieved + * \param[out] primitive The primitive mapped to the specified direction + * + * \return True if the feature and direction resolved to a driver primitive + */ + virtual bool GetRelativePointer(const FeatureName& feature, + RELATIVE_POINTER_DIRECTION direction, + CDriverPrimitive& primitive) = 0; + + /*! + * \brief Add or update a relative pointer direction + * + * \param feature Must be a relative pointer or this will return false + * \param direction The direction being mapped + * \param primitive The driver primitive for the specified analog stick and direction + * + * \return True if the analog stick was updated, false otherwise + */ + virtual void AddRelativePointer(const FeatureName& feature, + RELATIVE_POINTER_DIRECTION direction, + const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Get an accelerometer from the button map + * + * \param feature Must be an accelerometer or this will return false + * \param positiveX The semiaxis mapped to the positive X direction (possibly unknown) + * \param positiveY The semiaxis mapped to the positive Y direction (possibly unknown) + * \param positiveZ The semiaxis mapped to the positive Z direction (possibly unknown) + * + * \return True if the feature resolved to an accelerometer with at least 1 known axis + */ + virtual bool GetAccelerometer(const FeatureName& feature, + CDriverPrimitive& positiveX, + CDriverPrimitive& positiveY, + CDriverPrimitive& positiveZ) = 0; + + /*! + * \brief Get or update an accelerometer + * + * \param feature Must be an accelerometer or this will return false + * \param positiveX The semiaxis corresponding to the positive X direction + * \param positiveY The semiaxis corresponding to the positive Y direction + * \param positiveZ The semiaxis corresponding to the positive Z direction + * + * The driver primitives must be mapped to a semiaxis or this function will fail. + * + * \return True if the accelerometer was updated, false if unchanged or failure occurred + */ + virtual void AddAccelerometer(const FeatureName& feature, + const CDriverPrimitive& positiveX, + const CDriverPrimitive& positiveY, + const CDriverPrimitive& positiveZ) = 0; + + /*! + * \brief Get a wheel direction from the button map + * + * \param feature Must be a wheel or this will return false + * \param direction The direction whose primitive is to be retrieved + * \param[out] primitive The primitive mapped to the specified direction + * + * \return True if the feature and direction resolved to a driver primitive + */ + virtual bool GetWheel(const FeatureName& feature, + WHEEL_DIRECTION direction, + CDriverPrimitive& primitive) = 0; + + /*! + * \brief Add or update a wheel direction + * + * \param feature Must be a wheel or this will return false + * \param direction The direction being mapped + * \param primitive The driver primitive for the specified analog stick and direction + * + * \return True if the analog stick was updated, false otherwise + */ + virtual void AddWheel(const FeatureName& feature, + WHEEL_DIRECTION direction, + const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Get a throttle direction from the button map + * + * \param feature Must be a throttle or this will return false + * \param direction The direction whose primitive is to be retrieved + * \param[out] primitive The primitive mapped to the specified direction + * + * \return True if the feature and direction resolved to a driver primitive + */ + virtual bool GetThrottle(const FeatureName& feature, + THROTTLE_DIRECTION direction, + CDriverPrimitive& primitive) = 0; + + /*! + * \brief Add or update a throttle direction + * + * \param feature Must be a throttle or this will return false + * \param direction The direction being mapped + * \param primitive The driver primitive for the specified analog stick and direction + * + * \return True if the analog stick was updated, false otherwise + */ + virtual void AddThrottle(const FeatureName& feature, + THROTTLE_DIRECTION direction, + const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Get the driver primitive for a keyboard key + * + * \param feature Must be a key + * \param primitive The resolved driver primitive + * + * \return True if the feature resolved to a driver primitive, false if the + * feature didn't resolve or isn't a scalar feature + */ + virtual bool GetKey(const FeatureName& feature, CDriverPrimitive& primitive) = 0; + + /*! + * \brief Add or update a key + * + * \param feature Must be a key + * \param primitive The feature's driver primitive + * + * \return True if the feature was updated, false if the feature is + * unchanged or failure occurs + */ + virtual void AddKey(const FeatureName& feature, const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Set a list of driver primitives to be ignored + * + * This is necessary to prevent features from interfering with the button + * mapping process. This includes accelerometers, as well as PS4 triggers + * which send both a button press and an analog value. + * + * \param primitives The driver primitives to be ignored + */ + virtual void SetIgnoredPrimitives(const std::vector<CDriverPrimitive>& primitives) = 0; + + /*! + * \brief Check if a primitive is in the list of primitives to be ignored + * + * \param primitive The primitive to check + * + * \return True if the primitive should be ignored in the mapping process + */ + virtual bool IsIgnored(const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Get the properties of an axis + * + * \param axisIndex The index of the axis to check + * \param center[out] The center, if known + * \param range[out] The range, if known + * + * \return True if the properties are known, false otherwise + */ + virtual bool GetAxisProperties(unsigned int axisIndex, int& center, unsigned int& range) = 0; + + /*! + * \brief Save the button map + */ + virtual void SaveButtonMap() = 0; + + /*! + * \brief Revert changes to the button map since the last time it was loaded + * or committed to disk + */ + virtual void RevertButtonMap() = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IButtonMapCallback.h b/xbmc/input/joysticks/interfaces/IButtonMapCallback.h new file mode 100644 index 0000000..40744f8 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IButtonMapCallback.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \brief Interface for handling button maps + */ +class IButtonMapCallback +{ +public: + virtual ~IButtonMapCallback() = default; + + /*! + * \brief Save the button map + */ + virtual void SaveButtonMap() = 0; + + /*! + * \brief Clear the list of ignored driver primitives + * + * Called if the user begins capturing primitives to be ignored, and + * no primitives are captured before the dialog is accepted by the user. + * + * In this case, the button mapper won't have been given access to the + * button map, so a callback is needed to indicate that no primitives were + * captured and the user accepted this. + */ + virtual void ResetIgnoredPrimitives() = 0; + + /*! + * \brief Revert changes to the button map since the last time it was loaded + * or committed to disk + */ + virtual void RevertButtonMap() = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IButtonMapper.h b/xbmc/input/joysticks/interfaces/IButtonMapper.h new file mode 100644 index 0000000..d0784df --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IButtonMapper.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +#include <map> +#include <string> + +class IKeymap; + +namespace KODI +{ +namespace JOYSTICK +{ +class CDriverPrimitive; +class IButtonMap; +class IButtonMapCallback; + +/*! + * \ingroup joystick + * \brief Button mapper interface to assign the driver's raw button/hat/axis + * elements to physical joystick features using a provided button map. + * + * \sa IButtonMap + */ +class IButtonMapper +{ +public: + IButtonMapper() = default; + + virtual ~IButtonMapper() = default; + + /*! + * \brief The add-on ID of the game controller associated with this button mapper + * + * \return The ID of the add-on extending kodi.game.controller + */ + virtual std::string ControllerID(void) const = 0; + + /*! + * \brief Return true if the button mapper wants a cooldown between button + * mapping commands + * + * \return True to only send button mapping commands that occur after a small + * timeout from the previous command. + */ + virtual bool NeedsCooldown(void) const = 0; + + /*! + * \brief Return true if the button mapper accepts primitives of the given type + * + * \param type The primitive type + * + * \return True if the button mapper can map the primitive type, false otherwise + */ + virtual bool AcceptsPrimitive(PRIMITIVE_TYPE type) const = 0; + + /*! + * \brief Handle button/hat press or axis threshold + * + * \param buttonMap The button map being manipulated + * \param keymap An interface capable of translating features to Kodi actions + * \param primitive The driver primitive + * + * Called in the same thread as \ref IButtonMapper::OnFrame. + * + * \return True if driver primitive was mapped to a feature + */ + virtual bool MapPrimitive(IButtonMap* buttonMap, + IKeymap* keyMap, + const CDriverPrimitive& primitive) = 0; + + /*! + * \brief Called once per event frame to notify the implementation of motion status + * + * \param buttonMap The button map passed to MapPrimitive() (shall not be modified) + * \param bMotion True if a previously-mapped axis is still in motion + * + * This allows the implementer to wait for an axis to be centered before + * allowing it to be used as Kodi input. + * + * If mapping finishes on an axis, then the axis will still be pressed and + * sending input every frame when the mapping ends. For example, when the + * right analog stick is the last feature to be mapped, it is still pressed + * when mapping ends and immediately sends Volume Down actions. + * + * The fix is to allow implementers to wait until all axes are motionless + * before detaching themselves. + * + * Called in the same thread as \ref IButtonMapper::MapPrimitive. + */ + virtual void OnEventFrame(const IButtonMap* buttonMap, bool bMotion) = 0; + + /*! + * \brief Called when an axis has been detected after mapping began + * + * \param axisIndex The index of the axis being discovered + * + * Some joystick drivers don't report an initial value for analog axes. + * + * Called in the same thread as \ref IButtonMapper::MapPrimitive. + */ + virtual void OnLateAxis(const IButtonMap* buttonMap, unsigned int axisIndex) = 0; + + // Button map callback interface + void SetButtonMapCallback(const std::string& deviceLocation, IButtonMapCallback* callback) + { + m_callbacks[deviceLocation] = callback; + } + void ResetButtonMapCallbacks(void) { m_callbacks.clear(); } + std::map<std::string, IButtonMapCallback*>& ButtonMapCallbacks(void) { return m_callbacks; } + +private: + std::map<std::string, IButtonMapCallback*> m_callbacks; // Device location -> callback +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IButtonSequence.h b/xbmc/input/joysticks/interfaces/IButtonSequence.h new file mode 100644 index 0000000..5c642e6 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IButtonSequence.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonSequence +{ +public: + virtual ~IButtonSequence() = default; + + virtual bool OnButtonPress(const FeatureName& feature) = 0; + + /*! + * \brief Returns true if a sequence is being captured to prevent input + * from falling through to the application + */ + virtual bool IsCapturing() = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IDriverHandler.h b/xbmc/input/joysticks/interfaces/IDriverHandler.h new file mode 100644 index 0000000..48e4da5 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IDriverHandler.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \brief Interface defining methods to handle joystick events for raw driver + * elements (buttons, hats, axes) + */ +class IDriverHandler +{ +public: + virtual ~IDriverHandler() = default; + + /*! + * \brief Handle button motion + * + * \param buttonIndex The index of the button as reported by the driver + * \param bPressed true for press motion, false for release motion + * + * \return True if a press was handled, false otherwise + */ + virtual bool OnButtonMotion(unsigned int buttonIndex, bool bPressed) = 0; + + /*! + * \brief Handle hat motion + * + * \param hatIndex The index of the hat as reported by the driver + * \param state The direction the hat is now being pressed + * + * \return True if the new direction was handled, false otherwise + */ + virtual bool OnHatMotion(unsigned int hatIndex, HAT_STATE state) = 0; + + /*! + * \brief Handle axis motion + * + * If a joystick feature requires multiple axes (analog sticks, accelerometers), + * they can be buffered for later processing. + * + * \param axisIndex The index of the axis as reported by the driver + * \param position The position of the axis in the closed interval [-1.0, 1.0] + * \param center The center point of the axis (either -1, 0 or 1) + * \param range The maximum distance the axis can move (either 1 or 2) + * + * \return True if the motion was handled, false otherwise + */ + virtual bool OnAxisMotion(unsigned int axisIndex, + float position, + int center, + unsigned int range) = 0; + + /*! + * \brief Handle buffered input motion for features that require multiple axes + * + * OnInputFrame() is called at the end of the frame when all axis motions + * have been reported. This has several uses, including: + * + * - Combining multiple axes into a single analog stick or accelerometer event + * - Imitating an analog feature with a digital button so that events can be + * dispatched every frame. + */ + virtual void OnInputFrame(void) = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IDriverReceiver.h b/xbmc/input/joysticks/interfaces/IDriverReceiver.h new file mode 100644 index 0000000..2a4ff6c --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IDriverReceiver.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Interface for sending input events to joystick drivers + */ +class IDriverReceiver +{ +public: + virtual ~IDriverReceiver() = default; + + /*! + * \brief Set the value of a rumble motor + * + * \param motorIndex The driver index of the motor to rumble + * \param magnitude The motor's new magnitude of vibration in the closed interval [0, 1] + * + * \return True if the event was handled otherwise false + */ + virtual bool SetMotorState(unsigned int motorIndex, float magnitude) = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IInputHandler.h b/xbmc/input/joysticks/interfaces/IInputHandler.h new file mode 100644 index 0000000..2ddbf5c --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IInputHandler.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +#include <string> + +namespace KODI +{ +namespace JOYSTICK +{ +class IInputReceiver; + +/*! + * \ingroup joystick + * \brief Interface for handling input events for game controllers + */ +class IInputHandler +{ +public: + virtual ~IInputHandler() = default; + + /*! + * \brief The add-on ID of the game controller associated with this input handler + * + * \return The ID of the add-on extending kodi.game.controller + */ + virtual std::string ControllerID(void) const = 0; + + /*! + * \brief Return true if the input handler accepts the given feature + * + * \param feature A feature belonging to the controller specified by ControllerID() + * + * \return True if the feature is used for input, false otherwise + */ + virtual bool HasFeature(const FeatureName& feature) const = 0; + + /*! + * \brief Return true if the input handler is currently accepting input for the + * given feature + * + * \param feature A feature belonging to the controller specified by ControllerID() + * + * \return True if the feature is currently accepting input, false otherwise + * + * This does not prevent the input events from being called, but can return + * false to indicate that input wasn't handled for the specified feature. + */ + virtual bool AcceptsInput(const FeatureName& feature) const = 0; + + /*! + * \brief A digital button has been pressed or released + * + * \param feature The feature being pressed + * \param bPressed True if pressed, false if released + * + * \return True if the event was handled otherwise false + */ + virtual bool OnButtonPress(const FeatureName& feature, bool bPressed) = 0; + + /*! + * \brief A digital button has been pressed for more than one event frame + * + * \param feature The feature being held + * \param holdTimeMs The time elapsed since the initial press (ms) + * + * If OnButtonPress() returns true for the initial press, then this callback + * is invoked on subsequent frames until the button is released. + */ + virtual void OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) = 0; + + /*! + * \brief An analog button (trigger or a pressure-sensitive button) has changed state + * + * \param feature The feature changing state + * \param magnitude The button pressure or trigger travel distance in the + * closed interval [0, 1] + * \param motionTimeMs The time elapsed since the magnitude was 0 + * + * \return True if the event was handled otherwise false + */ + virtual bool OnButtonMotion(const FeatureName& feature, + float magnitude, + unsigned int motionTimeMs) = 0; + + /*! + * \brief An analog stick has moved + * + * \param feature The analog stick being moved + * \param x The x coordinate in the closed interval [-1, 1] + * \param y The y coordinate in the closed interval [-1, 1] + * \param motionTimeMs The time elapsed since this analog stick was centered, + * or 0 if the analog stick is centered + * + * \return True if the event was handled otherwise false + */ + virtual bool OnAnalogStickMotion(const FeatureName& feature, + float x, + float y, + unsigned int motionTimeMs) = 0; + + /*! + * \brief An accelerometer's state has changed + * + * \param feature The accelerometer being accelerated + * \param x The x coordinate in the closed interval [-1, 1] + * \param y The y coordinate in the closed interval [-1, 1] + * \param z The z coordinate in the closed interval [-1, 1] + * + * \return True if the event was handled otherwise false + */ + virtual bool OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z) + { + return false; + } + + /*! + * \brief A wheel has changed state + * + * Left is negative position, right is positive position + * + * \param feature The wheel changing state + * \param position The position in the closed interval [-1, 1] + * \param motionTimeMs The time elapsed since the position was 0 + * + * \return True if the event was handled otherwise false + */ + virtual bool OnWheelMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) = 0; + + /*! + * \brief A throttle has changed state + * + * Up is positive position, down is negative position. + * + * \param feature The wheel changing state + * \param position The position in the closed interval [-1, 1] + * \param motionTimeMs The time elapsed since the position was 0 + * + * \return True if the event was handled otherwise false + */ + virtual bool OnThrottleMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) = 0; + + /*! + * \brief Called at the end of the frame that provided input + */ + virtual void OnInputFrame() = 0; + + // Input receiver interface + void SetInputReceiver(IInputReceiver* receiver) { m_receiver = receiver; } + void ResetInputReceiver(void) { m_receiver = nullptr; } + IInputReceiver* InputReceiver(void) { return m_receiver; } + +private: + IInputReceiver* m_receiver = nullptr; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IInputProvider.h b/xbmc/input/joysticks/interfaces/IInputProvider.h new file mode 100644 index 0000000..09a0366 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IInputProvider.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +namespace KODI +{ +namespace JOYSTICK +{ +class IInputHandler; + +/*! + * \ingroup joystick + * \brief Interface for classes that can provide input + */ +class IInputProvider +{ +public: + virtual ~IInputProvider() = default; + + /*! + * \brief Register a handler for the provided input + * + * \param handler The handler to receive input provided by this class + * \param bPromiscuous If true, receives all input (including handled input) + * in the background + */ + virtual void RegisterInputHandler(IInputHandler* handler, bool bPromiscuous) = 0; + + /*! + * \brief Unregister a handler + * + * \param handler The handler that was receiving input + */ + virtual void UnregisterInputHandler(IInputHandler* handler) = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IInputReceiver.h b/xbmc/input/joysticks/interfaces/IInputReceiver.h new file mode 100644 index 0000000..400da3f --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IInputReceiver.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Interface for sending input events to game controllers + */ +class IInputReceiver +{ +public: + virtual ~IInputReceiver() = default; + + /*! + * \brief Set the value of a rumble motor + * + * \param feature The name of the motor to rumble + * \param magnitude The motor's new magnitude of vibration in the closed interval [0, 1] + * + * \return True if the event was handled otherwise false + */ + virtual bool SetRumbleState(const FeatureName& feature, float magnitude) = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IKeyHandler.h b/xbmc/input/joysticks/interfaces/IKeyHandler.h new file mode 100644 index 0000000..30f49c5 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IKeyHandler.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015-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. + */ + +#pragma once + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Interface for handling keymap keys + * + * Keys can be mapped to analog actions (e.g. "AnalogSeekForward") or digital + * actions (e.g. "Up"). + */ +class IKeyHandler +{ +public: + virtual ~IKeyHandler() = default; + + /*! + * \brief Return true if the key is "pressed" (has a magnitude greater + * than 0.5) + * + * \return True if the key is "pressed", false otherwise + */ + virtual bool IsPressed() const = 0; + + /*! + * \brief A key mapped to a digital feature has been pressed or released + * + * \param bPressed true if the key's button/axis is activated, false if deactivated + * \param holdTimeMs The held time in ms for pressed buttons, or 0 for released + * + * \return True if the key is mapped to an action, false otherwise + */ + virtual bool OnDigitalMotion(bool bPressed, unsigned int holdTimeMs) = 0; + + /*! + * \brief Callback for keys mapped to analog features + * + * \param magnitude The amount of the analog action + * \param motionTimeMs The time since the magnitude was 0 + * + * \return True if the key is mapped to an action, false otherwise + */ + virtual bool OnAnalogMotion(float magnitude, unsigned int motionTimeMs) = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/interfaces/IKeymapHandler.h b/xbmc/input/joysticks/interfaces/IKeymapHandler.h new file mode 100644 index 0000000..f0af427 --- /dev/null +++ b/xbmc/input/joysticks/interfaces/IKeymapHandler.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include <set> +#include <string> + +namespace KODI +{ +namespace JOYSTICK +{ +/*! + * \ingroup joystick + * \brief Interface for a class working with a keymap + */ +class IKeymapHandler +{ +public: + virtual ~IKeymapHandler() = default; + + /*! + * \brief Get the pressed state of the given keys + * + * \param keyNames The key names + * + * \return True if all keys are pressed or no keys are given, false otherwise + */ + virtual bool HotkeysPressed(const std::set<std::string>& keyNames) const = 0; + + /*! + * \brief Get the key name of the last button pressed + * + * \return The key name of the last-pressed button, or empty if no button + * is pressed + */ + virtual std::string GetLastPressed() const = 0; + + /*! + * \brief Called when a key has emitted an action after bring pressed + * + * \param keyName the key name that emitted the action + */ + virtual void OnPress(const std::string& keyName) = 0; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/keymaps/CMakeLists.txt b/xbmc/input/joysticks/keymaps/CMakeLists.txt new file mode 100644 index 0000000..c854b36 --- /dev/null +++ b/xbmc/input/joysticks/keymaps/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES KeyHandler.cpp + KeymapHandler.cpp + KeymapHandling.cpp +) + +set(HEADERS KeyHandler.h + KeymapHandler.h + KeymapHandling.h +) + +core_add_library(input_joystick_keymaps) diff --git a/xbmc/input/joysticks/keymaps/KeyHandler.cpp b/xbmc/input/joysticks/keymaps/KeyHandler.cpp new file mode 100644 index 0000000..cd0f30a --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeyHandler.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2017-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 "KeyHandler.h" + +#include "input/IKeymap.h" +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "input/joysticks/JoystickUtils.h" +#include "input/joysticks/interfaces/IKeymapHandler.h" +#include "interfaces/IActionListener.h" + +#include <algorithm> +#include <assert.h> + +using namespace KODI; +using namespace JOYSTICK; + +#define DIGITAL_ANALOG_THRESHOLD 0.5f + +#define HOLD_TIMEOUT_MS 500 +#define REPEAT_TIMEOUT_MS 50 + +CKeyHandler::CKeyHandler(const std::string& keyName, + IActionListener* actionHandler, + const IKeymap* keymap, + IKeymapHandler* keymapHandler) + : m_keyName(keyName), + m_actionHandler(actionHandler), + m_keymap(keymap), + m_keymapHandler(keymapHandler) +{ + assert(m_actionHandler != nullptr); + assert(m_keymap != nullptr); + assert(m_keymapHandler != nullptr); + + Reset(); +} + +void CKeyHandler::Reset() +{ + m_bHeld = false; + m_magnitude = 0.0f; + m_holdStartTimeMs = 0; + m_lastHoldTimeMs = 0; + m_bActionSent = false; + m_lastActionMs = 0; + m_activeWindowId = -1; + m_lastAction = CAction(); +} + +bool CKeyHandler::OnDigitalMotion(bool bPressed, unsigned int holdTimeMs) +{ + return OnAnalogMotion(bPressed ? 1.0f : 0.0f, holdTimeMs); +} + +bool CKeyHandler::OnAnalogMotion(float magnitude, unsigned int motionTimeMs) +{ + // Don't send deactivation event more than once + if (m_magnitude == 0.0f && magnitude == 0.0f) + return false; + + // Get actions for the key + const auto& actionGroup = m_keymap->GetActions(m_keyName); + const int windowId = actionGroup.windowId; + const auto& actions = actionGroup.actions; + + // Calculate press state + const bool bPressed = IsPressed(magnitude); + const bool bJustPressed = bPressed && !m_bHeld; + + if (bJustPressed) + { + // Reset key if just pressed + Reset(); + + // Record hold start time if just pressed + m_holdStartTimeMs = motionTimeMs; + + // Record window ID + if (windowId >= 0) + m_activeWindowId = windowId; + } + + // Calculate holdtime relative to when magnitude crossed the threshold + unsigned int holdTimeMs = 0; + if (bPressed) + holdTimeMs = motionTimeMs - m_holdStartTimeMs; + + // Give priority to actions with hotkeys + std::vector<const KeymapAction*> actionsWithHotkeys; + + for (const auto& action : actions) + { + if (!action.hotkeys.empty()) + actionsWithHotkeys.emplace_back(&action); + } + + CAction dispatchAction = + ProcessActions(std::move(actionsWithHotkeys), windowId, magnitude, holdTimeMs); + + // If that failed, try again with all actions + if (dispatchAction.GetID() == ACTION_NONE) + { + std::vector<const KeymapAction*> allActions; + + allActions.reserve(actions.size()); + for (const auto& action : actions) + allActions.emplace_back(&action); + + dispatchAction = ProcessActions(std::move(allActions), windowId, magnitude, holdTimeMs); + } + + // If specific action was sent last frame but not this one, send a release event + if (dispatchAction.GetID() != m_lastAction.GetID()) + { + if (CActionTranslator::IsAnalog(m_lastAction.GetID()) && m_lastAction.GetAmount() > 0.0f) + { + m_lastAction.ClearAmount(); + m_actionHandler->OnAction(m_lastAction); + } + } + + // Dispatch action + bool bHandled = false; + if (dispatchAction.GetID() != ACTION_NONE) + { + m_actionHandler->OnAction(dispatchAction); + bHandled = true; + } + + m_bHeld = bPressed; + m_magnitude = magnitude; + m_lastHoldTimeMs = holdTimeMs; + m_lastAction = dispatchAction; + + return bHandled; +} + +CAction CKeyHandler::ProcessActions(std::vector<const KeymapAction*> actions, + int windowId, + float magnitude, + unsigned int holdTimeMs) +{ + CAction dispatchAction; + + // Filter out actions without pressed hotkeys + actions.erase(std::remove_if(actions.begin(), actions.end(), + [this](const KeymapAction* action) { + return !m_keymapHandler->HotkeysPressed(action->hotkeys); + }), + actions.end()); + + if (actions.empty()) + return false; + + // Actions are sorted by holdtime, so the final action is the one with the + // greatest holdtime + const KeymapAction& finalAction = **actions.rbegin(); + const unsigned int maxHoldTimeMs = finalAction.holdTimeMs; + + const bool bHasDelay = (maxHoldTimeMs > 0); + if (!bHasDelay) + { + dispatchAction = ProcessAction(finalAction, windowId, magnitude, holdTimeMs); + } + else + { + // If holdtime has exceeded the last action, execute it now + if (holdTimeMs >= finalAction.holdTimeMs) + { + // Force holdtime to zero for the initial press + if (!m_bActionSent) + holdTimeMs = 0; + else + holdTimeMs -= finalAction.holdTimeMs; + + dispatchAction = ProcessAction(finalAction, windowId, magnitude, holdTimeMs); + } + else + { + // Calculate press state + const bool bPressed = IsPressed(magnitude); + const bool bJustReleased = m_bHeld && !bPressed; + + // If button was just released, send a release action + if (bJustReleased) + dispatchAction = ProcessRelease(actions, windowId); + } + } + + return dispatchAction; +} + +CAction CKeyHandler::ProcessRelease(std::vector<const KeymapAction*> actions, int windowId) +{ + CAction dispatchAction; + + // Use previous holdtime from before button release + const unsigned int holdTimeMs = m_lastHoldTimeMs; + + // Send an action on release if one occurs before the holdtime + for (auto it = actions.begin(); it != actions.end();) + { + const KeymapAction& action = **it; + + unsigned int thisHoldTime = (*it)->holdTimeMs; + + ++it; + if (it == actions.end()) + break; + + unsigned int nextHoldTime = (*it)->holdTimeMs; + + if (thisHoldTime <= holdTimeMs && holdTimeMs < nextHoldTime) + { + dispatchAction = ProcessAction(action, windowId, 1.0f, 0); + break; + } + } + + return dispatchAction; +} + +CAction CKeyHandler::ProcessAction(const KeymapAction& action, + int windowId, + float magnitude, + unsigned int holdTimeMs) +{ + CAction dispatchAction; + + bool bSendAction = false; + + if (windowId != m_activeWindowId) + { + // Don't send actions if the window has changed since being pressed + } + else if (CActionTranslator::IsAnalog(action.actionId)) + { + bSendAction = true; + } + else if (IsPressed(magnitude)) + { + // Dispatch action if button was pressed this frame + if (holdTimeMs == 0) + bSendAction = true; + else + bSendAction = SendRepeatAction(holdTimeMs); + } + + if (bSendAction) + { + const CAction guiAction(action.actionId, magnitude, 0.0f, action.actionString, holdTimeMs); + m_keymapHandler->OnPress(m_keyName); + m_bActionSent = true; + m_lastActionMs = holdTimeMs; + dispatchAction = guiAction; + } + + return dispatchAction; +} + +bool CKeyHandler::SendRepeatAction(unsigned int holdTimeMs) +{ + bool bSendRepeat = true; + + // Don't send a repeat action if the last key has changed + if (m_keymapHandler->GetLastPressed() != m_keyName) + bSendRepeat = false; + + // Ensure initial timeout has elapsed + else if (holdTimeMs < HOLD_TIMEOUT_MS) + bSendRepeat = false; + + // Ensure repeat timeout has elapsed + else if (holdTimeMs < m_lastActionMs + REPEAT_TIMEOUT_MS) + bSendRepeat = false; + + return bSendRepeat; +} + +bool CKeyHandler::IsPressed(float magnitude) +{ + return magnitude >= DIGITAL_ANALOG_THRESHOLD; +} diff --git a/xbmc/input/joysticks/keymaps/KeyHandler.h b/xbmc/input/joysticks/keymaps/KeyHandler.h new file mode 100644 index 0000000..3b5a46c --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeyHandler.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/actions/Action.h" +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IKeyHandler.h" + +#include <map> +#include <string> +#include <vector> + +class CAction; +class IActionListener; +class IKeymap; + +namespace KODI +{ +namespace JOYSTICK +{ +class IKeymapHandler; + +/*! + * \ingroup joystick + * \brief + */ +class CKeyHandler : public IKeyHandler +{ +public: + CKeyHandler(const std::string& keyName, + IActionListener* actionHandler, + const IKeymap* keymap, + IKeymapHandler* keymapHandler); + + ~CKeyHandler() override = default; + + // implementation of IKeyHandler + bool IsPressed() const override { return m_bHeld; } + bool OnDigitalMotion(bool bPressed, unsigned int holdTimeMs) override; + bool OnAnalogMotion(float magnitude, unsigned int motionTimeMs) override; + +private: + void Reset(); + + /*! + * \brief Process actions to see if an action should be dispatched + * + * \param actions All actions from the keymap defined for the current window + * \param windowId The current window ID + * \param magnitude The magnitude or distance of the feature being handled + * \param holdTimeMs The time which the feature has been past the hold threshold + * + * \return The action to dispatch, or action with ID ACTION_NONE if no action should be dispatched + */ + CAction ProcessActions(std::vector<const KeymapAction*> actions, + int windowId, + float magnitude, + unsigned int holdTimeMs); + + /*! + * \brief Process actions after release event to see if an action should be dispatched + * + * \param actions All actions from the keymap defined for the current window + * \param windowId The current window ID + * + * \return The action to dispatch, or action with ID ACTION_NONE if no action should be dispatched + */ + CAction ProcessRelease(std::vector<const KeymapAction*> actions, int windowId); + + /*! + * \brief Process an action to see if it should be dispatched + * + * \param action The action chosen to be dispatched + * \param windowId The current window ID + * \param magnitude The magnitude or distance of the feature being handled + * \param holdTimeMs The time which the feature has been past the hold threshold + * + * \return The action to dispatch, or action with ID ACTION_NONE if no action should be dispatched + */ + CAction ProcessAction(const KeymapAction& action, + int windowId, + float magnitude, + unsigned int holdTimeMs); + + // Check criteria for sending a repeat action + bool SendRepeatAction(unsigned int holdTimeMs); + + // Helper function + static bool IsPressed(float magnitude); + + // Construction parameters + const std::string m_keyName; + IActionListener* const m_actionHandler; + const IKeymap* const m_keymap; + IKeymapHandler* const m_keymapHandler; + + // State variables + bool m_bHeld; + float m_magnitude; + unsigned int m_holdStartTimeMs; + unsigned int m_lastHoldTimeMs; + bool m_bActionSent; + unsigned int m_lastActionMs; + int m_activeWindowId = -1; // Window that activated the key + CAction m_lastAction; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/keymaps/KeymapHandler.cpp b/xbmc/input/joysticks/keymaps/KeymapHandler.cpp new file mode 100644 index 0000000..24e2a0b --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeymapHandler.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2017-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 "KeymapHandler.h" + +#include "KeyHandler.h" +#include "games/controllers/Controller.h" +#include "input/IKeymap.h" +#include "input/IKeymapEnvironment.h" +#include "input/InputTranslator.h" +#include "input/joysticks/JoystickEasterEgg.h" +#include "input/joysticks/JoystickTranslator.h" +#include "input/joysticks/JoystickUtils.h" +#include "input/joysticks/interfaces/IKeyHandler.h" + +#include <algorithm> +#include <assert.h> +#include <cmath> +#include <utility> + +using namespace KODI; +using namespace JOYSTICK; + +CKeymapHandler::CKeymapHandler(IActionListener* actionHandler, const IKeymap* keymap) + : m_actionHandler(actionHandler), m_keymap(keymap) +{ + assert(m_actionHandler != nullptr); + assert(m_keymap != nullptr); + + if (m_keymap->Environment()->UseEasterEgg()) + m_easterEgg.reset(new CJoystickEasterEgg(ControllerID())); +} + +bool CKeymapHandler::HotkeysPressed(const std::set<std::string>& keyNames) const +{ + bool bHotkeysPressed = true; + + for (const auto& hotkey : keyNames) + { + auto it = m_keyHandlers.find(hotkey); + if (it == m_keyHandlers.end() || !it->second->IsPressed()) + { + bHotkeysPressed = false; + break; + } + } + + return bHotkeysPressed; +} + +std::string CKeymapHandler::ControllerID() const +{ + return m_keymap->ControllerID(); +} + +bool CKeymapHandler::AcceptsInput(const FeatureName& feature) const +{ + if (HasAction(CJoystickUtils::MakeKeyName(feature))) + return true; + + for (auto dir : CJoystickUtils::GetAnalogStickDirections()) + { + if (HasAction(CJoystickUtils::MakeKeyName(feature, dir))) + return true; + } + + return false; +} + +bool CKeymapHandler::OnButtonPress(const FeatureName& feature, bool bPressed) +{ + if (bPressed && m_easterEgg && m_easterEgg->OnButtonPress(feature)) + return true; + + const std::string keyName = CJoystickUtils::MakeKeyName(feature); + + IKeyHandler* handler = GetKeyHandler(keyName); + return handler->OnDigitalMotion(bPressed, 0); +} + +void CKeymapHandler::OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) +{ + if (m_easterEgg && m_easterEgg->IsCapturing()) + return; + + const std::string keyName = CJoystickUtils::MakeKeyName(feature); + + IKeyHandler* handler = GetKeyHandler(keyName); + handler->OnDigitalMotion(true, holdTimeMs); +} + +bool CKeymapHandler::OnButtonMotion(const FeatureName& feature, + float magnitude, + unsigned int motionTimeMs) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature); + + IKeyHandler* handler = GetKeyHandler(keyName); + return handler->OnAnalogMotion(magnitude, motionTimeMs); +} + +bool CKeymapHandler::OnAnalogStickMotion(const FeatureName& feature, + float x, + float y, + unsigned int motionTimeMs) +{ + using namespace INPUT; + + bool bHandled = false; + + // Calculate the direction of the stick's position + const ANALOG_STICK_DIRECTION analogStickDir = CInputTranslator::VectorToCardinalDirection(x, y); + + // Calculate the magnitude projected onto that direction + const float magnitude = std::max(std::fabs(x), std::fabs(y)); + + // Deactivate directions in which the stick is not pointing first + for (auto dir : CJoystickUtils::GetAnalogStickDirections()) + { + if (dir != analogStickDir) + DeactivateDirection(feature, dir); + } + + // Now activate direction the analog stick is pointing + if (analogStickDir != ANALOG_STICK_DIRECTION::NONE) + bHandled = ActivateDirection(feature, magnitude, analogStickDir, motionTimeMs); + + return bHandled; +} + +bool CKeymapHandler::OnWheelMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) +{ + bool bHandled = false; + + // Calculate the direction of the wheel's position + const WHEEL_DIRECTION direction = CJoystickTranslator::PositionToWheelDirection(position); + + // Calculate the magnitude projected onto that direction + const float magnitude = std::fabs(position); + + // Deactivate directions in which the wheel is not pointing first + for (auto dir : CJoystickUtils::GetWheelDirections()) + { + if (dir != direction) + DeactivateDirection(feature, dir); + } + + // Now activate direction in which the wheel is positioned + if (direction != WHEEL_DIRECTION::NONE) + bHandled = ActivateDirection(feature, magnitude, direction, motionTimeMs); + + return bHandled; +} + +bool CKeymapHandler::OnThrottleMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) +{ + bool bHandled = false; + + // Calculate the direction of the throttle's position + const THROTTLE_DIRECTION direction = CJoystickTranslator::PositionToThrottleDirection(position); + + // Calculate the magnitude projected onto that direction + const float magnitude = std::fabs(position); + + // Deactivate directions in which the throttle is not pointing first + for (auto dir : CJoystickUtils::GetThrottleDirections()) + { + if (dir != direction) + DeactivateDirection(feature, dir); + } + + // Now activate direction in which the throttle is positioned + if (direction != THROTTLE_DIRECTION::NONE) + bHandled = ActivateDirection(feature, magnitude, direction, motionTimeMs); + + return bHandled; +} + +bool CKeymapHandler::OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z) +{ + return false; //! @todo implement +} + +bool CKeymapHandler::ActivateDirection(const FeatureName& feature, + float magnitude, + ANALOG_STICK_DIRECTION dir, + unsigned int motionTimeMs) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir); + + IKeyHandler* handler = GetKeyHandler(keyName); + return handler->OnAnalogMotion(magnitude, motionTimeMs); +} + +void CKeymapHandler::DeactivateDirection(const FeatureName& feature, ANALOG_STICK_DIRECTION dir) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir); + + IKeyHandler* handler = GetKeyHandler(keyName); + handler->OnAnalogMotion(0.0f, 0); +} + +bool CKeymapHandler::ActivateDirection(const FeatureName& feature, + float magnitude, + WHEEL_DIRECTION dir, + unsigned int motionTimeMs) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir); + + IKeyHandler* handler = GetKeyHandler(keyName); + return handler->OnAnalogMotion(magnitude, motionTimeMs); +} + +void CKeymapHandler::DeactivateDirection(const FeatureName& feature, WHEEL_DIRECTION dir) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir); + + IKeyHandler* handler = GetKeyHandler(keyName); + handler->OnAnalogMotion(0.0f, 0); +} + +bool CKeymapHandler::ActivateDirection(const FeatureName& feature, + float magnitude, + THROTTLE_DIRECTION dir, + unsigned int motionTimeMs) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir); + + IKeyHandler* handler = GetKeyHandler(keyName); + return handler->OnAnalogMotion(magnitude, motionTimeMs); +} + +void CKeymapHandler::DeactivateDirection(const FeatureName& feature, THROTTLE_DIRECTION dir) +{ + const std::string keyName = CJoystickUtils::MakeKeyName(feature, dir); + + IKeyHandler* handler = GetKeyHandler(keyName); + handler->OnAnalogMotion(0.0f, 0); +} + +IKeyHandler* CKeymapHandler::GetKeyHandler(const std::string& keyName) +{ + auto it = m_keyHandlers.find(keyName); + if (it == m_keyHandlers.end()) + { + std::unique_ptr<IKeyHandler> handler(new CKeyHandler(keyName, m_actionHandler, m_keymap, this)); + m_keyHandlers.insert(std::make_pair(keyName, std::move(handler))); + it = m_keyHandlers.find(keyName); + } + + return it->second.get(); +} + +bool CKeymapHandler::HasAction(const std::string& keyName) const +{ + bool bHasAction = false; + + const auto& actions = m_keymap->GetActions(keyName).actions; + for (const auto& action : actions) + { + if (HotkeysPressed(action.hotkeys)) + { + bHasAction = true; + break; + } + } + + return bHasAction; +} diff --git a/xbmc/input/joysticks/keymaps/KeymapHandler.h b/xbmc/input/joysticks/keymaps/KeymapHandler.h new file mode 100644 index 0000000..a2b1d4f --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeymapHandler.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/joysticks/JoystickTypes.h" +#include "input/joysticks/interfaces/IButtonSequence.h" +#include "input/joysticks/interfaces/IInputHandler.h" +#include "input/joysticks/interfaces/IKeymapHandler.h" + +#include <map> +#include <memory> +#include <string> + +class IActionListener; +class IKeymap; + +namespace KODI +{ +namespace JOYSTICK +{ +class IKeyHandler; + +/*! + * \ingroup joystick + * \brief + */ +class CKeymapHandler : public IKeymapHandler, public IInputHandler +{ +public: + CKeymapHandler(IActionListener* actionHandler, const IKeymap* keymap); + + ~CKeymapHandler() override = default; + + // implementation of IKeymapHandler + bool HotkeysPressed(const std::set<std::string>& keyNames) const override; + std::string GetLastPressed() const override { return m_lastPressed; } + void OnPress(const std::string& keyName) override { m_lastPressed = keyName; } + + // implementation of IInputHandler + std::string ControllerID() const override; + bool HasFeature(const FeatureName& feature) const override { return true; } + bool AcceptsInput(const FeatureName& feature) const override; + bool OnButtonPress(const FeatureName& feature, bool bPressed) override; + void OnButtonHold(const FeatureName& feature, unsigned int holdTimeMs) override; + bool OnButtonMotion(const FeatureName& feature, + float magnitude, + unsigned int motionTimeMs) override; + bool OnAnalogStickMotion(const FeatureName& feature, + float x, + float y, + unsigned int motionTimeMs) override; + bool OnAccelerometerMotion(const FeatureName& feature, float x, float y, float z) override; + bool OnWheelMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) override; + bool OnThrottleMotion(const FeatureName& feature, + float position, + unsigned int motionTimeMs) override; + void OnInputFrame() override {} + +protected: + // Keep track of cheat code presses + std::unique_ptr<IButtonSequence> m_easterEgg; + +private: + // Analog stick helper functions + bool ActivateDirection(const FeatureName& feature, + float magnitude, + ANALOG_STICK_DIRECTION dir, + unsigned int motionTimeMs); + void DeactivateDirection(const FeatureName& feature, ANALOG_STICK_DIRECTION dir); + + // Wheel helper functions + bool ActivateDirection(const FeatureName& feature, + float magnitude, + WHEEL_DIRECTION dir, + unsigned int motionTimeMs); + void DeactivateDirection(const FeatureName& feature, WHEEL_DIRECTION dir); + + // Throttle helper functions + bool ActivateDirection(const FeatureName& feature, + float magnitude, + THROTTLE_DIRECTION dir, + unsigned int motionTimeMs); + void DeactivateDirection(const FeatureName& feature, THROTTLE_DIRECTION dir); + + // Helper functions + IKeyHandler* GetKeyHandler(const std::string& keyName); + bool HasAction(const std::string& keyName) const; + + // Construction parameters + IActionListener* const m_actionHandler; + const IKeymap* const m_keymap; + + // Handlers for individual keys + std::map<std::string, std::unique_ptr<IKeyHandler>> m_keyHandlers; // Key name -> handler + + // Last pressed key + std::string m_lastPressed; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/joysticks/keymaps/KeymapHandling.cpp b/xbmc/input/joysticks/keymaps/KeymapHandling.cpp new file mode 100644 index 0000000..b87ea68 --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeymapHandling.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-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 "KeymapHandling.h" + +#include "KeymapHandler.h" +#include "ServiceBroker.h" +#include "input/ButtonTranslator.h" +#include "input/InputManager.h" +#include "input/Keymap.h" +#include "input/joysticks/interfaces/IInputHandler.h" +#include "input/joysticks/interfaces/IInputProvider.h" + +#include <algorithm> +#include <utility> + +using namespace KODI; +using namespace JOYSTICK; + +CKeymapHandling::CKeymapHandling(IInputProvider* inputProvider, + bool pPromiscuous, + const IKeymapEnvironment* environment) + : m_inputProvider(inputProvider), m_pPromiscuous(pPromiscuous), m_environment(environment) +{ + LoadKeymaps(); + CServiceBroker::GetInputManager().RegisterObserver(this); +} + +CKeymapHandling::~CKeymapHandling() +{ + CServiceBroker::GetInputManager().UnregisterObserver(this); + UnloadKeymaps(); +} + +IInputReceiver* CKeymapHandling::GetInputReceiver(const std::string& controllerId) const +{ + auto it = std::find_if(m_inputHandlers.begin(), m_inputHandlers.end(), + [&controllerId](const std::unique_ptr<IInputHandler>& inputHandler) { + return inputHandler->ControllerID() == controllerId; + }); + + if (it != m_inputHandlers.end()) + return (*it)->InputReceiver(); + + return nullptr; +} + +IKeymap* CKeymapHandling::GetKeymap(const std::string& controllerId) const +{ + auto it = std::find_if(m_keymaps.begin(), m_keymaps.end(), + [&controllerId](const std::unique_ptr<IKeymap>& keymap) { + return keymap->ControllerID() == controllerId; + }); + + if (it != m_keymaps.end()) + return it->get(); + + return nullptr; +} + +void CKeymapHandling::Notify(const Observable& obs, const ObservableMessage msg) +{ + if (msg == ObservableMessageButtonMapsChanged) + LoadKeymaps(); +} + +void CKeymapHandling::LoadKeymaps() +{ + UnloadKeymaps(); + + auto& inputManager = CServiceBroker::GetInputManager(); + + for (auto& windowKeymap : inputManager.GetJoystickKeymaps()) + { + // Create keymap + std::unique_ptr<IKeymap> keymap(new CKeymap(std::move(windowKeymap), m_environment)); + + // Create keymap handler + std::unique_ptr<IInputHandler> inputHandler(new CKeymapHandler(&inputManager, keymap.get())); + + // Register the handler with the input provider + m_inputProvider->RegisterInputHandler(inputHandler.get(), m_pPromiscuous); + + // Save the keymap and handler + m_keymaps.emplace_back(std::move(keymap)); + m_inputHandlers.emplace_back(std::move(inputHandler)); + } +} + +void CKeymapHandling::UnloadKeymaps() +{ + if (m_inputProvider != nullptr) + { + for (auto it = m_inputHandlers.rbegin(); it != m_inputHandlers.rend(); ++it) + m_inputProvider->UnregisterInputHandler(it->get()); + } + m_inputHandlers.clear(); + m_keymaps.clear(); +} diff --git a/xbmc/input/joysticks/keymaps/KeymapHandling.h b/xbmc/input/joysticks/keymaps/KeymapHandling.h new file mode 100644 index 0000000..fdc64b7 --- /dev/null +++ b/xbmc/input/joysticks/keymaps/KeymapHandling.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "utils/Observer.h" + +#include <memory> +#include <string> +#include <vector> + +class IKeymap; +class IKeymapEnvironment; + +namespace KODI +{ +namespace JOYSTICK +{ +class IInputHandler; +class IInputProvider; +class IInputReceiver; + +/*! + * \ingroup joystick + * \brief + */ +class CKeymapHandling : public Observer +{ +public: + CKeymapHandling(IInputProvider* inputProvider, + bool pPromiscuous, + const IKeymapEnvironment* environment); + + ~CKeymapHandling() override; + + /*! + * \brief Unregister the input provider + * + * Call this if the input provider is invalidated, such as if a user + * disconnects a controller. This prevents accessing the invalidated + * input provider when keymaps are unloaded upon destruction. + */ + void UnregisterInputProvider() { m_inputProvider = nullptr; } + + /*! + * \brief + */ + IInputReceiver* GetInputReceiver(const std::string& controllerId) const; + + /*! + * \brief + */ + IKeymap* GetKeymap(const std::string& controllerId) const; + + // implementation of Observer + void Notify(const Observable& obs, const ObservableMessage msg) override; + +private: + void LoadKeymaps(); + void UnloadKeymaps(); + + // Construction parameter + IInputProvider* m_inputProvider; + const bool m_pPromiscuous; + const IKeymapEnvironment* const m_environment; + + std::vector<std::unique_ptr<IKeymap>> m_keymaps; + std::vector<std::unique_ptr<IInputHandler>> m_inputHandlers; +}; +} // namespace JOYSTICK +} // namespace KODI diff --git a/xbmc/input/keyboard/CMakeLists.txt b/xbmc/input/keyboard/CMakeLists.txt new file mode 100644 index 0000000..14d48a8 --- /dev/null +++ b/xbmc/input/keyboard/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SOURCES KeyboardEasterEgg.cpp + KeymapActionMap.cpp +) + +set(HEADERS interfaces/IActionMap.h + interfaces/IKeyboardDriverHandler.h + interfaces/IKeyboardInputHandler.h + interfaces/IKeyboardInputProvider.h + KeyboardEasterEgg.h + KeyboardTypes.h + KeymapActionMap.h +) + +core_add_library(input_keyboard) diff --git a/xbmc/input/keyboard/KeyboardEasterEgg.cpp b/xbmc/input/keyboard/KeyboardEasterEgg.cpp new file mode 100644 index 0000000..8b7049d --- /dev/null +++ b/xbmc/input/keyboard/KeyboardEasterEgg.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-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 "input/keyboard/KeyboardEasterEgg.h" + +#include "input/Key.h" +#include "input/joysticks/JoystickEasterEgg.h" + +using namespace KODI; +using namespace KEYBOARD; + +std::vector<XBMCVKey> CKeyboardEasterEgg::m_sequence = { + XBMCVK_UP, XBMCVK_UP, XBMCVK_DOWN, XBMCVK_DOWN, XBMCVK_LEFT, + XBMCVK_RIGHT, XBMCVK_LEFT, XBMCVK_RIGHT, XBMCVK_B, XBMCVK_A, +}; + +bool CKeyboardEasterEgg::OnKeyPress(const CKey& key) +{ + bool bHandled = false; + + // Update state + if (key.GetVKey() == m_sequence[m_state]) + m_state++; + else + m_state = 0; + + // Capture input when finished with arrows (2 x up/down/left/right) + if (m_state > 8) + { + bHandled = true; + + if (m_state >= m_sequence.size()) + { + JOYSTICK::CJoystickEasterEgg::OnFinish(); + m_state = 0; + } + } + + return bHandled; +} diff --git a/xbmc/input/keyboard/KeyboardEasterEgg.h b/xbmc/input/keyboard/KeyboardEasterEgg.h new file mode 100644 index 0000000..00c1791 --- /dev/null +++ b/xbmc/input/keyboard/KeyboardEasterEgg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/XBMC_vkeys.h" +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" + +#include <vector> + +namespace KODI +{ +namespace KEYBOARD +{ +/*! + * \brief Hush!!! + */ +class CKeyboardEasterEgg : public IKeyboardDriverHandler +{ +public: + ~CKeyboardEasterEgg() override = default; + + // implementation of IKeyboardDriverHandler + bool OnKeyPress(const CKey& key) override; + void OnKeyRelease(const CKey& key) override {} + +private: + static std::vector<XBMCVKey> m_sequence; + + unsigned int m_state = 0; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/KeyboardTypes.h b/xbmc/input/keyboard/KeyboardTypes.h new file mode 100644 index 0000000..5a5d39b --- /dev/null +++ b/xbmc/input/keyboard/KeyboardTypes.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/Key.h" +#include "input/XBMC_keysym.h" + +#include <string> + +namespace KODI +{ +namespace KEYBOARD +{ +/*! + * \brief Symbol of a hardware-independent key + */ +using KeySymbol = XBMCKey; + +/*! + * \brief Name of a hardware-indendent symbol representing a key + * + * Names are defined in the keyboard's controller profile. + */ +using KeyName = std::string; + +/*! + * \brief Modifier keys on a keyboard that can be held when + * sending a key press + * + * \todo Move CKey enum to this file + */ +using Modifier = CKey::Modifier; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/KeymapActionMap.cpp b/xbmc/input/keyboard/KeymapActionMap.cpp new file mode 100644 index 0000000..f6e6919 --- /dev/null +++ b/xbmc/input/keyboard/KeymapActionMap.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017-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 "KeymapActionMap.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/InputManager.h" +#include "input/Key.h" +#include "input/actions/Action.h" + +using namespace KODI; +using namespace KEYBOARD; + +unsigned int CKeymapActionMap::GetActionID(const CKey& key) +{ + CAction action = CServiceBroker::GetInputManager().GetAction( + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), key); + return action.GetID(); +} diff --git a/xbmc/input/keyboard/KeymapActionMap.h b/xbmc/input/keyboard/KeymapActionMap.h new file mode 100644 index 0000000..6ee9344 --- /dev/null +++ b/xbmc/input/keyboard/KeymapActionMap.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/keyboard/interfaces/IActionMap.h" + +namespace KODI +{ +namespace KEYBOARD +{ +class CKeymapActionMap : public IActionMap +{ +public: + CKeymapActionMap(void) = default; + + ~CKeymapActionMap(void) override = default; + + // implementation of IActionMap + unsigned int GetActionID(const CKey& key) override; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/generic/CMakeLists.txt b/xbmc/input/keyboard/generic/CMakeLists.txt new file mode 100644 index 0000000..993a1f4 --- /dev/null +++ b/xbmc/input/keyboard/generic/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES KeyboardInputHandling.cpp) + +set(HEADERS KeyboardInputHandling.h) + +core_add_library(input_keyboard_generic) diff --git a/xbmc/input/keyboard/generic/KeyboardInputHandling.cpp b/xbmc/input/keyboard/generic/KeyboardInputHandling.cpp new file mode 100644 index 0000000..5e84081 --- /dev/null +++ b/xbmc/input/keyboard/generic/KeyboardInputHandling.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017-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 "KeyboardInputHandling.h" + +#include "input/XBMC_keysym.h" +#include "input/joysticks/DriverPrimitive.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "input/keyboard/interfaces/IKeyboardInputHandler.h" + +using namespace KODI; +using namespace KEYBOARD; + +CKeyboardInputHandling::CKeyboardInputHandling(IKeyboardInputHandler* handler, + JOYSTICK::IButtonMap* buttonMap) + : m_handler(handler), m_buttonMap(buttonMap) +{ +} + +bool CKeyboardInputHandling::OnKeyPress(const CKey& key) +{ + bool bHandled = false; + + JOYSTICK::CDriverPrimitive source(static_cast<XBMCKey>(key.GetKeycode())); + + KeyName keyName; + if (m_buttonMap->GetFeature(source, keyName)) + { + const Modifier mod = static_cast<Modifier>(key.GetModifiers() | key.GetLockingModifiers()); + bHandled = m_handler->OnKeyPress(keyName, mod, key.GetUnicode()); + } + + return bHandled; +} + +void CKeyboardInputHandling::OnKeyRelease(const CKey& key) +{ + JOYSTICK::CDriverPrimitive source(static_cast<XBMCKey>(key.GetKeycode())); + + KeyName keyName; + if (m_buttonMap->GetFeature(source, keyName)) + { + const Modifier mod = static_cast<Modifier>(key.GetModifiers() | key.GetLockingModifiers()); + m_handler->OnKeyRelease(keyName, mod, key.GetUnicode()); + } +} diff --git a/xbmc/input/keyboard/generic/KeyboardInputHandling.h b/xbmc/input/keyboard/generic/KeyboardInputHandling.h new file mode 100644 index 0000000..4cd2d26 --- /dev/null +++ b/xbmc/input/keyboard/generic/KeyboardInputHandling.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/keyboard/interfaces/IKeyboardDriverHandler.h" + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} + +namespace KEYBOARD +{ +class IKeyboardInputHandler; + +/*! + * \ingroup keyboard + * \brief Class to translate input from Kodi keycodes to key names defined + * by the keyboard's controller profile + */ +class CKeyboardInputHandling : public IKeyboardDriverHandler +{ +public: + CKeyboardInputHandling(IKeyboardInputHandler* handler, JOYSTICK::IButtonMap* buttonMap); + + ~CKeyboardInputHandling(void) override = default; + + // implementation of IKeyboardDriverHandler + bool OnKeyPress(const CKey& key) override; + void OnKeyRelease(const CKey& key) override; + +private: + // Construction parameters + IKeyboardInputHandler* const m_handler; + JOYSTICK::IButtonMap* const m_buttonMap; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/interfaces/IActionMap.h b/xbmc/input/keyboard/interfaces/IActionMap.h new file mode 100644 index 0000000..e77643e --- /dev/null +++ b/xbmc/input/keyboard/interfaces/IActionMap.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +class CKey; + +namespace KODI +{ +namespace KEYBOARD +{ +/*! + * \brief Interface for translating keys to action IDs + */ +class IActionMap +{ +public: + virtual ~IActionMap() = default; + + /*! + * \brief Get the action ID mapped to the specified key + * + * \param key The key to look up + * + * \return The action ID from ActionIDs.h, or ACTION_NONE if no action is + * mapped to the specified key + */ + virtual unsigned int GetActionID(const CKey& key) = 0; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/interfaces/IKeyboardDriverHandler.h b/xbmc/input/keyboard/interfaces/IKeyboardDriverHandler.h new file mode 100644 index 0000000..828f0a9 --- /dev/null +++ b/xbmc/input/keyboard/interfaces/IKeyboardDriverHandler.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015-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. + */ + +#pragma once + +class CKey; + +namespace KODI +{ +namespace KEYBOARD +{ +/*! + * \ingroup keyboard + * \brief Interface for handling keyboard events + */ +class IKeyboardDriverHandler +{ +public: + virtual ~IKeyboardDriverHandler() = default; + + /*! + * \brief A key has been pressed + * + * \param key The pressed key + * + * \return True if the event was handled, false otherwise + */ + virtual bool OnKeyPress(const CKey& key) = 0; + + /*! + * \brief A key has been released + * + * \param key The released key + */ + virtual void OnKeyRelease(const CKey& key) = 0; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/interfaces/IKeyboardInputHandler.h b/xbmc/input/keyboard/interfaces/IKeyboardInputHandler.h new file mode 100644 index 0000000..f0df6a3 --- /dev/null +++ b/xbmc/input/keyboard/interfaces/IKeyboardInputHandler.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "input/keyboard/KeyboardTypes.h" + +#include <stdint.h> +#include <string> + +namespace KODI +{ +namespace KEYBOARD +{ +/*! + * \ingroup keyboard + * \brief Interface for handling input events for keyboards + * + * Input events are an abstraction over driver events. Keys are identified by + * the name defined in the keyboard's controller profile. + */ +class IKeyboardInputHandler +{ +public: + virtual ~IKeyboardInputHandler() = default; + + /*! + * \brief The add-on ID of the keyboard's controller profile + * + * \return The ID of the controller profile add-on + */ + virtual std::string ControllerID() const = 0; + + /*! + * \brief Return true if the input handler accepts the given key + * + * \param key A key belonging to the controller specified by ControllerID() + * + * \return True if the key is used for input, false otherwise + */ + virtual bool HasKey(const KeyName& key) const = 0; + + /*! + * \brief A key has been pressed + * + * \param key A key belonging to the controller specified by ControllerID() + * \param mod A combination of modifiers + * \param unicode The unicode value associated with the key, or 0 if unknown + * + * \return True if the event was handled, false otherwise + */ + virtual bool OnKeyPress(const KeyName& key, Modifier mod, uint32_t unicode) = 0; + + /*! + * \brief A key has been released + * + * \param key A key belonging to the controller specified by ControllerID() + * \param mod A combination of modifiers + * \param unicode The unicode value associated with the key, or 0 if unknown + */ + virtual void OnKeyRelease(const KeyName& key, Modifier mod, uint32_t unicode) = 0; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/keyboard/interfaces/IKeyboardInputProvider.h b/xbmc/input/keyboard/interfaces/IKeyboardInputProvider.h new file mode 100644 index 0000000..5d964d5 --- /dev/null +++ b/xbmc/input/keyboard/interfaces/IKeyboardInputProvider.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +namespace KODI +{ +namespace KEYBOARD +{ +class IKeyboardInputHandler; + +/*! + * \ingroup mouse + * \brief Interface for classes that can provide keyboard input + */ +class IKeyboardInputProvider +{ +public: + virtual ~IKeyboardInputProvider() = default; + + /*! + * \brief Registers a handler to be called on keyboard input + * + * \param handler The handler to receive keyboard input provided by this class + * \param bPromiscuous True to observe all events without affecting the + * input's destination + */ + virtual void RegisterKeyboardHandler(IKeyboardInputHandler* handler, bool bPromiscuous) = 0; + + /*! + * \brief Unregisters handler from keyboard input + * + * \param handler The handler that was receiving keyboard input + */ + virtual void UnregisterKeyboardHandler(IKeyboardInputHandler* handler) = 0; +}; +} // namespace KEYBOARD +} // namespace KODI diff --git a/xbmc/input/mouse/CMakeLists.txt b/xbmc/input/mouse/CMakeLists.txt new file mode 100644 index 0000000..214adf0 --- /dev/null +++ b/xbmc/input/mouse/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES MouseStat.cpp + MouseTranslator.cpp +) + +set(HEADERS interfaces/IMouseDriverHandler.h + interfaces/IMouseInputHandler.h + interfaces/IMouseInputProvider.h + MouseStat.h + MouseTranslator.h + MouseTypes.h +) + +core_add_library(input_mouse) diff --git a/xbmc/input/mouse/MouseStat.cpp b/xbmc/input/mouse/MouseStat.cpp new file mode 100644 index 0000000..8b6ce3b --- /dev/null +++ b/xbmc/input/mouse/MouseStat.cpp @@ -0,0 +1,378 @@ +/* + * 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 "MouseStat.h" + +#include "ServiceBroker.h" +#include "input/Key.h" +#include "utils/TimeUtils.h" +#include "windowing/WinSystem.h" + +#include <algorithm> +#include <cstring> + +CMouseStat::CMouseStat() +{ + SetEnabled(); + m_Key = KEY_MOUSE_NOOP; +} + +CMouseStat::~CMouseStat() = default; + +void CMouseStat::Initialize() +{ + // Set the default resolution (PAL) + SetResolution(720, 576, 1, 1); +} + +void CMouseStat::HandleEvent(const XBMC_Event& newEvent) +{ + // Save the mouse position and the size of the last move + int dx, dy; + if (newEvent.type == XBMC_MOUSEMOTION) + { + dx = newEvent.motion.x - m_mouseState.x; + dy = newEvent.motion.y - m_mouseState.y; + } + else if (newEvent.type == XBMC_MOUSEBUTTONDOWN || newEvent.type == XBMC_MOUSEBUTTONUP) + { + dx = newEvent.button.x - m_mouseState.x; + dy = newEvent.button.y - m_mouseState.y; + } + else + { + return; + } + m_mouseState.dx = dx; + m_mouseState.dy = dy; + m_mouseState.x = std::max(0, std::min(m_maxX, m_mouseState.x + dx)); + m_mouseState.y = std::max(0, std::min(m_maxY, m_mouseState.y + dy)); + + // Fill in the public members + if (newEvent.type == XBMC_MOUSEBUTTONDOWN) + { + if (newEvent.button.button == XBMC_BUTTON_LEFT) + m_mouseState.button[MOUSE_LEFT_BUTTON] = true; + if (newEvent.button.button == XBMC_BUTTON_RIGHT) + m_mouseState.button[MOUSE_RIGHT_BUTTON] = true; + if (newEvent.button.button == XBMC_BUTTON_MIDDLE) + m_mouseState.button[MOUSE_MIDDLE_BUTTON] = true; + if (newEvent.button.button == XBMC_BUTTON_X1) + m_mouseState.button[MOUSE_EXTRA_BUTTON1] = true; + if (newEvent.button.button == XBMC_BUTTON_X2) + m_mouseState.button[MOUSE_EXTRA_BUTTON2] = true; + if (newEvent.button.button == XBMC_BUTTON_X3) + m_mouseState.button[MOUSE_EXTRA_BUTTON3] = true; + if (newEvent.button.button == XBMC_BUTTON_X4) + m_mouseState.button[MOUSE_EXTRA_BUTTON4] = true; + if (newEvent.button.button == XBMC_BUTTON_WHEELUP) + m_mouseState.dz = 1; + if (newEvent.button.button == XBMC_BUTTON_WHEELDOWN) + m_mouseState.dz = -1; + } + else if (newEvent.type == XBMC_MOUSEBUTTONUP) + { + if (newEvent.button.button == XBMC_BUTTON_LEFT) + m_mouseState.button[MOUSE_LEFT_BUTTON] = false; + if (newEvent.button.button == XBMC_BUTTON_RIGHT) + m_mouseState.button[MOUSE_RIGHT_BUTTON] = false; + if (newEvent.button.button == XBMC_BUTTON_MIDDLE) + m_mouseState.button[MOUSE_MIDDLE_BUTTON] = false; + if (newEvent.button.button == XBMC_BUTTON_X1) + m_mouseState.button[MOUSE_EXTRA_BUTTON1] = false; + if (newEvent.button.button == XBMC_BUTTON_X2) + m_mouseState.button[MOUSE_EXTRA_BUTTON2] = false; + if (newEvent.button.button == XBMC_BUTTON_X3) + m_mouseState.button[MOUSE_EXTRA_BUTTON3] = false; + if (newEvent.button.button == XBMC_BUTTON_X4) + m_mouseState.button[MOUSE_EXTRA_BUTTON4] = false; + if (newEvent.button.button == XBMC_BUTTON_WHEELUP) + m_mouseState.dz = 0; + if (newEvent.button.button == XBMC_BUTTON_WHEELDOWN) + m_mouseState.dz = 0; + } + + // Now check the current message and the previous state to find out if + // this is a click, doubleclick, drag etc + uint32_t now = CTimeUtils::GetFrameTime(); + bool bNothingDown = true; + + for (int i = 0; i < MOUSE_MAX_BUTTON; i++) + { + bClick[i] = false; + bLongClick[i] = false; + bDoubleClick[i] = false; + m_hold[i] = HoldAction::NONE; + + // CButtonState::Update does the hard work of checking the button state + // and spotting drags, doubleclicks etc + CButtonState::BUTTON_ACTION action = + m_buttonState[i].Update(now, m_mouseState.x, m_mouseState.y, m_mouseState.button[i]); + switch (action) + { + case CButtonState::MB_SHORT_CLICK: + bClick[i] = true; + bNothingDown = false; + break; + case CButtonState::MB_LONG_CLICK: + bLongClick[i] = true; + bNothingDown = false; + break; + case CButtonState::MB_DOUBLE_CLICK: + bDoubleClick[i] = true; + bNothingDown = false; + break; + case CButtonState::MB_DRAG_START: + m_hold[i] = HoldAction::DRAG_START; + bNothingDown = false; + break; + case CButtonState::MB_DRAG: + m_hold[i] = HoldAction::DRAG; + bNothingDown = false; + break; + case CButtonState::MB_DRAG_END: + m_hold[i] = HoldAction::DRAG_END; + bNothingDown = false; + break; + default: + break; + } + } + + // Now work out what action ID to send to XBMC. + + // ignore any mouse messages by default + m_Key = KEY_MOUSE_NOOP; + + for (int button = 0; button < MOUSE_MAX_BUTTON; ++button) + { + // The bClick array is set true if CButtonState::Update spots a click + // i.e. a button down followed by a button up. + if (bClick[button]) + m_Key = KEY_MOUSE_CLICK + button; + // The bDoubleClick array is set true if CButtonState::Update spots a + // button down within double_click_time (500ms) of the last click + else if (bDoubleClick[button]) + m_Key = KEY_MOUSE_DOUBLE_CLICK + button; + else if (bLongClick[button]) + m_Key = KEY_MOUSE_LONG_CLICK + button; + + if (m_Key != KEY_MOUSE_NOOP) + break; + } + + if (m_Key == KEY_MOUSE_NOOP) + { + // The m_hold array is set to the drag action + if (m_hold[MOUSE_LEFT_BUTTON] != HoldAction::NONE) + { + switch (m_hold[MOUSE_LEFT_BUTTON]) + { + case HoldAction::DRAG: + m_Key = KEY_MOUSE_DRAG; + break; + case HoldAction::DRAG_START: + m_Key = KEY_MOUSE_DRAG_START; + break; + case HoldAction::DRAG_END: + m_Key = KEY_MOUSE_DRAG_END; + break; + default: + break; + } + } + else if (m_hold[MOUSE_RIGHT_BUTTON] != HoldAction::NONE) + { + switch (m_hold[MOUSE_RIGHT_BUTTON]) + { + case HoldAction::DRAG: + m_Key = KEY_MOUSE_RDRAG; + break; + case HoldAction::DRAG_START: + m_Key = KEY_MOUSE_RDRAG_START; + break; + case HoldAction::DRAG_END: + m_Key = KEY_MOUSE_RDRAG_END; + break; + default: + break; + } + } + + // dz is +1 on wheel up and -1 on wheel down + else if (m_mouseState.dz > 0) + m_Key = KEY_MOUSE_WHEEL_UP; + else if (m_mouseState.dz < 0) + m_Key = KEY_MOUSE_WHEEL_DOWN; + + // Check for a mouse move that isn't a drag, ignoring messages with no movement at all + else if (newEvent.type == XBMC_MOUSEMOTION && (m_mouseState.dx || m_mouseState.dy)) + m_Key = KEY_MOUSE_MOVE; + } + + // activate the mouse pointer if we have an action or the mouse has moved far enough + if ((MovedPastThreshold() && m_Key == KEY_MOUSE_MOVE) || + (m_Key != KEY_MOUSE_NOOP && m_Key != KEY_MOUSE_MOVE)) + SetActive(); + + // reset the mouse state if nothing is held down + if (bNothingDown) + SetState(MOUSE_STATE_NORMAL); +} + +void CMouseStat::SetResolution(int maxX, int maxY, float speedX, float speedY) +{ + m_maxX = maxX; + m_maxY = maxY; + + // speed is currently unused + m_speedX = speedX; + m_speedY = speedY; +} + +void CMouseStat::SetActive(bool active /*=true*/) +{ + m_lastActiveTime = CTimeUtils::GetFrameTime(); + m_mouseState.active = active; + // we show the OS mouse if: + // 1. The mouse is active (it has been moved) AND + // 2. The XBMC mouse is disabled in settings AND + // 3. XBMC is not in fullscreen. + CWinSystemBase* winSystem = CServiceBroker::GetWinSystem(); + if (winSystem) + winSystem->ShowOSMouse(m_mouseState.active && !IsEnabled() && + !CServiceBroker::GetWinSystem()->IsFullScreen()); +} + +// IsActive - returns true if we have been active in the last MOUSE_ACTIVE_LENGTH period +bool CMouseStat::IsActive() +{ + if (m_mouseState.active && (CTimeUtils::GetFrameTime() - m_lastActiveTime > MOUSE_ACTIVE_LENGTH)) + SetActive(false); + return (m_mouseState.active && IsEnabled()); +} + +void CMouseStat::SetEnabled(bool enabled) +{ + m_mouseEnabled = enabled; + SetActive(enabled); +} + +// IsEnabled - returns true if mouse is enabled +bool CMouseStat::IsEnabled() const +{ + return m_mouseEnabled; +} + +bool CMouseStat::MovedPastThreshold() const +{ + return (m_mouseState.dx * m_mouseState.dx + m_mouseState.dy * m_mouseState.dy >= + MOUSE_MINIMUM_MOVEMENT * MOUSE_MINIMUM_MOVEMENT); +} + +uint32_t CMouseStat::GetKey() const +{ + return m_Key; +} + +HoldAction CMouseStat::GetHold(int ButtonID) const +{ + switch (ButtonID) + { + case MOUSE_LEFT_BUTTON: + return m_hold[MOUSE_LEFT_BUTTON]; + } + return HoldAction::NONE; +} + +CMouseStat::CButtonState::CButtonState() +{ + m_state = STATE_RELEASED; + m_time = 0; + m_x = 0; + m_y = 0; +} + +bool CMouseStat::CButtonState::InClickRange(int x, int y) const +{ + int dx = x - m_x; + int dy = y - m_y; + return (unsigned int)(dx * dx + dy * dy) <= click_confines * click_confines; +} + +CMouseStat::CButtonState::BUTTON_ACTION CMouseStat::CButtonState::Update(unsigned int time, + int x, + int y, + bool down) +{ + if (m_state == STATE_IN_DRAG) + { + if (down) + return MB_DRAG; + m_state = STATE_RELEASED; + return MB_DRAG_END; + } + else if (m_state == STATE_RELEASED) + { + if (down) + { + m_state = STATE_IN_CLICK; + m_time = time; + m_x = x; + m_y = y; + } + } + else if (m_state == STATE_IN_CLICK) + { + if (down) + { + if (!InClickRange(x, y)) + { // beginning a drag + m_state = STATE_IN_DRAG; + return MB_DRAG_START; + } + } + else + { // button up + if (time - m_time < short_click_time) + { // single click + m_state = STATE_IN_DOUBLE_CLICK; + m_time = time; // double click time and positioning is measured from the + m_x = x; // end of a single click + m_y = y; + return MB_SHORT_CLICK; + } + else + { // long click + m_state = STATE_RELEASED; + return MB_LONG_CLICK; + } + } + } + else if (m_state == STATE_IN_DOUBLE_CLICK) + { + if (time - m_time > double_click_time || !InClickRange(x, y)) + { // too long, or moved to much - reset to released state and re-update, as we may be starting a + // new click + m_state = STATE_RELEASED; + return Update(time, x, y, down); + } + if (down) + { + m_state = STATE_IN_DOUBLE_IGNORE; + return MB_DOUBLE_CLICK; + } + } + else if (m_state == STATE_IN_DOUBLE_IGNORE) + { + if (!down) + m_state = STATE_RELEASED; + } + + return MB_NONE; +} diff --git a/xbmc/input/mouse/MouseStat.h b/xbmc/input/mouse/MouseStat.h new file mode 100644 index 0000000..354d1c3 --- /dev/null +++ b/xbmc/input/mouse/MouseStat.h @@ -0,0 +1,227 @@ +/* + * 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. + */ + +#pragma once + +#include "windowing/XBMC_events.h" + +#define XBMC_BUTTON(X) (1 << ((X)-1)) +#define XBMC_BUTTON_LEFT 1 +#define XBMC_BUTTON_MIDDLE 2 +#define XBMC_BUTTON_RIGHT 3 +#define XBMC_BUTTON_WHEELUP 4 +#define XBMC_BUTTON_WHEELDOWN 5 +#define XBMC_BUTTON_X1 6 +#define XBMC_BUTTON_X2 7 +#define XBMC_BUTTON_X3 8 +#define XBMC_BUTTON_X4 9 +#define XBMC_BUTTON_LMASK XBMC_BUTTON(XBMC_BUTTON_LEFT) +#define XBMC_BUTTON_MMASK XBMC_BUTTON(XBMC_BUTTON_MIDDLE) +#define XBMC_BUTTON_RMASK XBMC_BUTTON(XBMC_BUTTON_RIGHT) +#define XBMC_BUTTON_X1MASK XBMC_BUTTON(XBMC_BUTTON_X1) +#define XBMC_BUTTON_X2MASK XBMC_BUTTON(XBMC_BUTTON_X2) +#define XBMC_BUTTON_X3MASK XBMC_BUTTON(XBMC_BUTTON_X3) +#define XBMC_BUTTON_X4MASK XBMC_BUTTON(XBMC_BUTTON_X4) + +#define MOUSE_MINIMUM_MOVEMENT 2 +#define MOUSE_DOUBLE_CLICK_LENGTH 500L +#define MOUSE_ACTIVE_LENGTH 5000L + +#define MOUSE_MAX_BUTTON 7 + +enum MOUSE_STATE +{ + /*! Normal state */ + MOUSE_STATE_NORMAL = 1, + /*! Control below the mouse is currently in focus */ + MOUSE_STATE_FOCUS, + /*! A drag operation is being performed */ + MOUSE_STATE_DRAG, + /*! A mousebutton is being clicked */ + MOUSE_STATE_CLICK +}; + +enum MOUSE_BUTTON +{ + MOUSE_LEFT_BUTTON = 0, + MOUSE_RIGHT_BUTTON, + MOUSE_MIDDLE_BUTTON, + MOUSE_EXTRA_BUTTON1, + MOUSE_EXTRA_BUTTON2, + MOUSE_EXTRA_BUTTON3, + MOUSE_EXTRA_BUTTON4 +}; + +enum class HoldAction +{ + /*! No action should occur */ + NONE, + /*! A drag action has started */ + DRAG_START, + /*! A drag action is in progress */ + DRAG, + /*! A drag action has finished */ + DRAG_END +}; + +//! Holds everything we know about the current state of the mouse +struct MouseState +{ + /*! X location */ + int x; + /*! Y location */ + int y; + /*! Change in x */ + int16_t dx; + /*! Change in y */ + int16_t dy; + /*! Change in z (wheel) */ + int8_t dz; + /*! Current state of the buttons */ + bool button[MOUSE_MAX_BUTTON]; + /*! True if the mouse is active */ + bool active; +}; + +struct MousePosition +{ + int x; + int y; +}; + +class CAction; + +class CMouseStat +{ +public: + CMouseStat(); + virtual ~CMouseStat(); + + void Initialize(); + void HandleEvent(const XBMC_Event& newEvent); + void SetResolution(int maxX, int maxY, float speedX, float speedY); + bool IsActive(); + bool IsEnabled() const; + + void SetActive(bool active = true); + void SetState(MOUSE_STATE state) { m_pointerState = state; } + void SetEnabled(bool enabled = true); + MOUSE_STATE GetState() const { return m_pointerState; } + uint32_t GetKey() const; + + HoldAction GetHold(int ButtonID) const; + inline int GetX(void) const { return m_mouseState.x; } + inline int GetY(void) const { return m_mouseState.y; } + inline int GetDX(void) const { return m_mouseState.dx; } + inline int GetDY(void) const { return m_mouseState.dy; } + MousePosition GetPosition() { return MousePosition{m_mouseState.x, m_mouseState.y}; } + +private: + /*! \brief Holds information regarding a particular mouse button state + + The CButtonState class is used to track where in a button event the mouse currently is. + There is effectively 5 BUTTON_STATE's available, and transitioning between those states + is handled by the Update() function. + + The actions we detect are: + * short clicks - down/up press of the mouse within short_click_time ms, where the pointer stays + within click_confines pixels + * long clicks - down/up press of the mouse greater than short_click_time ms, where the pointers + stays within click_confines pixels + * double clicks - a further down press of the mouse within double_click_time of the up press of + a short click, where the pointer stays within click_confines pixels + * drag - the mouse is down and has been moved more than click_confines pixels + + \sa CMouseStat + */ + class CButtonState + { + public: + /*! \brief enum for the actions to perform as a result of an Update function + */ + enum BUTTON_ACTION + { + MB_NONE = 0, ///< no action should occur + MB_SHORT_CLICK, ///< a short click has occurred (a double click may be in process) + MB_LONG_CLICK, ///< a long click has occurred + MB_DOUBLE_CLICK, ///< a double click has occurred + MB_DRAG_START, ///< a drag action has started + MB_DRAG, ///< a drag action is in progress + MB_DRAG_END + }; ///< a drag action has finished + + CButtonState(); + + /*! \brief Update the button state, with where the mouse is, and whether the button is down or + not + + \param time frame time in ms + \param x horizontal coordinate of the mouse + \param y vertical coordinate of the mouse + \param down true if the button is down + \return action that should be performed + */ + BUTTON_ACTION Update(unsigned int time, int x, int y, bool down); + + private: + //! number of pixels that the pointer may move while the button is down to + //! trigger a click + static const unsigned int click_confines = 5; + + //! Time for mouse down/up to trigger a short click rather than a long click + static const unsigned int short_click_time = 1000; + + //! Time for mouse down following a short click to trigger a double click + static const unsigned int double_click_time = 500; + + bool InClickRange(int x, int y) const; + + enum BUTTON_STATE + { + STATE_RELEASED = 0, ///< mouse button is released, no events pending + STATE_IN_CLICK, ///< mouse button is down, a click is pending + STATE_IN_DOUBLE_CLICK, ///< mouse button is released, pending double click + STATE_IN_DOUBLE_IGNORE, ///< mouse button is down following double click + STATE_IN_DRAG + }; ///< mouse button is down during a drag + + BUTTON_STATE m_state; + unsigned int m_time; + int m_x; + int m_y; + }; + + /*! \brief detect whether the mouse has moved + + Uses a trigger threshold of 2 pixels to detect mouse movement + + \return whether the mouse has moved past the trigger threshold. + */ + bool MovedPastThreshold() const; + + // state of the mouse + MOUSE_STATE m_pointerState{MOUSE_STATE_NORMAL}; + MouseState m_mouseState{}; + bool m_mouseEnabled; + CButtonState m_buttonState[MOUSE_MAX_BUTTON]; + + int m_maxX{0}; + int m_maxY{0}; + float m_speedX{0.0f}; + float m_speedY{0.0f}; + + // active/click timers + unsigned int m_lastActiveTime; + + bool bClick[MOUSE_MAX_BUTTON]{}; + bool bDoubleClick[MOUSE_MAX_BUTTON]{}; + HoldAction m_hold[MOUSE_MAX_BUTTON]{}; + bool bLongClick[MOUSE_MAX_BUTTON]{}; + + uint32_t m_Key; +}; diff --git a/xbmc/input/mouse/MouseTranslator.cpp b/xbmc/input/mouse/MouseTranslator.cpp new file mode 100644 index 0000000..b5cac7d --- /dev/null +++ b/xbmc/input/mouse/MouseTranslator.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2017-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 "MouseTranslator.h" + +#include "MouseStat.h" +#include "input/Key.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <map> +#include <string> + +using namespace KODI; +using namespace MOUSE; + +namespace +{ + +using ActionName = std::string; +using KeyID = uint32_t; + +static const std::map<ActionName, KeyID> MouseKeys = {{"click", KEY_MOUSE_CLICK}, + {"leftclick", KEY_MOUSE_CLICK}, + {"rightclick", KEY_MOUSE_RIGHTCLICK}, + {"middleclick", KEY_MOUSE_MIDDLECLICK}, + {"doubleclick", KEY_MOUSE_DOUBLE_CLICK}, + {"longclick", KEY_MOUSE_LONG_CLICK}, + {"wheelup", KEY_MOUSE_WHEEL_UP}, + {"wheeldown", KEY_MOUSE_WHEEL_DOWN}, + {"mousemove", KEY_MOUSE_MOVE}, + {"mousedrag", KEY_MOUSE_DRAG}, + {"mousedragstart", KEY_MOUSE_DRAG_START}, + {"mousedragend", KEY_MOUSE_DRAG_END}, + {"mouserdrag", KEY_MOUSE_RDRAG}, + {"mouserdragstart", KEY_MOUSE_RDRAG_START}, + {"mouserdragend", KEY_MOUSE_RDRAG_END}}; + +} // anonymous namespace + +uint32_t CMouseTranslator::TranslateCommand(const TiXmlElement* pButton) +{ + uint32_t buttonId = 0; + + if (pButton != nullptr) + { + std::string szKey = pButton->ValueStr(); + if (!szKey.empty()) + { + StringUtils::ToLower(szKey); + + auto it = MouseKeys.find(szKey); + if (it != MouseKeys.end()) + buttonId = it->second; + + if (buttonId == 0) + { + CLog::Log(LOGERROR, "Unknown mouse action ({}), skipping", pButton->Value()); + } + else + { + int id = 0; + if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS)) + { + if (0 <= id && id < MOUSE_MAX_BUTTON) + buttonId += id; + } + } + } + } + + return buttonId; +} + +bool CMouseTranslator::TranslateEventID(unsigned int eventId, BUTTON_ID& buttonId) +{ + switch (eventId) + { + case XBMC_BUTTON_LEFT: + { + buttonId = BUTTON_ID::LEFT; + return true; + } + case XBMC_BUTTON_MIDDLE: + { + buttonId = BUTTON_ID::MIDDLE; + return true; + } + case XBMC_BUTTON_RIGHT: + { + buttonId = BUTTON_ID::RIGHT; + return true; + } + case XBMC_BUTTON_WHEELUP: + { + buttonId = BUTTON_ID::WHEEL_UP; + return true; + } + case XBMC_BUTTON_WHEELDOWN: + { + buttonId = BUTTON_ID::WHEEL_DOWN; + return true; + } + case XBMC_BUTTON_X1: + { + buttonId = BUTTON_ID::BUTTON4; + return true; + } + case XBMC_BUTTON_X2: + { + buttonId = BUTTON_ID::BUTTON5; + return true; + } + case XBMC_BUTTON_X3: + { + buttonId = BUTTON_ID::HORIZ_WHEEL_LEFT; + return true; + } + case XBMC_BUTTON_X4: + { + buttonId = BUTTON_ID::HORIZ_WHEEL_RIGHT; + return true; + } + default: + break; + } + + return false; +} diff --git a/xbmc/input/mouse/MouseTranslator.h b/xbmc/input/mouse/MouseTranslator.h new file mode 100644 index 0000000..e3d4d4b --- /dev/null +++ b/xbmc/input/mouse/MouseTranslator.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "MouseTypes.h" + +#include <stdint.h> + +class TiXmlElement; + +class CMouseTranslator +{ +public: + /*! + * \brief Translate a keymap element to a key ID + */ + static uint32_t TranslateCommand(const TiXmlElement* pButton); + + /*! + * \brief Translate a mouse event ID to a mouse button index + * + * \param eventId The event ID from MouseStat.h + * \param[out] buttonId The button ID from MouseTypes.h, or unmodified if unsuccessful + * + * \return True if successful, false otherwise + */ + static bool TranslateEventID(unsigned int eventId, KODI::MOUSE::BUTTON_ID& buttonId); +}; diff --git a/xbmc/input/mouse/MouseTypes.h b/xbmc/input/mouse/MouseTypes.h new file mode 100644 index 0000000..f11f7ea --- /dev/null +++ b/xbmc/input/mouse/MouseTypes.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +#include "input/InputTypes.h" + +#include <string> + +namespace KODI +{ +namespace MOUSE +{ +/*! + * \brief Buttons on a mouse + */ +enum class BUTTON_ID +{ + UNKNOWN, + LEFT, + RIGHT, + MIDDLE, + BUTTON4, + BUTTON5, + WHEEL_UP, + WHEEL_DOWN, + HORIZ_WHEEL_LEFT, + HORIZ_WHEEL_RIGHT, +}; + +/*! + * \brief Name of a mouse button + * + * Names are defined in the mouse's controller profile. + */ +using ButtonName = std::string; + +/*! + * \brief Directions of motion for a mouse pointer + */ +using POINTER_DIRECTION = INPUT::CARDINAL_DIRECTION; + +/*! + * \brief Name of the mouse pointer + * + * Names are defined in the mouse's controller profile. + */ +using PointerName = std::string; +} // namespace MOUSE +} // namespace KODI diff --git a/xbmc/input/mouse/generic/CMakeLists.txt b/xbmc/input/mouse/generic/CMakeLists.txt new file mode 100644 index 0000000..b5c2858 --- /dev/null +++ b/xbmc/input/mouse/generic/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES MouseInputHandling.cpp) + +set(HEADERS MouseInputHandling.h) + +core_add_library(input_mouse_generic) diff --git a/xbmc/input/mouse/generic/MouseInputHandling.cpp b/xbmc/input/mouse/generic/MouseInputHandling.cpp new file mode 100644 index 0000000..0f51ede --- /dev/null +++ b/xbmc/input/mouse/generic/MouseInputHandling.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2016-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 "MouseInputHandling.h" + +#include "input/InputTranslator.h" +#include "input/joysticks/interfaces/IButtonMap.h" +#include "input/mouse/interfaces/IMouseInputHandler.h" + +using namespace KODI; +using namespace MOUSE; + +CMouseInputHandling::CMouseInputHandling(IMouseInputHandler* handler, + JOYSTICK::IButtonMap* buttonMap) + : m_handler(handler), m_buttonMap(buttonMap) +{ +} + +bool CMouseInputHandling::OnPosition(int x, int y) +{ + using namespace JOYSTICK; + + if (!m_bHasPosition) + { + m_bHasPosition = true; + m_x = x; + m_y = y; + return true; + } + + int dx = x - m_x; + int dy = y - m_y; + + bool bHandled = false; + + // Get direction of motion + POINTER_DIRECTION dir = GetPointerDirection(dx, dy); + + CDriverPrimitive source(dir); + if (source.IsValid()) + { + // Get pointer in direction of motion + PointerName pointerName; + if (m_buttonMap->GetFeature(source, pointerName)) + { + // Get orthogonal direction of motion + POINTER_DIRECTION dirCCW = GetOrthogonalDirectionCCW(dir); + + // Get mapped directions of motion for rotation and reflection + CDriverPrimitive target; + CDriverPrimitive targetCCW; + + if (m_buttonMap->GetRelativePointer(pointerName, dir, target)) + m_buttonMap->GetRelativePointer(pointerName, dirCCW, targetCCW); + + if (target.IsValid()) + { + // Invert y to right-handed cartesian system + dy *= -1; + + // Perform rotation + int rotation[2][2] = {{1, 0}, {0, 1}}; + + GetRotation(dir, target.PointerDirection(), rotation); + + dx = rotation[0][0] * dx + rotation[0][1] * dy; + dy = rotation[1][0] * dx + rotation[1][1] * dy; + + if (targetCCW.IsValid()) + { + // Perform reflection + int reflection[2][2] = {{1, 0}, {0, 1}}; + + GetReflectionCCW(target.PointerDirection(), targetCCW.PointerDirection(), reflection); + + dx = reflection[0][0] * dx + reflection[0][1] * dy; + dy = reflection[1][0] * dx + reflection[1][1] * dy; + } + + // Invert y back to left-handed coordinate system + dy *= -1; + } + + bHandled = m_handler->OnMotion(pointerName, dx, dy); + } + } + else + { + // Don't fall through - might disrupt the game + bHandled = true; + } + + m_x = x; + m_y = y; + + return bHandled; +} + +bool CMouseInputHandling::OnButtonPress(BUTTON_ID button) +{ + bool bHandled = false; + + JOYSTICK::CDriverPrimitive source(button); + + ButtonName buttonName; + if (m_buttonMap->GetFeature(source, buttonName)) + bHandled = m_handler->OnButtonPress(buttonName); + + return bHandled; +} + +void CMouseInputHandling::OnButtonRelease(BUTTON_ID button) +{ + JOYSTICK::CDriverPrimitive source(button); + + ButtonName buttonName; + if (m_buttonMap->GetFeature(source, buttonName)) + m_handler->OnButtonRelease(buttonName); +} + +POINTER_DIRECTION CMouseInputHandling::GetPointerDirection(int x, int y) +{ + using namespace INPUT; + + return CInputTranslator::VectorToCardinalDirection(static_cast<float>(x), static_cast<float>(-y)); +} + +POINTER_DIRECTION CMouseInputHandling::GetOrthogonalDirectionCCW(POINTER_DIRECTION direction) +{ + switch (direction) + { + case POINTER_DIRECTION::RIGHT: + return POINTER_DIRECTION::UP; + case POINTER_DIRECTION::UP: + return POINTER_DIRECTION::LEFT; + case POINTER_DIRECTION::LEFT: + return POINTER_DIRECTION::DOWN; + case POINTER_DIRECTION::DOWN: + return POINTER_DIRECTION::RIGHT; + default: + break; + } + + return POINTER_DIRECTION::NONE; +} + +void CMouseInputHandling::GetRotation(POINTER_DIRECTION source, + POINTER_DIRECTION target, + int (&rotation)[2][2]) +{ + switch (source) + { + case POINTER_DIRECTION::RIGHT: + { + switch (target) + { + case POINTER_DIRECTION::UP: + GetRotation(90, rotation); + break; + case POINTER_DIRECTION::LEFT: + GetRotation(180, rotation); + break; + case POINTER_DIRECTION::DOWN: + GetRotation(270, rotation); + break; + default: + break; + } + break; + } + case POINTER_DIRECTION::UP: + { + switch (target) + { + case POINTER_DIRECTION::LEFT: + GetRotation(90, rotation); + break; + case POINTER_DIRECTION::DOWN: + GetRotation(180, rotation); + break; + case POINTER_DIRECTION::RIGHT: + GetRotation(270, rotation); + break; + default: + break; + } + break; + } + case POINTER_DIRECTION::LEFT: + { + switch (target) + { + case POINTER_DIRECTION::DOWN: + GetRotation(90, rotation); + break; + case POINTER_DIRECTION::RIGHT: + GetRotation(180, rotation); + break; + case POINTER_DIRECTION::UP: + GetRotation(270, rotation); + break; + default: + break; + } + break; + } + case POINTER_DIRECTION::DOWN: + { + switch (target) + { + case POINTER_DIRECTION::RIGHT: + GetRotation(90, rotation); + break; + case POINTER_DIRECTION::UP: + GetRotation(180, rotation); + break; + case POINTER_DIRECTION::LEFT: + GetRotation(270, rotation); + break; + default: + break; + } + break; + } + default: + break; + } +} + +void CMouseInputHandling::GetRotation(int deg, int (&rotation)[2][2]) +{ + switch (deg) + { + case 90: + { + rotation[0][0] = 0; + rotation[0][1] = -1; + rotation[1][0] = 1; + rotation[1][1] = 0; + break; + } + case 180: + { + rotation[0][0] = -1; + rotation[0][1] = 0; + rotation[1][0] = 0; + rotation[1][1] = -1; + break; + } + case 270: + { + rotation[0][0] = 0; + rotation[0][1] = 1; + rotation[1][0] = -1; + rotation[1][1] = 0; + break; + } + default: + break; + } +} + +void CMouseInputHandling::GetReflectionCCW(POINTER_DIRECTION source, + POINTER_DIRECTION target, + int (&rotation)[2][2]) +{ + switch (source) + { + case POINTER_DIRECTION::RIGHT: + { + switch (target) + { + case POINTER_DIRECTION::DOWN: + GetReflection(0, rotation); + break; + default: + break; + } + break; + } + case POINTER_DIRECTION::UP: + { + switch (target) + { + case POINTER_DIRECTION::RIGHT: + GetReflection(90, rotation); + break; + default: + break; + } + break; + } + case POINTER_DIRECTION::LEFT: + { + switch (target) + { + case POINTER_DIRECTION::UP: + GetReflection(180, rotation); + break; + default: + break; + } + break; + } + case POINTER_DIRECTION::DOWN: + { + switch (target) + { + case POINTER_DIRECTION::LEFT: + GetReflection(270, rotation); + break; + default: + break; + } + break; + } + default: + break; + } +} + +void CMouseInputHandling::GetReflection(int deg, int (&reflection)[2][2]) +{ + switch (deg) + { + case 0: + case 180: + { + reflection[0][0] = 1; + reflection[0][1] = 0; + reflection[1][0] = 0; + reflection[1][1] = -1; + break; + } + case 90: + case 270: + { + reflection[0][0] = -1; + reflection[0][1] = 0; + reflection[1][0] = 0; + reflection[1][1] = 1; + break; + } + default: + break; + } +} diff --git a/xbmc/input/mouse/generic/MouseInputHandling.h b/xbmc/input/mouse/generic/MouseInputHandling.h new file mode 100644 index 0000000..6d19da9 --- /dev/null +++ b/xbmc/input/mouse/generic/MouseInputHandling.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/mouse/MouseTypes.h" +#include "input/mouse/interfaces/IMouseDriverHandler.h" + +namespace KODI +{ +namespace JOYSTICK +{ +class IButtonMap; +} + +namespace MOUSE +{ +class IMouseInputHandler; + +/*! + * \ingroup mouse + * \brief Class to translate input from driver info to higher-level features + */ +class CMouseInputHandling : public IMouseDriverHandler +{ +public: + CMouseInputHandling(IMouseInputHandler* handler, JOYSTICK::IButtonMap* buttonMap); + + ~CMouseInputHandling(void) override = default; + + // implementation of IMouseDriverHandler + bool OnPosition(int x, int y) override; + bool OnButtonPress(BUTTON_ID button) override; + void OnButtonRelease(BUTTON_ID button) override; + +private: + // Utility functions + static POINTER_DIRECTION GetPointerDirection(int x, int y); + static POINTER_DIRECTION GetOrthogonalDirectionCCW(POINTER_DIRECTION direction); + + static void GetRotation(POINTER_DIRECTION source, + POINTER_DIRECTION target, + int (&rotation)[2][2]); + static void GetRotation(int deg, int (&rotation)[2][2]); + + static void GetReflectionCCW(POINTER_DIRECTION source, + POINTER_DIRECTION target, + int (&reflection)[2][2]); + static void GetReflection(int deg, int (&reflection)[2][2]); + + // Construction parameters + IMouseInputHandler* const m_handler; + JOYSTICK::IButtonMap* const m_buttonMap; + + // Mouse parameters + bool m_bHasPosition = false; + int m_x = 0; + int m_y = 0; +}; +} // namespace MOUSE +} // namespace KODI diff --git a/xbmc/input/mouse/interfaces/IMouseDriverHandler.h b/xbmc/input/mouse/interfaces/IMouseDriverHandler.h new file mode 100644 index 0000000..7989e39 --- /dev/null +++ b/xbmc/input/mouse/interfaces/IMouseDriverHandler.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/mouse/MouseTypes.h" + +namespace KODI +{ +namespace MOUSE +{ +/*! + * \ingroup mouse + * \brief Interface for handling mouse driver events + */ +class IMouseDriverHandler +{ +public: + virtual ~IMouseDriverHandler(void) = default; + + /*! + * \brief Handle mouse position updates + * + * \param x The new x coordinate of the pointer + * \param y The new y coordinate of the pointer + * + * The mouse uses a left-handed (graphics) cartesian coordinate system. + * Positive X is right, positive Y is down. + * + * \return True if the event was handled, false otherwise + */ + virtual bool OnPosition(int x, int y) = 0; + + /*! + * \brief A mouse button has been pressed + * + * \param button The index of the pressed button + * + * \return True if the event was handled, otherwise false + */ + virtual bool OnButtonPress(BUTTON_ID button) = 0; + + /*! + * \brief A mouse button has been released + * + * \param button The index of the released button + */ + virtual void OnButtonRelease(BUTTON_ID button) = 0; +}; +} // namespace MOUSE +} // namespace KODI diff --git a/xbmc/input/mouse/interfaces/IMouseInputHandler.h b/xbmc/input/mouse/interfaces/IMouseInputHandler.h new file mode 100644 index 0000000..910ddd4 --- /dev/null +++ b/xbmc/input/mouse/interfaces/IMouseInputHandler.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016-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. + */ + +#pragma once + +#include "input/mouse/MouseTypes.h" + +#include <string> + +namespace KODI +{ +namespace MOUSE +{ +/*! + * \ingroup mouse + * \brief Interface for handling mouse events + */ +class IMouseInputHandler +{ +public: + virtual ~IMouseInputHandler(void) = default; + + /*! + * \brief The controller profile for this mouse input handler + * + * \return The ID of the add-on extending kodi.game.controller + */ + virtual std::string ControllerID(void) const = 0; + + /*! + * \brief A relative pointer has moved + * + * \param relpointer The name of the relative pointer being moved + * \param dx The relative x coordinate of motion + * \param dy The relative y coordinate of motion + * + * The mouse uses a left-handed (graphics) cartesian coordinate system. + * Positive X is right, positive Y is down. + * + * \return True if the event was handled, otherwise false + */ + virtual bool OnMotion(const PointerName& relpointer, int dx, int dy) = 0; + + /*! + * \brief A mouse button has been pressed + * + * \param button The name of the feature being pressed + * + * \return True if the event was handled, otherwise false + */ + virtual bool OnButtonPress(const ButtonName& button) = 0; + + /*! + * \brief A mouse button has been released + * + * \param button The name of the feature being released + */ + virtual void OnButtonRelease(const ButtonName& button) = 0; +}; +} // namespace MOUSE +} // namespace KODI diff --git a/xbmc/input/mouse/interfaces/IMouseInputProvider.h b/xbmc/input/mouse/interfaces/IMouseInputProvider.h new file mode 100644 index 0000000..f289070 --- /dev/null +++ b/xbmc/input/mouse/interfaces/IMouseInputProvider.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +namespace KODI +{ +namespace MOUSE +{ +class IMouseInputHandler; + +/*! + * \ingroup mouse + * \brief Interface for classes that can provide mouse input + */ +class IMouseInputProvider +{ +public: + virtual ~IMouseInputProvider() = default; + + /*! + * \brief Registers a handler to be called on mouse input + * + * \param handler The handler to receive mouse input provided by this class + * \param bPromiscuous True to observe all events without affecting + * subsequent handlers + */ + virtual void RegisterMouseHandler(IMouseInputHandler* handler, bool bPromiscuous) = 0; + + /*! + * \brief Unregisters handler from mouse input + * + * \param handler The handler that was receiving mouse input + */ + virtual void UnregisterMouseHandler(IMouseInputHandler* handler) = 0; +}; +} // namespace MOUSE +} // namespace KODI diff --git a/xbmc/input/remote/IRRemote.h b/xbmc/input/remote/IRRemote.h new file mode 100644 index 0000000..84a7dac --- /dev/null +++ b/xbmc/input/remote/IRRemote.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#pragma once + +#define XINPUT_IR_REMOTE_DISPLAY 213 +#define XINPUT_IR_REMOTE_REVERSE 226 +#define XINPUT_IR_REMOTE_PLAY 234 +#define XINPUT_IR_REMOTE_FORWARD 227 +#define XINPUT_IR_REMOTE_SKIP_MINUS 221 +#define XINPUT_IR_REMOTE_STOP 224 +#define XINPUT_IR_REMOTE_PAUSE 230 +#define XINPUT_IR_REMOTE_SKIP_PLUS 223 +#define XINPUT_IR_REMOTE_TITLE 229 +#define XINPUT_IR_REMOTE_INFO 195 + +#define XINPUT_IR_REMOTE_UP 166 +#define XINPUT_IR_REMOTE_DOWN 167 +#define XINPUT_IR_REMOTE_LEFT 169 +#define XINPUT_IR_REMOTE_RIGHT 168 + +#define XINPUT_IR_REMOTE_SELECT 11 +#define XINPUT_IR_REMOTE_ENTER 22 + +#define XINPUT_IR_REMOTE_SUBTITLE 44 +#define XINPUT_IR_REMOTE_LANGUAGE 45 + +#define XINPUT_IR_REMOTE_MENU 247 +#define XINPUT_IR_REMOTE_BACK 216 + +#define XINPUT_IR_REMOTE_1 206 +#define XINPUT_IR_REMOTE_2 205 +#define XINPUT_IR_REMOTE_3 204 +#define XINPUT_IR_REMOTE_4 203 +#define XINPUT_IR_REMOTE_5 202 +#define XINPUT_IR_REMOTE_6 201 +#define XINPUT_IR_REMOTE_7 200 +#define XINPUT_IR_REMOTE_8 199 +#define XINPUT_IR_REMOTE_9 198 +#define XINPUT_IR_REMOTE_0 207 + +// additional keys from the media center extender for xbox remote +#define XINPUT_IR_REMOTE_POWER 196 +#define XINPUT_IR_REMOTE_MY_TV 49 +#define XINPUT_IR_REMOTE_MY_MUSIC 9 +#define XINPUT_IR_REMOTE_MY_PICTURES 6 +#define XINPUT_IR_REMOTE_MY_VIDEOS 7 + +#define XINPUT_IR_REMOTE_RECORD 232 + +#define XINPUT_IR_REMOTE_START 37 +#define XINPUT_IR_REMOTE_VOLUME_PLUS 208 +#define XINPUT_IR_REMOTE_VOLUME_MINUS 209 +#define XINPUT_IR_REMOTE_CHANNEL_PLUS 210 +#define XINPUT_IR_REMOTE_CHANNEL_MINUS 211 +#define XINPUT_IR_REMOTE_MUTE 192 + +#define XINPUT_IR_REMOTE_RECORDED_TV 101 +#define XINPUT_IR_REMOTE_LIVE_TV 24 +#define XINPUT_IR_REMOTE_STAR 40 +#define XINPUT_IR_REMOTE_HASH 41 +#define XINPUT_IR_REMOTE_CLEAR 249 + +// additional keys not defined by xbox remotes but present on generic remotes +#define XINPUT_IR_REMOTE_TELETEXT 250 +#define XINPUT_IR_REMOTE_RED 251 +#define XINPUT_IR_REMOTE_GREEN 252 +#define XINPUT_IR_REMOTE_YELLOW 253 +#define XINPUT_IR_REMOTE_BLUE 254 +#define XINPUT_IR_REMOTE_PLAYLIST 255 +#define XINPUT_IR_REMOTE_GUIDE 50 + +#define XINPUT_IR_REMOTE_LIVE_RADIO 248 +#define XINPUT_IR_REMOTE_EPG_SEARCH 246 + +#define XINPUT_IR_REMOTE_EJECT 235 +#define XINPUT_IR_REMOTE_CONTENTS_MENU 236 +#define XINPUT_IR_REMOTE_ROOT_MENU 237 +#define XINPUT_IR_REMOTE_TOP_MENU 238 +#define XINPUT_IR_REMOTE_DVD_MENU 239 + +#define XINPUT_IR_REMOTE_PRINT 240 + +// Reserved 256 -> ... +// Key.h +// KEY_BUTTON_* + +typedef struct _XINPUT_IR_REMOTE +{ + unsigned char wButtons; + unsigned char region; // just a guess + + //! Some value that is changing while a button is pressed... could be the + //! state of the buffer + unsigned char counter; + + //! If > 0: first event triggered after a button was pressed on the remote + //! If 0: not first event + unsigned char firstEvent; + +} XINPUT_IR_REMOTE, *PIR_REMOTE; diff --git a/xbmc/input/touch/CMakeLists.txt b/xbmc/input/touch/CMakeLists.txt new file mode 100644 index 0000000..12a228b --- /dev/null +++ b/xbmc/input/touch/CMakeLists.txt @@ -0,0 +1,8 @@ +set(SOURCES ITouchInputHandling.cpp) + +set(HEADERS ITouchActionHandler.h + ITouchInputHandler.h + ITouchInputHandling.h + TouchTypes.h) + +core_add_library(input_touch) diff --git a/xbmc/input/touch/ITouchActionHandler.h b/xbmc/input/touch/ITouchActionHandler.h new file mode 100644 index 0000000..28afdd7 --- /dev/null +++ b/xbmc/input/touch/ITouchActionHandler.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include <stdint.h> + +/*! + * \ingroup touch + * \brief Directions in which a touch can moved + * + * These values can be combined (bitwise OR) to specify multiple directions. + */ +typedef enum +{ + TouchMoveDirectionNone = 0x0, + TouchMoveDirectionLeft = 0x1, + TouchMoveDirectionRight = 0x2, + TouchMoveDirectionUp = 0x4, + TouchMoveDirectionDown = 0x8 +} TouchMoveDirection; + +/*! + * \ingroup touch + * \brief Interface defining all supported touch action events + */ +class ITouchActionHandler +{ +public: + virtual ~ITouchActionHandler() = default; + + /*! + * \brief A touch action has been aborted + */ + virtual void OnTouchAbort() {} + + /*! + * \brief A single touch has started + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * + * \return True if the event was handled otherwise false + * + * \sa OnSingleTap + */ + virtual bool OnSingleTouchStart(float x, float y) { return true; } + /*! + * \brief A single touch has been held down for a certain amount of time + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * + * \return True if the event was handled otherwise false + * + * \sa OnSingleLongPress + */ + virtual bool OnSingleTouchHold(float x, float y) { return true; } + /*! + * \brief A single touch has moved + * + * \param x The x coordinate (with sub-pixel) of the current touch + * \param y The y coordinate (with sub-pixel) of the current touch + * \param offsetX The covered distance on the x axis (with sub-pixel) + * \param offsetX The covered distance on the y axis (with sub-pixel) + * \param velocityX The velocity of the gesture in x direction (pixels/second) + * \param velocityX The velocity of the gesture in y direction (pixels/second) + * + * \return True if the event was handled otherwise false + * + * \sa OnTouchGesturePan + */ + virtual bool OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) + { + return true; + } + /*! + * \brief A single touch has been lifted + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * + * \return True if the event was handled otherwise false + * + * \sa OnSingleTap + */ + virtual bool OnSingleTouchEnd(float x, float y) { return true; } + + /*! + * \brief An additional touch has been performed + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param pointer The pointer that has performed the touch + * + * \return True if the event was handled otherwise false + */ + virtual bool OnMultiTouchDown(float x, float y, int32_t pointer) { return true; } + /*! + * \brief Multiple simultaneous touches have been held down for a certain amount of time + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param pointers The number of pointers involved (default 2) + * + * \return True if the event was handled otherwise false + */ + virtual bool OnMultiTouchHold(float x, float y, int32_t pointers = 2) { return true; } + /*! + * \brief A touch has moved + * + * \param x The x coordinate (with sub-pixel) of the current touch + * \param y The y coordinate (with sub-pixel) of the current touch + * \param offsetX The covered distance on the x axis (with sub-pixel) + * \param offsetX The covered distance on the y axis (with sub-pixel) + * \param velocityX The velocity of the gesture in x direction (pixels/second) + * \param velocityX The velocity of the gesture in y direction (pixels/second) + * \param pointer The pointer that has performed the touch + * + * \return True if the event was handled otherwise false + */ + virtual bool OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) + { + return true; + } + /*! + * \brief A touch has been lifted (but there are still active touches) + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param pointer The pointer that has performed the touch + * + * \return True if the event was handled otherwise false + */ + virtual bool OnMultiTouchUp(float x, float y, int32_t pointer) { return true; } + + /*! + * \brief A pan gesture with a single touch has been started + * + * \param x The x coordinate (with sub-pixel) of the initial touch + * \param y The y coordinate (with sub-pixel) of the initial touch + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchGestureStart(float x, float y) { return true; } + /*! + * \brief A pan gesture with a single touch is in progress + * + * \param x The x coordinate (with sub-pixel) of the current touch + * \param y The y coordinate (with sub-pixel) of the current touch + * \param offsetX The covered distance on the x axis (with sub-pixel) + * \param offsetX The covered distance on the y axis (with sub-pixel) + * \param velocityX The velocity of the gesture in x direction (pixels/second) + * \param velocityX The velocity of the gesture in y direction (pixels/second) + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) + { + return true; + } + /*! + * \brief A pan gesture with a single touch has ended + * + * \param x The x coordinate (with sub-pixel) of the current touch + * \param y The y coordinate (with sub-pixel) of the current touch + * \param offsetX The covered distance on the x axis (with sub-pixel) + * \param offsetX The covered distance on the y axis (with sub-pixel) + * \param velocityX The velocity of the gesture in x direction (pixels/second) + * \param velocityX The velocity of the gesture in y direction (pixels/second) + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) + { + return true; + } + + // convenience events + /*! + * \brief A tap with a one or more touches has been performed + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param pointers The number of pointers involved (default 1) + * + * \return True if the event was handled otherwise false + */ + virtual void OnTap(float x, float y, int32_t pointers = 1) {} + /*! + * \brief One or more touches have been held down for a certain amount of time + * + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param pointers The number of pointers involved (default 1) + * + * \return True if the event was handled otherwise false + * + * \sa OnSingleTouchHold + */ + virtual void OnLongPress(float x, float y, int32_t pointers = 1) {} + /*! + * \brief One or more touches has been moved quickly in a single direction in a short time + * + * \param direction The direction (left, right, up, down) of the swipe gesture + * \param xDown The x coordinate (with sub-pixel) of the first touch + * \param yDown The y coordinate (with sub-pixel) of the first touch + * \param xUp The x coordinate (with sub-pixel) of the last touch + * \param yUp The y coordinate (with sub-pixel) of the last touch + * \param velocityX The velocity of the gesture in x direction (pixels/second) + * \param velocityX The velocity of the gesture in y direction (pixels/second) + * \param pointers The number of pointers involved (default 1) + * + * \return True if the event was handled otherwise false + */ + virtual void OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers = 1) + { + } + /*! + * \brief Two simultaneous touches have been held down and moved to perform a zooming/pinching + * gesture + * + * \param centerX The x coordinate (with sub-pixel) of the center of the two touches + * \param centerY The y coordinate (with sub-pixel) of the center of the two touches + * \param zoomFactor The zoom (> 1.0) or pinch (< 1.0) factor of the two touches + * + * \return True if the event was handled otherwise false + */ + virtual void OnZoomPinch(float centerX, float centerY, float zoomFactor) {} + /*! + * \brief Two simultaneous touches have been held down and moved to perform a rotating gesture + * + * \param centerX The x coordinate (with sub-pixel) of the center of the two touches + * \param centerY The y coordinate (with sub-pixel) of the center of the two touches + * \param angle The clockwise angle in degrees of the rotation + * + * \return True if the event was handled otherwise false + */ + virtual void OnRotate(float centerX, float centerY, float angle) {} +}; diff --git a/xbmc/input/touch/ITouchInputHandler.h b/xbmc/input/touch/ITouchInputHandler.h new file mode 100644 index 0000000..1275839 --- /dev/null +++ b/xbmc/input/touch/ITouchInputHandler.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/ITouchInputHandling.h" + +#include <atomic> +#include <stdint.h> + +/*! + * \ingroup touch + * \brief Touch input event + */ +typedef enum +{ + TouchInputUnchanged = 0, + TouchInputAbort, + TouchInputDown, + TouchInputUp, + TouchInputMove +} TouchInput; + +/*! + * \ingroup touch + * \brief Interface (implements ITouchInputHandling) defining methods to handle + * raw touch input events (down, up, move). + * + * This interface should be implemented on platforms only supporting low level + * (raw) touch input events like touch down/up/move and with no gesture + * recognition logic. + */ +class ITouchInputHandler : public ITouchInputHandling +{ +public: + ITouchInputHandler() : m_dpi(160.0f) {} + ~ITouchInputHandler() override = default; + + /*! + * \brief Handle a touch event + * + * Handles the given touch event at the given location. + * This takes into account all the currently active pointers + * which need to be updated before calling this method to + * actually interpret and handle the changes in touch. + * + * \param event The actual touch event (abort, down, up, move) + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param time The time (in nanoseconds) when this touch occurred + * \param pointer The number of the touch pointer which caused this event (default 0) + * \param size The size of the touch pointer (with sub-pixel) (default 0.0) + * + * \return True if the event was handled otherwise false. + * + * \sa Update + */ + virtual bool HandleTouchInput( + TouchInput event, float x, float y, int64_t time, int32_t pointer = 0, float size = 0.0f) = 0; + + /*! + * \brief Update the coordinates of a pointer + * + * Depending on how a platform handles touch input and provides the necessary events + * this method needs to be called at different times. If there's an event for every + * touch action this method does not need to be called at all. If there's only a + * touch event for the primary pointer (and no special events for any secondary + * pointers in a multi touch gesture) this method should be called for every active + * secondary pointer before calling Handle. + * + * \param pointer The number of the touch pointer which caused this event (default 0) + * \param x The x coordinate (with sub-pixel) of the touch + * \param y The y coordinate (with sub-pixel) of the touch + * \param time The time (in nanoseconds) when this touch occurred + * \param size The size of the touch pointer (with sub-pixel) (default 0.0) + * + * \return True if the pointer was updated otherwise false. + * + * \sa Handle + */ + virtual bool UpdateTouchPointer( + int32_t pointer, float x, float y, int64_t time, float size = 0.0f) + { + return false; + } + + void SetScreenDPI(float dpi) + { + if (dpi > 0.0f) + m_dpi = dpi; + } + float GetScreenDPI() { return m_dpi; } + +protected: + /*! + * \brief DPI value of the touch screen + */ + std::atomic<float> m_dpi; +}; diff --git a/xbmc/input/touch/ITouchInputHandling.cpp b/xbmc/input/touch/ITouchInputHandling.cpp new file mode 100644 index 0000000..e799f72 --- /dev/null +++ b/xbmc/input/touch/ITouchInputHandling.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012-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 "ITouchInputHandling.h" + +void ITouchInputHandling::RegisterHandler(ITouchActionHandler* touchHandler) +{ + m_handler = touchHandler; +} + +void ITouchInputHandling::UnregisterHandler() +{ + m_handler = NULL; +} + +void ITouchInputHandling::OnTouchAbort() +{ + if (m_handler) + m_handler->OnTouchAbort(); +} + +bool ITouchInputHandling::OnSingleTouchStart(float x, float y) +{ + if (m_handler) + return m_handler->OnSingleTouchStart(x, y); + + return true; +} + +bool ITouchInputHandling::OnSingleTouchHold(float x, float y) +{ + if (m_handler) + return m_handler->OnSingleTouchHold(x, y); + + return true; +} + +bool ITouchInputHandling::OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + if (m_handler) + return m_handler->OnSingleTouchMove(x, y, offsetX, offsetY, velocityX, velocityY); + + return true; +} + +bool ITouchInputHandling::OnSingleTouchEnd(float x, float y) +{ + if (m_handler) + return m_handler->OnSingleTouchEnd(x, y); + + return true; +} + +bool ITouchInputHandling::OnMultiTouchDown(float x, float y, int32_t pointer) +{ + if (m_handler) + return m_handler->OnMultiTouchDown(x, y, pointer); + + return true; +} + +bool ITouchInputHandling::OnMultiTouchHold(float x, float y, int32_t pointers /* = 2 */) +{ + if (m_handler) + return m_handler->OnMultiTouchHold(x, y, pointers); + + return true; +} + +bool ITouchInputHandling::OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) +{ + if (m_handler) + return m_handler->OnMultiTouchMove(x, y, offsetX, offsetY, velocityX, velocityY, pointer); + + return true; +} + +bool ITouchInputHandling::OnMultiTouchUp(float x, float y, int32_t pointer) +{ + if (m_handler) + return m_handler->OnMultiTouchUp(x, y, pointer); + + return true; +} + +bool ITouchInputHandling::OnTouchGestureStart(float x, float y) +{ + if (m_handler) + return m_handler->OnTouchGestureStart(x, y); + + return true; +} + +bool ITouchInputHandling::OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + if (m_handler) + return m_handler->OnTouchGesturePan(x, y, offsetX, offsetY, velocityX, velocityY); + + return true; +} + +bool ITouchInputHandling::OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + if (m_handler) + return m_handler->OnTouchGestureEnd(x, y, offsetX, offsetY, velocityX, velocityY); + + return true; +} + +void ITouchInputHandling::OnTap(float x, float y, int32_t pointers /* = 1 */) +{ + if (m_handler) + m_handler->OnTap(x, y, pointers); +} + +void ITouchInputHandling::OnLongPress(float x, float y, int32_t pointers /* = 1 */) +{ + if (m_handler) + m_handler->OnLongPress(x, y, pointers); +} + +void ITouchInputHandling::OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers /* = 1 */) +{ + if (m_handler) + m_handler->OnSwipe(direction, xDown, yDown, xUp, yUp, velocityX, velocityY, pointers); +} + +void ITouchInputHandling::OnZoomPinch(float centerX, float centerY, float zoomFactor) +{ + if (m_handler) + m_handler->OnZoomPinch(centerX, centerY, zoomFactor); +} + +void ITouchInputHandling::OnRotate(float centerX, float centerY, float angle) +{ + if (m_handler) + m_handler->OnRotate(centerX, centerY, angle); +} diff --git a/xbmc/input/touch/ITouchInputHandling.h b/xbmc/input/touch/ITouchInputHandling.h new file mode 100644 index 0000000..cabbf47 --- /dev/null +++ b/xbmc/input/touch/ITouchInputHandling.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/ITouchActionHandler.h" + +#include <stdlib.h> + +/*! + * \ingroup touch + * \brief Convenience interface implementing ITouchActionHandler with an + * implementation that forwards any ITouchActionHandler-related calls + * to a previously registered ITouchActionHandler + * + * \sa ITouchActionHandler + */ +class ITouchInputHandling : protected ITouchActionHandler +{ +public: + ITouchInputHandling() : m_handler(NULL) {} + ~ITouchInputHandling() override = default; + + /*! + * \brief Register a touch input handler + * + * There can only be one touch input handler. + * + * \param touchHandler An instance of a touch handler implementing the + * ITouchActionHandler interface + * + * \sa UnregisterHandler + */ + void RegisterHandler(ITouchActionHandler* touchHandler); + /*! + * \brief Unregister the previously registered touch handler + * + * \sa RegisterHandler + */ + void UnregisterHandler(); + +protected: + // implementation of ITouchActionHandler + void OnTouchAbort() override; + + bool OnSingleTouchStart(float x, float y) override; + bool OnSingleTouchHold(float x, float y) override; + bool OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + bool OnSingleTouchEnd(float x, float y) override; + + bool OnMultiTouchDown(float x, float y, int32_t pointer) override; + bool OnMultiTouchHold(float x, float y, int32_t pointers = 2) override; + bool OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) override; + bool OnMultiTouchUp(float x, float y, int32_t pointer) override; + + bool OnTouchGestureStart(float x, float y) override; + bool OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + bool OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + + // convenience events + void OnTap(float x, float y, int32_t pointers = 1) override; + void OnLongPress(float x, float y, int32_t pointers = 1) override; + void OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers = 1) override; + void OnZoomPinch(float centerX, float centerY, float zoomFactor) override; + void OnRotate(float centerX, float centerY, float angle) override; + +private: + ITouchActionHandler* m_handler; +}; diff --git a/xbmc/input/touch/TouchTypes.h b/xbmc/input/touch/TouchTypes.h new file mode 100644 index 0000000..b3450c4 --- /dev/null +++ b/xbmc/input/touch/TouchTypes.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "utils/Vector.h" + +/*! + * \ingroup touch + * \brief A class representing a touch consisting of an x and y coordinate and + * a time + */ +class Touch : public CVector +{ +public: + /*! + * \brief Checks if the touch is valid i.e. if the x/y coordinates and the + * time are >= 0 + * + * \return True if the touch is valid otherwise false + */ + bool valid() const { return x >= 0.0f && y >= 0.0f && time >= 0; } + + /*! + * \brief Copies the x/y coordinates and the time from the given touch + * + * \param other Touch to copy x/y coordinates and time from + */ + void copy(const Touch& other) + { + x = other.x; + y = other.y; + time = other.time; + } + + int64_t time = -1; // in nanoseconds +}; + +/*! + * \ingroup touch + * \brief A class representing a touch pointer interaction consisting of an + * down touch, the last touch and the current touch. + */ +class Pointer +{ +public: + Pointer() { reset(); } + + /*! + * \brief Resets the pointer and all its touches + */ + void reset() + { + down = {}; + last = {}; + moving = false; + size = 0.0f; + } + + /*! + * \brief Checks if the "down" touch is valid + * + * \return True if the "down" touch is valid otherwise false + */ + bool valid() const { return down.valid(); } + + /*! + * \brief Calculates the velocity in x/y direction using the "down" and + * either the "last" or "current" touch + * + * \param velocityX Placeholder for velocity in x direction + * \param velocityY Placeholder for velocity in y direction + * \param fromLast Whether to calculate the velocity with the "last" or + * the "current" touch + * + * \return True if the velocity is valid otherwise false + */ + bool velocity(float& velocityX, float& velocityY, bool fromLast = true) const + { + int64_t fromTime = last.time; + float fromX = last.x; + float fromY = last.y; + if (!fromLast) + { + fromTime = down.time; + fromX = down.x; + fromY = down.y; + } + + velocityX = 0.0f; // number of pixels per second + velocityY = 0.0f; // number of pixels per second + + int64_t timeDiff = current.time - fromTime; + if (timeDiff <= 0) + return false; + + velocityX = ((current.x - fromX) * 1000000000) / timeDiff; + velocityY = ((current.y - fromY) * 1000000000) / timeDiff; + return true; + } + + Touch down; + Touch last; + Touch current; + bool moving; + float size; +}; diff --git a/xbmc/input/touch/generic/CMakeLists.txt b/xbmc/input/touch/generic/CMakeLists.txt new file mode 100644 index 0000000..d5e8adc --- /dev/null +++ b/xbmc/input/touch/generic/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SOURCES GenericTouchActionHandler.cpp + GenericTouchInputHandler.cpp + GenericTouchPinchDetector.cpp + GenericTouchRotateDetector.cpp + GenericTouchSwipeDetector.cpp) + +set(HEADERS GenericTouchActionHandler.h + GenericTouchInputHandler.h + GenericTouchPinchDetector.h + GenericTouchRotateDetector.h + GenericTouchSwipeDetector.h + IGenericTouchGestureDetector.h) + +core_add_library(input_touch_generic) diff --git a/xbmc/input/touch/generic/GenericTouchActionHandler.cpp b/xbmc/input/touch/generic/GenericTouchActionHandler.cpp new file mode 100644 index 0000000..d601074 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchActionHandler.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2013-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 "GenericTouchActionHandler.h" + +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" + +#include <cmath> + +using namespace KODI::MESSAGING; + +CGenericTouchActionHandler& CGenericTouchActionHandler::GetInstance() +{ + static CGenericTouchActionHandler sTouchAction; + return sTouchAction; +} + +void CGenericTouchActionHandler::OnTouchAbort() +{ + sendEvent(ACTION_GESTURE_ABORT, 0.0f, 0.0f); +} + +bool CGenericTouchActionHandler::OnSingleTouchStart(float x, float y) +{ + focusControl(x, y); + + return true; +} + +bool CGenericTouchActionHandler::OnSingleTouchHold(float x, float y) +{ + return true; +} + +bool CGenericTouchActionHandler::OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + return true; +} + +bool CGenericTouchActionHandler::OnSingleTouchEnd(float x, float y) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchDown(float x, float y, int32_t pointer) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchHold(float x, float y, int32_t pointers /* = 2 */) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) +{ + return true; +} + +bool CGenericTouchActionHandler::OnMultiTouchUp(float x, float y, int32_t pointer) +{ + return true; +} + +bool CGenericTouchActionHandler::OnTouchGestureStart(float x, float y) +{ + sendEvent(ACTION_GESTURE_BEGIN, x, y, 0.0f, 0.0f); + + return true; +} + +bool CGenericTouchActionHandler::OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + sendEvent(ACTION_GESTURE_PAN, x, y, offsetX, offsetY, velocityX, velocityY); + + return true; +} + +bool CGenericTouchActionHandler::OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) +{ + sendEvent(ACTION_GESTURE_END, velocityX, velocityY, x, y, offsetX, offsetY); + + return true; +} + +void CGenericTouchActionHandler::OnTap(float x, float y, int32_t pointers /* = 1 */) +{ + if (pointers <= 0 || pointers > 10) + return; + + sendEvent(ACTION_TOUCH_TAP, x, y, 0.0f, 0.0f, 0.0f, 0.0f, pointers); +} + +void CGenericTouchActionHandler::OnLongPress(float x, float y, int32_t pointers /* = 1 */) +{ + if (pointers <= 0 || pointers > 10) + return; + + sendEvent(ACTION_TOUCH_LONGPRESS, x, y, 0.0f, 0.0f, 0.0f, 0.0f, pointers); +} + +void CGenericTouchActionHandler::OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers /* = 1 */) +{ + if (pointers <= 0 || pointers > 10) + return; + + int actionId = 0; + if (direction == TouchMoveDirectionLeft) + actionId = ACTION_GESTURE_SWIPE_LEFT; + else if (direction == TouchMoveDirectionRight) + actionId = ACTION_GESTURE_SWIPE_RIGHT; + else if (direction == TouchMoveDirectionUp) + actionId = ACTION_GESTURE_SWIPE_UP; + else if (direction == TouchMoveDirectionDown) + actionId = ACTION_GESTURE_SWIPE_DOWN; + else + return; + + sendEvent(actionId, xUp, yUp, velocityX, velocityY, xDown, yDown, pointers); +} + +void CGenericTouchActionHandler::OnZoomPinch(float centerX, float centerY, float zoomFactor) +{ + sendEvent(ACTION_GESTURE_ZOOM, centerX, centerY, zoomFactor, 0.0f); +} + +void CGenericTouchActionHandler::OnRotate(float centerX, float centerY, float angle) +{ + sendEvent(ACTION_GESTURE_ROTATE, centerX, centerY, angle, 0.0f); +} + +int CGenericTouchActionHandler::QuerySupportedGestures(float x, float y) +{ + CGUIMessage msg(GUI_MSG_GESTURE_NOTIFY, 0, 0, static_cast<int>(std::round(x)), + static_cast<int>(std::round(y))); + if (!CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg)) + return 0; + + int result = 0; + if (msg.GetPointer()) + { + int* p = static_cast<int*>(msg.GetPointer()); + msg.SetPointer(nullptr); + result = *p; + delete p; + } + return result; +} + +void CGenericTouchActionHandler::sendEvent(int actionId, + float x, + float y, + float x2 /* = 0.0f */, + float y2 /* = 0.0f */, + float x3, + float y3, + int pointers /* = 1 */) +{ + XBMC_Event newEvent{}; + newEvent.type = XBMC_TOUCH; + + newEvent.touch.action = actionId; + newEvent.touch.x = x; + newEvent.touch.y = y; + newEvent.touch.x2 = x2; + newEvent.touch.y2 = y2; + newEvent.touch.x3 = x3; + newEvent.touch.y3 = y3; + newEvent.touch.pointers = pointers; + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); +} + +void CGenericTouchActionHandler::focusControl(float x, float y) +{ + XBMC_Event newEvent{}; + newEvent.type = XBMC_SETFOCUS; + + newEvent.focus.x = static_cast<int>(std::round(x)); + newEvent.focus.y = static_cast<int>(std::round(y)); + + std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); +} diff --git a/xbmc/input/touch/generic/GenericTouchActionHandler.h b/xbmc/input/touch/generic/GenericTouchActionHandler.h new file mode 100644 index 0000000..e8697ce --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchActionHandler.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/ITouchActionHandler.h" + +/*! + * \ingroup touch_generic + * \brief Generic implementation of ITouchActionHandler to translate + * touch actions into XBMC specific and mappable actions. + * + * \sa ITouchActionHandler + */ +class CGenericTouchActionHandler : public ITouchActionHandler +{ +public: + /*! + \brief Get an instance of the touch input manager + */ + static CGenericTouchActionHandler& GetInstance(); + + // implementation of ITouchActionHandler + void OnTouchAbort() override; + + bool OnSingleTouchStart(float x, float y) override; + bool OnSingleTouchHold(float x, float y) override; + bool OnSingleTouchMove( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + bool OnSingleTouchEnd(float x, float y) override; + + bool OnMultiTouchDown(float x, float y, int32_t pointer) override; + bool OnMultiTouchHold(float x, float y, int32_t pointers = 2) override; + bool OnMultiTouchMove(float x, + float y, + float offsetX, + float offsetY, + float velocityX, + float velocityY, + int32_t pointer) override; + bool OnMultiTouchUp(float x, float y, int32_t pointer) override; + + bool OnTouchGestureStart(float x, float y) override; + bool OnTouchGesturePan( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + bool OnTouchGestureEnd( + float x, float y, float offsetX, float offsetY, float velocityX, float velocityY) override; + + // convenience events + void OnTap(float x, float y, int32_t pointers = 1) override; + void OnLongPress(float x, float y, int32_t pointers = 1) override; + void OnSwipe(TouchMoveDirection direction, + float xDown, + float yDown, + float xUp, + float yUp, + float velocityX, + float velocityY, + int32_t pointers = 1) override; + void OnZoomPinch(float centerX, float centerY, float zoomFactor) override; + void OnRotate(float centerX, float centerY, float angle) override; + + /*! + \brief Asks the control at the given coordinates for a list of the supported gestures. + + \param x The x coordinate (with sub-pixel) of the touch + \param y The y coordinate (with sub-pixel) of the touch + + \return EVENT_RESULT value of bitwise ORed gestures. + */ + int QuerySupportedGestures(float x, float y); + +private: + // private construction, and no assignments; use the provided singleton methods + CGenericTouchActionHandler() = default; + CGenericTouchActionHandler(const CGenericTouchActionHandler&) = delete; + CGenericTouchActionHandler const& operator=(CGenericTouchActionHandler const&) = delete; + ~CGenericTouchActionHandler() override = default; + + void sendEvent(int actionId, + float x, + float y, + float x2 = 0.0f, + float y2 = 0.0f, + float x3 = 0.0f, + float y3 = 0.0f, + int pointers = 1); + void focusControl(float x, float y); +}; diff --git a/xbmc/input/touch/generic/GenericTouchInputHandler.cpp b/xbmc/input/touch/generic/GenericTouchInputHandler.cpp new file mode 100644 index 0000000..6290860 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchInputHandler.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2012-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 "GenericTouchInputHandler.h" + +#include "input/touch/generic/GenericTouchPinchDetector.h" +#include "input/touch/generic/GenericTouchRotateDetector.h" +#include "input/touch/generic/GenericTouchSwipeDetector.h" +#include "utils/log.h" + +#include <algorithm> +#include <cmath> +#include <mutex> + +using namespace std::chrono_literals; + +namespace +{ +constexpr auto TOUCH_HOLD_TIMEOUT = 500ms; +} + +CGenericTouchInputHandler::CGenericTouchInputHandler() : m_holdTimer(new CTimer(this)) +{ +} + +CGenericTouchInputHandler::~CGenericTouchInputHandler() = default; + +CGenericTouchInputHandler& CGenericTouchInputHandler::GetInstance() +{ + static CGenericTouchInputHandler sTouchInput; + return sTouchInput; +} + +float CGenericTouchInputHandler::AdjustPointerSize(float size) +{ + if (size > 0.0f) + return size; + else + // Set a default size if touch input layer does not have anything useful, + // approx. 3.2mm + return m_dpi / 8.0f; +} + +bool CGenericTouchInputHandler::HandleTouchInput(TouchInput event, + float x, + float y, + int64_t time, + int32_t pointer /* = 0 */, + float size /* = 0.0f */) +{ + if (time < 0 || pointer < 0 || pointer >= MAX_POINTERS) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + + bool result = true; + + m_pointers[pointer].current.x = x; + m_pointers[pointer].current.y = y; + m_pointers[pointer].current.time = time; + + switch (event) + { + case TouchInputAbort: + { + triggerDetectors(event, pointer); + + setGestureState(TouchGestureUnknown); + for (auto& pointer : m_pointers) + pointer.reset(); + + OnTouchAbort(); + break; + } + + case TouchInputDown: + { + m_pointers[pointer].down.x = x; + m_pointers[pointer].down.y = y; + m_pointers[pointer].down.time = time; + m_pointers[pointer].moving = false; + m_pointers[pointer].size = AdjustPointerSize(size); + + // If this is the down event of the primary pointer + // we start by assuming that it's a single touch + if (pointer == 0) + { + // create new gesture detectors + m_detectors.emplace(new CGenericTouchSwipeDetector(this, m_dpi)); + m_detectors.emplace(new CGenericTouchPinchDetector(this, m_dpi)); + m_detectors.emplace(new CGenericTouchRotateDetector(this, m_dpi)); + triggerDetectors(event, pointer); + + setGestureState(TouchGestureSingleTouch); + result = OnSingleTouchStart(x, y); + + m_holdTimer->Start(TOUCH_HOLD_TIMEOUT); + } + // Otherwise it's the down event of another pointer + else + { + triggerDetectors(event, pointer); + + // If we so far assumed single touch or still have the primary + // pointer of a previous multi touch pressed down, we can update to multi touch + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold || + m_gestureState == TouchGestureMultiTouchDone) + { + result = OnMultiTouchDown(x, y, pointer); + m_holdTimer->Stop(true); + + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold) + m_holdTimer->Start(TOUCH_HOLD_TIMEOUT); + + setGestureState(TouchGestureMultiTouchStart); + } + // Otherwise we should ignore this pointer + else + { + m_pointers[pointer].reset(); + break; + } + } + return result; + } + + case TouchInputUp: + { + // unexpected event => abort + if (!m_pointers[pointer].valid() || m_gestureState == TouchGestureUnknown) + break; + + triggerDetectors(event, pointer); + + m_holdTimer->Stop(false); + + // Just a single tap with a pointer + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold) + { + result = OnSingleTouchEnd(x, y); + + if (m_gestureState == TouchGestureSingleTouch) + OnTap(x, y, 1); + } + // A pan gesture started with a single pointer (ignoring any other pointers) + else if (m_gestureState == TouchGesturePan) + { + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + m_pointers[pointer].velocity(velocityX, velocityY, false); + + result = OnTouchGestureEnd(x, y, x - m_pointers[pointer].down.x, + y - m_pointers[pointer].down.y, velocityX, velocityY); + } + // we are in multi-touch + else + result = OnMultiTouchUp(x, y, pointer); + + // If we were in multi touch mode and lifted one pointer + // we can go into the TouchGestureMultiTouchDone state which will allow + // the user to go back into multi touch mode without lifting the primary pointer + if (m_gestureState == TouchGestureMultiTouchStart || + m_gestureState == TouchGestureMultiTouchHold || m_gestureState == TouchGestureMultiTouch) + { + setGestureState(TouchGestureMultiTouchDone); + + // after lifting the primary pointer, the secondary pointer will + // become the primary pointer in the next event + if (pointer == 0) + { + m_pointers[0] = m_pointers[1]; + pointer = 1; + } + } + // Otherwise abort + else + { + if (m_gestureState == TouchGestureMultiTouchDone) + { + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + m_pointers[pointer].velocity(velocityX, velocityY, false); + + result = OnTouchGestureEnd(x, y, x - m_pointers[pointer].down.x, + y - m_pointers[pointer].down.y, velocityX, velocityY); + + // if neither of the two pointers moved we have a single tap with multiple pointers + if (m_gestureStateOld != TouchGestureMultiTouchHold && + m_gestureStateOld != TouchGestureMultiTouch) + OnTap(std::abs((m_pointers[0].down.x + m_pointers[1].down.x) / 2), + std::abs((m_pointers[0].down.y + m_pointers[1].down.y) / 2), 2); + } + + setGestureState(TouchGestureUnknown); + } + m_pointers[pointer].reset(); + + return result; + } + + case TouchInputMove: + { + // unexpected event => abort + if (!m_pointers[pointer].valid() || m_gestureState == TouchGestureUnknown || + m_gestureState == TouchGestureMultiTouchDone) + break; + + bool moving = std::any_of(m_pointers.cbegin(), m_pointers.cend(), + [](Pointer const& p) { return p.valid() && p.moving; }); + + if (moving) + { + m_holdTimer->Stop(); + + // the touch is moving so we start a gesture + if (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureMultiTouchStart) + result = OnTouchGestureStart(m_pointers[pointer].down.x, m_pointers[pointer].down.y); + } + + triggerDetectors(event, pointer); + + // Check if the touch has moved far enough to count as movement + if ((m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureMultiTouchStart) && + !m_pointers[pointer].moving) + break; + + if (m_gestureState == TouchGestureSingleTouch) + { + m_pointers[pointer].last.copy(m_pointers[pointer].down); + setGestureState(TouchGesturePan); + } + else if (m_gestureState == TouchGestureMultiTouchStart) + { + setGestureState(TouchGestureMultiTouch); + + // set the starting point + saveLastTouch(); + } + + float offsetX = x - m_pointers[pointer].last.x; + float offsetY = y - m_pointers[pointer].last.y; + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + m_pointers[pointer].velocity(velocityX, velocityY); + + if (m_pointers[pointer].moving && + (m_gestureState == TouchGestureSingleTouch || + m_gestureState == TouchGestureSingleTouchHold || m_gestureState == TouchGesturePan)) + result = OnSingleTouchMove(x, y, offsetX, offsetY, velocityX, velocityY); + + // Let's see if we have a pan gesture (i.e. the primary and only pointer moving) + if (m_gestureState == TouchGesturePan) + { + result = OnTouchGesturePan(x, y, offsetX, offsetY, velocityX, velocityY); + + m_pointers[pointer].last.x = x; + m_pointers[pointer].last.y = y; + } + else if (m_gestureState == TouchGestureMultiTouch) + { + if (moving) + result = OnMultiTouchMove(x, y, offsetX, offsetY, velocityX, velocityY, pointer); + } + else + break; + + return result; + } + + default: + CLog::Log(LOGDEBUG, "CGenericTouchInputHandler: unknown TouchInput"); + break; + } + + return false; +} + +bool CGenericTouchInputHandler::UpdateTouchPointer( + int32_t pointer, float x, float y, int64_t time, float size /* = 0.0f */) +{ + if (pointer < 0 || pointer >= MAX_POINTERS) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + + m_pointers[pointer].last.copy(m_pointers[pointer].current); + + m_pointers[pointer].current.x = x; + m_pointers[pointer].current.y = y; + m_pointers[pointer].current.time = time; + m_pointers[pointer].size = AdjustPointerSize(size); + + // calculate whether the pointer has moved at all + if (!m_pointers[pointer].moving) + { + CVector down = m_pointers[pointer].down; + CVector current = m_pointers[pointer].current; + CVector distance = down - current; + + if (distance.length() > m_pointers[pointer].size) + m_pointers[pointer].moving = true; + } + + for (auto const& detector : m_detectors) + detector->OnTouchUpdate(pointer, m_pointers[pointer]); + + return true; +} + +void CGenericTouchInputHandler::saveLastTouch() +{ + for (auto& pointer : m_pointers) + pointer.last.copy(pointer.current); +} + +void CGenericTouchInputHandler::OnTimeout() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + + switch (m_gestureState) + { + case TouchGestureSingleTouch: + setGestureState(TouchGestureSingleTouchHold); + + OnSingleTouchHold(m_pointers[0].down.x, m_pointers[0].down.y); + OnLongPress(m_pointers[0].down.x, m_pointers[0].down.y, 1); + break; + + case TouchGestureMultiTouchStart: + if (!m_pointers[0].moving && !m_pointers[1].moving) + { + setGestureState(TouchGestureMultiTouchHold); + + OnMultiTouchHold(m_pointers[0].down.x, m_pointers[0].down.y); + OnLongPress(std::abs((m_pointers[0].down.x + m_pointers[1].down.x) / 2), + std::abs((m_pointers[0].down.y + m_pointers[1].down.y) / 2), 2); + } + break; + + default: + break; + } +} + +void CGenericTouchInputHandler::triggerDetectors(TouchInput event, int32_t pointer) +{ + switch (event) + { + case TouchInputAbort: + { + m_detectors.clear(); + break; + } + + case TouchInputDown: + { + for (auto const& detector : m_detectors) + detector->OnTouchDown(pointer, m_pointers[pointer]); + break; + } + + case TouchInputUp: + { + for (auto const& detector : m_detectors) + detector->OnTouchUp(pointer, m_pointers[pointer]); + break; + } + + case TouchInputMove: + { + for (auto const& detector : m_detectors) + detector->OnTouchMove(pointer, m_pointers[pointer]); + break; + } + + default: + return; + } + + for (auto it = m_detectors.begin(); it != m_detectors.end();) + { + if ((*it)->IsDone()) + it = m_detectors.erase(it); + else + it++; + } +} diff --git a/xbmc/input/touch/generic/GenericTouchInputHandler.h b/xbmc/input/touch/generic/GenericTouchInputHandler.h new file mode 100644 index 0000000..154f5f2 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchInputHandler.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012-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. + */ + +#pragma once + +#include "input/touch/ITouchInputHandler.h" +#include "input/touch/TouchTypes.h" +#include "threads/CriticalSection.h" +#include "threads/Timer.h" + +#include <array> +#include <memory> +#include <set> + +class IGenericTouchGestureDetector; + +/*! + * \ingroup touch_generic + * \brief Generic implementation of ITouchInputHandler to handle low level (raw) + * touch events and translate them into touch actions which are passed + * on to the registered ITouchActionHandler implementation. + * + * The generic implementation supports single a double touch and hold + * actions and basic gesture recognition for panning, swiping, pinching/zooming + * and rotating. + * + * \sa ITouchInputHandler + */ +class CGenericTouchInputHandler : public ITouchInputHandler, private ITimerCallback +{ +public: + /*! + \brief Get an instance of the touch input manager + */ + static CGenericTouchInputHandler& GetInstance(); + static constexpr int MAX_POINTERS = 2; + + // implementation of ITouchInputHandler + bool HandleTouchInput(TouchInput event, + float x, + float y, + int64_t time, + int32_t pointer = 0, + float size = 0.0f) override; + bool UpdateTouchPointer( + int32_t pointer, float x, float y, int64_t time, float size = 0.0f) override; + +private: + // private construction, and no assignments; use the provided singleton methods + CGenericTouchInputHandler(); + ~CGenericTouchInputHandler() override; + CGenericTouchInputHandler(const CGenericTouchInputHandler&) = delete; + CGenericTouchInputHandler const& operator=(CGenericTouchInputHandler const&) = delete; + + typedef enum + { + TouchGestureUnknown = 0, + // only primary pointer active but stationary so far + TouchGestureSingleTouch, + // primary pointer active but stationary for a certain time + TouchGestureSingleTouchHold, + // primary pointer moving + TouchGesturePan, + // at least two pointers active but stationary so far + TouchGestureMultiTouchStart, + // at least two pointers active but stationary for a certain time + TouchGestureMultiTouchHold, + // at least two pointers active and moving + TouchGestureMultiTouch, + // all but primary pointer have been lifted + TouchGestureMultiTouchDone + } TouchGestureState; + + // implementation of ITimerCallback + void OnTimeout() override; + + void saveLastTouch(); + void setGestureState(TouchGestureState gestureState) + { + m_gestureStateOld = m_gestureState; + m_gestureState = gestureState; + } + void triggerDetectors(TouchInput event, int32_t pointer); + float AdjustPointerSize(float size); + + CCriticalSection m_critical; + std::unique_ptr<CTimer> m_holdTimer; + std::array<Pointer, MAX_POINTERS> m_pointers; + std::set<std::unique_ptr<IGenericTouchGestureDetector>> m_detectors; + + TouchGestureState m_gestureState = TouchGestureUnknown; + TouchGestureState m_gestureStateOld = TouchGestureUnknown; +}; diff --git a/xbmc/input/touch/generic/GenericTouchPinchDetector.cpp b/xbmc/input/touch/generic/GenericTouchPinchDetector.cpp new file mode 100644 index 0000000..3cff00d --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchPinchDetector.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013-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 "GenericTouchPinchDetector.h" + +bool CGenericTouchPinchDetector::OnTouchDown(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + m_pointers[index] = pointer; + return true; +} + +bool CGenericTouchPinchDetector::OnTouchUp(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // after lifting the primary pointer, the secondary pointer will + // become the primary pointer in the next event + if (index == 0) + { + m_pointers[0] = m_pointers[1]; + index = 1; + } + + m_pointers[index].reset(); + + if (!m_pointers[0].valid() && !m_pointers[1].valid()) + m_done = true; + + return true; +} + +bool CGenericTouchPinchDetector::OnTouchMove(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // update the internal pointers + m_pointers[index] = pointer; + + const Pointer& primaryPointer = m_pointers[0]; + const Pointer& secondaryPointer = m_pointers[1]; + + if (!primaryPointer.valid() || !secondaryPointer.valid() || + (!primaryPointer.moving && !secondaryPointer.moving)) + return false; + + // calculate zoom/pinch + CVector primary = primaryPointer.down; + CVector secondary = secondaryPointer.down; + CVector diagonal = primary - secondary; + + float baseDiffLength = diagonal.length(); + if (baseDiffLength != 0.0f) + { + CVector primaryNow = primaryPointer.current; + CVector secondaryNow = secondaryPointer.current; + CVector diagonalNow = primaryNow - secondaryNow; + float curDiffLength = diagonalNow.length(); + + float centerX = (primary.x + secondary.x) / 2; + float centerY = (primary.y + secondary.y) / 2; + + float zoom = curDiffLength / baseDiffLength; + + OnZoomPinch(centerX, centerY, zoom); + } + + return true; +} diff --git a/xbmc/input/touch/generic/GenericTouchPinchDetector.h b/xbmc/input/touch/generic/GenericTouchPinchDetector.h new file mode 100644 index 0000000..191e950 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchPinchDetector.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/generic/IGenericTouchGestureDetector.h" + +/*! + * \ingroup touch_generic + * \brief Implementation of IGenericTouchGestureDetector to detect pinch/zoom + * gestures with at least two active touch pointers. + * + * \sa IGenericTouchGestureDetector + */ +class CGenericTouchPinchDetector : public IGenericTouchGestureDetector +{ +public: + CGenericTouchPinchDetector(ITouchActionHandler* handler, float dpi) + : IGenericTouchGestureDetector(handler, dpi) + { + } + ~CGenericTouchPinchDetector() override = default; + + bool OnTouchDown(unsigned int index, const Pointer& pointer) override; + bool OnTouchUp(unsigned int index, const Pointer& pointer) override; + bool OnTouchMove(unsigned int index, const Pointer& pointer) override; +}; diff --git a/xbmc/input/touch/generic/GenericTouchRotateDetector.cpp b/xbmc/input/touch/generic/GenericTouchRotateDetector.cpp new file mode 100644 index 0000000..79db638 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchRotateDetector.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013-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 "GenericTouchRotateDetector.h" + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795028842 +#endif + +CGenericTouchRotateDetector::CGenericTouchRotateDetector(ITouchActionHandler* handler, float dpi) + : IGenericTouchGestureDetector(handler, dpi), m_angle(0.0f) +{ +} + +bool CGenericTouchRotateDetector::OnTouchDown(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + m_pointers[index] = pointer; + m_angle = 0.0f; + return true; +} + +bool CGenericTouchRotateDetector::OnTouchUp(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // after lifting the primary pointer, the secondary pointer will + // become the primary pointer in the next event + if (index == 0) + { + m_pointers[0] = m_pointers[1]; + index = 1; + } + + m_pointers[index].reset(); + + if (!m_pointers[0].valid() && !m_pointers[1].valid()) + m_done = true; + + return true; +} + +bool CGenericTouchRotateDetector::OnTouchMove(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // update the internal pointers + m_pointers[index] = pointer; + + Pointer& primaryPointer = m_pointers[0]; + Pointer& secondaryPointer = m_pointers[1]; + + if (!primaryPointer.valid() || !secondaryPointer.valid() || + (!primaryPointer.moving && !secondaryPointer.moving)) + return false; + + CVector last = primaryPointer.last - secondaryPointer.last; + CVector current = primaryPointer.current - secondaryPointer.current; + + float length = last.length() * current.length(); + if (length != 0.0f) + { + float centerX = (primaryPointer.current.x + secondaryPointer.current.x) / 2; + float centerY = (primaryPointer.current.y + secondaryPointer.current.y) / 2; + float scalar = last.scalar(current); + float angle = acos(scalar / length) * 180.0f / static_cast<float>(M_PI); + + // make sure the result of acos is a valid number + if (angle == angle) + { + // calculate the direction of the rotation using the + // z-component of the cross-product of last and current + float direction = last.x * current.y - current.x * last.y; + if (direction < 0.0f) + m_angle -= angle; + else + m_angle += angle; + + OnRotate(centerX, centerY, m_angle); + } + } + + return true; +} + +bool CGenericTouchRotateDetector::OnTouchUpdate(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + // update the internal pointers + m_pointers[index] = pointer; + return true; +} diff --git a/xbmc/input/touch/generic/GenericTouchRotateDetector.h b/xbmc/input/touch/generic/GenericTouchRotateDetector.h new file mode 100644 index 0000000..695879a --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchRotateDetector.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/generic/IGenericTouchGestureDetector.h" + +/*! + * \ingroup touch_generic + * \brief Implementation of IGenericTouchGestureDetector to detect rotation + * gestures with at least two active touch pointers. + * + * \sa IGenericTouchGestureDetector + */ +class CGenericTouchRotateDetector : public IGenericTouchGestureDetector +{ +public: + CGenericTouchRotateDetector(ITouchActionHandler* handler, float dpi); + ~CGenericTouchRotateDetector() override = default; + + bool OnTouchDown(unsigned int index, const Pointer& pointer) override; + bool OnTouchUp(unsigned int index, const Pointer& pointer) override; + bool OnTouchMove(unsigned int index, const Pointer& pointer) override; + bool OnTouchUpdate(unsigned int index, const Pointer& pointer) override; + +private: + /*! + * \brief Angle of the detected rotation + */ + float m_angle; +}; diff --git a/xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp b/xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp new file mode 100644 index 0000000..7d772b5 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchSwipeDetector.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2013-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. + */ + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#include "GenericTouchSwipeDetector.h" + +#include <math.h> +#include <stdlib.h> + +// maximum time between touch down and up (in nanoseconds) +#define SWIPE_MAX_TIME 500000000 +// maximum swipe distance between touch down and up (in multiples of screen DPI) +#define SWIPE_MIN_DISTANCE 0.5f +// original maximum variance of the touch movement +#define SWIPE_MAX_VARIANCE 0.2f +// tangents of the maximum angle (20 degrees) the touch movement may vary in a +// direction perpendicular to the swipe direction (in radians) +// => tan(20 deg) = tan(20 * M_PI / 180) +#define SWIPE_MAX_VARIANCE_ANGLE 0.36397023f + +CGenericTouchSwipeDetector::CGenericTouchSwipeDetector(ITouchActionHandler* handler, float dpi) + : IGenericTouchGestureDetector(handler, dpi), + m_directions(TouchMoveDirectionLeft | TouchMoveDirectionRight | TouchMoveDirectionUp | + TouchMoveDirectionDown), + m_swipeDetected(false), + m_size(0) +{ +} + +bool CGenericTouchSwipeDetector::OnTouchDown(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + m_size += 1; + if (m_size > 1) + return true; + + // reset all values + m_done = false; + m_swipeDetected = false; + m_directions = TouchMoveDirectionLeft | TouchMoveDirectionRight | TouchMoveDirectionUp | + TouchMoveDirectionDown; + + return true; +} + +bool CGenericTouchSwipeDetector::OnTouchUp(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + m_size -= 1; + if (m_done) + return false; + + m_done = true; + + // check if a swipe has been detected and if it has a valid direction + if (!m_swipeDetected || m_directions == TouchMoveDirectionNone) + return false; + + // check if the swipe has been performed in the proper time span + if ((pointer.current.time - pointer.down.time) > SWIPE_MAX_TIME) + return false; + + // calculate the velocity of the swipe + float velocityX = 0.0f; // number of pixels per second + float velocityY = 0.0f; // number of pixels per second + pointer.velocity(velocityX, velocityY, false); + + // call the OnSwipe() callback + OnSwipe((TouchMoveDirection)m_directions, pointer.down.x, pointer.down.y, pointer.current.x, + pointer.current.y, velocityX, velocityY, m_size + 1); + return true; +} + +bool CGenericTouchSwipeDetector::OnTouchMove(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + // only handle swipes of moved pointers + if (index >= m_size || m_done || !pointer.moving) + return false; + + float deltaXmovement = pointer.current.x - pointer.last.x; + float deltaYmovement = pointer.current.y - pointer.last.y; + + if (deltaXmovement > 0.0f) + m_directions &= ~TouchMoveDirectionLeft; + else if (deltaXmovement < 0.0f) + m_directions &= ~TouchMoveDirectionRight; + + if (deltaYmovement > 0.0f) + m_directions &= ~TouchMoveDirectionUp; + else if (deltaYmovement < 0.0f) + m_directions &= ~TouchMoveDirectionDown; + + if (m_directions == TouchMoveDirectionNone) + { + m_done = true; + return false; + } + + float deltaXabs = fabs(pointer.current.x - pointer.down.x); + float deltaYabs = fabs(pointer.current.y - pointer.down.y); + float varXabs = deltaYabs * SWIPE_MAX_VARIANCE_ANGLE + (m_dpi * SWIPE_MAX_VARIANCE) / 2; + float varYabs = deltaXabs * SWIPE_MAX_VARIANCE_ANGLE + (m_dpi * SWIPE_MAX_VARIANCE) / 2; + + if (m_directions & TouchMoveDirectionLeft) + { + // check if the movement went too much in Y direction + if (deltaYabs > varYabs) + m_directions &= ~TouchMoveDirectionLeft; + // check if the movement went far enough in the X direction + else if (deltaXabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions & TouchMoveDirectionRight) + { + // check if the movement went too much in Y direction + if (deltaYabs > varYabs) + m_directions &= ~TouchMoveDirectionRight; + // check if the movement went far enough in the X direction + else if (deltaXabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions & TouchMoveDirectionUp) + { + // check if the movement went too much in X direction + if (deltaXabs > varXabs) + m_directions &= ~TouchMoveDirectionUp; + // check if the movement went far enough in the Y direction + else if (deltaYabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions & TouchMoveDirectionDown) + { + // check if the movement went too much in X direction + if (deltaXabs > varXabs) + m_directions &= ~TouchMoveDirectionDown; + // check if the movement went far enough in the Y direction + else if (deltaYabs > m_dpi * SWIPE_MIN_DISTANCE) + m_swipeDetected = true; + } + + if (m_directions == TouchMoveDirectionNone) + { + m_done = true; + return false; + } + + return true; +} + +bool CGenericTouchSwipeDetector::OnTouchUpdate(unsigned int index, const Pointer& pointer) +{ + if (index >= MAX_POINTERS) + return false; + + if (m_done) + return true; + + return OnTouchMove(index, pointer); +} diff --git a/xbmc/input/touch/generic/GenericTouchSwipeDetector.h b/xbmc/input/touch/generic/GenericTouchSwipeDetector.h new file mode 100644 index 0000000..7fe88b1 --- /dev/null +++ b/xbmc/input/touch/generic/GenericTouchSwipeDetector.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/generic/IGenericTouchGestureDetector.h" + +/*! + * \ingroup touch_generic + * \brief Implementation of IGenericTouchGestureDetector to detect swipe + * gestures in any direction. + * + * \sa IGenericTouchGestureDetector + */ +class CGenericTouchSwipeDetector : public IGenericTouchGestureDetector +{ +public: + CGenericTouchSwipeDetector(ITouchActionHandler* handler, float dpi); + ~CGenericTouchSwipeDetector() override = default; + + bool OnTouchDown(unsigned int index, const Pointer& pointer) override; + bool OnTouchUp(unsigned int index, const Pointer& pointer) override; + bool OnTouchMove(unsigned int index, const Pointer& pointer) override; + bool OnTouchUpdate(unsigned int index, const Pointer& pointer) override; + +private: + /*! + * \brief Swipe directions that are still possible to detect + * + * The directions are stored as a combination (bitwise OR) of + * TouchMoveDirection enum values + * + * \sa TouchMoveDirection + */ + unsigned int m_directions; + /*! + * \brief Whether a swipe gesture has been detected or not + */ + bool m_swipeDetected; + /*! + * \brief Number of active pointers + */ + unsigned int m_size; +}; diff --git a/xbmc/input/touch/generic/IGenericTouchGestureDetector.h b/xbmc/input/touch/generic/IGenericTouchGestureDetector.h new file mode 100644 index 0000000..7e2cb88 --- /dev/null +++ b/xbmc/input/touch/generic/IGenericTouchGestureDetector.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2013-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. + */ + +#pragma once + +#include "input/touch/ITouchInputHandling.h" +#include "input/touch/TouchTypes.h" + +#include <array> + +/*! + * \ingroup touch_generic + * \brief Interface defining methods to perform gesture recognition + */ +class IGenericTouchGestureDetector : public ITouchInputHandling +{ +public: + IGenericTouchGestureDetector(ITouchActionHandler* handler, float dpi) : m_done(false), m_dpi(dpi) + { + RegisterHandler(handler); + } + ~IGenericTouchGestureDetector() override = default; + static constexpr int MAX_POINTERS = 2; + + /*! + * \brief Check whether the gesture recognition is finished or not + * + * \return True if the gesture recognition is finished otherwise false + */ + bool IsDone() { return m_done; } + + /*! + * \brief A new touch pointer has been recognised. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchDown(unsigned int index, const Pointer& pointer) = 0; + /*! + * \brief An active touch pointer has vanished. + * + * If the first touch pointer is lifted and there are more active touch + * pointers, the remaining pointers change their index. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchUp(unsigned int index, const Pointer& pointer) { return false; } + /*! + * \brief An active touch pointer has moved. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchMove(unsigned int index, const Pointer& pointer) { return false; } + /*! + * \brief An active touch pointer's values have been updated but no event has + * occurred. + * + * \param index Index of the given touch pointer + * \param pointer Touch pointer that has changed + * + * \return True if the event was handled otherwise false + */ + virtual bool OnTouchUpdate(unsigned int index, const Pointer& pointer) { return false; } + +protected: + /*! + * \brief Whether the gesture recognition is finished or not + */ + bool m_done; + /*! + * \brief DPI value of the touch screen + */ + float m_dpi; + /*! + * \brief Local list of all known touch pointers + */ + std::array<Pointer, MAX_POINTERS> m_pointers; +}; |