diff options
author | AlTheKiller <AlTheKiller@svn> | 2009-09-23 01:49:50 +0000 |
---|---|---|
committer | AlTheKiller <AlTheKiller@svn> | 2009-09-23 01:49:50 +0000 |
commit | 45285e8a9300cd754a760560640b75b09f98035e (patch) | |
tree | ad9f093885ad5c98e9dd4156674e7691c22ed0a2 /tools/EventClients/lib |
step 3/4: Move linuxport to trunk. How'd I get roped into this?
git-svn-id: https://xbmc.svn.sourceforge.net/svnroot/xbmc/trunk@23097 568bbfeb-2a22-0410-94d2-cc84cf5bfa90
Diffstat (limited to 'tools/EventClients/lib')
22 files changed, 3569 insertions, 0 deletions
diff --git a/tools/EventClients/lib/c#/EventClient.cs b/tools/EventClients/lib/c#/EventClient.cs new file mode 100644 index 0000000000..f904dcaa93 --- /dev/null +++ b/tools/EventClients/lib/c#/EventClient.cs @@ -0,0 +1,529 @@ +using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+
+namespace XBMC
+{
+
+ public enum IconType
+ {
+ ICON_NONE = 0x00,
+ ICON_JPEG = 0x01,
+ ICON_PNG = 0x02,
+ ICON_GIF = 0x03
+ }
+
+ public enum ButtonFlagsType
+ {
+ BTN_USE_NAME = 0x01,
+ BTN_DOWN = 0x02,
+ BTN_UP = 0x04,
+ BTN_USE_AMOUNT = 0x08,
+ BTN_QUEUE = 0x10,
+ BTN_NO_REPEAT = 0x20,
+ BTN_VKEY = 0x40,
+ BTN_AXIS = 0x80
+ }
+
+ public enum MouseFlagsType
+ {
+ MS_ABSOLUTE = 0x01
+ }
+
+ public enum LogTypeEnum
+ {
+ LOGDEBUG = 0,
+ LOGINFO = 1,
+ LOGNOTICE = 2,
+ LOGWARNING = 3,
+ LOGERROR = 4,
+ LOGSEVERE = 5,
+ LOGFATAL = 6,
+ LOGNONE = 7
+ }
+
+ public enum ActionType
+ {
+ ACTION_EXECBUILTIN = 0x01,
+ ACTION_BUTTON = 0x02
+ }
+
+ public class EventClient
+ {
+
+ /************************************************************************/
+ /* Written by Peter Tribe aka EqUiNox (TeamBlackbolt) */
+ /* Based upon XBMC's xbmcclient.cpp class */
+ /************************************************************************/
+
+ private enum PacketType
+ {
+ PT_HELO = 0x01,
+ PT_BYE = 0x02,
+ PT_BUTTON = 0x03,
+ PT_MOUSE = 0x04,
+ PT_PING = 0x05,
+ PT_BROADCAST = 0x06, //Currently not implemented
+ PT_NOTIFICATION = 0x07,
+ PT_BLOB = 0x08,
+ PT_LOG = 0x09,
+ PT_ACTION = 0x0A,
+ PT_DEBUG = 0xFF //Currently not implemented
+ }
+
+ private const int STD_PORT = 9777;
+ private const int MAX_PACKET_SIZE = 1024;
+ private const int HEADER_SIZE = 32;
+ private const int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
+ private const byte MAJOR_VERSION = 2;
+ private const byte MINOR_VERSION = 0;
+
+ private uint uniqueToken;
+ private Socket socket;
+
+ public bool Connect(string Address)
+ {
+ return Connect(Address, STD_PORT, (uint)System.DateTime.Now.TimeOfDay.Milliseconds);
+ }
+
+ public bool Connect(string Address, int Port)
+ {
+ return Connect(Address, Port, (uint)System.DateTime.Now.TimeOfDay.Milliseconds);
+ }
+
+
+ public bool Connect(string Address, int Port, uint UID)
+ {
+ try
+ {
+ if (socket != null) Disconnect();
+ uniqueToken = UID;
+ socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ socket.Connect(Dns.GetHostByName(Address).AddressList[0].ToString(), Port);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public bool Connected
+ {
+ get
+ {
+ if (socket == null) return false;
+ return socket.Connected;
+ }
+ }
+
+ public void Disconnect()
+ {
+ try
+ {
+ if (socket != null)
+ {
+ socket.Shutdown(SocketShutdown.Both);
+ socket.Close();
+ socket = null;
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ private byte[] Header(PacketType PacketType, int NumberOfPackets, int CurrentPacket, int PayloadSize)
+ {
+
+ byte[] header = new byte[HEADER_SIZE];
+
+ header[0] = (byte)'X';
+ header[1] = (byte)'B';
+ header[2] = (byte)'M';
+ header[3] = (byte)'C';
+
+ header[4] = MAJOR_VERSION;
+ header[5] = MINOR_VERSION;
+
+ if (CurrentPacket == 1)
+ {
+ header[6] = (byte)(((ushort)PacketType & 0xff00) >> 8);
+ header[7] = (byte)((ushort)PacketType & 0x00ff);
+ }
+ else
+ {
+ header[6] = (byte)(((ushort)PacketType.PT_BLOB & 0xff00) >> 8);
+ header[7] = (byte)((ushort)PacketType.PT_BLOB & 0x00ff);
+ }
+
+ header[8] = (byte)((CurrentPacket & 0xff000000) >> 24);
+ header[9] = (byte)((CurrentPacket & 0x00ff0000) >> 16);
+ header[10] = (byte)((CurrentPacket & 0x0000ff00) >> 8);
+ header[11] = (byte)(CurrentPacket & 0x000000ff);
+
+ header[12] = (byte)((NumberOfPackets & 0xff000000) >> 24);
+ header[13] = (byte)((NumberOfPackets & 0x00ff0000) >> 16);
+ header[14] = (byte)((NumberOfPackets & 0x0000ff00) >> 8);
+ header[15] = (byte)(NumberOfPackets & 0x000000ff);
+
+ header[16] = (byte)((PayloadSize & 0xff00) >> 8);
+ header[17] = (byte)(PayloadSize & 0x00ff);
+
+ header[18] = (byte)((uniqueToken & 0xff000000) >> 24);
+ header[19] = (byte)((uniqueToken & 0x00ff0000) >> 16);
+ header[20] = (byte)((uniqueToken & 0x0000ff00) >> 8);
+ header[21] = (byte)(uniqueToken & 0x000000ff);
+
+ return header;
+
+ }
+
+ private bool Send(PacketType PacketType, byte[] Payload)
+ {
+ try
+ {
+
+ bool successfull = true;
+ int packetCount = (Payload.Length / MAX_PAYLOAD_SIZE) + 1;
+ int bytesToSend = 0;
+ int bytesSent = 0;
+ int bytesLeft = Payload.Length;
+
+ for (int Package = 1; Package <= packetCount; Package++)
+ {
+
+ if (bytesLeft > MAX_PAYLOAD_SIZE)
+ {
+ bytesToSend = MAX_PAYLOAD_SIZE;
+ bytesLeft -= bytesToSend;
+ }
+ else
+ {
+ bytesToSend = bytesLeft;
+ bytesLeft = 0;
+ }
+
+ byte[] header = Header(PacketType, packetCount, Package, bytesToSend);
+ byte[] packet = new byte[MAX_PACKET_SIZE];
+
+ Array.Copy(header, 0, packet, 0, header.Length);
+ Array.Copy(Payload, bytesSent, packet, header.Length, bytesToSend);
+
+ int sendSize = socket.Send(packet, header.Length + bytesToSend, SocketFlags.None);
+
+ if (sendSize != (header.Length + bytesToSend))
+ {
+ successfull = false;
+ break;
+ }
+
+ bytesSent += bytesToSend;
+
+ }
+
+ return successfull;
+
+ }
+ catch
+ {
+
+ return false;
+
+ }
+
+ }
+
+ /************************************************************************/
+ /* SendHelo - 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 ) */
+ /************************************************************************/
+ public bool SendHelo(string DevName, IconType IconType, string IconFile)
+ {
+
+ byte[] icon = new byte[0];
+ if (IconType != IconType.ICON_NONE)
+ icon = File.ReadAllBytes(IconFile);
+
+ byte[] payload = new byte[DevName.Length + 12 + icon.Length];
+
+ int offset = 0;
+
+ for (int i = 0; i < DevName.Length; i++)
+ payload[offset++] = (byte)DevName[i];
+ payload[offset++] = (byte)'\0';
+
+ payload[offset++] = (byte)IconType;
+
+ payload[offset++] = (byte)0;
+ payload[offset++] = (byte)'\0';
+
+ for (int i = 0; i < 8; i++)
+ payload[offset++] = (byte)0;
+
+ Array.Copy(icon, 0, payload, DevName.Length + 12, icon.Length);
+
+ return Send(PacketType.PT_HELO, payload);
+
+ }
+
+ public bool SendHelo(string DevName)
+ {
+ return SendHelo(DevName, IconType.ICON_NONE, "");
+ }
+
+ /************************************************************************/
+ /* SendNotification - 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 ) */
+ /************************************************************************/
+ public bool SendNotification(string Caption, string Message, IconType IconType, string IconFile)
+ {
+
+ byte[] icon = new byte[0];
+ if (IconType != IconType.ICON_NONE)
+ icon = File.ReadAllBytes(IconFile);
+
+ byte[] payload = new byte[Caption.Length + Message.Length + 7 + icon.Length];
+
+ int offset = 0;
+
+ for (int i = 0; i < Caption.Length; i++)
+ payload[offset++] = (byte)Caption[i];
+ payload[offset++] = (byte)'\0';
+
+ for (int i = 0; i < Message.Length; i++)
+ payload[offset++] = (byte)Message[i];
+ payload[offset++] = (byte)'\0';
+
+ payload[offset++] = (byte)IconType;
+
+ for (int i = 0; i < 4; i++)
+ payload[offset++] = (byte)0;
+
+ Array.Copy(icon, 0, payload, Caption.Length + Message.Length + 7, icon.Length);
+
+ return Send(PacketType.PT_NOTIFICATION, payload);
+
+ }
+
+ public bool SendNotification(string Caption, string Message)
+ {
+ return SendNotification(Caption, Message, IconType.ICON_NONE, "");
+ }
+
+ /************************************************************************/
+ /* SendButton - 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 */
+ /* 0x80 => 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<num>: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 bool SendButton(string Button, ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags, short Amount)
+ {
+
+ if (Button.Length != 0)
+ {
+ if ((Flags & ButtonFlagsType.BTN_USE_NAME) == 0)
+ Flags |= ButtonFlagsType.BTN_USE_NAME;
+ ButtonCode = 0;
+ }
+ else
+ Button = "";
+
+ if (Amount > 0)
+ {
+ if ((Flags & ButtonFlagsType.BTN_USE_AMOUNT) == 0)
+ Flags |= ButtonFlagsType.BTN_USE_AMOUNT;
+ }
+
+ if ((Flags & ButtonFlagsType.BTN_DOWN) == 0 || (Flags & ButtonFlagsType.BTN_UP) == 0)
+ Flags |= ButtonFlagsType.BTN_DOWN;
+
+ byte[] payload = new byte[Button.Length + DeviceMap.Length + 8];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)((ButtonCode & 0xff00) >> 8);
+ payload[offset++] = (byte)(ButtonCode & 0x00ff);
+
+ payload[offset++] = (byte)(((ushort)Flags & 0xff00) >> 8);
+ payload[offset++] = (byte)((ushort)Flags & 0x00ff);
+
+ payload[offset++] = (byte)((Amount & 0xff00) >> 8);
+ payload[offset++] = (byte)(Amount & 0x00ff);
+
+ for (int i = 0; i < DeviceMap.Length; i++)
+ payload[offset++] = (byte)DeviceMap[i];
+ payload[offset++] = (byte)'\0';
+
+ for (int i = 0; i < Button.Length; i++)
+ payload[offset++] = (byte)Button[i];
+ payload[offset++] = (byte)'\0';
+
+ return Send(PacketType.PT_BUTTON, payload);
+
+ }
+
+ public bool SendButton(string Button, string DeviceMap, ButtonFlagsType Flags, short Amount)
+ {
+ return SendButton(Button, 0, DeviceMap, Flags, Amount);
+ }
+
+ public bool SendButton(string Button, string DeviceMap, ButtonFlagsType Flags)
+ {
+ return SendButton(Button, 0, DeviceMap, Flags, 0);
+ }
+
+ public bool SendButton(ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags, short Amount)
+ {
+ return SendButton("", ButtonCode, DeviceMap, Flags, Amount);
+ }
+
+ public bool SendButton(ushort ButtonCode, string DeviceMap, ButtonFlagsType Flags)
+ {
+ return SendButton("", ButtonCode, DeviceMap, Flags, 0);
+ }
+
+ public bool SendButton(ushort ButtonCode, ButtonFlagsType Flags, short Amount)
+ {
+ return SendButton("", ButtonCode, "", Flags, Amount);
+ }
+
+ public bool SendButton(ushort ButtonCode, ButtonFlagsType Flags)
+ {
+ return SendButton("", ButtonCode, "", Flags, 0);
+ }
+
+ public bool SendButton()
+ {
+ return SendButton("", 0, "", ButtonFlagsType.BTN_UP, 0);
+ }
+
+ /************************************************************************/
+ /* SendPing - No payload */
+ /************************************************************************/
+ public bool SendPing()
+ {
+ byte[] payload = new byte[0];
+ return Send(PacketType.PT_PING, payload);
+ }
+
+ /************************************************************************/
+ /* SendBye - No payload */
+ /************************************************************************/
+ public bool SendBye()
+ {
+ byte[] payload = new byte[0];
+ return Send(PacketType.PT_BYE, payload);
+ }
+
+ /************************************************************************/
+ /* SendMouse - Payload format */
+ /* %c - flags */
+ /* - 0x01 absolute position */
+ /* %i - mousex (0-65535 => maps to screen width) */
+ /* %i - mousey (0-65535 => maps to screen height) */
+ /************************************************************************/
+ public bool SendMouse(ushort X, ushort Y, MouseFlagsType Flags)
+ {
+
+ byte[] payload = new byte[9];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)Flags;
+
+ payload[offset++] = (byte)((X & 0xff00) >> 8);
+ payload[offset++] = (byte)(X & 0x00ff);
+
+ payload[offset++] = (byte)((Y & 0xff00) >> 8);
+ payload[offset++] = (byte)(Y & 0x00ff);
+
+ return Send(PacketType.PT_MOUSE, payload);
+
+ }
+
+ public bool SendMouse(ushort X, ushort Y)
+ {
+ return SendMouse(X, Y, MouseFlagsType.MS_ABSOLUTE);
+ }
+
+ /************************************************************************/
+ /* SendLog - Payload format */
+ /* %c - log type */
+ /* %s - message */
+ /************************************************************************/
+ public bool SendLog(LogTypeEnum LogLevel, string Message)
+ {
+
+ byte[] payload = new byte[Message.Length + 2];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)LogLevel;
+
+ for (int i = 0; i < Message.Length; i++)
+ payload[offset++] = (byte)Message[i];
+ payload[offset++] = (byte)'\0';
+
+ return Send(PacketType.PT_LOG, payload);
+
+ }
+
+ /************************************************************************/
+ /* SendAction - Payload format */
+ /* %c - action type */
+ /* %s - action message */
+ /************************************************************************/
+ public bool SendAction(ActionType Action, string Message)
+ {
+
+ byte[] payload = new byte[Message.Length + 2];
+
+ int offset = 0;
+
+ payload[offset++] = (byte)Action;
+
+ for (int i = 0; i < Message.Length; i++)
+ payload[offset++] = (byte)Message[i];
+ payload[offset++] = (byte)'\0';
+
+ return Send(PacketType.PT_ACTION, payload);
+
+ }
+
+ public bool SendAction(string Message)
+ {
+ return SendAction(ActionType.ACTION_EXECBUILTIN, Message);
+ }
+
+ }
+}
diff --git a/tools/EventClients/lib/c++/xbmcclient.h b/tools/EventClients/lib/c++/xbmcclient.h new file mode 100644 index 0000000000..f7456baef8 --- /dev/null +++ b/tools/EventClients/lib/c++/xbmcclient.h @@ -0,0 +1,816 @@ +#ifndef __XBMC_CLIENT_H__ +#define __XBMC_CLIENT_H__ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#ifdef _WIN32 +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#endif +#include <vector> +#include <iostream> +#include <fstream> +#include <time.h> + +#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() {} + ~XBMCClientUtils() {} + 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() + { } + + bool Send(int Socket, CAddress &Addr, unsigned int UID = XBMCClientUtils::GetUniqueIdentifier()) + { + if (m_Payload.size() == 0) + ConstructPayload(); + bool SendSuccessfull = 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)) + SendSuccessfull = false; + + Sent += Send; + } + return SendSuccessfull; + } +protected: + char m_Header[HEADER_SIZE]; + unsigned short m_PacketType; + + std::vector<char> 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<char> m_DeviceName; + unsigned short m_IconType; + char *m_IconData; + unsigned short m_IconSize; +public: + virtual void ConstructPayload() + { + 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; + } + } + + virtual ~CPacketHELO() + { + m_DeviceName.clear(); + if (m_IconData) + free(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<char> m_Title; + std::vector<char> m_Message; + unsigned short m_IconType; + char *m_IconData; + unsigned short m_IconSize; +public: + virtual void ConstructPayload() + { + 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; + + 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; + m_IconSize = 0; + } + } + + virtual ~CPacketNOTIFICATION() + { + m_Title.clear(); + m_Message.clear(); + if (m_IconData) + free(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<num>: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<char> m_DeviceMap; + std::vector<char> m_Button; + unsigned short m_ButtonCode; + unsigned short m_Amount; + unsigned short m_Flags; +public: + virtual void ConstructPayload() + { + 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; + } + + virtual ~CPacketBUTTON() + { + 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; + } + virtual ~CPacketPING() + { } +}; + +class CPacketBYE : public CPacket +{ + /************************************************************************/ + /* no payload */ + /************************************************************************/ +public: + CPacketBYE() : CPacket() + { + m_PacketType = PT_BYE; + } + virtual ~CPacketBYE() + { } +}; + +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; + } + + virtual void ConstructPayload() + { + 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)); + } + + virtual ~CPacketMOUSE() + { } +}; + +class CPacketLOG : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %c - log type */ + /* %s - message */ + /************************************************************************/ +private: + std::vector<char> 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; + } + + virtual void ConstructPayload() + { + 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'); + } + + virtual ~CPacketLOG() + { } +}; + +class CPacketACTION : public CPacket +{ + /************************************************************************/ + /* Payload format */ + /* %c - action type */ + /* %s - action message */ + /************************************************************************/ +private: + unsigned char m_ActionType; + std::vector<char> 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]); + } + + virtual void ConstructPayload() + { + 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'); + } + + virtual ~CPacketACTION() + { } +}; + +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); + } +}; + +#endif diff --git a/tools/EventClients/lib/java/build.xml b/tools/EventClients/lib/java/build.xml new file mode 100755 index 0000000000..fe25bde90a --- /dev/null +++ b/tools/EventClients/lib/java/build.xml @@ -0,0 +1,72 @@ +<project default="jar" name="JXBMCEventClient"> + <description description="JXBMCEventClient"/> + <target name="init"> + <tstamp/> + <property name="srcdir" value="${basedir}/src"/> + <property name="classdir" value="${basedir}/classes"/> + <property name="apidir" value="${basedir}/doc/api"/> + <property name="libdir" value="/usr/local/share/java/classes"/> + <property name="projectname" value="JXBMCEventClient"/> + <property name="jarfile" value="${projectname}.jar"/> + <property name="distdir" value="${basedir}/../dist"/> + <fileset id="eventclient.files" dir="${classdir}" includes="**/*"/> + <path id="classpath"> + <pathelement location="${libdir}/junit.jar" /> + </path> + </target> + + + <target name="jar" depends="compile"> + <jar jarfile="${jarfile}" index="true"> + <fileset refid="eventclient.files"/> + </jar> + </target> + + <target name="dist" depends="jar, javadoc"> + <fail unless="version">use: ant dist -Dversion=x.x</fail> + <mkdir dir="${distdir}"/> + <copy todir="${distdir}/doc"> + <fileset dir="${apidir}"/> + </copy> + <property name="filename" value="${projectname}-${version}"/> + <copy file="${jarfile}" tofile="${distdir}/${projectname}-${version}.jar" /> + <tar destfile="${distdir}/${filename}.tar.gz" compression="gzip"> + <tarfileset dir="${basedir}" prefix="${filename}"> + <exclude name="classes/**"/> + <exclude name="**/CVS"/> + <exclude name="**/.*"/> + <exclude name="${jarfile}"/> + <exclude name="doc/**"/> + </tarfileset> + </tar> + </target> + + <target name="compile" depends="init" description="Compiles all classes"> + <mkdir dir="${classdir}"/> + <javac debug="yes" deprecation="true" destdir="${classdir}" srcdir="${srcdir}" classpathref="classpath"/> + </target> + + <target name="javadoc" depends="init"> + <mkdir dir="${apidir}"/> + <javadoc + packagenames="org.xbmc.eventclient.*" + sourcepath="${srcdir}" + defaultexcludes="yes" + destdir="${apidir}" + author="true" + version="true" + use="true" + private="false" + windowtitle="${projectname} API" + classpathref="classpath"> + <link href="http://java.sun.com/j2se/1.4.2/docs/api/"/> + </javadoc> + </target> + + <target name="clean" depends="init" description="cleans all classes and jars"> + <delete dir="${classdir}"/> + <delete dir="${apidir}"/> + <delete file="${jarfile}"/> + </target> + +</project> diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/Packet.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/Packet.java new file mode 100755 index 0000000000..4904d5c9b1 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/Packet.java @@ -0,0 +1,271 @@ +package org.xbmc.eventclient; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +/** + * XBMC Event Client Class + * <p> + * Implementation of XBMC's UDP based input system. + * A set of classes that abstract the various packets that the event server + * currently supports. In addition, there's also a class, XBMCClient, that + * provides functions that sends the various packets. Use XBMCClient if you + * don't need complete control over packet structure. + * </p> + * <p> + * The basic workflow involves: + * <ol> + * <li>Send a HELO packet</li> + * <li>Send x number of valid packets</li> + * <li>Send a BYE packet</li> + * </ol> + * </p> + * <p> + * IMPORTANT NOTE ABOUT TIMEOUTS: + * A client is considered to be timed out if XBMC doesn't received a packet + * at least once every 60 seconds. To "ping" XBMC with an empty packet use + * PacketPING or XBMCClient.ping(). See the documentation for details. + * </p> + * <p> + * 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 + * </p> + * <pre> + * ----------------------------- + * | -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 Payloadsize of packet | - 1 x UNSIGNED SHORT 2B + * | -H7 Client's unique token | - 1 x UNSIGNED LONG 4B + * | -H8 Reserved | - 10 x UNSIGNED CHAR 10B + * |---------------------------| + * | -P1 payload | - + * ----------------------------- + * </pre> + * @author Stefan Agner + * + */ +public abstract class Packet { + + private byte[] sig; + private byte[] payload = new byte[0]; + private byte minver; + private byte majver; + + private short packettype; + + + private final static short MAX_PACKET_SIZE = 1024; + private final static short HEADER_SIZE = 32; + private final static short MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE; + + protected final static byte PT_HELO = 0x01; + protected final static byte PT_BYE = 0x02; + protected final static byte PT_BUTTON = 0x03; + protected final static byte PT_MOUSE = 0x04; + protected final static byte PT_PING = 0x05; + protected final static byte PT_BROADCAST = 0x06; + protected final static byte PT_NOTIFICATION = 0x07; + protected final static byte PT_BLOB = 0x08; + protected final static byte PT_LOG = 0x09; + protected final static byte PT_ACTION = 0x0A; + protected final static byte PT_DEBUG = (byte)0xFF; + + public final static byte ICON_NONE = 0x00; + public final static byte ICON_JPEG = 0x01; + public final static byte ICON_PNG = 0x02; + public final static byte ICON_GIF = 0x03; + + private static int uid = (int)(Math.random()*Integer.MAX_VALUE); + + /** + * This is an Abstract class and cannot be instanced. Please use one of the Packet implementation Classes + * (PacketXXX). + * + * Implements an XBMC Event Client Packet. Type is to be specified at creation time, Payload can be added + * with the various appendPayload methods. Packet can be sent through UDP-Socket with method "send". + * @param packettype Type of Packet (PT_XXX) + */ + protected Packet(short packettype) + { + sig = new byte[] {'X', 'B', 'M', 'C' }; + minver = 0; + majver = 2; + this.packettype = packettype; + } + + /** + * Appends a String to the payload (terminated with 0x00) + * @param payload Payload as String + */ + protected void appendPayload(String payload) + { + byte[] payloadarr = payload.getBytes(); + int oldpayloadsize = this.payload.length; + byte[] oldpayload = this.payload; + this.payload = new byte[oldpayloadsize+payloadarr.length+1]; // Create new Array with more place (+1 for string terminator) + System.arraycopy(oldpayload, 0, this.payload, 0, oldpayloadsize); + System.arraycopy(payloadarr, 0, this.payload, oldpayloadsize, payloadarr.length); + } + + /** + * Appends a single Byte to the payload + * @param payload Payload + */ + protected void appendPayload(byte payload) + { + appendPayload(new byte[] { payload }); + } + + /** + * Appends a Byte-Array to the payload + * @param payloadarr Payload + */ + protected void appendPayload(byte[] payloadarr) + { + int oldpayloadsize = this.payload.length; + byte[] oldpayload = this.payload; + this.payload = new byte[oldpayloadsize+payloadarr.length]; + System.arraycopy(oldpayload, 0, this.payload, 0, oldpayloadsize); + System.arraycopy(payloadarr, 0, this.payload, oldpayloadsize, payloadarr.length); + } + + /** + * Appends an integer to the payload + * @param i Payload + */ + protected void appendPayload(int i) { + appendPayload(intToByteArray(i)); + } + + /** + * Appends a short to the payload + * @param s Payload + */ + protected void appendPayload(short s) { + appendPayload(shortToByteArray(s)); + } + + /** + * Get Number of Packets which will be sent with current Payload... + * @return Number of Packets + */ + public int getNumPackets() + { + return (int)((payload.length + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE); + } + + /** + * Get Header for a specific Packet in this sequence... + * @param seq Current sequence number + * @param maxseq Maximal sequence number + * @param actpayloadsize Payloadsize of this packet + * @return Byte-Array with Header information (currently 32-Byte long, see HEADER_SIZE) + */ + private byte[] getHeader(int seq, int maxseq, short actpayloadsize) + { + byte[] header = new byte[HEADER_SIZE]; + System.arraycopy(sig, 0, header, 0, 4); + header[4] = majver; + header[5] = minver; + byte[] packettypearr = shortToByteArray(this.packettype); + System.arraycopy(packettypearr, 0, header, 6, 2); + byte[] seqarr = intToByteArray(seq); + System.arraycopy(seqarr, 0, header, 8, 4); + byte[] maxseqarr = intToByteArray(maxseq); + System.arraycopy(maxseqarr, 0, header, 12, 4); + byte[] payloadsize = shortToByteArray(actpayloadsize); + System.arraycopy(payloadsize, 0, header, 16, 2); + byte[] uid = intToByteArray(Packet.uid); + System.arraycopy(uid, 0, header, 18, 4); + byte[] reserved = new byte[10]; + System.arraycopy(reserved, 0, header, 22, 10); + + return header; + } + + /** + * Generates the whole UDP-Message with Header and Payload of a specific Packet in sequence + * @param seq Current sequence number + * @return Byte-Array with UDP-Message + */ + private byte[] getUDPMessage(int seq) + { + int maxseq = (int)((payload.length + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE); + if(seq > maxseq) + return null; + + short actpayloadsize; + + if(seq == maxseq) + actpayloadsize = (short)(payload.length%MAX_PAYLOAD_SIZE); + + else + actpayloadsize = (short)MAX_PAYLOAD_SIZE; + + byte[] pack = new byte[HEADER_SIZE+actpayloadsize]; + + System.arraycopy(getHeader(seq, maxseq, actpayloadsize), 0, pack, 0, HEADER_SIZE); + System.arraycopy(payload, (seq-1)*MAX_PAYLOAD_SIZE, pack, HEADER_SIZE, actpayloadsize); + + return pack; + } + + /** + * Sends this packet to the EventServer + * @param adr Address of the EventServer + * @param port Port of the EventServer + * @throws IOException + */ + public void send(InetAddress adr, int port) throws IOException + { + int maxseq = getNumPackets(); + DatagramSocket s = new DatagramSocket(); + + // For each Packet in Sequence... + for(int seq=1;seq<=maxseq;seq++) + { + // Get Message and send them... + byte[] pack = getUDPMessage(seq); + DatagramPacket p = new DatagramPacket(pack, pack.length); + p.setAddress(adr); + p.setPort(port); + s.send(p); + } + } + + /** + * Helper Method to convert an integer to a Byte array + * @param value + * @return Byte-Array + */ + private static final byte[] intToByteArray(int value) { + return new byte[] { + (byte)(value >>> 24), + (byte)(value >>> 16), + (byte)(value >>> 8), + (byte)value}; + } + + /** + * Helper Method to convert an short to a Byte array + * @param value + * @return Byte-Array + */ + private static final byte[] shortToByteArray(short value) { + return new byte[] { + (byte)(value >>> 8), + (byte)value}; + } + + +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketACTION.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketACTION.java new file mode 100755 index 0000000000..ae633c783a --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketACTION.java @@ -0,0 +1,43 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * The idea is that this will be as in scripting/skining and keymapping, just triggered from afar. + * @author Stefan Agner + * + */ +public class PacketACTION extends Packet { + + public final static byte ACTION_EXECBUILTIN = 0x01; + public final static byte ACTION_BUTTON = 0x02; + + + /** + * An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * @param actionmessage Actionmessage (as in scripting/skinning) + */ + public PacketACTION(String actionmessage) + { + super(PT_ACTION); + byte actiontype = ACTION_EXECBUILTIN; + appendPayload(actionmessage, actiontype); + } + + /** + * An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * @param actionmessage Actionmessage (as in scripting/skinning) + * @param actiontype Actiontype (ACTION_EXECBUILTIN or ACTION_BUTTON) + */ + public PacketACTION(String actionmessage, byte actiontype) + { + super(PT_ACTION); + appendPayload(actionmessage, actiontype); + } + + private void appendPayload(String actionmessage, byte actiontype) + { + appendPayload(actiontype); + appendPayload(actionmessage); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBUTTON.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBUTTON.java new file mode 100755 index 0000000000..cc0ff35efb --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBUTTON.java @@ -0,0 +1,139 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A button packet send a key press or release event to XBMC + * @author Stefan Agner + * + */ +public class PacketBUTTON extends Packet { + + protected final static byte BT_USE_NAME = 0x01; + protected final static byte BT_DOWN = 0x02; + protected final static byte BT_UP = 0x04; + protected final static byte BT_USE_AMOUNT = 0x08; + protected final static byte BT_QUEUE = 0x10; + protected final static byte BT_NO_REPEAT = 0x20; + protected final static byte BT_VKEY = 0x40; + protected final static byte BT_AXIS = (byte)0x80; + protected final static byte BT_AXISSINGLE = (byte)0x100; + + /** + * A button packet send a key press or release event to XBMC + * @param code raw button code (default: 0) + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public PacketBUTTON(short code, boolean repeat, boolean down, boolean queue, short amount, byte axis) + { + super(PT_BUTTON); + String map_name = ""; + String button_name = ""; + short flags = 0; + appendPayload(code, map_name, button_name, repeat, down, queue, amount, axis, flags); + } + + /** + * A button packet send a key press or release event to XBMC + * @param map_name a combination of map_name and button_name refers to a + * mapping in the user's Keymap.xml or Lircmap.xml. + * map_name can be one of the following: + * <ul> + * <li>"KB" => standard keyboard map ( <keyboard> section )</li> + * <li>"XG" => xbox gamepad map ( <gamepad> section )</li> + * <li>"R1" => xbox remote map ( <remote> section )</li> + * <li>"R2" => xbox universal remote map ( <universalremote> section )</li> + * <li>"LI:devicename" => LIRC remote map where 'devicename' is the + * actual device's name</li></ul> + * @param button_name a button name defined in the map specified in map_name. + * For example, if map_name is "KB" refering to the <keyboard> section in Keymap.xml + * then, valid button_names include "printscreen", "minus", "x", etc. + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public PacketBUTTON(String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis) + { + super(PT_BUTTON); + short code = 0; + short flags = BT_USE_NAME; + appendPayload(code, map_name, button_name, repeat, down, queue, amount, axis, flags); + } + + /** + * Appends Payload for a Button Packet (this method is used by the different Constructors of this Packet) + * @param code raw button code (default: 0) + * @param map_name a combination of map_name and button_name refers to a + * mapping in the user's Keymap.xml or Lircmap.xml. + * map_name can be one of the following: + * <ul> + * <li>"KB" => standard keyboard map ( <keyboard> section )</li> + * <li>"XG" => xbox gamepad map ( <gamepad> section )</li> + * <li>"R1" => xbox remote map ( <remote> section )</li> + * <li>"R2" => xbox universal remote map ( <universalremote> section )</li> + * <li>"LI:devicename" => LIRC remote map where 'devicename' is the + * actual device's name</li></ul> + * @param button_name a button name defined in the map specified in map_name. + * For example, if map_name is "KB" refering to the <keyboard> section in Keymap.xml + * then, valid button_names include "printscreen", "minus", "x", etc. + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + * @param flags Packet specific flags + */ + private void appendPayload(short code, String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis, short flags) + { + if(amount>0) + flags |= BT_USE_AMOUNT; + else + amount = 0; + + if(down) + flags |= BT_DOWN; + else + flags |= BT_UP; + + if(!repeat) + flags |= BT_NO_REPEAT; + + if(queue) + flags |= BT_QUEUE; + + if(axis == 1) + flags |= BT_AXISSINGLE; + else if (axis == 2) + flags |= BT_AXIS; + + + appendPayload(code); + appendPayload(flags); + appendPayload(amount); + appendPayload(map_name); + appendPayload(button_name); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBYE.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBYE.java new file mode 100755 index 0000000000..8c831b1ac0 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketBYE.java @@ -0,0 +1,19 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A BYE packet terminates the connection to XBMC. + * @author Stefan Agner + * + */ +public class PacketBYE extends Packet +{ + + /** + * A BYE packet terminates the connection to XBMC. + */ + public PacketBYE() + { + super(PT_BYE); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketHELO.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketHELO.java new file mode 100755 index 0000000000..f68ba46421 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketHELO.java @@ -0,0 +1,46 @@ +package org.xbmc.eventclient; +import java.nio.charset.Charset; + +/** + * XBMC Event Client Class + * + * A HELO packet establishes a valid connection to XBMC. It is the + * first packet that should be sent. + * @author Stefan Agner + * + */ +public class PacketHELO extends Packet { + + + /** + * A HELO packet establishes a valid connection to XBMC. + * @param devicename Name of the device which connects to XBMC + */ + public PacketHELO(String devicename) + { + super(PT_HELO); + this.appendPayload(devicename); + this.appendPayload(ICON_NONE); + this.appendPayload((short)0); // port no + this.appendPayload(0); // reserved1 + this.appendPayload(0); // reserved2 + } + + /** + * A HELO packet establishes a valid connection to XBMC. + * @param devicename Name of the device which connects to XBMC + * @param iconType Type of the icon (Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon as a Byte-Array + */ + public PacketHELO(String devicename, byte iconType, byte[] iconData) + { + super(PT_HELO); + this.appendPayload(devicename); + this.appendPayload(iconType); + this.appendPayload((short)0); // port no + this.appendPayload(0); // reserved1 + this.appendPayload(0); // reserved2 + this.appendPayload(iconData); // reserved2 + } + +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketLOG.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketLOG.java new file mode 100755 index 0000000000..693ad71ea6 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketLOG.java @@ -0,0 +1,30 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A LOG packet tells XBMC to log the message to xbmc.log with the loglevel as specified. + * @author Stefan Agner + * + */ +public class PacketLOG extends Packet { + + /** + * A LOG packet tells XBMC to log the message to xbmc.log with the loglevel as specified. + * @param loglevel the loglevel, follows XBMC standard. + * <ul> + * <li>0 = DEBUG</li> + * <li>1 = INFO</li> + * <li>2 = NOTICE</li> + * <li>3 = WARNING</li> + * <li>4 = ERROR</li> + * <li>5 = SEVERE</li> + * </ul> + * @param logmessage the message to log + */ + public PacketLOG(byte loglevel, String logmessage) + { + super(PT_LOG); + appendPayload(loglevel); + appendPayload(logmessage); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketMOUSE.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketMOUSE.java new file mode 100755 index 0000000000..d7755ef0cc --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketMOUSE.java @@ -0,0 +1,27 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A MOUSE packets sets the mouse position in XBMC + * @author Stefan Agner + * + */ +public class PacketMOUSE extends Packet { + + protected final static byte MS_ABSOLUTE = 0x01; + + /** + * A MOUSE packets sets the mouse position in XBMC + * @param x horitontal position ranging from 0 to 65535 + * @param y vertical position ranging from 0 to 65535 + */ + public PacketMOUSE(int x, int y) + { + super(PT_MOUSE); + byte flags = 0; + flags |= MS_ABSOLUTE; + appendPayload(flags); + appendPayload((short)x); + appendPayload((short)y); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketNOTIFICATION.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketNOTIFICATION.java new file mode 100755 index 0000000000..15a5ec8f27 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketNOTIFICATION.java @@ -0,0 +1,53 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * This packet displays a notification window in XBMC. It can contain + * a caption, a message and an icon. + * @author Stefan Agner + * + */ +public class PacketNOTIFICATION extends Packet { + + /** + * This packet displays a notification window in XBMC. + * @param title Message title + * @param message The actual message + * @param iconType Type of the icon (Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon as a Byte-Array + */ + public PacketNOTIFICATION(String title, String message, byte iconType, byte[] iconData) + { + super(PT_NOTIFICATION); + appendPayload(title, message, iconType, iconData); + } + + /** + * This packet displays a notification window in XBMC. + * @param title Message title + * @param message The actual message + */ + public PacketNOTIFICATION(String title, String message) + { + super(PT_NOTIFICATION); + appendPayload(title, message, Packet.ICON_NONE, null); + } + + /** + * Appends the payload to the packet... + * @param title Message title + * @param message The actual message + * @param iconType Type of the icon (Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon as a Byte-Array + */ + private void appendPayload(String title, String message, byte iconType, byte[] iconData) + { + appendPayload(title); + appendPayload(message); + appendPayload(iconType); + appendPayload(0); // reserved + if(iconData!=null) + appendPayload(iconData); + + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketPING.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketPING.java new file mode 100755 index 0000000000..e405812f32 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/PacketPING.java @@ -0,0 +1,19 @@ +package org.xbmc.eventclient; +/** + * XBMC Event Client Class + * + * A PING packet tells XBMC that the client is still alive. All valid + * packets act as ping (not just this one). A client needs to ping + * XBMC at least once in 60 seconds or it will time + * @author Stefan Agner + * + */ +public class PacketPING extends Packet { + /** + * A PING packet tells XBMC that the client is still alive. + */ + public PacketPING() + { + super(PT_PING); + } +} diff --git a/tools/EventClients/lib/java/src/org/xbmc/eventclient/XBMCClient.java b/tools/EventClients/lib/java/src/org/xbmc/eventclient/XBMCClient.java new file mode 100755 index 0000000000..4a0f85fd02 --- /dev/null +++ b/tools/EventClients/lib/java/src/org/xbmc/eventclient/XBMCClient.java @@ -0,0 +1,300 @@ +package org.xbmc.eventclient; + +import java.io.FileInputStream; +import java.io.IOException; +import java.net.InetAddress; + +/** + * XBMC Event Client Class + * + * Implements an XBMC-Client. This class can be used to implement your own application which + * should act as a Input device for XBMC. Also starts a Ping-Thread, which tells the XBMC EventServer + * that the client is alive. Therefore if you close your application you SHOULD call stopClient()! + * @author Stefan Agner + * + */ +public class XBMCClient +{ + private boolean hasIcon = false; + private String deviceName; + private PingThread oPingThread; + private byte iconType = Packet.ICON_PNG; + private byte[] iconData; + private InetAddress hostAddress; + private int hostPort; + + /** + * Starts a XBMC EventClient. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @param iconFile Path to the Iconfile (PNG, JPEG or GIF) + * @throws IOException + */ + public XBMCClient(InetAddress hostAddress, int hostPort, String deviceName, String iconFile) throws IOException + { + byte iconType = Packet.ICON_PNG; + // Assume png as icon type + if(iconFile.toLowerCase().endsWith(".jpeg")) + iconType = Packet.ICON_JPEG; + if(iconFile.toLowerCase().endsWith(".jpg")) + iconType = Packet.ICON_JPEG; + if(iconFile.toLowerCase().endsWith(".gif")) + iconType = Packet.ICON_GIF; + + // Read the icon file to the byte array... + FileInputStream iconFileStream = new FileInputStream(iconFile); + byte[] iconData = new byte[iconFileStream.available()]; + iconFileStream.read(iconData); + + hasIcon = true; + + // Call start-Method... + startClient(hostAddress, hostPort, deviceName, iconType, iconData); + } + + + /** + * Starts a XBMC EventClient. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @param iconType Type of the icon file (see Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon itself as a Byte-Array + * @throws IOException + */ + public XBMCClient(InetAddress hostAddress, int hostPort, String deviceName, byte iconType, byte[] iconData) throws IOException + { + hasIcon = true; + startClient(hostAddress, hostPort, deviceName, iconType, iconData); + } + + /** + * Starts a XBMC EventClient without an icon. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @throws IOException + */ + public XBMCClient(InetAddress hostAddress, int hostPort, String deviceName) throws IOException + { + hasIcon = false; + byte iconType = Packet.ICON_NONE; + byte[] iconData = null; + startClient(hostAddress, hostPort, deviceName, iconType, iconData); + } + + + /** + * Starts a XBMC EventClient. + * @param hostAddress Address of the Host running XBMC + * @param hostPort Port of the Host running XBMC (default 9777) + * @param deviceName Name of the Device + * @param iconType Type of the icon file (see Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) + * @param iconData The icon itself as a Byte-Array + * @throws IOException + */ + private void startClient(InetAddress hostAddress, int hostPort, String deviceName, byte iconType, byte[] iconData) throws IOException + { + // Save host address and port + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.deviceName = deviceName; + + this.iconType = iconType; + this.iconData = iconData; + + // Send Hello Packet... + PacketHELO p; + if(hasIcon) + p = new PacketHELO(deviceName, iconType, iconData); + else + p = new PacketHELO(deviceName); + + p.send(hostAddress, hostPort); + + // Start Thread (for Ping packets...) + oPingThread = new PingThread(hostAddress, hostPort, 20000); + oPingThread.start(); + } + + /** + * Stops the XBMC EventClient (especially the Ping-Thread) + * @throws IOException + */ + public void stopClient() throws IOException + { + // Stop Ping-Thread... + oPingThread.giveup(); + oPingThread.interrupt(); + + PacketBYE p = new PacketBYE(); + p.send(hostAddress, hostPort); + } + + + /** + * Displays a notification window in XBMC. + * @param title Message title + * @param message The actual message + */ + public void sendNotification(String title, String message) throws IOException + { + PacketNOTIFICATION p; + if(hasIcon) + p = new PacketNOTIFICATION(title, message, iconType, iconData); + else + p = new PacketNOTIFICATION(title, message); + p.send(hostAddress, hostPort); + } + + /** + * Sends a Button event + * @param code raw button code (default: 0) + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public void sendButton(short code, boolean repeat, boolean down, boolean queue, short amount, byte axis) throws IOException + { + PacketBUTTON p = new PacketBUTTON(code, repeat, down, queue, amount, axis); + p.send(hostAddress, hostPort); + } + + /** + * Sends a Button event + * @param map_name a combination of map_name and button_name refers to a + * mapping in the user's Keymap.xml or Lircmap.xml. + * map_name can be one of the following: + * <ul> + * <li>"KB" => standard keyboard map ( <keyboard> section )</li> + * <li>"XG" => xbox gamepad map ( <gamepad> section )</li> + * <li>"R1" => xbox remote map ( <remote> section )</li> + * <li>"R2" => xbox universal remote map ( <universalremote> section )</li> + * <li>"LI:devicename" => LIRC remote map where 'devicename' is the + * actual device's name</li></ul> + * @param button_name a button name defined in the map specified in map_name. + * For example, if map_name is "KB" refering to the <keyboard> section in Keymap.xml + * then, valid button_names include "printscreen", "minus", "x", etc. + * @param repeat this key press should repeat until released (default: 1) + * Note that queued pressed cannot repeat. + * @param down if this is 1, it implies a press event, 0 implies a release + * event. (default: 1) + * @param queue a queued key press means that the button event is + * executed just once after which the next key press is processed. + * It can be used for macros. Currently there is no support for + * time delays between queued presses. (default: 0) + * @param amount unimplemented for now; in the future it will be used for + * specifying magnitude of analog key press events + * @param axis + */ + public void sendButton(String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis) throws IOException + { + PacketBUTTON p = new PacketBUTTON(map_name, button_name, repeat, down, queue, amount, axis); + p.send(hostAddress, hostPort); + } + + /** + * Sets the mouse position in XBMC + * @param x horitontal position ranging from 0 to 65535 + * @param y vertical position ranging from 0 to 65535 + */ + public void sendMouse(int x, int y) throws IOException + { + PacketMOUSE p = new PacketMOUSE(x, y); + p.send(hostAddress, hostPort); + } + + /** + * Sends a ping to the XBMC EventServer + * @throws IOException + */ + public void ping() throws IOException + { + PacketPING p = new PacketPING(); + p.send(hostAddress, hostPort); + } + + /** + * Tells XBMC to log the message to xbmc.log with the loglevel as specified. + * @param loglevel the loglevel, follows XBMC standard. + * <ul> + * <li>0 = DEBUG</li> + * <li>1 = INFO</li> + * <li>2 = NOTICE</li> + * <li>3 = WARNING</li> + * <li>4 = ERROR</li> + * <li>5 = SEVERE</li> + * </ul> + * @param logmessage the message to log + */ + public void sendLog(byte loglevel, String logmessage) throws IOException + { + PacketLOG p = new PacketLOG(loglevel, logmessage); + p.send(hostAddress, hostPort); + } + + /** + * Tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + * @param actionmessage Actionmessage (as in scripting/skinning) + */ + public void sendAction(String actionmessage) throws IOException + { + PacketACTION p = new PacketACTION(actionmessage); + p.send(hostAddress, hostPort); + } + + /** + * Implements a PingThread which tells XBMC EventServer that the Client is alive (this should + * be done at least every 60 seconds! + * @author Stefan Agner + * + */ + class PingThread extends Thread + { + private InetAddress hostAddress; + private int hostPort; + private int sleepTime; + private boolean giveup = false; + + public PingThread(InetAddress hostAddress, int hostPort, int sleepTime) + { + super("XBMC EventClient Ping-Thread"); + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.sleepTime = sleepTime; + } + + public void giveup() + { + giveup = true; + } + + public void run() + { + while(!giveup) + { + try { + PacketPING p = new PacketPING(); + p.send(hostAddress, hostPort); + } catch (IOException e) { + + e.printStackTrace(); + } + + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + } + } + } + } +} diff --git a/tools/EventClients/lib/python/__init__.py b/tools/EventClients/lib/python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/EventClients/lib/python/__init__.py diff --git a/tools/EventClients/lib/python/bt/__init__.py b/tools/EventClients/lib/python/bt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/EventClients/lib/python/bt/__init__.py diff --git a/tools/EventClients/lib/python/bt/bt.py b/tools/EventClients/lib/python/bt/bt.py new file mode 100644 index 0000000000..de4fd1bb65 --- /dev/null +++ b/tools/EventClients/lib/python/bt/bt.py @@ -0,0 +1,65 @@ +BLUEZ=0 + +try: + import bluetooth + BLUEZ=1 +except: + try: + import lightblue + except: + print "ERROR: You need to have either LightBlue or PyBluez installed\n"\ + " in order to use this program." + print "- PyBluez (Linux / Windows XP) http://org.csail.mit.edu/pybluez/" + print "- LightBlue (Mac OS X / Linux) http://lightblue.sourceforge.net/" + exit() + +def bt_create_socket(): + if BLUEZ: + sock = bluetooth.BluetoothSocket(bluetooth.L2CAP) + else: + sock = lightblue.socket(lightblue.L2CAP) + return sock + +def bt_create_rfcomm_socket(): + if BLUEZ: + sock = bluetooth.BluetoothSocket( bluetooth.RFCOMM ) + sock.bind(("",bluetooth.PORT_ANY)) + else: + sock = lightblue.socket(lightblue.RFCOMM) + sock.bind(("",0)) + return sock + +def bt_discover_devices(): + if BLUEZ: + nearby = bluetooth.discover_devices() + else: + nearby = lightblue.finddevices() + return nearby + +def bt_lookup_name(bdaddr): + if BLUEZ: + bname = bluetooth.lookup_name( bdaddr ) + else: + bname = bdaddr[1] + return bname + +def bt_lookup_addr(bdaddr): + if BLUEZ: + return bdaddr + else: + return bdaddr[0] + +def bt_advertise(name, uuid, socket): + if BLUEZ: + bluetooth.advertise_service( socket, name, + service_id = uuid, + service_classes = [ uuid, bluetooth.SERIAL_PORT_CLASS ], + profiles = [ bluetooth.SERIAL_PORT_PROFILE ] ) + else: + lightblue.advertise(name, socket, lightblue.RFCOMM) + +def bt_stop_advertising(socket): + if BLUEZ: + stop_advertising(socket) + else: + lightblue.stopadvertise(socket) diff --git a/tools/EventClients/lib/python/bt/hid.py b/tools/EventClients/lib/python/bt/hid.py new file mode 100644 index 0000000000..7017185dc4 --- /dev/null +++ b/tools/EventClients/lib/python/bt/hid.py @@ -0,0 +1,54 @@ +from bluetooth import * + +class HID: + def __init__(self, bdaddress=None): + self.cport = 0x11 # HID's control PSM + self.iport = 0x13 # HID' interrupt PSM + self.backlog = 1 + + self.address = "" + if bdaddress: + self.address = bdaddress + + # create the HID control socket + self.csock = BluetoothSocket( L2CAP ) + self.csock.bind((self.address, self.cport)) + set_l2cap_mtu(self.csock, 64) + self.csock.settimeout(2) + self.csock.listen(self.backlog) + + # create the HID interrupt socket + self.isock = BluetoothSocket( L2CAP ) + self.isock.bind((self.address, self.iport)) + set_l2cap_mtu(self.isock, 64) + self.isock.settimeout(2) + self.isock.listen(self.backlog) + + self.connected = False + + + def listen(self): + try: + (self.client_csock, self.caddress) = self.csock.accept() + print "Accepted Control connection from %s" % self.caddress[0] + (self.client_isock, self.iaddress) = self.isock.accept() + print "Accepted Interrupt connection from %s" % self.iaddress[0] + self.connected = True + return True + except Exception, e: + self.connected = False + return False + + + def get_control_socket(self): + if self.connected: + return (self.client_csock, self.caddress) + else: + return None + + + def get_interrupt_socket(self): + if self.connected: + return (self.client_isock, self.iaddress) + else: + return None diff --git a/tools/EventClients/lib/python/ps3/__init__.py b/tools/EventClients/lib/python/ps3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tools/EventClients/lib/python/ps3/__init__.py diff --git a/tools/EventClients/lib/python/ps3/keymaps.py b/tools/EventClients/lib/python/ps3/keymaps.py new file mode 100644 index 0000000000..abe7dd6427 --- /dev/null +++ b/tools/EventClients/lib/python/ps3/keymaps.py @@ -0,0 +1,118 @@ +# PS3 Remote and Controller Keymaps + +keymap_remote = { + "16": 'power' ,#EJECT + "64": None ,#AUDIO + "65": None ,#ANGLE + "63": None ,#SUBTITLE + "0f": None ,#CLEAR + "28": None ,#TIME + + "00": 'one' ,#1 + "01": 'two' ,#2 + "02": 'three' ,#3 + "03": 'four' ,#4 + "04": 'five' ,#5 + "05": 'six' ,#6 + "06": 'seven' ,#7 + "07": 'eight' ,#8 + "08": 'nine' ,#9 + "09": 'zero' ,#0 + + "81": 'mytv' ,#RED + "82": 'mymusic' ,#GREEN + "80": 'mypictures' ,#BLUE + "83": 'myvideo' ,#YELLOW + + "70": 'display' ,#DISPLAY + "1a": None ,#TOP MENU + "40": 'menu' ,#POP UP/MENU + "0e": None ,#RETURN + + "5c": 'menu' ,#OPTIONS/TRIANGLE + "5d": 'back' ,#BACK/CIRCLE + "5e": 'info' ,#X + "5f": 'title' ,#VIEW/SQUARE + + "54": 'up' ,#UP + "55": 'right' ,#RIGHT + "56": 'down' ,#DOWN + "57": 'left' ,#LEFT + "0b": 'select' ,#ENTER + + "5a": 'volumeplus' ,#L1 + "58": 'volumeminus' ,#L2 + "51": 'Mute' ,#L3 + "5b": 'pageplus' ,#R1 + "59": 'pageminus' ,#R2 + "52": None ,#R3 + + "43": None ,#PLAYSTATION + "50": None ,#SELECT + "53": None ,#START + + "33": 'reverse' ,#<-SCAN + "34": 'forward' ,# SCAN-> + "30": 'skipminus' ,#PREV + "31": 'skipplus' ,#NEXT + "60": None ,#<-SLOW/STEP + "61": None ,# SLOW/STEP-> + "32": 'play' ,#PLAY + "38": 'stop' ,#STOP + "39": 'pause' ,#PAUSE + } + + +SX_SQUARE = 32768 +SX_X = 16384 +SX_CIRCLE = 8192 +SX_TRIANGLE = 4096 +SX_R1 = 2048 +SX_R2 = 512 +SX_R3 = 4 +SX_L1 = 1024 +SX_L2 = 256 +SX_L3 = 2 +SX_DUP = 16 +SX_DDOWN = 64 +SX_DLEFT = 128 +SX_DRIGHT = 32 +SX_SELECT = 1 +SX_START = 8 + +SX_LSTICK_X = 0 +SX_LSTICK_Y = 1 +SX_RSTICK_X = 2 +SX_RSTICK_Y = 3 + +# (map, key, amount index, axis) +keymap_sixaxis = { + SX_X : ('XG', 'A', 0, 0), + SX_CIRCLE : ('XG', 'B', 0, 0), + SX_SQUARE : ('XG', 'X', 0, 0), + SX_TRIANGLE : ('XG', 'Y', 0, 0), + + SX_DUP : ('XG', 'dpadup', 0, 0), + SX_DDOWN : ('XG', 'dpaddown', 0, 0), + SX_DLEFT : ('XG', 'dpadleft', 0, 0), + SX_DRIGHT : ('XG', 'dpadright', 0, 0), + + SX_START : ('XG', 'start', 0, 0), + SX_SELECT : ('XG', 'back', 0, 0), + + SX_R1 : ('XG', 'white', 0, 0), + SX_R2 : ('XG', 'rightanalogtrigger', 6, 1), + SX_L2 : ('XG', 'leftanalogtrigger', 5, 1), + SX_L1 : ('XG', 'black', 0, 0), + + SX_L3 : ('XG', 'leftthumbbutton', 0, 0), + SX_R3 : ('XG', 'rightthumbbutton', 0, 0), +} + +# (data index, left map, left action, right map, right action) +axismap_sixaxis = { + SX_LSTICK_X : ('XG', 'leftthumbstickleft' , 'leftthumbstickright'), + SX_LSTICK_Y : ('XG', 'leftthumbstickup' , 'leftthumbstickdown'), + SX_RSTICK_X : ('XG', 'rightthumbstickleft', 'rightthumbstickright'), + SX_RSTICK_Y : ('XG', 'rightthumbstickup' , 'rightthumbstickdown'), +} diff --git a/tools/EventClients/lib/python/ps3/sixaxis.py b/tools/EventClients/lib/python/ps3/sixaxis.py new file mode 100644 index 0000000000..15014b8ea0 --- /dev/null +++ b/tools/EventClients/lib/python/ps3/sixaxis.py @@ -0,0 +1,206 @@ +#!/usr/bin/python + +import time +import sys +import struct +import math +import binascii +from bluetooth import set_l2cap_mtu +from keymaps import keymap_sixaxis +from keymaps import axismap_sixaxis + +xval = 0 +yval = 0 +num_samples = 16 +sumx = [0] * num_samples +sumy = [0] * num_samples +sumr = [0] * num_samples +axis_amount = [0, 0, 0, 0] + +def normalize(val): + upperlimit = 65281 + lowerlimit = 2 + val_range = upperlimit - lowerlimit + offset = 10000 + + val = (val + val_range / 2) % val_range + upperlimit -= offset + lowerlimit += offset + + if val < lowerlimit: + val = lowerlimit + if val > upperlimit: + val = upperlimit + + val = ((float(val) - offset) / (float(upperlimit) - + lowerlimit)) * 65535.0 + if val <= 0: + val = 1 + return val + +def normalize_axis(val, deadzone): + + val = float(val) - 127.5 + val = val / 127.5 + + if abs(val) < deadzone: + return 0.0 + + if val > 0.0: + val = (val - deadzone) / (1.0 - deadzone) + else: + val = (val + deadzone) / (1.0 - deadzone) + + return 65536.0 * val + +def normalize_angle(val, valrange): + valrange *= 2 + + val = val / valrange + if val > 1.0: + val = 1.0 + if val < -1.0: + val = -1.0 + return (val + 0.5) * 65535.0 + +def initialize(control_sock, interrupt_sock): + # sixaxis needs this to enable it + # 0x53 => HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE + control_sock.send("\x53\xf4\x42\x03\x00\x00") + time.sleep(0.25) + data = control_sock.recv(1) + + set_l2cap_mtu(control_sock, 64) + set_l2cap_mtu(interrupt_sock, 64) + + # This command will turn on the gyro and set the leds + # I wonder if turning on the gyro makes it draw more current?? + # it's probably a flag somewhere in the following command + + # HID Command: HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUTPUT + # HID Report:1 + bytes = [0x52, 0x1] + bytes.extend([0x00, 0x00, 0x00]) + bytes.extend([0xFF, 0x72]) + bytes.extend([0x00, 0x00, 0x00, 0x00]) + bytes.extend([0x02]) # 0x02 LED1, 0x04 LED2 ... 0x10 LED4 + # The following sections should set the blink frequncy of + # the leds on the controller, but i've not figured out how. + # These values where suggusted in a mailing list, but no explination + # for how they should be combined to the 5 bytes per led + #0xFF = 0.5Hz + #0x80 = 1Hz + #0x40 = 2Hz + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED4 [0xff, 0xff, 0x10, 0x10, 0x10] + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED3 [0xff, 0x40, 0x08, 0x10, 0x10] + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED2 [0xff, 0x00, 0x10, 0x30, 0x30] + bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED1 [0xff, 0x00, 0x10, 0x40, 0x10] + bytes.extend([0x00, 0x00, 0x00, 0x00, 0x00]) + bytes.extend([0x00, 0x00, 0x00, 0x00, 0x00]) + + control_sock.send(struct.pack("42B", *bytes)) + time.sleep(0.25) + data = control_sock.recv(1) + + + return data + + +def read_input(isock): + return isock.recv(50) + + +def process_input(data, xbmc=None, mouse_enabled=0): + if len(data) < 3: + return (0, 0, 0) + + # make sure this is the correct report + if struct.unpack("BBB", data[0:3]) != (0xa1, 0x01, 0x00): + return (0, 0, 0) + + if len(data) >= 48: + v1 = struct.unpack("h", data[42:44]) + v2 = struct.unpack("h", data[44:46]) + v3 = struct.unpack("h", data[46:48]) + else: + v1 = [0,0] + v2 = [0,0] + v3 = [0,0] + + if len(data) >= 50: + v4 = struct.unpack("h", data[48:50]) + else: + v4 = [0,0] + + ax = float(v1[0]) + ay = float(v2[0]) + az = float(v3[0]) + rz = float(v4[0]) + at = math.sqrt(ax*ax + ay*ay + az*az) + + bflags = struct.unpack("H", data[3:5])[0] + psflags = struct.unpack("B", data[5:6])[0] + if len(data) > 27: + pressure = struct.unpack("BBBBBBBBBBBB", data[15:27]) + else: + pressure = [0,0,0,0,0,0,0,0,0,0,0,0,0] + + roll = -math.atan2(ax, math.sqrt(ay*ay + az*az)) + pitch = math.atan2(ay, math.sqrt(ax*ax + az*az)) + + pitch -= math.radians(20); + + xpos = normalize_angle(roll, math.radians(30)) + ypos = normalize_angle(pitch, math.radians(30)) + + # update our sliding window array + sumx.insert(0, xpos) + sumy.insert(0, ypos) + sumx.pop(num_samples) + sumy.pop(num_samples) + + # reset average + xval = 0 + yval = 0 + + # do a sliding window average to remove high frequency + # noise in accelerometer sampling + for i in range(0, num_samples): + xval += sumx[i] + yval += sumy[i] + + axis = struct.unpack("BBBB", data[7:11]) + if xbmc: + for i in range(4): + config = axismap_sixaxis[i] + axis_amount[i] = send_singleaxis(xbmc, axis[i], axis_amount[i], config[0], config[1], config[2]) + + # send the mouse position to xbmc + if mouse_enabled == 1: + xbmc.send_mouse_position(xval/num_samples, yval/num_samples) + + return (bflags, psflags, pressure) + +def send_singleaxis(xbmc, axis, last_amount, mapname, action_min, action_pos): + amount = normalize_axis(axis, 0.30) + if last_amount < 0: + last_action = action_min + elif last_amount > 0: + last_action = action_pos + else: + last_action = None + + if amount < 0: + new_action = action_min + elif amount > 0: + new_action = action_pos + else: + new_action = None + + if last_action and new_action != last_action: + xbmc.send_button_state(map=mapname, button=last_action, amount=0, axis=1) + + if new_action and amount != last_amount: + xbmc.send_button_state(map=mapname, button=new_action, amount=abs(amount), axis=1) + + return amount diff --git a/tools/EventClients/lib/python/xbmcclient.py b/tools/EventClients/lib/python/xbmcclient.py new file mode 100644 index 0000000000..d62f2b9ee3 --- /dev/null +++ b/tools/EventClients/lib/python/xbmcclient.py @@ -0,0 +1,621 @@ +#!/usr/bin/python + +""" +Implementation of XBMC's UDP based input system. + +A set of classes that abstract the various packets that the event server +currently supports. In addition, there's also a class, XBMCClient, that +provides functions that sends the various packets. Use XBMCClient if you +don't need complete control over packet structure. + +The basic workflow involves: + +1. Send a HELO packet +2. Send x number of valid packets +3. Send a BYE packet + +IMPORTANT NOTE ABOUT TIMEOUTS: +A client is considered to be timed out if XBMC doesn't received a packet +at least once every 60 seconds. To "ping" XBMC with an empty packet use +PacketPING or XBMCClient.ping(). See the documentation for details. +""" + +__author__ = "d4rk@xbmc.org" +__version__ = "0.0.3" + +from struct import pack +from socket import * +import time + +MAX_PACKET_SIZE = 1024 +HEADER_SIZE = 32 +MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE +UNIQUE_IDENTIFICATION = (int)(time.time()) + +PT_HELO = 0x01 +PT_BYE = 0x02 +PT_BUTTON = 0x03 +PT_MOUSE = 0x04 +PT_PING = 0x05 +PT_BROADCAST = 0x06 +PT_NOTIFICATION = 0x07 +PT_BLOB = 0x08 +PT_LOG = 0x09 +PT_ACTION = 0x0A +PT_DEBUG = 0xFF + +ICON_NONE = 0x00 +ICON_JPEG = 0x01 +ICON_PNG = 0x02 +ICON_GIF = 0x03 + +BT_USE_NAME = 0x01 +BT_DOWN = 0x02 +BT_UP = 0x04 +BT_USE_AMOUNT = 0x08 +BT_QUEUE = 0x10 +BT_NO_REPEAT = 0x20 +BT_VKEY = 0x40 +BT_AXIS = 0x80 +BT_AXISSINGLE = 0x100 + +MS_ABSOLUTE = 0x01 + +LOGDEBUG = 0x00 +LOGINFO = 0x01 +LOGNOTICE = 0x02 +LOGWARNING = 0x03 +LOGERROR = 0x04 +LOGSEVERE = 0x05 +LOGFATAL = 0x06 +LOGNONE = 0x07 + +ACTION_EXECBUILTIN = 0x01 +ACTION_BUTTON = 0x02 + +###################################################################### +# Helper Functions +###################################################################### + +def format_string(msg): + """ """ + return msg + "\0" + +def format_uint32(num): + """ """ + return pack ("!I", num) + +def format_uint16(num): + """ """ + if num<0: + num = 0 + elif num>65535: + num = 65535 + return pack ("!H", num) + + +###################################################################### +# Packet Classes +###################################################################### + +class Packet: + """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 + | -H7 Client's unique token | - 1 x UNSIGNED LONG 4B + | -H8 Reserved | - 10 x UNSIGNED CHAR 10B + |---------------------------| + | -P1 payload | - + ----------------------------- + """ + def __init__(self): + self.sig = "XBMC" + self.minver = 0 + self.majver = 2 + self.seq = 1 + self.maxseq = 1 + self.payloadsize = 0 + self.uid = UNIQUE_IDENTIFICATION + self.reserved = "\0" * 10 + self.payload = "" + return + + + def append_payload(self, blob): + """Append to existing payload + + Arguments: + blob -- binary data to append to the current payload + """ + self.set_payload(self.payload + blob) + + + def set_payload(self, payload): + """Set the payload for this packet + + Arguments: + payload -- binary data that contains the payload + """ + self.payload = payload + self.payloadsize = len(self.payload) + self.maxseq = int((self.payloadsize + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE) + + + def num_packets(self): + """ Return the number of packets required for payload """ + return self.maxseq + + def get_header(self, packettype=-1, seq=1, maxseq=1, payload_size=0): + """Construct a header and return as string + + Keyword arguments: + packettype -- valid packet types are PT_HELO, PT_BYE, PT_BUTTON, + PT_MOUSE, PT_PING, PT_BORADCAST, PT_NOTIFICATION, + PT_BLOB, PT_DEBUG + seq -- the sequence of this packet for a multi packet message + (default 1) + maxseq -- the total number of packets for a multi packet message + (default 1) + payload_size -- the size of the payload of this packet (default 0) + """ + if packettype < 0: + packettype = self.packettype + header = self.sig + header += chr(self.majver) + header += chr(self.minver) + header += format_uint16(packettype) + header += format_uint32(seq) + header += format_uint32(maxseq) + header += format_uint16(payload_size) + header += format_uint32(self.uid) + header += self.reserved + return header + + def get_payload_size(self, seq): + """Returns the calculated payload size for the particular packet + + Arguments: + seq -- the sequence number + """ + if self.maxseq == 1: + return self.payloadsize + + if seq < self.maxseq: + return MAX_PAYLOAD_SIZE + + return self.payloadsize % MAX_PAYLOAD_SIZE + + + def get_udp_message(self, packetnum=1): + """Construct the UDP message for the specified packetnum and return + as string + + Keyword arguments: + packetnum -- the packet no. for which to construct the message + (default 1) + """ + if packetnum > self.num_packets() or packetnum < 1: + return "" + header = "" + if packetnum==1: + header = self.get_header(self.packettype, packetnum, self.maxseq, + self.get_payload_size(packetnum)) + else: + header = self.get_header(PT_BLOB, packetnum, self.maxseq, + self.get_payload_size(packetnum)) + + payload = self.payload[ (packetnum-1) * MAX_PAYLOAD_SIZE : + (packetnum-1) * MAX_PAYLOAD_SIZE+ + self.get_payload_size(packetnum) ] + return header + payload + + def send(self, sock, addr, uid=UNIQUE_IDENTIFICATION): + """Send the entire message to the specified socket and address. + + Arguments: + sock -- datagram socket object (socket.socket) + addr -- address, port pair (eg: ("127.0.0.1", 9777) ) + uid -- unique identification + """ + self.uid = uid + for a in range ( 0, self.num_packets() ): + try: + sock.sendto(self.get_udp_message(a+1), addr) + return True + except: + return False + + +class PacketHELO (Packet): + """A HELO packet + + A HELO packet establishes a valid connection to XBMC. It is the + first packet that should be sent. + """ + def __init__(self, devicename=None, icon_type=ICON_NONE, icon_file=None): + """ + Keyword arguments: + devicename -- the string that identifies the client + icon_type -- one of ICON_NONE, ICON_JPEG, ICON_PNG, ICON_GIF + icon_file -- location of icon file with respect to current working + directory if icon_type is not ICON_NONE + """ + Packet.__init__(self) + self.packettype = PT_HELO + self.icontype = icon_type + self.set_payload ( format_string(devicename)[0:128] ) + self.append_payload( chr (icon_type) ) + self.append_payload( format_uint16 (0) ) # port no + self.append_payload( format_uint32 (0) ) # reserved1 + self.append_payload( format_uint32 (0) ) # reserved2 + if icon_type != ICON_NONE and icon_file: + self.append_payload( file(icon_file).read() ) + +class PacketNOTIFICATION (Packet): + """A NOTIFICATION packet + + This packet displays a notification window in XBMC. It can contain + a caption, a message and an icon. + """ + def __init__(self, title, message, icon_type=ICON_NONE, icon_file=None): + """ + Keyword arguments: + title -- the notification caption / title + message -- the main text of the notification + icon_type -- one of ICON_NONE, ICON_JPEG, ICON_PNG, ICON_GIF + icon_file -- location of icon file with respect to current working + directory if icon_type is not ICON_NONE + """ + Packet.__init__(self) + self.packettype = PT_NOTIFICATION + self.title = title + self.message = message + self.set_payload ( format_string(title) ) + self.append_payload( format_string(message) ) + self.append_payload( chr (icon_type) ) + self.append_payload( format_uint32 (0) ) # reserved + if icon_type != ICON_NONE and icon_file: + self.append_payload( file(icon_file).read() ) + +class PacketBUTTON (Packet): + """A BUTTON packet + + A button packet send a key press or release event to XBMC + """ + def __init__(self, code=0, repeat=1, down=1, queue=0, + map_name="", button_name="", amount=0, axis=0): + """ + Keyword arguments: + code -- raw button code (default: 0) + repeat -- this key press should repeat until released (default: 1) + Note that queued pressed cannot repeat. + down -- if this is 1, it implies a press event, 0 implies a release + event. (default: 1) + queue -- a queued key press means that the button event is + executed just once after which the next key press is + processed. It can be used for macros. Currently there + is no support for time delays between queued presses. + (default: 0) + map_name -- a combination of map_name and button_name refers to a + mapping in the user's Keymap.xml or Lircmap.xml. + map_name can be one of the following: + "KB" => standard keyboard map ( <keyboard> section ) + "XG" => xbox gamepad map ( <gamepad> section ) + "R1" => xbox remote map ( <remote> section ) + "R2" => xbox universal remote map ( <universalremote> + section ) + "LI:devicename" => LIRC remote map where 'devicename' is the + actual device's name + button_name -- a button name defined in the map specified in map_name. + For example, if map_name is "KB" refering to the + <keyboard> section in Keymap.xml then, valid + button_names include "printscreen", "minus", "x", etc. + amount -- unimplemented for now; in the future it will be used for + specifying magnitude of analog key press events + """ + Packet.__init__(self) + self.flags = 0 + self.packettype = PT_BUTTON + if type (code ) == str: + code = ord(code) + + # assign code only if we don't have a map and button name + if not (map_name and button_name): + self.code = code + else: + self.flags |= BT_USE_NAME + self.code = 0 + if (amount != None): + self.flags |= BT_USE_AMOUNT + self.amount = int(amount) + else: + self.amount = 0 + + if down: + self.flags |= BT_DOWN + else: + self.flags |= BT_UP + if not repeat: + self.flags |= BT_NO_REPEAT + if queue: + self.flags |= BT_QUEUE + if axis == 1: + self.flags |= BT_AXISSINGLE + elif axis == 2: + self.flags |= BT_AXIS + + self.set_payload ( format_uint16(self.code) ) + self.append_payload( format_uint16(self.flags) ) + self.append_payload( format_uint16(self.amount) ) + self.append_payload( format_string (map_name) ) + self.append_payload( format_string (button_name) ) + +class PacketMOUSE (Packet): + """A MOUSE packet + + A MOUSE packets sets the mouse position in XBMC + """ + def __init__(self, x, y): + """ + Arguments: + x -- horitontal position ranging from 0 to 65535 + y -- vertical position ranging from 0 to 65535 + + The range will be mapped to the screen width and height in XBMC + """ + Packet.__init__(self) + self.packettype = PT_MOUSE + self.flags = MS_ABSOLUTE + self.append_payload( chr (self.flags) ) + self.append_payload( format_uint16(x) ) + self.append_payload( format_uint16(y) ) + +class PacketBYE (Packet): + """A BYE packet + + A BYE packet terminates the connection to XBMC. + """ + def __init__(self): + Packet.__init__(self) + self.packettype = PT_BYE + + +class PacketPING (Packet): + """A PING packet + + A PING packet tells XBMC that the client is still alive. All valid + packets act as ping (not just this one). A client needs to ping + XBMC at least once in 60 seconds or it will time out. + """ + def __init__(self): + Packet.__init__(self) + self.packettype = PT_PING + +class PacketLOG (Packet): + """A LOG packet + + A LOG packet tells XBMC to log the message to xbmc.log with the loglevel as specified. + """ + def __init__(self, loglevel=0, logmessage="", autoprint=True): + """ + Keyword arguments: + loglevel -- the loglevel, follows XBMC standard. + logmessage -- the message to log + autoprint -- if the logmessage should automaticly be printed to stdout + """ + Packet.__init__(self) + self.packettype = PT_LOG + self.append_payload( chr (loglevel) ) + self.append_payload( format_string(logmessage) ) + if (autoprint): + print logmessage + +class PacketACTION (Packet): + """An ACTION packet + + An ACTION packet tells XBMC to do the action specified, based on the type it knows were it needs to be sent. + The idea is that this will be as in scripting/skining and keymapping, just triggered from afar. + """ + def __init__(self, actionmessage="", actiontype=ACTION_EXECBUILTIN): + """ + Keyword arguments: + loglevel -- the loglevel, follows XBMC standard. + logmessage -- the message to log + autoprint -- if the logmessage should automaticly be printed to stdout + """ + Packet.__init__(self) + self.packettype = PT_ACTION + self.append_payload( chr (actiontype) ) + self.append_payload( format_string(actionmessage) ) + +###################################################################### +# XBMC Client Class +###################################################################### + +class XBMCClient: + """An XBMC event client""" + + def __init__(self, name ="", icon_file=None, broadcast=False, uid=UNIQUE_IDENTIFICATION, + ip="127.0.0.1"): + """ + Keyword arguments: + name -- Name of the client + icon_file -- location of an icon file, if any (png, jpg or gif) + uid -- unique identification + """ + self.name = str(name) + self.icon_file = icon_file + self.icon_type = self._get_icon_type(icon_file) + self.ip = ip + self.port = 9777 + self.sock = socket(AF_INET,SOCK_DGRAM) + if broadcast: + self.sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + self.uid = uid + + + def connect(self, ip=None, port=None): + """Initialize connection to XBMC + ip -- IP Address of XBMC + port -- port that the event server on XBMC is listening on + """ + if ip: + self.ip = ip + if port: + self.port = int(port) + self.addr = (self.ip, self.port) + packet = PacketHELO(self.name, self.icon_type, self.icon_file) + return packet.send(self.sock, self.addr, self.uid) + + + def close(self): + """Close the current connection""" + packet = PacketBYE() + return packet.send(self.sock, self.addr, self.uid) + + + def ping(self): + """Send a PING packet""" + packet = PacketPING() + return packet.send(self.sock, self.addr, self.uid) + + + def send_notification(self, title="", message="", icon_file=None): + """Send a notification to the connected XBMC + Keyword Arguments: + title -- The title/heading for the notifcation + message -- The message to be displayed + icon_file -- location of an icon file, if any (png, jpg, gif) + """ + self.connect() + packet = PacketNOTIFICATION(title, message, + self._get_icon_type(icon_file), + icon_file) + return packet.send(self.sock, self.addr, self.uid) + + + def send_keyboard_button(self, button=None): + """Send a keyboard event to XBMC + Keyword Arguments: + button -- name of the keyboard button to send (same as in Keymap.xml) + """ + if not button: + return + return self.send_button(map="KB", button=button) + + + def send_remote_button(self, button=None): + """Send a remote control event to XBMC + Keyword Arguments: + button -- name of the remote control button to send (same as in Keymap.xml) + """ + if not button: + return + return self.send_button(map="R1", button=button) + + + def release_button(self): + """Release all buttons""" + packet = PacketBUTTON(code=0x01, down=0) + return packet.send(self.sock, self.addr, self.uid) + + + def send_button(self, map="", button="", amount=0): + """Send a button event to XBMC + Keyword arguments: + map -- a combination of map_name and button_name refers to a + mapping in the user's Keymap.xml or Lircmap.xml. + map_name can be one of the following: + "KB" => standard keyboard map ( <keyboard> section ) + "XG" => xbox gamepad map ( <gamepad> section ) + "R1" => xbox remote map ( <remote> section ) + "R2" => xbox universal remote map ( <universalremote> + section ) + "LI:devicename" => LIRC remote map where 'devicename' is the + actual device's name + button -- a button name defined in the map specified in map, above. + For example, if map is "KB" refering to the <keyboard> + section in Keymap.xml then, valid buttons include + "printscreen", "minus", "x", etc. + """ + packet = PacketBUTTON(map_name=str(map), button_name=str(button), amount=amount) + return packet.send(self.sock, self.addr, self.uid) + + def send_button_state(self, map="", button="", amount=0, down=0, axis=0): + """Send a button event to XBMC + Keyword arguments: + map -- a combination of map_name and button_name refers to a + mapping in the user's Keymap.xml or Lircmap.xml. + map_name can be one of the following: + "KB" => standard keyboard map ( <keyboard> section ) + "XG" => xbox gamepad map ( <gamepad> section ) + "R1" => xbox remote map ( <remote> section ) + "R2" => xbox universal remote map ( <universalremote> + section ) + "LI:devicename" => LIRC remote map where 'devicename' is the + actual device's name + button -- a button name defined in the map specified in map, above. + For example, if map is "KB" refering to the <keyboard> + section in Keymap.xml then, valid buttons include + "printscreen", "minus", "x", etc. + """ + if axis: + if amount == 0: + down = 0 + else: + down = 1 + + packet = PacketBUTTON(map_name=str(map), button_name=str(button), amount=amount, down=down, queue=1, axis=axis) + return packet.send(self.sock, self.addr, self.uid) + + def send_mouse_position(self, x=0, y=0): + """Send a mouse event to XBMC + Keywords Arguments: + x -- absolute x position of mouse ranging from 0 to 65535 + which maps to the entire screen width + y -- same a 'x' but relates to the screen height + """ + packet = PacketMOUSE(int(x), int(y)) + return packet.send(self.sock, self.addr, self.uid) + + def send_log(self, loglevel=0, logmessage="", autoprint=True): + """ + Keyword arguments: + loglevel -- the loglevel, follows XBMC standard. + logmessage -- the message to log + autoprint -- if the logmessage should automaticly be printed to stdout + """ + packet = PacketLOG(loglevel, logmessage) + return packet.send(self.sock, self.addr, self.uid) + + def send_action(self, actionmessage="", actiontype=ACTION_EXECBUILTIN): + """ + Keyword arguments: + actionmessage -- the ActionString + actiontype -- The ActionType the ActionString should be sent to. + """ + packet = PacketACTION(actionmessage, actiontype) + return packet.send(self.sock, self.addr, self.uid) + + def _get_icon_type(self, icon_file): + if icon_file: + if icon_file.lower()[-3:] == "png": + return ICON_PNG + elif icon_file.lower()[-3:] == "gif": + return ICON_GIF + elif icon_file.lower()[-3:] == "jpg": + return ICON_JPG + return ICON_NONE diff --git a/tools/EventClients/lib/python/zeroconf.py b/tools/EventClients/lib/python/zeroconf.py new file mode 100644 index 0000000000..9fc82f5909 --- /dev/null +++ b/tools/EventClients/lib/python/zeroconf.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +""" +Simple wrapper around Avahi +""" + +__author__ = "d4rk@xbmc.org" +__version__ = "0.1" + +try: + import time + import dbus, gobject, avahi + from dbus import DBusException + from dbus.mainloop.glib import DBusGMainLoop +except Exception, e: + print "Zeroconf support disabled. To enable, install the following Python modules:" + print " dbus, gobject, avahi" + pass + +SERVICE_FOUND = 1 +SERVICE_LOST = 2 + +class Browser: + """ Simple Zeroconf Browser """ + + def __init__( self, service_types = {} ): + """ + service_types - dictionary of services => handlers + """ + self._stop = False + self.loop = DBusGMainLoop() + self.bus = dbus.SystemBus( mainloop=self.loop ) + self.server = dbus.Interface( self.bus.get_object( avahi.DBUS_NAME, '/' ), + 'org.freedesktop.Avahi.Server') + self.handlers = {} + + for type in service_types.keys(): + self.add_service( type, service_types[ type ] ) + + + def add_service( self, type, handler = None ): + """ + Add a service that the browser should watch for + """ + self.sbrowser = dbus.Interface( + self.bus.get_object( + avahi.DBUS_NAME, + self.server.ServiceBrowserNew( + avahi.IF_UNSPEC, + avahi.PROTO_UNSPEC, + type, + 'local', + dbus.UInt32(0) + ) + ), + avahi.DBUS_INTERFACE_SERVICE_BROWSER) + self.handlers[ type ] = handler + self.sbrowser.connect_to_signal("ItemNew", self._new_item_handler) + self.sbrowser.connect_to_signal("ItemRemove", self._remove_item_handler) + + + def run(self): + """ + Run the gobject event loop + """ + # Don't use loop.run() because Python's GIL will block all threads + loop = gobject.MainLoop() + context = loop.get_context() + while not self._stop: + if context.pending(): + context.iteration( True ) + else: + time.sleep(1) + + def stop(self): + """ + Stop the gobject event loop + """ + self._stop = True + + + def _new_item_handler(self, interface, protocol, name, stype, domain, flags): + if flags & avahi.LOOKUP_RESULT_LOCAL: + # local service, skip + pass + + self.server.ResolveService( + interface, + protocol, + name, + stype, + domain, + avahi.PROTO_UNSPEC, + dbus.UInt32(0), + reply_handler = self._service_resolved_handler, + error_handler = self._error_handler + ) + return + + + def _remove_item_handler(self, interface, protocol, name, stype, domain, flags): + if self.handlers[ stype ]: + # FIXME: more details needed here + try: + self.handlers[ stype ]( SERVICE_LOST, { 'type' : stype, 'name' : name } ) + except: + pass + + + def _service_resolved_handler( self, *args ): + service = {} + service['type'] = str( args[3] ) + service['name'] = str( args[2] ) + service['address'] = str( args[7] ) + service['hostname'] = str( args[5] ) + service['port'] = int( args[8] ) + + # if the service type has a handler call it + try: + if self.handlers[ args[3] ]: + self.handlers[ args[3] ]( SERVICE_FOUND, service ) + except: + pass + + + def _error_handler( self, *args ): + print 'ERROR: %s ' % str( args[0] ) + + +if __name__ == "__main__": + def service_handler( found, service ): + print "---------------------" + print ['Found Service', 'Lost Service'][found-1] + for key in service.keys(): + print key+" : "+str( service[key] ) + + browser = Browser( { + '_xbmc-events._udp' : service_handler, + '_xbmc-web._tcp' : service_handler + } ) + browser.run() + |