/*
* Copyright (C) 2008-2015 Team Kodi
* http://kodi.tv
*
* 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 Kodi; see the file COPYING. If not, see
* .
*
*/
#pragma once
#include
#include
#include
#include
#ifdef _WIN32
#include
#else
#include
#include
#include
#include
#endif
#include
#include
#include
#include
#define STD_PORT 9777
#define MS_ABSOLUTE 0x01
//#define MS_RELATIVE 0x02
#define BTN_USE_NAME 0x01
#define BTN_DOWN 0x02
#define BTN_UP 0x04
#define BTN_USE_AMOUNT 0x08
#define BTN_QUEUE 0x10
#define BTN_NO_REPEAT 0x20
#define BTN_VKEY 0x40
#define BTN_AXIS 0x80
#define PT_HELO 0x01
#define PT_BYE 0x02
#define PT_BUTTON 0x03
#define PT_MOUSE 0x04
#define PT_PING 0x05
#define PT_BROADCAST 0x06
#define PT_NOTIFICATION 0x07
#define PT_BLOB 0x08
#define PT_LOG 0x09
#define PT_ACTION 0x0A
#define PT_DEBUG 0xFF
#define ICON_NONE 0x00
#define ICON_JPEG 0x01
#define ICON_PNG 0x02
#define ICON_GIF 0x03
#define MAX_PACKET_SIZE 1024
#define HEADER_SIZE 32
#define MAX_PAYLOAD_SIZE (MAX_PACKET_SIZE - HEADER_SIZE)
#define MAJOR_VERSION 2
#define MINOR_VERSION 0
#define LOGDEBUG 0
#define LOGINFO 1
#define LOGNOTICE 2
#define LOGWARNING 3
#define LOGERROR 4
#define LOGSEVERE 5
#define LOGFATAL 6
#define LOGNONE 7
#define ACTION_EXECBUILTIN 0x01
#define ACTION_BUTTON 0x02
class CAddress
{
private:
struct sockaddr_in m_Addr;
public:
CAddress(int Port = STD_PORT)
{
m_Addr.sin_family = AF_INET;
m_Addr.sin_port = htons(Port);
m_Addr.sin_addr.s_addr = INADDR_ANY;
memset(m_Addr.sin_zero, '\0', sizeof m_Addr.sin_zero);
}
CAddress(const char *Address, int Port = STD_PORT)
{
m_Addr.sin_port = htons(Port);
struct hostent *h;
if (Address == NULL || (h=gethostbyname(Address)) == NULL)
{
if (Address != NULL)
printf("Error: Get host by name\n");
m_Addr.sin_addr.s_addr = INADDR_ANY;
m_Addr.sin_family = AF_INET;
}
else
{
m_Addr.sin_family = h->h_addrtype;
m_Addr.sin_addr = *((struct in_addr *)h->h_addr);
}
memset(m_Addr.sin_zero, '\0', sizeof m_Addr.sin_zero);
}
void SetPort(int port)
{
m_Addr.sin_port = htons(port);
}
const sockaddr *GetAddress()
{
return ((struct sockaddr *)&m_Addr);
}
bool Bind(int Sockfd)
{
return (bind(Sockfd, (struct sockaddr *)&m_Addr, sizeof m_Addr) == 0);
}
};
class XBMCClientUtils
{
public:
XBMCClientUtils() = default;
~XBMCClientUtils() = default;
static unsigned int GetUniqueIdentifier()
{
static time_t id = time(NULL);
return id;
}
static void Clean()
{
#ifdef _WIN32
WSACleanup();
#endif
}
static bool Initialize()
{
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1, 1), &wsaData))
return false;
#endif
return true;
}
};
class CPacket
{
/* Base class that implements a single event packet.
- Generic packet structure (maximum 1024 bytes per packet)
- Header is 32 bytes long, so 992 bytes available for payload
- large payloads can be split into multiple packets using H4 and H5
H5 should contain total no. of packets in such a case
- H6 contains length of P1, which is limited to 992 bytes
- if H5 is 0 or 1, then H4 will be ignored (single packet msg)
- H7 must be set to zeros for now
-----------------------------
| -H1 Signature ("XBMC") | - 4 x CHAR 4B
| -H2 Version (eg. 2.0) | - 2 x UNSIGNED CHAR 2B
| -H3 PacketType | - 1 x UNSIGNED SHORT 2B
| -H4 Sequence number | - 1 x UNSIGNED LONG 4B
| -H5 No. of packets in msg | - 1 x UNSIGNED LONG 4B
| -H6 Payload size | - 1 x UNSIGNED SHORT 2B
| -H7 Client's unique token | - 1 x UNSIGNED LONG 4B
| -H8 Reserved | - 10 x UNSIGNED CHAR 10B
|---------------------------|
| -P1 payload | -
-----------------------------
*/
public:
CPacket()
{
m_PacketType = 0;
}
virtual ~CPacket() = default;
bool Send(int Socket, CAddress &Addr, unsigned int UID = XBMCClientUtils::GetUniqueIdentifier())
{
if (m_Payload.empty())
ConstructPayload();
bool SendSuccessful = true;
int NbrOfPackages = (m_Payload.size() / MAX_PAYLOAD_SIZE) + 1;
int Send = 0;
int Sent = 0;
int Left = m_Payload.size();
for (int Package = 1; Package <= NbrOfPackages; Package++)
{
if (Left > MAX_PAYLOAD_SIZE)
{
Send = MAX_PAYLOAD_SIZE;
Left -= Send;
}
else
{
Send = Left;
Left = 0;
}
ConstructHeader(m_PacketType, NbrOfPackages, Package, Send, UID, m_Header);
char t[MAX_PACKET_SIZE];
int i, j;
for (i = 0; i < 32; i++)
t[i] = m_Header[i];
for (j = 0; j < Send; j++)
t[(32 + j)] = m_Payload[j + Sent];
int rtn = sendto(Socket, t, (32 + Send), 0, Addr.GetAddress(), sizeof(struct sockaddr));
if (rtn != (32 + Send))
SendSuccessful = false;
Sent += Send;
}
return SendSuccessful;
}
protected:
char m_Header[HEADER_SIZE];
unsigned short m_PacketType;
std::vector m_Payload;
static void ConstructHeader(int PacketType, int NumberOfPackets, int CurrentPacket, unsigned short PayloadSize, unsigned int UniqueToken, char *Header)
{
sprintf(Header, "XBMC");
for (int i = 4; i < HEADER_SIZE; i++)
Header[i] = 0;
Header[4] = MAJOR_VERSION;
Header[5] = MINOR_VERSION;
if (CurrentPacket == 1)
{
Header[6] = ((PacketType & 0xff00) >> 8);
Header[7] = (PacketType & 0x00ff);
}
else
{
Header[6] = ((PT_BLOB & 0xff00) >> 8);
Header[7] = (PT_BLOB & 0x00ff);
}
Header[8] = ((CurrentPacket & 0xff000000) >> 24);
Header[9] = ((CurrentPacket & 0x00ff0000) >> 16);
Header[10] = ((CurrentPacket & 0x0000ff00) >> 8);
Header[11] = (CurrentPacket & 0x000000ff);
Header[12] = ((NumberOfPackets & 0xff000000) >> 24);
Header[13] = ((NumberOfPackets & 0x00ff0000) >> 16);
Header[14] = ((NumberOfPackets & 0x0000ff00) >> 8);
Header[15] = (NumberOfPackets & 0x000000ff);
Header[16] = ((PayloadSize & 0xff00) >> 8);
Header[17] = (PayloadSize & 0x00ff);
Header[18] = ((UniqueToken & 0xff000000) >> 24);
Header[19] = ((UniqueToken & 0x00ff0000) >> 16);
Header[20] = ((UniqueToken & 0x0000ff00) >> 8);
Header[21] = (UniqueToken & 0x000000ff);
}
virtual void ConstructPayload()
{ }
};
class CPacketHELO : public CPacket
{
/************************************************************************/
/* Payload format */
/* %s - device name (max 128 chars) */
/* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF ) */
/* %s - my port ( 0=>not listening ) */
/* %d - reserved1 ( 0 ) */
/* %d - reserved2 ( 0 ) */
/* XX - imagedata ( can span multiple packets ) */
/************************************************************************/
private:
std::vector m_DeviceName;
unsigned short m_IconType;
char *m_IconData;
unsigned short m_IconSize;
public:
void ConstructPayload() override
{
m_Payload.clear();
for (unsigned int i = 0; i < m_DeviceName.size(); i++)
m_Payload.push_back(m_DeviceName[i]);
m_Payload.push_back('\0');
m_Payload.push_back(m_IconType);
m_Payload.push_back(0);
m_Payload.push_back('\0');
for (int j = 0; j < 8; j++)
m_Payload.push_back(0);
for (int ico = 0; ico < m_IconSize; ico++)
m_Payload.push_back(m_IconData[ico]);
}
CPacketHELO(const char *DevName, unsigned short IconType, const char *IconFile = NULL) : CPacket()
{
m_PacketType = PT_HELO;
unsigned int len = strlen(DevName);
for (unsigned int i = 0; i < len; i++)
m_DeviceName.push_back(DevName[i]);
m_IconType = IconType;
if (IconType == ICON_NONE || IconFile == NULL)
{
m_IconData = NULL;
m_IconSize = 0;
return;
}
std::ifstream::pos_type size;
std::ifstream file (IconFile, std::ios::in|std::ios::binary|std::ios::ate);
if (file.is_open())
{
size = file.tellg();
m_IconData = new char [size];
file.seekg (0, std::ios::beg);
file.read (m_IconData, size);
file.close();
m_IconSize = size;
}
else
{
m_IconType = ICON_NONE;
m_IconSize = 0;
}
}
~CPacketHELO() override
{
m_DeviceName.clear();
delete[] m_IconData;
}
};
class CPacketNOTIFICATION : public CPacket
{
/************************************************************************/
/* Payload format: */
/* %s - caption */
/* %s - message */
/* %c - icontype ( 0=>NOICON, 1=>JPEG , 2=>PNG , 3=>GIF ) */
/* %d - reserved ( 0 ) */
/* XX - imagedata ( can span multiple packets ) */
/************************************************************************/
private:
std::vector m_Title;
std::vector m_Message;
unsigned short m_IconType;
char *m_IconData;
unsigned short m_IconSize;
public:
void ConstructPayload() override
{
m_Payload.clear();
for (unsigned int i = 0; i < m_Title.size(); i++)
m_Payload.push_back(m_Title[i]);
m_Payload.push_back('\0');
for (unsigned int i = 0; i < m_Message.size(); i++)
m_Payload.push_back(m_Message[i]);
m_Payload.push_back('\0');
m_Payload.push_back(m_IconType);
for (int i = 0; i < 4; i++)
m_Payload.push_back(0);
for (int ico = 0; ico < m_IconSize; ico++)
m_Payload.push_back(m_IconData[ico]);
}
CPacketNOTIFICATION(const char *Title, const char *Message, unsigned short IconType, const char *IconFile = NULL) : CPacket()
{
m_PacketType = PT_NOTIFICATION;
m_IconData = NULL;
m_IconSize = 0;
unsigned int len = 0;
if (Title != NULL)
{
len = strlen(Title);
for (unsigned int i = 0; i < len; i++)
m_Title.push_back(Title[i]);
}
if (Message != NULL)
{
len = strlen(Message);
for (unsigned int i = 0; i < len; i++)
m_Message.push_back(Message[i]);
}
m_IconType = IconType;
if (IconType == ICON_NONE || IconFile == NULL)
return;
std::ifstream::pos_type size;
std::ifstream file (IconFile, std::ios::in|std::ios::binary|std::ios::ate);
if (file.is_open())
{
size = file.tellg();
m_IconData = new char [size];
file.seekg (0, std::ios::beg);
file.read (m_IconData, size);
file.close();
m_IconSize = size;
}
else
m_IconType = ICON_NONE;
}
~CPacketNOTIFICATION() override
{
m_Title.clear();
m_Message.clear();
delete[] m_IconData;
}
};
class CPacketBUTTON : public CPacket
{
/************************************************************************/
/* Payload format */
/* %i - button code */
/* %i - flags 0x01 => use button map/name instead of code */
/* 0x02 => btn down */
/* 0x04 => btn up */
/* 0x08 => use amount */
/* 0x10 => queue event */
/* 0x20 => do not repeat */
/* 0x40 => virtual key */
/* 0x40 => axis key */
/* %i - amount ( 0 => 65k maps to -1 => 1 ) */
/* %s - device map (case sensitive and required if flags & 0x01) */
/* "KB" - Standard keyboard map */
/* "XG" - Xbox Gamepad */
/* "R1" - Xbox Remote */
/* "R2" - Xbox Universal Remote */
/* "LI:devicename" - valid LIRC device map where 'devicename' */
/* is the actual name of the LIRC device */
/* "JS:joyname" - valid Joystick device map where */
/* 'joyname' is the name specified in */
/* the keymap. JS only supports button code */
/* and not button name currently (!0x01). */
/* %s - button name (required if flags & 0x01) */
/************************************************************************/
private:
std::vector m_DeviceMap;
std::vector m_Button;
unsigned short m_ButtonCode;
unsigned short m_Amount;
unsigned short m_Flags;
public:
void ConstructPayload() override
{
m_Payload.clear();
if (m_Button.size() != 0)
{
if (!(m_Flags & BTN_USE_NAME)) // If the BTN_USE_NAME isn't flagged for some reason
m_Flags |= BTN_USE_NAME;
m_ButtonCode = 0;
}
else
m_Button.clear();
if (m_Amount > 0)
{
if (!(m_Flags & BTN_USE_AMOUNT))
m_Flags |= BTN_USE_AMOUNT;
}
if (!((m_Flags & BTN_DOWN) || (m_Flags & BTN_UP))) //If none of them are tagged.
m_Flags |= BTN_DOWN;
m_Payload.push_back(((m_ButtonCode & 0xff00) >> 8));
m_Payload.push_back( (m_ButtonCode & 0x00ff));
m_Payload.push_back(((m_Flags & 0xff00) >> 8) );
m_Payload.push_back( (m_Flags & 0x00ff));
m_Payload.push_back(((m_Amount & 0xff00) >> 8) );
m_Payload.push_back( (m_Amount & 0x00ff));
for (unsigned int i = 0; i < m_DeviceMap.size(); i++)
m_Payload.push_back(m_DeviceMap[i]);
m_Payload.push_back('\0');
for (unsigned int i = 0; i < m_Button.size(); i++)
m_Payload.push_back(m_Button[i]);
m_Payload.push_back('\0');
}
CPacketBUTTON(const char *Button, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0) : CPacket()
{
m_PacketType = PT_BUTTON;
m_Flags = Flags;
m_ButtonCode = 0;
m_Amount = Amount;
unsigned int len = strlen(DeviceMap);
for (unsigned int i = 0; i < len; i++)
m_DeviceMap.push_back(DeviceMap[i]);
len = strlen(Button);
for (unsigned int i = 0; i < len; i++)
m_Button.push_back(Button[i]);
}
CPacketBUTTON(unsigned short ButtonCode, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0) : CPacket()
{
m_PacketType = PT_BUTTON;
m_Flags = Flags;
m_ButtonCode = ButtonCode;
m_Amount = Amount;
unsigned int len = strlen(DeviceMap);
for (unsigned int i = 0; i < len; i++)
m_DeviceMap.push_back(DeviceMap[i]);
}
CPacketBUTTON(unsigned short ButtonCode, unsigned short Flags, unsigned short Amount = 0) : CPacket()
{
m_PacketType = PT_BUTTON;
m_Flags = Flags;
m_ButtonCode = ButtonCode;
m_Amount = Amount;
}
// Used to send a release event
CPacketBUTTON() : CPacket()
{
m_PacketType = PT_BUTTON;
m_Flags = BTN_UP;
m_Amount = 0;
m_ButtonCode = 0;
}
~CPacketBUTTON() override
{
m_DeviceMap.clear();
m_Button.clear();
}
inline unsigned short GetFlags() { return m_Flags; }
inline unsigned short GetButtonCode() { return m_ButtonCode; }
};
class CPacketPING : public CPacket
{
/************************************************************************/
/* no payload */
/************************************************************************/
public:
CPacketPING() : CPacket()
{
m_PacketType = PT_PING;
}
~CPacketPING() override = default;
};
class CPacketBYE : public CPacket
{
/************************************************************************/
/* no payload */
/************************************************************************/
public:
CPacketBYE() : CPacket()
{
m_PacketType = PT_BYE;
}
~CPacketBYE() override = default;
};
class CPacketMOUSE : public CPacket
{
/************************************************************************/
/* Payload format */
/* %c - flags */
/* - 0x01 absolute position */
/* %i - mousex (0-65535 => maps to screen width) */
/* %i - mousey (0-65535 => maps to screen height) */
/************************************************************************/
private:
unsigned short m_X;
unsigned short m_Y;
unsigned char m_Flag;
public:
CPacketMOUSE(int X, int Y, unsigned char Flag = MS_ABSOLUTE)
{
m_PacketType = PT_MOUSE;
m_Flag = Flag;
m_X = X;
m_Y = Y;
}
void ConstructPayload() override
{
m_Payload.clear();
m_Payload.push_back(m_Flag);
m_Payload.push_back(((m_X & 0xff00) >> 8));
m_Payload.push_back( (m_X & 0x00ff));
m_Payload.push_back(((m_Y & 0xff00) >> 8));
m_Payload.push_back( (m_Y & 0x00ff));
}
~CPacketMOUSE() override = default;
};
class CPacketLOG : public CPacket
{
/************************************************************************/
/* Payload format */
/* %c - log type */
/* %s - message */
/************************************************************************/
private:
std::vector m_Message;
unsigned char m_LogLevel;
bool m_AutoPrintf;
public:
CPacketLOG(int LogLevel, const char *Message, bool AutoPrintf = true)
{
m_PacketType = PT_LOG;
unsigned int len = strlen(Message);
for (unsigned int i = 0; i < len; i++)
m_Message.push_back(Message[i]);
m_LogLevel = LogLevel;
m_AutoPrintf = AutoPrintf;
}
void ConstructPayload() override
{
m_Payload.clear();
m_Payload.push_back( (m_LogLevel & 0x00ff) );
if (m_AutoPrintf)
{
char* str=&m_Message[0];
printf("%s\n", str);
}
for (unsigned int i = 0; i < m_Message.size(); i++)
m_Payload.push_back(m_Message[i]);
m_Payload.push_back('\0');
}
~CPacketLOG() override = default;
};
class CPacketACTION : public CPacket
{
/************************************************************************/
/* Payload format */
/* %c - action type */
/* %s - action message */
/************************************************************************/
private:
unsigned char m_ActionType;
std::vector m_Action;
public:
CPacketACTION(const char *Action, unsigned char ActionType = ACTION_EXECBUILTIN)
{
m_PacketType = PT_ACTION;
m_ActionType = ActionType;
unsigned int len = strlen(Action);
for (unsigned int i = 0; i < len; i++)
m_Action.push_back(Action[i]);
}
void ConstructPayload() override
{
m_Payload.clear();
m_Payload.push_back(m_ActionType);
for (unsigned int i = 0; i < m_Action.size(); i++)
m_Payload.push_back(m_Action[i]);
m_Payload.push_back('\0');
}
~CPacketACTION() override = default;
};
class CXBMCClient
{
private:
CAddress m_Addr;
int m_Socket;
unsigned int m_UID;
public:
CXBMCClient(const char *IP = "127.0.0.1", int Port = 9777, int Socket = -1, unsigned int UID = 0)
{
m_Addr = CAddress(IP, Port);
if (Socket == -1)
m_Socket = socket(AF_INET, SOCK_DGRAM, 0);
else
m_Socket = Socket;
if (UID)
m_UID = UID;
else
m_UID = XBMCClientUtils::GetUniqueIdentifier();
}
void SendNOTIFICATION(const char *Title, const char *Message, unsigned short IconType, const char *IconFile = NULL)
{
if (m_Socket < 0)
return;
CPacketNOTIFICATION notification(Title, Message, IconType, IconFile);
notification.Send(m_Socket, m_Addr, m_UID);
}
void SendHELO(const char *DevName, unsigned short IconType, const char *IconFile = NULL)
{
if (m_Socket < 0)
return;
CPacketHELO helo(DevName, IconType, IconFile);
helo.Send(m_Socket, m_Addr, m_UID);
}
void SendButton(const char *Button, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0)
{
if (m_Socket < 0)
return;
CPacketBUTTON button(Button, DeviceMap, Flags, Amount);
button.Send(m_Socket, m_Addr, m_UID);
}
void SendButton(unsigned short ButtonCode, const char *DeviceMap, unsigned short Flags, unsigned short Amount = 0)
{
if (m_Socket < 0)
return;
CPacketBUTTON button(ButtonCode, DeviceMap, Flags, Amount);
button.Send(m_Socket, m_Addr, m_UID);
}
void SendButton(unsigned short ButtonCode, unsigned Flags, unsigned short Amount = 0)
{
if (m_Socket < 0)
return;
CPacketBUTTON button(ButtonCode, Flags, Amount);
button.Send(m_Socket, m_Addr, m_UID);
}
void SendMOUSE(int X, int Y, unsigned char Flag = MS_ABSOLUTE)
{
if (m_Socket < 0)
return;
CPacketMOUSE mouse(X, Y, Flag);
mouse.Send(m_Socket, m_Addr, m_UID);
}
void SendLOG(int LogLevel, const char *Message, bool AutoPrintf = true)
{
if (m_Socket < 0)
return;
CPacketLOG log(LogLevel, Message, AutoPrintf);
log.Send(m_Socket, m_Addr, m_UID);
}
void SendACTION(const char *ActionMessage, int ActionType = ACTION_EXECBUILTIN)
{
if (m_Socket < 0)
return;
CPacketACTION action(ActionMessage, ActionType);
action.Send(m_Socket, m_Addr, m_UID);
}
};