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