/* | |
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); | |
} |