mirror of https://github.com/xbmc/xbmc
961 lines
30 KiB
C++
961 lines
30 KiB
C++
/*
|
|
* Copyright (C) 2005-2024 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 "ServiceBroker.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/actions/Action.h"
|
|
#include "input/actions/ActionIDs.h"
|
|
#include "input/keyboard/Key.h"
|
|
#include "input/keyboard/KeyIDs.h"
|
|
#include "input/keyboard/KeyboardEasterEgg.h"
|
|
#include "input/keyboard/XBMC_vkeys.h"
|
|
#include "input/keyboard/interfaces/IKeyboardDriverHandler.h"
|
|
#include "input/keymaps/ButtonTranslator.h"
|
|
#include "input/keymaps/KeymapEnvironment.h"
|
|
#include "input/keymaps/joysticks/JoystickMapper.h"
|
|
#include "input/keymaps/remote/CustomControllerTranslator.h"
|
|
#include "input/keymaps/touch/TouchTranslator.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>
|
|
#include <unordered_map>
|
|
|
|
using EVENTSERVER::CEventServer;
|
|
|
|
using namespace KODI;
|
|
|
|
const std::string CInputManager::SETTING_INPUT_ENABLE_CONTROLLER = "input.enablejoystick";
|
|
|
|
namespace
|
|
{
|
|
const std::unordered_map<uint8_t, int> keyComposeactionEventMap = {
|
|
{XBMC_KEYCOMPOSING_COMPOSING, ACTION_KEYBOARD_COMPOSING_KEY},
|
|
{XBMC_KEYCOMPOSING_CANCELLED, ACTION_KEYBOARD_COMPOSING_KEY_CANCELLED},
|
|
{XBMC_KEYCOMPOSING_FINISHED, ACTION_KEYBOARD_COMPOSING_KEY_FINISHED}};
|
|
}
|
|
|
|
CInputManager::CInputManager()
|
|
: m_keymapEnvironment(new KEYMAP::CKeymapEnvironment),
|
|
m_buttonTranslator(new KEYMAP::CButtonTranslator),
|
|
m_customControllerTranslator(new KEYMAP::CCustomControllerTranslator),
|
|
m_touchTranslator(new KEYMAP::CTouchTranslator),
|
|
m_joystickTranslator(new KEYMAP::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());
|
|
|
|
CServiceBroker::GetSettingsComponent()->GetSettings()->RegisterCallback(
|
|
this, {CSettings::SETTING_INPUT_ENABLEMOUSE, SETTING_INPUT_ENABLE_CONTROLLER});
|
|
}
|
|
|
|
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.empty())
|
|
{
|
|
// 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, 0, wKeyID));
|
|
}
|
|
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 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 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_KEYCOMPOSING_COMPOSING:
|
|
case XBMC_KEYCOMPOSING_CANCELLED:
|
|
case XBMC_KEYCOMPOSING_FINISHED:
|
|
{
|
|
const CAction action = CAction(keyComposeactionEventMap.find(newEvent.type)->second,
|
|
static_cast<wchar_t>(newEvent.key.keysym.unicode));
|
|
ExecuteInputAction(action);
|
|
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);
|
|
}
|
|
}
|
|
|
|
const KEYMAP::IKeymapEnvironment* CInputManager::KeymapEnvironment() const
|
|
{
|
|
return m_keymapEnvironment.get();
|
|
}
|
|
|
|
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 KEYMAP::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());
|
|
}
|