summaryrefslogtreecommitdiffstats
path: root/xbmc/network/EventServer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/network/EventServer.cpp')
-rw-r--r--xbmc/network/EventServer.cpp352
1 files changed, 352 insertions, 0 deletions
diff --git a/xbmc/network/EventServer.cpp b/xbmc/network/EventServer.cpp
new file mode 100644
index 0000000..3e25b59
--- /dev/null
+++ b/xbmc/network/EventServer.cpp
@@ -0,0 +1,352 @@
+/*
+ * 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 "EventServer.h"
+
+#include "EventClient.h"
+#include "EventPacket.h"
+#include "ServiceBroker.h"
+#include "Socket.h"
+#include "Zeroconf.h"
+#include "application/Application.h"
+#include "guilib/GUIAudioManager.h"
+#include "input/Key.h"
+#include "input/actions/ActionTranslator.h"
+#include "interfaces/builtins/Builtins.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+
+#include <cassert>
+#include <map>
+#include <mutex>
+#include <queue>
+
+using namespace EVENTSERVER;
+using namespace EVENTPACKET;
+using namespace EVENTCLIENT;
+using namespace SOCKETS;
+using namespace std::chrono_literals;
+
+/************************************************************************/
+/* CEventServer */
+/************************************************************************/
+std::unique_ptr<CEventServer> CEventServer::m_pInstance;
+
+CEventServer::CEventServer() : CThread("EventServer")
+{
+ m_bStop = false;
+ m_bRefreshSettings = false;
+
+ // default timeout in ms for receiving a single packet
+ m_iListenTimeout = 1000;
+}
+
+void CEventServer::RemoveInstance()
+{
+ m_pInstance.reset();
+}
+
+CEventServer* CEventServer::GetInstance()
+{
+ if (!m_pInstance)
+ m_pInstance = std::make_unique<CEventServer>();
+
+ return m_pInstance.get();
+}
+
+void CEventServer::StartServer()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_bRunning)
+ return;
+
+ // set default port
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ m_iPort = settings->GetInt(CSettings::SETTING_SERVICES_ESPORT);
+ assert(m_iPort <= 65535 && m_iPort >= 1);
+
+ // max clients
+ m_iMaxClients = settings->GetInt(CSettings::SETTING_SERVICES_ESMAXCLIENTS);
+ if (m_iMaxClients < 0)
+ {
+ CLog::Log(LOGERROR, "ES: Invalid maximum number of clients specified {}", m_iMaxClients);
+ m_iMaxClients = 20;
+ }
+
+ CThread::Create();
+}
+
+void CEventServer::StopServer(bool bWait)
+{
+ CZeroconf::GetInstance()->RemoveService("services.eventserver");
+ StopThread(bWait);
+}
+
+void CEventServer::Cleanup()
+{
+ if (m_pSocket)
+ m_pSocket->Close();
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ m_clients.clear();
+}
+
+int CEventServer::GetNumberOfClients()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_clients.size();
+}
+
+void CEventServer::Process()
+{
+ while(!m_bStop)
+ {
+ Run();
+ if (!m_bStop)
+ CThread::Sleep(1000ms);
+ }
+}
+
+void CEventServer::Run()
+{
+ CSocketListener listener;
+ int packetSize = 0;
+
+ CLog::Log(LOGINFO, "ES: Starting UDP Event server on port {}", m_iPort);
+
+ Cleanup();
+
+ // create socket and initialize buffer
+ m_pSocket = CSocketFactory::CreateUDPSocket();
+ if (!m_pSocket)
+ {
+ CLog::Log(LOGERROR, "ES: Could not create socket, aborting!");
+ return;
+ }
+
+ m_pPacketBuffer.resize(PACKET_SIZE);
+
+ // bind to IP and start listening on port
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ int port_range = settings->GetInt(CSettings::SETTING_SERVICES_ESPORTRANGE);
+ if (port_range < 1 || port_range > 100)
+ {
+ CLog::Log(LOGERROR, "ES: Invalid port range specified {}, defaulting to 10", port_range);
+ port_range = 10;
+ }
+ if (!m_pSocket->Bind(!settings->GetBool(CSettings::SETTING_SERVICES_ESALLINTERFACES), m_iPort, port_range))
+ {
+ CLog::Log(LOGERROR, "ES: Could not listen on port {}", m_iPort);
+ return;
+ }
+
+ // publish service
+ std::vector<std::pair<std::string, std::string> > txt;
+ CZeroconf::GetInstance()->PublishService("servers.eventserver",
+ "_xbmc-events._udp",
+ CSysInfo::GetDeviceName(),
+ m_iPort,
+ txt);
+
+ // add our socket to the 'select' listener
+ listener.AddSocket(m_pSocket.get());
+
+ m_bRunning = true;
+
+ while (!m_bStop)
+ {
+ try
+ {
+ // start listening until we timeout
+ if (listener.Listen(m_iListenTimeout))
+ {
+ CAddress addr;
+ if ((packetSize = m_pSocket->Read(addr, PACKET_SIZE, m_pPacketBuffer.data())) > -1)
+ {
+ ProcessPacket(addr, packetSize);
+ }
+ }
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "ES: Exception caught while listening for socket");
+ break;
+ }
+
+ // process events and queue the necessary actions and button codes
+ ProcessEvents();
+
+ // refresh client list
+ RefreshClients();
+
+ // broadcast
+ // BroadcastBeacon();
+ }
+
+ CLog::Log(LOGINFO, "ES: UDP Event server stopped");
+ m_bRunning = false;
+ Cleanup();
+}
+
+void CEventServer::ProcessPacket(CAddress& addr, int pSize)
+{
+ // check packet validity
+ std::unique_ptr<CEventPacket> packet =
+ std::make_unique<CEventPacket>(pSize, m_pPacketBuffer.data());
+ if (!packet)
+ {
+ CLog::Log(LOGERROR, "ES: Out of memory, cannot accept packet");
+ return;
+ }
+
+ unsigned int clientToken;
+
+ if (!packet->IsValid())
+ {
+ CLog::Log(LOGDEBUG, "ES: Received invalid packet");
+ return;
+ }
+
+ clientToken = packet->ClientToken();
+ if (!clientToken)
+ clientToken = addr.ULong(); // use IP if packet doesn't have a token
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // first check if we have a client for this address
+ auto iter = m_clients.find(clientToken);
+
+ if ( iter == m_clients.end() )
+ {
+ if ( m_clients.size() >= (unsigned int)m_iMaxClients)
+ {
+ CLog::Log(LOGWARNING, "ES: Cannot accept any more clients, maximum client count reached");
+ return;
+ }
+
+ // new client
+ auto client = std::make_unique<CEventClient>(addr);
+ if (!client)
+ {
+ CLog::Log(LOGERROR, "ES: Out of memory, cannot accept new client connection");
+ return;
+ }
+
+ m_clients[clientToken] = std::move(client);
+ }
+ m_clients[clientToken]->AddPacket(std::move(packet));
+}
+
+void CEventServer::RefreshClients()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto iter = m_clients.begin();
+
+ while ( iter != m_clients.end() )
+ {
+ if (! (iter->second->Alive()))
+ {
+ CLog::Log(LOGINFO, "ES: Client {} from {} timed out", iter->second->Name(),
+ iter->second->Address().Address());
+ m_clients.erase(iter);
+ iter = m_clients.begin();
+ }
+ else
+ {
+ if (m_bRefreshSettings)
+ {
+ iter->second->RefreshSettings();
+ }
+ ++iter;
+ }
+ }
+ m_bRefreshSettings = false;
+}
+
+void CEventServer::ProcessEvents()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto iter = m_clients.begin();
+
+ while (iter != m_clients.end())
+ {
+ iter->second->ProcessEvents();
+ ++iter;
+ }
+}
+
+bool CEventServer::ExecuteNextAction()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CEventAction actionEvent;
+ auto iter = m_clients.begin();
+
+ while (iter != m_clients.end())
+ {
+ if (iter->second->GetNextAction(actionEvent))
+ {
+ // Leave critical section before processing action
+ lock.unlock();
+ switch(actionEvent.actionType)
+ {
+ case AT_EXEC_BUILTIN:
+ CBuiltins::GetInstance().Execute(actionEvent.actionName);
+ break;
+
+ case AT_BUTTON:
+ {
+ unsigned int actionID;
+ CActionTranslator::TranslateString(actionEvent.actionName, actionID);
+ CAction action(actionID, 1.0f, 0.0f, actionEvent.actionName);
+ CGUIComponent* gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetAudioManager().PlayActionSound(action);
+
+ g_application.OnAction(action);
+ }
+ break;
+ }
+ return true;
+ }
+ ++iter;
+ }
+
+ return false;
+}
+
+unsigned int CEventServer::GetButtonCode(std::string& strMapName, bool& isAxis, float& fAmount, bool &isJoystick)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto iter = m_clients.begin();
+ unsigned int bcode = 0;
+
+ while (iter != m_clients.end())
+ {
+ bcode = iter->second->GetButtonCode(strMapName, isAxis, fAmount, isJoystick);
+ if (bcode)
+ return bcode;
+ ++iter;
+ }
+ return bcode;
+}
+
+bool CEventServer::GetMousePos(float &x, float &y)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto iter = m_clients.begin();
+
+ while (iter != m_clients.end())
+ {
+ if (iter->second->GetMousePos(x, y))
+ return true;
+ ++iter;
+ }
+ return false;
+}