/* * 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 "EventClient.h" #include "EventPacket.h" #include "ServiceBroker.h" #include "dialogs/GUIDialogKaiToast.h" #include "filesystem/File.h" #include "guilib/LocalizeStrings.h" #include "input/ButtonTranslator.h" #include "input/GamepadTranslator.h" #include "input/IRTranslator.h" #include "input/Key.h" #include "input/KeyboardTranslator.h" #include "utils/StringUtils.h" #include "utils/TimeUtils.h" #include "utils/log.h" #include "windowing/GraphicContext.h" #include #include #include using namespace EVENTCLIENT; using namespace EVENTPACKET; struct ButtonStateFinder { explicit ButtonStateFinder(const CEventButtonState& state) : m_keycode(state.m_iKeyCode) , m_map(state.m_mapName) , m_button(state.m_buttonName) {} bool operator()(const CEventButtonState& state) { return state.m_mapName == m_map && state.m_iKeyCode == m_keycode && state.m_buttonName == m_button; } private: unsigned short m_keycode; std::string m_map; std::string m_button; }; /************************************************************************/ /* CEventButtonState */ /************************************************************************/ void CEventButtonState::Load() { if ( m_iKeyCode == 0 ) { if ( (m_mapName.length() > 0) && (m_buttonName.length() > 0) ) { m_iKeyCode = CButtonTranslator::TranslateString(m_mapName, m_buttonName); if (m_iKeyCode == 0) { Reset(); CLog::Log(LOGERROR, "ES: Could not map {} : {} to a key", m_mapName, m_buttonName); } } } else { if (m_mapName.length() > 3 && (StringUtils::StartsWith(m_mapName, "JS")) ) { m_joystickName = m_mapName.substr(2); // :joyname m_iControllerNumber = (unsigned char)(*(m_joystickName.c_str())) - (unsigned char)'0'; // convert to int m_joystickName = m_joystickName.substr(2); // extract joyname } if (m_mapName.length() > 3 && (StringUtils::StartsWith(m_mapName, "CC")) ) // custom map - CC: { m_customControllerName = m_mapName.substr(3); } } } /************************************************************************/ /* CEventClient */ /************************************************************************/ bool CEventClient::AddPacket(std::unique_ptr packet) { if (!packet) return false; ResetTimeout(); if ( packet->Size() > 1 ) { //! @todo limit payload size if (m_seqPackets[packet->Sequence()]) { if(!m_bSequenceError) CLog::Log(LOGWARNING, "CEventClient::AddPacket - received packet with same sequence number ({}) as " "previous packet from eventclient {}", packet->Sequence(), m_deviceName); m_bSequenceError = true; m_seqPackets.erase(packet->Sequence()); } unsigned int sequence = packet->Sequence(); m_seqPackets[sequence] = std::move(packet); if (m_seqPackets.size() == m_seqPackets[sequence]->Size()) { unsigned int iSeqPayloadSize = 0; for (unsigned int i = 1; i <= m_seqPackets[sequence]->Size(); i++) { iSeqPayloadSize += m_seqPackets[i]->PayloadSize(); } std::vector newPayload(iSeqPayloadSize); auto newPayloadIter = newPayload.begin(); unsigned int packets = m_seqPackets[sequence]->Size(); // packet can be deleted in this loop for (unsigned int i = 1; i <= packets; i++) { newPayloadIter = std::copy(m_seqPackets[i]->Payload(), m_seqPackets[i]->Payload() + m_seqPackets[i]->PayloadSize(), newPayloadIter); if (i > 1) m_seqPackets.erase(i); } m_seqPackets[1]->SetPayload(newPayload); m_readyPackets.push(std::move(m_seqPackets[1])); m_seqPackets.clear(); } } else { m_readyPackets.push(std::move(packet)); } return true; } void CEventClient::ProcessEvents() { if (!m_readyPackets.empty()) { while ( ! m_readyPackets.empty() ) { ProcessPacket(m_readyPackets.front().get()); if ( ! m_readyPackets.empty() ) // in case the BYE packet cleared the queues m_readyPackets.pop(); } } } bool CEventClient::GetNextAction(CEventAction &action) { std::unique_lock lock(m_critSection); if (!m_actionQueue.empty()) { // grab the next action in line action = m_actionQueue.front(); m_actionQueue.pop(); return true; } else { // we got nothing return false; } } bool CEventClient::ProcessPacket(CEventPacket *packet) { if (!packet) return false; bool valid = false; switch (packet->Type()) { case PT_HELO: valid = OnPacketHELO(packet); break; case PT_BYE: valid = OnPacketBYE(packet); break; case PT_BUTTON: valid = OnPacketBUTTON(packet); break; case EVENTPACKET::PT_MOUSE: valid = OnPacketMOUSE(packet); break; case PT_NOTIFICATION: valid = OnPacketNOTIFICATION(packet); break; case PT_PING: valid = true; break; case PT_LOG: valid = OnPacketLOG(packet); break; case PT_ACTION: valid = OnPacketACTION(packet); break; default: CLog::Log(LOGDEBUG, "ES: Got Unknown Packet"); break; } if (valid) ResetTimeout(); return valid; } bool CEventClient::OnPacketHELO(CEventPacket *packet) { //! @todo check it last HELO packet was received less than 5 minutes back //! if so, do not show notification of connection. if (Greeted()) return false; unsigned char *payload = (unsigned char *)packet->Payload(); int psize = (int)packet->PayloadSize(); // parse device name if (!ParseString(payload, psize, m_deviceName)) return false; CLog::Log(LOGINFO, "ES: Incoming connection from {}", m_deviceName); // icon type unsigned char ltype; if (!ParseByte(payload, psize, ltype)) return false; m_eLogoType = (LogoType)ltype; // client's port (if any) unsigned short dport; if (!ParseUInt16(payload, psize, dport)) return false; m_iRemotePort = (unsigned int)dport; // 2 x reserved uint32 (8 bytes) unsigned int reserved; ParseUInt32(payload, psize, reserved); ParseUInt32(payload, psize, reserved); // image data if any std::string iconfile = "special://temp/helo"; if (m_eLogoType != LT_NONE && psize>0) { switch (m_eLogoType) { case LT_JPEG: iconfile += ".jpg"; break; case LT_GIF: iconfile += ".gif"; break; default: iconfile += ".png"; break; } XFILE::CFile file; if (!file.OpenForWrite(iconfile, true) || file.Write((const void *)payload, psize) != psize) { CLog::Log(LOGERROR, "ES: Could not write icon file"); m_eLogoType = LT_NONE; } } m_bGreeted = true; if (m_eLogoType == LT_NONE) { CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(33200), m_deviceName); } else { CGUIDialogKaiToast::QueueNotification(iconfile, g_localizeStrings.Get(33200), m_deviceName); } return true; } bool CEventClient::OnPacketBYE(CEventPacket *packet) { if (!Greeted()) return false; m_bGreeted = false; FreePacketQueues(); m_currentButton.Reset(); return true; } bool CEventClient::OnPacketBUTTON(CEventPacket *packet) { unsigned char *payload = (unsigned char *)packet->Payload(); int psize = (int)packet->PayloadSize(); std::string map, button; unsigned short flags; unsigned short bcode; unsigned short amount; // parse the button code if (!ParseUInt16(payload, psize, bcode)) return false; // parse flags if (!ParseUInt16(payload, psize, flags)) return false; // parse amount if (!ParseUInt16(payload, psize, amount)) return false; // parse the map to use if (!ParseString(payload, psize, map)) return false; // parse button name if (flags & PTB_USE_NAME) { if (!ParseString(payload, psize, button)) return false; } unsigned int keycode; if(flags & PTB_USE_NAME) keycode = 0; else if(flags & PTB_VKEY) keycode = bcode|KEY_VKEY; else if(flags & PTB_UNICODE) keycode = bcode|ES_FLAG_UNICODE; else keycode = bcode; float famount = 0; bool active = (flags & PTB_DOWN) ? true : false; if (flags & PTB_USE_NAME) CLog::Log(LOGDEBUG, "EventClient: button name \"{}\" map \"{}\" {}", button, map, active ? "pressed" : "released"); else CLog::Log(LOGDEBUG, "EventClient: button code {} {}", bcode, active ? "pressed" : "released"); if(flags & PTB_USE_AMOUNT) { if(flags & PTB_AXIS) famount = (float)amount/65535.0f*2.0f-1.0f; else famount = (float)amount/65535.0f; } else famount = (active ? 1.0f : 0.0f); if(flags & PTB_QUEUE) { /* find the last queued item of this type */ std::unique_lock lock(m_critSection); CEventButtonState state( keycode, map, button, famount, (flags & (PTB_AXIS|PTB_AXISSINGLE)) ? true : false, (flags & PTB_NO_REPEAT) ? false : true, (flags & PTB_USE_AMOUNT) ? true : false ); /* correct non active events so they work with rest of code */ if(!active) { state.m_bActive = false; state.m_bRepeat = false; state.m_fAmount = 0.0; } std::list::reverse_iterator it; it = find_if( m_buttonQueue.rbegin() , m_buttonQueue.rend(), ButtonStateFinder(state)); if(it == m_buttonQueue.rend()) { if(active) m_buttonQueue.push_back(state); } else { if(!active && it->m_bActive) { /* since modifying the list invalidates the reverse iterator */ std::list::iterator it2 = (++it).base(); /* if last event had an amount, we must resend without amount */ if (it2->m_bUseAmount && it2->m_fAmount != 0.0f) { m_buttonQueue.push_back(state); } /* if the last event was waiting for a repeat interval, it has executed already.*/ if(it2->m_bRepeat) { if (it2->m_iNextRepeat.time_since_epoch().count() > 0) { m_buttonQueue.erase(it2); } else { it2->m_bRepeat = false; it2->m_bActive = false; } } } else if(active && !it->m_bActive) { m_buttonQueue.push_back(state); if (!state.m_bRepeat && state.m_bAxis && state.m_fAmount != 0.0f) { state.m_bActive = false; state.m_bRepeat = false; state.m_fAmount = 0.0; m_buttonQueue.push_back(state); } } else it->m_fAmount = state.m_fAmount; } } else { std::unique_lock lock(m_critSection); if ( flags & PTB_DOWN ) { m_currentButton.m_iKeyCode = keycode; m_currentButton.m_mapName = map; m_currentButton.m_buttonName = button; m_currentButton.m_fAmount = famount; m_currentButton.m_bRepeat = (flags & PTB_NO_REPEAT) ? false : true; m_currentButton.m_bAxis = (flags & PTB_AXIS) ? true : false; m_currentButton.m_iNextRepeat = {}; m_currentButton.SetActive(); m_currentButton.Load(); } else { /* when a button is released that had amount, make sure * * to resend the keypress with an amount of 0 */ if ((flags & PTB_USE_AMOUNT) && m_currentButton.m_fAmount > 0.0f) { CEventButtonState state( m_currentButton.m_iKeyCode, m_currentButton.m_mapName, m_currentButton.m_buttonName, 0.0, m_currentButton.m_bAxis, false, true ); m_buttonQueue.push_back (state); } m_currentButton.Reset(); } } return true; } bool CEventClient::OnPacketMOUSE(CEventPacket *packet) { unsigned char *payload = (unsigned char *)packet->Payload(); int psize = (int)packet->PayloadSize(); unsigned char flags; unsigned short mx, my; // parse flags if (!ParseByte(payload, psize, flags)) return false; // parse x position if (!ParseUInt16(payload, psize, mx)) return false; // parse x position if (!ParseUInt16(payload, psize, my)) return false; { std::unique_lock lock(m_critSection); if ( flags & PTM_ABSOLUTE ) { m_iMouseX = mx; m_iMouseY = my; m_bMouseMoved = true; } } return true; } bool CEventClient::OnPacketNOTIFICATION(CEventPacket *packet) { unsigned char *payload = (unsigned char *)packet->Payload(); int psize = (int)packet->PayloadSize(); std::string title, message; // parse caption if (!ParseString(payload, psize, title)) return false; // parse message if (!ParseString(payload, psize, message)) return false; // icon type unsigned char ltype; if (!ParseByte(payload, psize, ltype)) return false; m_eLogoType = (LogoType)ltype; // reserved uint32 unsigned int reserved; ParseUInt32(payload, psize, reserved); // image data if any std::string iconfile = "special://temp/notification"; if (m_eLogoType != LT_NONE && psize>0) { switch (m_eLogoType) { case LT_JPEG: iconfile += ".jpg"; break; case LT_GIF: iconfile += ".gif"; break; default: iconfile += ".png"; break; } XFILE::CFile file; if (!file.OpenForWrite(iconfile, true) || file.Write((const void *)payload, psize) != psize) { CLog::Log(LOGERROR, "ES: Could not write icon file"); m_eLogoType = LT_NONE; } } if (m_eLogoType == LT_NONE) { CGUIDialogKaiToast::QueueNotification(title, message); } else { CGUIDialogKaiToast::QueueNotification(iconfile, title, message); } return true; } bool CEventClient::OnPacketLOG(CEventPacket *packet) { unsigned char *payload = (unsigned char *)packet->Payload(); int psize = (int)packet->PayloadSize(); std::string logmsg; unsigned char ltype; if (!ParseByte(payload, psize, ltype)) return false; if (!ParseString(payload, psize, logmsg)) return false; CLog::Log((int)ltype, "{}", logmsg); return true; } bool CEventClient::OnPacketACTION(CEventPacket *packet) { unsigned char *payload = (unsigned char *)packet->Payload(); int psize = (int)packet->PayloadSize(); std::string actionString; unsigned char actionType; if (!ParseByte(payload, psize, actionType)) return false; if (!ParseString(payload, psize, actionString)) return false; switch(actionType) { case AT_EXEC_BUILTIN: case AT_BUTTON: { std::unique_lock lock(m_critSection); m_actionQueue.push(CEventAction(actionString.c_str(), actionType)); } break; default: CLog::Log(LOGDEBUG, "ES: Failed - ActionType: {} ActionString: {}", actionType, actionString); return false; break; } return true; } bool CEventClient::ParseString(unsigned char* &payload, int &psize, std::string& parsedVal) { if (psize <= 0) return false; unsigned char *pos = (unsigned char *)memchr((void*)payload, (int)'\0', psize); if (!pos) return false; parsedVal = (char*)payload; psize -= ((pos - payload) + 1); payload = pos+1; return true; } bool CEventClient::ParseByte(unsigned char* &payload, int &psize, unsigned char& parsedVal) { if (psize <= 0) return false; parsedVal = *payload; payload++; psize--; return true; } bool CEventClient::ParseUInt32(unsigned char* &payload, int &psize, unsigned int& parsedVal) { if (psize < 4) return false; parsedVal = ntohl(*((unsigned int *)payload)); payload+=4; psize-=4; return true; } bool CEventClient::ParseUInt16(unsigned char* &payload, int &psize, unsigned short& parsedVal) { if (psize < 2) return false; parsedVal = ntohs(*((unsigned short *)payload)); payload+=2; psize-=2; return true; } void CEventClient::FreePacketQueues() { std::unique_lock lock(m_critSection); while ( ! m_readyPackets.empty() ) m_readyPackets.pop(); m_seqPackets.clear(); } unsigned int CEventClient::GetButtonCode(std::string& strMapName, bool& isAxis, float& amount, bool &isJoystick) { std::unique_lock lock(m_critSection); unsigned int bcode = 0; if ( m_currentButton.Active() ) { bcode = m_currentButton.KeyCode(); strMapName = m_currentButton.JoystickName(); isJoystick = true; if (strMapName.length() == 0) { strMapName = m_currentButton.CustomControllerName(); isJoystick = false; } isAxis = m_currentButton.Axis(); amount = m_currentButton.Amount(); if ( ! m_currentButton.Repeat() ) m_currentButton.Reset(); else { if ( ! CheckButtonRepeat(m_currentButton.m_iNextRepeat) ) bcode = 0; } return bcode; } if(m_buttonQueue.empty()) return 0; std::list repeat; std::list::iterator it; for(it = m_buttonQueue.begin(); bcode == 0 && it != m_buttonQueue.end(); ++it) { bcode = it->KeyCode(); strMapName = it->JoystickName(); isJoystick = true; if (strMapName.length() == 0) { strMapName = it->CustomControllerName(); isJoystick = false; } isAxis = it->Axis(); amount = it->Amount(); if(it->Repeat()) { /* MUST update m_iNextRepeat before resend */ bool skip = !it->Axis() && !CheckButtonRepeat(it->m_iNextRepeat); repeat.push_back(*it); if(skip) { bcode = 0; continue; } } } m_buttonQueue.erase(m_buttonQueue.begin(), it); m_buttonQueue.insert(m_buttonQueue.end(), repeat.begin(), repeat.end()); return bcode; } bool CEventClient::GetMousePos(float& x, float& y) { std::unique_lock lock(m_critSection); if (m_bMouseMoved) { x = (m_iMouseX / 65535.0f) * CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(); y = (m_iMouseY / 65535.0f) * CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(); m_bMouseMoved = false; return true; } return false; } bool CEventClient::CheckButtonRepeat(std::chrono::time_point& next) { auto now = std::chrono::steady_clock::now(); if (next.time_since_epoch().count() == 0) { next = now + m_iRepeatDelay; return true; } else if ( now > next ) { next = now + m_iRepeatSpeed; return true; } return false; } bool CEventClient::Alive() const { // 60 seconds timeout if ( (time(NULL) - m_lastPing) > 60 ) return false; return true; }