/*
uSynergy client -- Implementation for the embedded Synergy client library
version 1.0.0, July 7th, 2012
Copyright (c) 2012 Alex Evans
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "uSynergy.h"
#include <stdio.h>
#include <string.h>
//---------------------------------------------------------------------------------------------------------------------
// Internal helpers
//---------------------------------------------------------------------------------------------------------------------
/**
@brief Read 16 bit integer in network byte order and convert to native byte order
**/
static int16_t sNetToNative16(const unsigned char *value)
{
#ifdef USYNERGY_LITTLE_ENDIAN
return value[1] | (value[0] << 8);
#else
return value[0] | (value[1] << 8);
#endif
}
/**
@brief Read 32 bit integer in network byte order and convert to native byte order
**/
static int32_t sNetToNative32(const unsigned char *value)
{
#ifdef USYNERGY_LITTLE_ENDIAN
return value[3] | (value[2] << 8) | (value[1] << 16) | (value[0] << 24);
#else
return value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
#endif
}
/**
@brief Trace text to client
**/
static void sTrace(uSynergyContext *context, const char* text)
{
// Don't trace if we don't have a trace function
if (context->m_traceFunc != 0L)
context->m_traceFunc(context->m_cookie, text);
}
/**
@brief Add string to reply packet
**/
static void sAddString(uSynergyContext *context, const char *string)
{
size_t len = strlen(string);
memcpy(context->m_replyCur, string, len);
context->m_replyCur += len;
}
/**
@brief Add uint8 to reply packet
**/
static void sAddUInt8(uSynergyContext *context, uint8_t value)
{
*context->m_replyCur++ = value;
}
/**
@brief Add uint16 to reply packet
**/
static void sAddUInt16(uSynergyContext *context, uint16_t value)
{
uint8_t *reply = context->m_replyCur;
*reply++ = (uint8_t)(value >> 8);
*reply++ = (uint8_t)value;
context->m_replyCur = reply;
}
/**
@brief Add uint32 to reply packet
**/
static void sAddUInt32(uSynergyContext *context, uint32_t value)
{
uint8_t *reply = context->m_replyCur;
*reply++ = (uint8_t)(value >> 24);
*reply++ = (uint8_t)(value >> 16);
*reply++ = (uint8_t)(value >> 8);
*reply++ = (uint8_t)value;
context->m_replyCur = reply;
}
/**
@brief Send reply packet
**/
static uSynergyBool sSendReply(uSynergyContext *context)
{
// Set header size
uint8_t *reply_buf = context->m_replyBuffer;
uint32_t reply_len = (uint32_t)(context->m_replyCur - reply_buf); /* Total size of reply */
uint32_t body_len = reply_len - 4; /* Size of body */
uSynergyBool ret;
reply_buf[0] = (uint8_t)(body_len >> 24);
reply_buf[1] = (uint8_t)(body_len >> 16);
reply_buf[2] = (uint8_t)(body_len >> 8);
reply_buf[3] = (uint8_t)body_len;
// Send reply
ret = context->m_sendFunc(context->m_cookie, context->m_replyBuffer, reply_len);
// Reset reply buffer write pointer
context->m_replyCur = context->m_replyBuffer+4;
return ret;
}
/**
@brief Call mouse callback after a mouse event
**/
static void sSendMouseCallback(uSynergyContext *context)
{
// Skip if no callback is installed
if (context->m_mouseCallback == 0L)
return;
// Send callback
context->m_mouseCallback(context->m_cookie, context->m_mouseX, context->m_mouseY, context->m_mouseWheelX,
context->m_mouseWheelY, context->m_mouseButtonLeft, context->m_mouseButtonRight, context->m_mouseButtonMiddle);
}
/**
@brief Send keyboard callback when a key has been pressed or released
**/
static void sSendKeyboardCallback(uSynergyContext *context, uint16_t key, uint16_t modifiers, uSynergyBool down, uSynergyBool repeat)
{
// Skip if no callback is installed
if (context->m_keyboardCallback == 0L)
return;
// Send callback
context->m_keyboardCallback(context->m_cookie, key, modifiers, down, repeat);
}
/**
@brief Send joystick callback
**/
static void sSendJoystickCallback(uSynergyContext *context, uint8_t joyNum)
{
int8_t *sticks;
// Skip if no callback is installed
if (context->m_joystickCallback == 0L)
return;
// Send callback
sticks = context->m_joystickSticks[joyNum];
context->m_joystickCallback(context->m_cookie, joyNum, context->m_joystickButtons[joyNum], sticks[0], sticks[1], sticks[2], sticks[3]);
}
/**
@brief Parse a single client message, update state, send callbacks and send replies
**/
#define USYNERGY_IS_PACKET(pkt_id) memcmp(message+4, pkt_id, 4)==0
static void sProcessMessage(uSynergyContext *context, const uint8_t *message)
{
// We have a packet!
if (memcmp(message+4, "Synergy", 7)==0)
{
// Welcome message
// kMsgHello = "Synergy%2i%2i"
// kMsgHelloBack = "Synergy%2i%2i%s"
sAddString(context, "Synergy");
sAddUInt16(context, USYNERGY_PROTOCOL_MAJOR);
sAddUInt16(context, USYNERGY_PROTOCOL_MINOR);
sAddUInt32(context, (uint32_t)strlen(context->m_clientName));
sAddString(context, context->m_clientName);
if (!sSendReply(context))
{
// Send reply failed, let's try to reconnect
sTrace(context, "SendReply failed, trying to reconnect in a second");
context->m_connected = USYNERGY_FALSE;
context->m_sleepFunc(context->m_cookie, 1000);
}
else
{
// Let's assume we're connected
char buffer[256+1];
sprintf(buffer, "Connected as client \"%s\"", context->m_clientName);
sTrace(context, buffer);
context->m_hasReceivedHello = USYNERGY_TRUE;
}
return;
}
else if (USYNERGY_IS_PACKET("QINF"))
{
// Screen info. Reply with DINF
// kMsgQInfo = "QINF"
// kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i"
uint16_t x = 0, y = 0, warp = 0;
sAddString(context, "DINF");
sAddUInt16(context, x);
sAddUInt16(context, y);
sAddUInt16(context, context->m_clientWidth);
sAddUInt16(context, context->m_clientHeight);
sAddUInt16(context, warp);
sAddUInt16(context, 0); // mx?
sAddUInt16(context, 0); // my?
sSendReply(context);
return;
}
else if (USYNERGY_IS_PACKET("CIAK"))
{
// Do nothing?
// kMsgCInfoAck = "CIAK"
return;
}
else if (USYNERGY_IS_PACKET("CROP"))
{
// Do nothing?
// kMsgCResetOptions = "CROP"
return;
}
else if (USYNERGY_IS_PACKET("CINN"))
{
// Screen enter. Reply with CNOP
// kMsgCEnter = "CINN%2i%2i%4i%2i"
// Obtain the Synergy sequence number
context->m_sequenceNumber = sNetToNative32(message + 12);
context->m_isCaptured = USYNERGY_TRUE;
// Call callback
if (context->m_screenActiveCallback != 0L)
context->m_screenActiveCallback(context->m_cookie, USYNERGY_TRUE);
}
else if (USYNERGY_IS_PACKET("COUT"))
{
// Screen leave
// kMsgCLeave = "COUT"
context->m_isCaptured = USYNERGY_FALSE;
// Call callback
if (context->m_screenActiveCallback != 0L)
context->m_screenActiveCallback(context->m_cookie, USYNERGY_FALSE);
}
else if (USYNERGY_IS_PACKET("DMDN"))
{
// Mouse down
// kMsgDMouseDown = "DMDN%1i"
char btn = message[8]-1;
if (btn==2)
context->m_mouseButtonRight = USYNERGY_TRUE;
else if (btn==1)
context->m_mouseButtonMiddle = USYNERGY_TRUE;
else
context->m_mouseButtonLeft = USYNERGY_TRUE;
sSendMouseCallback(context);
}
else if (USYNERGY_IS_PACKET("DMUP"))
{
// Mouse up
// kMsgDMouseUp = "DMUP%1i"
char btn = message[8]-1;
if (btn==2)
context->m_mouseButtonRight = USYNERGY_FALSE;
else if (btn==1)
context->m_mouseButtonMiddle = USYNERGY_FALSE;
else
context->m_mouseButtonLeft = USYNERGY_FALSE;
sSendMouseCallback(context);
}
else if (USYNERGY_IS_PACKET("DMMV"))
{
// Mouse move. Reply with CNOP
// kMsgDMouseMove = "DMMV%2i%2i"
context->m_mouseX = sNetToNative16(message+8);
context->m_mouseY = sNetToNative16(message+10);
sSendMouseCallback(context);
}
else if (USYNERGY_IS_PACKET("DMWM"))
{
// Mouse wheel
// kMsgDMouseWheel = "DMWM%2i%2i"
// kMsgDMouseWheel1_0 = "DMWM%2i"
context->m_mouseWheelX += sNetToNative16(message+8);
context->m_mouseWheelY += sNetToNative16(message+10);
sSendMouseCallback(context);
}
else if (USYNERGY_IS_PACKET("DKDN"))
{
// Key down
// kMsgDKeyDown = "DKDN%2i%2i%2i"
// kMsgDKeyDown1_0 = "DKDN%2i%2i"
//uint16_t id = sNetToNative16(message+8);
uint16_t mod = sNetToNative16(message+10);
uint16_t key = sNetToNative16(message+12);
sSendKeyboardCallback(context, key, mod, USYNERGY_TRUE, USYNERGY_FALSE);
}
else if (USYNERGY_IS_PACKET("DKRP"))
{
// Key repeat
// kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"
// kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"
uint16_t mod = sNetToNative16(message+10);
// uint16_t count = sNetToNative16(message+12);
uint16_t key = sNetToNative16(message+14);
sSendKeyboardCallback(context, key, mod, USYNERGY_TRUE, USYNERGY_TRUE);
}
else if (USYNERGY_IS_PACKET("DKUP"))
{
// Key up
// kMsgDKeyUp = "DKUP%2i%2i%2i"
// kMsgDKeyUp1_0 = "DKUP%2i%2i"
//uint16 id=Endian::sNetToNative(sbuf[4]);
uint16_t mod = sNetToNative16(message+10);
uint16_t key = sNetToNative16(message+12);
sSendKeyboardCallback(context, key, mod, USYNERGY_FALSE, USYNERGY_FALSE);
}
else if (USYNERGY_IS_PACKET("DGBT"))
{
// Joystick buttons
// kMsgDGameButtons = "DGBT%1i%2i";
uint8_t joy_num = message[8];
if (joy_num<USYNERGY_NUM_JOYSTICKS)
{
// Copy button state, then send callback
context->m_joystickButtons[joy_num] = (message[9] << 8) | message[10];
sSendJoystickCallback(context, joy_num);
}
}
else if (USYNERGY_IS_PACKET("DGST"))
{
// Joystick sticks
// kMsgDGameSticks = "DGST%1i%1i%1i%1i%1i";
uint8_t joy_num = message[8];
if (joy_num<USYNERGY_NUM_JOYSTICKS)
{
// Copy stick state, then send callback
memcpy(context->m_joystickSticks[joy_num], message+9, 4);
sSendJoystickCallback(context, joy_num);
}
}
else if (USYNERGY_IS_PACKET("DSOP"))
{
// Set options
// kMsgDSetOptions = "DSOP%4I"
}
else if (USYNERGY_IS_PACKET("CALV"))
{
// Keepalive, reply with CALV and then CNOP
// kMsgCKeepAlive = "CALV"
sAddString(context, "CALV");
sSendReply(context);
// now reply with CNOP
}
else if (USYNERGY_IS_PACKET("DCLP"))
{
// Clipboard message
// kMsgDClipboard = "DCLP%1i%4i%s"
//
// The clipboard message contains:
// 1 uint32: The size of the message
// 4 chars: The identifier ("DCLP")
// 1 uint8: The clipboard index
// 1 uint32: The sequence number. It's zero, because this message is always coming from the server?
// 1 uint32: The total size of the remaining 'string' (as per the Synergy %s string format (which is 1 uint32 for size followed by a char buffer (not necessarily null terminated)).
// 1 uint32: The number of formats present in the message
// And then 'number of formats' times the following:
// 1 uint32: The format of the clipboard data
// 1 uint32: The size n of the clipboard data
// n uint8: The clipboard data
const uint8_t * parse_msg = message+17;
uint32_t num_formats = sNetToNative32(parse_msg);
parse_msg += 4;
for (; num_formats; num_formats--)
{
// Parse clipboard format header
uint32_t format = sNetToNative32(parse_msg);
uint32_t size = sNetToNative32(parse_msg+4);
parse_msg += 8;
// Call callback
if (context->m_clipboardCallback)
context->m_clipboardCallback(context->m_cookie, format, parse_msg, size);
parse_msg += size;
}
}
else
{
// Unknown packet, could be any of these
// kMsgCNoop = "CNOP"
// kMsgCClose = "CBYE"
// kMsgCClipboard = "CCLP%1i%4i"
// kMsgCScreenSaver = "CSEC%1i"
// kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"
// kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"
// kMsgDMouseRelMove = "DMRM%2i%2i"
// kMsgEIncompatible = "EICV%2i%2i"
// kMsgEBusy = "EBSY"
// kMsgEUnknown = "EUNK"
// kMsgEBad = "EBAD"
char buffer[64];
sprintf(buffer, "Unknown packet '%c%c%c%c'", message[4], message[5], message[6], message[7]);
sTrace(context, buffer);
return;
}
// Reply with CNOP maybe?
sAddString(context, "CNOP");
sSendReply(context);
}
#undef USYNERGY_IS_PACKET
/**
@brief Mark context as being disconnected
**/
static void sSetDisconnected(uSynergyContext *context)
{
context->m_connected = USYNERGY_FALSE;
context->m_hasReceivedHello = USYNERGY_FALSE;
context->m_isCaptured = USYNERGY_FALSE;
context->m_replyCur = context->m_replyBuffer + 4;
context->m_sequenceNumber = 0;
}
/**
@brief Update a connected context
**/
static void sUpdateContext(uSynergyContext *context)
{
/* Receive data (blocking) */
int receive_size = USYNERGY_RECEIVE_BUFFER_SIZE - context->m_receiveOfs;
int num_received = 0;
int packlen = 0;
if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer + context->m_receiveOfs, receive_size, &num_received) == USYNERGY_FALSE)
{
/* Receive failed, let's try to reconnect */
char buffer[128];
sprintf(buffer, "Receive failed (%d bytes asked, %d bytes received), trying to reconnect in a second", receive_size, num_received);
sTrace(context, buffer);
sSetDisconnected(context);
context->m_sleepFunc(context->m_cookie, 1000);
return;
}
context->m_receiveOfs += num_received;
/* If we didn't receive any data then we're probably still polling to get connected and
therefore not getting any data back. To avoid overloading the system with a Synergy
thread that would hammer on polling, we let it rest for a bit if there's no data. */
if (num_received == 0)
context->m_sleepFunc(context->m_cookie, 500);
/* Check for timeouts */
if (context->m_hasReceivedHello)
{
uint32_t cur_time = context->m_getTimeFunc();
if (num_received == 0)
{
/* Timeout after 2 secs of inactivity (we received no CALV) */
if ((cur_time - context->m_lastMessageTime) > USYNERGY_IDLE_TIMEOUT)
sSetDisconnected(context);
}
else
context->m_lastMessageTime = cur_time;
}
/* Eat packets */
for (;;)
{
/* Grab packet length and bail out if the packet goes beyond the end of the buffer */
packlen = sNetToNative32(context->m_receiveBuffer);
if (packlen+4 > context->m_receiveOfs)
break;
/* Process message */
sProcessMessage(context, context->m_receiveBuffer);
/* Move packet to front of buffer */
memmove(context->m_receiveBuffer, context->m_receiveBuffer+packlen+4, context->m_receiveOfs-packlen-4);
context->m_receiveOfs -= packlen+4;
}
/* Throw away over-sized packets */
if (packlen > USYNERGY_RECEIVE_BUFFER_SIZE)
{
/* Oversized packet, ditch tail end */
char buffer[128];
sprintf(buffer, "Oversized packet: '%c%c%c%c' (length %d)", context->m_receiveBuffer[4], context->m_receiveBuffer[5], context->m_receiveBuffer[6], context->m_receiveBuffer[7], packlen);
sTrace(context, buffer);
num_received = context->m_receiveOfs-4; // 4 bytes for the size field
while (num_received != packlen)
{
int buffer_left = packlen - num_received;
int to_receive = buffer_left < USYNERGY_RECEIVE_BUFFER_SIZE ? buffer_left : USYNERGY_RECEIVE_BUFFER_SIZE;
int ditch_received = 0;
if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer, to_receive, &ditch_received) == USYNERGY_FALSE)
{
/* Receive failed, let's try to reconnect */
sTrace(context, "Receive failed, trying to reconnect in a second");
sSetDisconnected(context);
context->m_sleepFunc(context->m_cookie, 1000);
break;
}
else
{
num_received += ditch_received;
}
}
context->m_receiveOfs = 0;
}
}
//---------------------------------------------------------------------------------------------------------------------
// Public interface
//---------------------------------------------------------------------------------------------------------------------
/**
@brief Initialize uSynergy context
**/
void uSynergyInit(uSynergyContext *context)
{
/* Zero memory */
memset(context, 0, sizeof(uSynergyContext));
/* Initialize to default state */
sSetDisconnected(context);
}
/**
@brief Update uSynergy
**/
void uSynergyUpdate(uSynergyContext *context)
{
if (context->m_connected)
{
/* Update context, receive data, call callbacks */
sUpdateContext(context);
}
else
{
/* Try to connect */
if (context->m_connectFunc(context->m_cookie))
context->m_connected = USYNERGY_TRUE;
}
}
/**
@brief Send clipboard data
**/
void uSynergySendClipboard(uSynergyContext *context, const char *text)
{
// Calculate maximum size that will fit in a reply packet
uint32_t overhead_size = 4 + /* Message size */
4 + /* Message ID */
1 + /* Clipboard index */
4 + /* Sequence number */
4 + /* Rest of message size (because it's a Synergy string from here on) */
4 + /* Number of clipboard formats */
4 + /* Clipboard format */
4; /* Clipboard data length */
uint32_t max_length = USYNERGY_REPLY_BUFFER_SIZE - overhead_size;
// Clip text to max length
uint32_t text_length = (uint32_t)strlen(text);
if (text_length > max_length)
{
char buffer[128];
sprintf(buffer, "Clipboard buffer too small, clipboard truncated at %d characters", max_length);
sTrace(context, buffer);
text_length = max_length;
}
// Assemble packet
sAddString(context, "DCLP");
sAddUInt8(context, 0); /* Clipboard index */
sAddUInt32(context, context->m_sequenceNumber);
sAddUInt32(context, 4+4+4+text_length); /* Rest of message size: numFormats, format, length, data */
sAddUInt32(context, 1); /* Number of formats (only text for now) */
sAddUInt32(context, USYNERGY_CLIPBOARD_FORMAT_TEXT);
sAddUInt32(context, text_length);
sAddString(context, text);
sSendReply(context);
}