summaryrefslogtreecommitdiffstats
path: root/xbmc/network/EventClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/network/EventClient.cpp')
-rw-r--r--xbmc/network/EventClient.cpp788
1 files changed, 788 insertions, 0 deletions
diff --git a/xbmc/network/EventClient.cpp b/xbmc/network/EventClient.cpp
new file mode 100644
index 0000000..d3b67dc
--- /dev/null
+++ b/xbmc/network/EventClient.cpp
@@ -0,0 +1,788 @@
+/*
+ * 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 <map>
+#include <mutex>
+#include <queue>
+
+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); // <num>:joyname
+ m_iControllerNumber = (unsigned char)(*(m_joystickName.c_str()))
+ - (unsigned char)'0'; // convert <num> to int
+ m_joystickName = m_joystickName.substr(2); // extract joyname
+ }
+
+ if (m_mapName.length() > 3 &&
+ (StringUtils::StartsWith(m_mapName, "CC")) ) // custom map - CC:<controllerName>
+ {
+ m_customControllerName = m_mapName.substr(3);
+ }
+ }
+}
+
+/************************************************************************/
+/* CEventClient */
+/************************************************************************/
+bool CEventClient::AddPacket(std::unique_ptr<CEventPacket> 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<uint8_t> 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<CCriticalSection> 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<CCriticalSection> 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<CEventButtonState>::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<CEventButtonState>::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<CCriticalSection> 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<CCriticalSection> 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<CCriticalSection> 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<CCriticalSection> 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<CCriticalSection> 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<CEventButtonState> repeat;
+ std::list<CEventButtonState>::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<CCriticalSection> 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<std::chrono::steady_clock>& 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;
+}