summaryrefslogtreecommitdiffstats
path: root/xbmc/input/joysticks/keymaps/KeyHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/input/joysticks/keymaps/KeyHandler.cpp')
-rw-r--r--xbmc/input/joysticks/keymaps/KeyHandler.cpp290
1 files changed, 290 insertions, 0 deletions
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;
+}