diff options
Diffstat (limited to 'src/network/EventClient.cpp')
-rw-r--r-- | src/network/EventClient.cpp | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/src/network/EventClient.cpp b/src/network/EventClient.cpp new file mode 100644 index 0000000000..85b291495b --- /dev/null +++ b/src/network/EventClient.cpp @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "threads/SystemClock.h" +#include "system.h" + +#ifdef HAS_EVENT_SERVER + +#include "EventClient.h" +#include "EventPacket.h" +#include "threads/SingleLock.h" +#include "input/ButtonTranslator.h" +#include <map> +#include <queue> +#include "filesystem/File.h" +#include "utils/log.h" +#include "utils/TimeUtils.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GraphicContext.h" +#include "guilib/Key.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" + +using namespace EVENTCLIENT; +using namespace EVENTPACKET; +using namespace std; + + +struct ButtonStateFinder +{ + 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; + string m_map; + string m_button; +}; + +/************************************************************************/ +/* CEventButtonState */ +/************************************************************************/ +void CEventButtonState::Load() +{ + if ( m_iKeyCode == 0 ) + { + if ( (m_mapName.length() > 0) && (m_buttonName.length() > 0) ) + { + if ( m_mapName.compare("KB") == 0 ) // standard keyboard map + { + m_iKeyCode = CButtonTranslator::TranslateKeyboardString( m_buttonName.c_str() ); + } + else if ( m_mapName.compare("XG") == 0 ) // xbox gamepad map + { + m_iKeyCode = CButtonTranslator::TranslateGamepadString( m_buttonName.c_str() ); + } + else if ( m_mapName.compare("R1") == 0 ) // xbox remote map + { + m_iKeyCode = CButtonTranslator::TranslateRemoteString( m_buttonName.c_str() ); + } + else if ( m_mapName.compare("R2") == 0 ) // xbox unviversal remote map + { + m_iKeyCode = CButtonTranslator::TranslateUniversalRemoteString( m_buttonName.c_str() ); + } + else if ( (m_mapName.length() > 3) && + (StringUtils::StartsWith(m_mapName, "LI:")) ) // starts with LI: ? + { +#if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE) + string lircDevice = m_mapName.substr(3); + m_iKeyCode = CButtonTranslator::GetInstance().TranslateLircRemoteString( lircDevice.c_str(), + m_buttonName.c_str() ); +#else + CLog::Log(LOGERROR, "ES: LIRC support not enabled"); +#endif + } + else + { + Reset(); // disable key since its invalid + CLog::Log(LOGERROR, "ES: Could not map %s : %s to a key", m_mapName.c_str(), + m_buttonName.c_str()); + } + } + } + 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 + } + } +} + +/************************************************************************/ +/* CEventClient */ +/************************************************************************/ +bool CEventClient::AddPacket(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 (%d) as previous packet from eventclient %s", packet->Sequence(), m_deviceName.c_str()); + m_bSequenceError = true; + delete m_seqPackets[ packet->Sequence() ]; + } + + m_seqPackets[ packet->Sequence() ] = packet; + if (m_seqPackets.size() == packet->Size()) + { + unsigned int iSeqPayloadSize = 0; + for (unsigned int i = 1 ; i<=packet->Size() ; i++) + { + iSeqPayloadSize += m_seqPackets[i]->PayloadSize(); + } + unsigned int offset = 0; + void *newPayload = NULL; + newPayload = malloc(iSeqPayloadSize); + if (newPayload) + { + unsigned char *payloadPtr = (unsigned char *)newPayload; + unsigned int packets = packet->Size(); // packet can be deleted in this loop + for (unsigned int i = 1 ; i<=packets ; i++) + { + memcpy((void*)(payloadPtr + offset), m_seqPackets[i]->Payload(), + m_seqPackets[i]->PayloadSize()); + offset += m_seqPackets[i]->PayloadSize(); + if (i>1) + { + delete m_seqPackets[i]; + m_seqPackets[i] = NULL; + } + } + m_seqPackets[1]->SetPayload(iSeqPayloadSize, newPayload); + m_readyPackets.push(m_seqPackets[1]); + m_seqPackets.clear(); + } + else + { + CLog::Log(LOGERROR, "ES: Could not assemble packets, Out of Memory"); + FreePacketQueues(); + return false; + } + } + } + else + { + m_readyPackets.push(packet); + } + return true; +} + +void CEventClient::ProcessEvents() +{ + if (m_readyPackets.size() > 0) + { + while ( ! m_readyPackets.empty() ) + { + ProcessPacket( m_readyPackets.front() ); + if ( ! m_readyPackets.empty() ) // in case the BYE packet cleared the queues + { + delete m_readyPackets.front(); + m_readyPackets.pop(); + } + } + } +} + +bool CEventClient::GetNextAction(CEventAction &action) +{ + CSingleLock lock(m_critSection); + if (m_actionQueue.size() > 0) + { + // 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 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(LOGNOTICE, "ES: Incoming connection from %s", m_deviceName.c_str()); + + // 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 + 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.c_str()); + } + else + { + CGUIDialogKaiToast::QueueNotification(iconfile.c_str(), + g_localizeStrings.Get(33200), + m_deviceName.c_str()); + } + 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(); + + 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_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 */ + CSingleLock 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; + } + + 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 referse iteratator */ + 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.0) + 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 > 0) + m_buttonQueue.erase(it2); + else + it2->m_bRepeat = false; + } + + } + else if(active && !it->m_bActive) + { + m_buttonQueue.push_back(state); + if(!state.m_bRepeat && state.m_bAxis && state.m_fAmount != 0.0) + { + 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 + { + CSingleLock 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 = 0; + 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.0) + { + 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; + + { + CSingleLock 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(); + 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 + 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.c_str(), + message.c_str()); + } + else + { + CGUIDialogKaiToast::QueueNotification(iconfile.c_str(), + title.c_str(), + message.c_str()); + } + return true; +} + +bool CEventClient::OnPacketLOG(CEventPacket *packet) +{ + unsigned char *payload = (unsigned char *)packet->Payload(); + int psize = (int)packet->PayloadSize(); + string logmsg; + unsigned char ltype; + + if (!ParseByte(payload, psize, ltype)) + return false; + if (!ParseString(payload, psize, logmsg)) + return false; + + CLog::Log((int)ltype, "%s", logmsg.c_str()); + return true; +} + +bool CEventClient::OnPacketACTION(CEventPacket *packet) +{ + unsigned char *payload = (unsigned char *)packet->Payload(); + int psize = (int)packet->PayloadSize(); + 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: + { + CSingleLock lock(m_critSection); + m_actionQueue.push(CEventAction(actionString.c_str(), actionType)); + } + break; + + default: + CLog::Log(LOGDEBUG, "ES: Failed - ActionType: %i ActionString: %s", actionType, actionString.c_str()); + return false; + break; + } + return true; +} + +bool CEventClient::ParseString(unsigned char* &payload, int &psize, 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() +{ + CSingleLock lock(m_critSection); + while ( ! m_readyPackets.empty() ) + { + delete m_readyPackets.front(); + m_readyPackets.pop(); + } + + map<unsigned int, EVENTPACKET::CEventPacket*>::iterator iter = m_seqPackets.begin(); + while (iter != m_seqPackets.end()) + { + if (iter->second) + { + delete iter->second; + } + iter++; + } + m_seqPackets.clear(); +} + +unsigned int CEventClient::GetButtonCode(string& joystickName, bool& isAxis, float& amount) +{ + CSingleLock lock(m_critSection); + unsigned int bcode = 0; + + if ( m_currentButton.Active() ) + { + bcode = m_currentButton.KeyCode(); + joystickName = m_currentButton.JoystickName(); + 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; + + + list<CEventButtonState> repeat; + list<CEventButtonState>::iterator it; + for(it = m_buttonQueue.begin(); bcode == 0 && it != m_buttonQueue.end(); it++) + { + bcode = it->KeyCode(); + joystickName = it->JoystickName(); + 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) +{ + CSingleLock lock(m_critSection); + if (m_bMouseMoved) + { + x = (float)((m_iMouseX / 65535.0f) * g_graphicsContext.GetWidth()); + y = (float)((m_iMouseY / 65535.0f) * g_graphicsContext.GetHeight()); + m_bMouseMoved = false; + return true; + } + return false; +} + +bool CEventClient::CheckButtonRepeat(unsigned int &next) +{ + unsigned int now = XbmcThreads::SystemClockMillis(); + + if ( next == 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; +} + +#endif // HAS_EVENT_SERVER |