uSynergy.c (18289B)
1 /* 2 uSynergy client -- Implementation for the embedded Synergy client library 3 version 1.0.0, July 7th, 2012 4 5 Copyright (c) 2012 Alex Evans 6 7 This software is provided 'as-is', without any express or implied 8 warranty. In no event will the authors be held liable for any damages 9 arising from the use of this software. 10 11 Permission is granted to anyone to use this software for any purpose, 12 including commercial applications, and to alter it and redistribute it 13 freely, subject to the following restrictions: 14 15 1. The origin of this software must not be misrepresented; you must not 16 claim that you wrote the original software. If you use this software 17 in a product, an acknowledgment in the product documentation would be 18 appreciated but is not required. 19 20 2. Altered source versions must be plainly marked as such, and must not be 21 misrepresented as being the original software. 22 23 3. This notice may not be removed or altered from any source 24 distribution. 25 */ 26 #include "uSynergy.h" 27 #include <stdio.h> 28 #include <string.h> 29 30 31 32 //--------------------------------------------------------------------------------------------------------------------- 33 // Internal helpers 34 //--------------------------------------------------------------------------------------------------------------------- 35 36 37 38 /** 39 @brief Read 16 bit integer in network byte order and convert to native byte order 40 **/ 41 static int16_t sNetToNative16(const unsigned char *value) 42 { 43 #ifdef USYNERGY_LITTLE_ENDIAN 44 return value[1] | (value[0] << 8); 45 #else 46 return value[0] | (value[1] << 8); 47 #endif 48 } 49 50 51 52 /** 53 @brief Read 32 bit integer in network byte order and convert to native byte order 54 **/ 55 static int32_t sNetToNative32(const unsigned char *value) 56 { 57 #ifdef USYNERGY_LITTLE_ENDIAN 58 return value[3] | (value[2] << 8) | (value[1] << 16) | (value[0] << 24); 59 #else 60 return value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); 61 #endif 62 } 63 64 65 66 /** 67 @brief Trace text to client 68 **/ 69 static void sTrace(uSynergyContext *context, const char* text) 70 { 71 // Don't trace if we don't have a trace function 72 if (context->m_traceFunc != 0L) 73 context->m_traceFunc(context->m_cookie, text); 74 } 75 76 77 78 /** 79 @brief Add string to reply packet 80 **/ 81 static void sAddString(uSynergyContext *context, const char *string) 82 { 83 size_t len = strlen(string); 84 memcpy(context->m_replyCur, string, len); 85 context->m_replyCur += len; 86 } 87 88 89 90 /** 91 @brief Add uint8 to reply packet 92 **/ 93 static void sAddUInt8(uSynergyContext *context, uint8_t value) 94 { 95 *context->m_replyCur++ = value; 96 } 97 98 99 100 /** 101 @brief Add uint16 to reply packet 102 **/ 103 static void sAddUInt16(uSynergyContext *context, uint16_t value) 104 { 105 uint8_t *reply = context->m_replyCur; 106 *reply++ = (uint8_t)(value >> 8); 107 *reply++ = (uint8_t)value; 108 context->m_replyCur = reply; 109 } 110 111 112 113 /** 114 @brief Add uint32 to reply packet 115 **/ 116 static void sAddUInt32(uSynergyContext *context, uint32_t value) 117 { 118 uint8_t *reply = context->m_replyCur; 119 *reply++ = (uint8_t)(value >> 24); 120 *reply++ = (uint8_t)(value >> 16); 121 *reply++ = (uint8_t)(value >> 8); 122 *reply++ = (uint8_t)value; 123 context->m_replyCur = reply; 124 } 125 126 127 128 /** 129 @brief Send reply packet 130 **/ 131 static uSynergyBool sSendReply(uSynergyContext *context) 132 { 133 // Set header size 134 uint8_t *reply_buf = context->m_replyBuffer; 135 uint32_t reply_len = (uint32_t)(context->m_replyCur - reply_buf); /* Total size of reply */ 136 uint32_t body_len = reply_len - 4; /* Size of body */ 137 uSynergyBool ret; 138 reply_buf[0] = (uint8_t)(body_len >> 24); 139 reply_buf[1] = (uint8_t)(body_len >> 16); 140 reply_buf[2] = (uint8_t)(body_len >> 8); 141 reply_buf[3] = (uint8_t)body_len; 142 143 // Send reply 144 ret = context->m_sendFunc(context->m_cookie, context->m_replyBuffer, reply_len); 145 146 // Reset reply buffer write pointer 147 context->m_replyCur = context->m_replyBuffer+4; 148 return ret; 149 } 150 151 152 153 /** 154 @brief Call mouse callback after a mouse event 155 **/ 156 static void sSendMouseCallback(uSynergyContext *context) 157 { 158 // Skip if no callback is installed 159 if (context->m_mouseCallback == 0L) 160 return; 161 162 // Send callback 163 context->m_mouseCallback(context->m_cookie, context->m_mouseX, context->m_mouseY, context->m_mouseWheelX, 164 context->m_mouseWheelY, context->m_mouseButtonLeft, context->m_mouseButtonRight, context->m_mouseButtonMiddle); 165 } 166 167 168 169 /** 170 @brief Send keyboard callback when a key has been pressed or released 171 **/ 172 static void sSendKeyboardCallback(uSynergyContext *context, uint16_t key, uint16_t modifiers, uSynergyBool down, uSynergyBool repeat) 173 { 174 // Skip if no callback is installed 175 if (context->m_keyboardCallback == 0L) 176 return; 177 178 // Send callback 179 context->m_keyboardCallback(context->m_cookie, key, modifiers, down, repeat); 180 } 181 182 183 184 /** 185 @brief Send joystick callback 186 **/ 187 static void sSendJoystickCallback(uSynergyContext *context, uint8_t joyNum) 188 { 189 int8_t *sticks; 190 191 // Skip if no callback is installed 192 if (context->m_joystickCallback == 0L) 193 return; 194 195 // Send callback 196 sticks = context->m_joystickSticks[joyNum]; 197 context->m_joystickCallback(context->m_cookie, joyNum, context->m_joystickButtons[joyNum], sticks[0], sticks[1], sticks[2], sticks[3]); 198 } 199 200 201 202 /** 203 @brief Parse a single client message, update state, send callbacks and send replies 204 **/ 205 #define USYNERGY_IS_PACKET(pkt_id) memcmp(message+4, pkt_id, 4)==0 206 static void sProcessMessage(uSynergyContext *context, const uint8_t *message) 207 { 208 // We have a packet! 209 if (memcmp(message+4, "Synergy", 7)==0) 210 { 211 // Welcome message 212 // kMsgHello = "Synergy%2i%2i" 213 // kMsgHelloBack = "Synergy%2i%2i%s" 214 sAddString(context, "Synergy"); 215 sAddUInt16(context, USYNERGY_PROTOCOL_MAJOR); 216 sAddUInt16(context, USYNERGY_PROTOCOL_MINOR); 217 sAddUInt32(context, (uint32_t)strlen(context->m_clientName)); 218 sAddString(context, context->m_clientName); 219 if (!sSendReply(context)) 220 { 221 // Send reply failed, let's try to reconnect 222 sTrace(context, "SendReply failed, trying to reconnect in a second"); 223 context->m_connected = USYNERGY_FALSE; 224 context->m_sleepFunc(context->m_cookie, 1000); 225 } 226 else 227 { 228 // Let's assume we're connected 229 char buffer[256+1]; 230 sprintf(buffer, "Connected as client \"%s\"", context->m_clientName); 231 sTrace(context, buffer); 232 context->m_hasReceivedHello = USYNERGY_TRUE; 233 } 234 return; 235 } 236 else if (USYNERGY_IS_PACKET("QINF")) 237 { 238 // Screen info. Reply with DINF 239 // kMsgQInfo = "QINF" 240 // kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i" 241 uint16_t x = 0, y = 0, warp = 0; 242 sAddString(context, "DINF"); 243 sAddUInt16(context, x); 244 sAddUInt16(context, y); 245 sAddUInt16(context, context->m_clientWidth); 246 sAddUInt16(context, context->m_clientHeight); 247 sAddUInt16(context, warp); 248 sAddUInt16(context, 0); // mx? 249 sAddUInt16(context, 0); // my? 250 sSendReply(context); 251 return; 252 } 253 else if (USYNERGY_IS_PACKET("CIAK")) 254 { 255 // Do nothing? 256 // kMsgCInfoAck = "CIAK" 257 return; 258 } 259 else if (USYNERGY_IS_PACKET("CROP")) 260 { 261 // Do nothing? 262 // kMsgCResetOptions = "CROP" 263 return; 264 } 265 else if (USYNERGY_IS_PACKET("CINN")) 266 { 267 // Screen enter. Reply with CNOP 268 // kMsgCEnter = "CINN%2i%2i%4i%2i" 269 270 // Obtain the Synergy sequence number 271 context->m_sequenceNumber = sNetToNative32(message + 12); 272 context->m_isCaptured = USYNERGY_TRUE; 273 274 // Call callback 275 if (context->m_screenActiveCallback != 0L) 276 context->m_screenActiveCallback(context->m_cookie, USYNERGY_TRUE); 277 } 278 else if (USYNERGY_IS_PACKET("COUT")) 279 { 280 // Screen leave 281 // kMsgCLeave = "COUT" 282 context->m_isCaptured = USYNERGY_FALSE; 283 284 // Call callback 285 if (context->m_screenActiveCallback != 0L) 286 context->m_screenActiveCallback(context->m_cookie, USYNERGY_FALSE); 287 } 288 else if (USYNERGY_IS_PACKET("DMDN")) 289 { 290 // Mouse down 291 // kMsgDMouseDown = "DMDN%1i" 292 char btn = message[8]-1; 293 if (btn==2) 294 context->m_mouseButtonRight = USYNERGY_TRUE; 295 else if (btn==1) 296 context->m_mouseButtonMiddle = USYNERGY_TRUE; 297 else 298 context->m_mouseButtonLeft = USYNERGY_TRUE; 299 sSendMouseCallback(context); 300 } 301 else if (USYNERGY_IS_PACKET("DMUP")) 302 { 303 // Mouse up 304 // kMsgDMouseUp = "DMUP%1i" 305 char btn = message[8]-1; 306 if (btn==2) 307 context->m_mouseButtonRight = USYNERGY_FALSE; 308 else if (btn==1) 309 context->m_mouseButtonMiddle = USYNERGY_FALSE; 310 else 311 context->m_mouseButtonLeft = USYNERGY_FALSE; 312 sSendMouseCallback(context); 313 } 314 else if (USYNERGY_IS_PACKET("DMMV")) 315 { 316 // Mouse move. Reply with CNOP 317 // kMsgDMouseMove = "DMMV%2i%2i" 318 context->m_mouseX = sNetToNative16(message+8); 319 context->m_mouseY = sNetToNative16(message+10); 320 sSendMouseCallback(context); 321 } 322 else if (USYNERGY_IS_PACKET("DMWM")) 323 { 324 // Mouse wheel 325 // kMsgDMouseWheel = "DMWM%2i%2i" 326 // kMsgDMouseWheel1_0 = "DMWM%2i" 327 context->m_mouseWheelX += sNetToNative16(message+8); 328 context->m_mouseWheelY += sNetToNative16(message+10); 329 sSendMouseCallback(context); 330 } 331 else if (USYNERGY_IS_PACKET("DKDN")) 332 { 333 // Key down 334 // kMsgDKeyDown = "DKDN%2i%2i%2i" 335 // kMsgDKeyDown1_0 = "DKDN%2i%2i" 336 //uint16_t id = sNetToNative16(message+8); 337 uint16_t mod = sNetToNative16(message+10); 338 uint16_t key = sNetToNative16(message+12); 339 sSendKeyboardCallback(context, key, mod, USYNERGY_TRUE, USYNERGY_FALSE); 340 } 341 else if (USYNERGY_IS_PACKET("DKRP")) 342 { 343 // Key repeat 344 // kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i" 345 // kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i" 346 uint16_t mod = sNetToNative16(message+10); 347 // uint16_t count = sNetToNative16(message+12); 348 uint16_t key = sNetToNative16(message+14); 349 sSendKeyboardCallback(context, key, mod, USYNERGY_TRUE, USYNERGY_TRUE); 350 } 351 else if (USYNERGY_IS_PACKET("DKUP")) 352 { 353 // Key up 354 // kMsgDKeyUp = "DKUP%2i%2i%2i" 355 // kMsgDKeyUp1_0 = "DKUP%2i%2i" 356 //uint16 id=Endian::sNetToNative(sbuf[4]); 357 uint16_t mod = sNetToNative16(message+10); 358 uint16_t key = sNetToNative16(message+12); 359 sSendKeyboardCallback(context, key, mod, USYNERGY_FALSE, USYNERGY_FALSE); 360 } 361 else if (USYNERGY_IS_PACKET("DGBT")) 362 { 363 // Joystick buttons 364 // kMsgDGameButtons = "DGBT%1i%2i"; 365 uint8_t joy_num = message[8]; 366 if (joy_num<USYNERGY_NUM_JOYSTICKS) 367 { 368 // Copy button state, then send callback 369 context->m_joystickButtons[joy_num] = (message[9] << 8) | message[10]; 370 sSendJoystickCallback(context, joy_num); 371 } 372 } 373 else if (USYNERGY_IS_PACKET("DGST")) 374 { 375 // Joystick sticks 376 // kMsgDGameSticks = "DGST%1i%1i%1i%1i%1i"; 377 uint8_t joy_num = message[8]; 378 if (joy_num<USYNERGY_NUM_JOYSTICKS) 379 { 380 // Copy stick state, then send callback 381 memcpy(context->m_joystickSticks[joy_num], message+9, 4); 382 sSendJoystickCallback(context, joy_num); 383 } 384 } 385 else if (USYNERGY_IS_PACKET("DSOP")) 386 { 387 // Set options 388 // kMsgDSetOptions = "DSOP%4I" 389 } 390 else if (USYNERGY_IS_PACKET("CALV")) 391 { 392 // Keepalive, reply with CALV and then CNOP 393 // kMsgCKeepAlive = "CALV" 394 sAddString(context, "CALV"); 395 sSendReply(context); 396 // now reply with CNOP 397 } 398 else if (USYNERGY_IS_PACKET("DCLP")) 399 { 400 // Clipboard message 401 // kMsgDClipboard = "DCLP%1i%4i%s" 402 // 403 // The clipboard message contains: 404 // 1 uint32: The size of the message 405 // 4 chars: The identifier ("DCLP") 406 // 1 uint8: The clipboard index 407 // 1 uint32: The sequence number. It's zero, because this message is always coming from the server? 408 // 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)). 409 // 1 uint32: The number of formats present in the message 410 // And then 'number of formats' times the following: 411 // 1 uint32: The format of the clipboard data 412 // 1 uint32: The size n of the clipboard data 413 // n uint8: The clipboard data 414 const uint8_t * parse_msg = message+17; 415 uint32_t num_formats = sNetToNative32(parse_msg); 416 parse_msg += 4; 417 for (; num_formats; num_formats--) 418 { 419 // Parse clipboard format header 420 uint32_t format = sNetToNative32(parse_msg); 421 uint32_t size = sNetToNative32(parse_msg+4); 422 parse_msg += 8; 423 424 // Call callback 425 if (context->m_clipboardCallback) 426 context->m_clipboardCallback(context->m_cookie, format, parse_msg, size); 427 428 parse_msg += size; 429 } 430 } 431 else 432 { 433 // Unknown packet, could be any of these 434 // kMsgCNoop = "CNOP" 435 // kMsgCClose = "CBYE" 436 // kMsgCClipboard = "CCLP%1i%4i" 437 // kMsgCScreenSaver = "CSEC%1i" 438 // kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i" 439 // kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i" 440 // kMsgDMouseRelMove = "DMRM%2i%2i" 441 // kMsgEIncompatible = "EICV%2i%2i" 442 // kMsgEBusy = "EBSY" 443 // kMsgEUnknown = "EUNK" 444 // kMsgEBad = "EBAD" 445 char buffer[64]; 446 sprintf(buffer, "Unknown packet '%c%c%c%c'", message[4], message[5], message[6], message[7]); 447 sTrace(context, buffer); 448 return; 449 } 450 451 // Reply with CNOP maybe? 452 sAddString(context, "CNOP"); 453 sSendReply(context); 454 } 455 #undef USYNERGY_IS_PACKET 456 457 458 459 /** 460 @brief Mark context as being disconnected 461 **/ 462 static void sSetDisconnected(uSynergyContext *context) 463 { 464 context->m_connected = USYNERGY_FALSE; 465 context->m_hasReceivedHello = USYNERGY_FALSE; 466 context->m_isCaptured = USYNERGY_FALSE; 467 context->m_replyCur = context->m_replyBuffer + 4; 468 context->m_sequenceNumber = 0; 469 } 470 471 472 473 /** 474 @brief Update a connected context 475 **/ 476 static void sUpdateContext(uSynergyContext *context) 477 { 478 /* Receive data (blocking) */ 479 int receive_size = USYNERGY_RECEIVE_BUFFER_SIZE - context->m_receiveOfs; 480 int num_received = 0; 481 int packlen = 0; 482 if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer + context->m_receiveOfs, receive_size, &num_received) == USYNERGY_FALSE) 483 { 484 /* Receive failed, let's try to reconnect */ 485 char buffer[128]; 486 sprintf(buffer, "Receive failed (%d bytes asked, %d bytes received), trying to reconnect in a second", receive_size, num_received); 487 sTrace(context, buffer); 488 sSetDisconnected(context); 489 context->m_sleepFunc(context->m_cookie, 1000); 490 return; 491 } 492 context->m_receiveOfs += num_received; 493 494 /* If we didn't receive any data then we're probably still polling to get connected and 495 therefore not getting any data back. To avoid overloading the system with a Synergy 496 thread that would hammer on polling, we let it rest for a bit if there's no data. */ 497 if (num_received == 0) 498 context->m_sleepFunc(context->m_cookie, 500); 499 500 /* Check for timeouts */ 501 if (context->m_hasReceivedHello) 502 { 503 uint32_t cur_time = context->m_getTimeFunc(); 504 if (num_received == 0) 505 { 506 /* Timeout after 2 secs of inactivity (we received no CALV) */ 507 if ((cur_time - context->m_lastMessageTime) > USYNERGY_IDLE_TIMEOUT) 508 sSetDisconnected(context); 509 } 510 else 511 context->m_lastMessageTime = cur_time; 512 } 513 514 /* Eat packets */ 515 for (;;) 516 { 517 /* Grab packet length and bail out if the packet goes beyond the end of the buffer */ 518 packlen = sNetToNative32(context->m_receiveBuffer); 519 if (packlen+4 > context->m_receiveOfs) 520 break; 521 522 /* Process message */ 523 sProcessMessage(context, context->m_receiveBuffer); 524 525 /* Move packet to front of buffer */ 526 memmove(context->m_receiveBuffer, context->m_receiveBuffer+packlen+4, context->m_receiveOfs-packlen-4); 527 context->m_receiveOfs -= packlen+4; 528 } 529 530 /* Throw away over-sized packets */ 531 if (packlen > USYNERGY_RECEIVE_BUFFER_SIZE) 532 { 533 /* Oversized packet, ditch tail end */ 534 char buffer[128]; 535 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); 536 sTrace(context, buffer); 537 num_received = context->m_receiveOfs-4; // 4 bytes for the size field 538 while (num_received != packlen) 539 { 540 int buffer_left = packlen - num_received; 541 int to_receive = buffer_left < USYNERGY_RECEIVE_BUFFER_SIZE ? buffer_left : USYNERGY_RECEIVE_BUFFER_SIZE; 542 int ditch_received = 0; 543 if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer, to_receive, &ditch_received) == USYNERGY_FALSE) 544 { 545 /* Receive failed, let's try to reconnect */ 546 sTrace(context, "Receive failed, trying to reconnect in a second"); 547 sSetDisconnected(context); 548 context->m_sleepFunc(context->m_cookie, 1000); 549 break; 550 } 551 else 552 { 553 num_received += ditch_received; 554 } 555 } 556 context->m_receiveOfs = 0; 557 } 558 } 559 560 561 //--------------------------------------------------------------------------------------------------------------------- 562 // Public interface 563 //--------------------------------------------------------------------------------------------------------------------- 564 565 566 567 /** 568 @brief Initialize uSynergy context 569 **/ 570 void uSynergyInit(uSynergyContext *context) 571 { 572 /* Zero memory */ 573 memset(context, 0, sizeof(uSynergyContext)); 574 575 /* Initialize to default state */ 576 sSetDisconnected(context); 577 } 578 579 580 /** 581 @brief Update uSynergy 582 **/ 583 void uSynergyUpdate(uSynergyContext *context) 584 { 585 if (context->m_connected) 586 { 587 /* Update context, receive data, call callbacks */ 588 sUpdateContext(context); 589 } 590 else 591 { 592 /* Try to connect */ 593 if (context->m_connectFunc(context->m_cookie)) 594 context->m_connected = USYNERGY_TRUE; 595 } 596 } 597 598 599 600 /** 601 @brief Send clipboard data 602 **/ 603 void uSynergySendClipboard(uSynergyContext *context, const char *text) 604 { 605 // Calculate maximum size that will fit in a reply packet 606 uint32_t overhead_size = 4 + /* Message size */ 607 4 + /* Message ID */ 608 1 + /* Clipboard index */ 609 4 + /* Sequence number */ 610 4 + /* Rest of message size (because it's a Synergy string from here on) */ 611 4 + /* Number of clipboard formats */ 612 4 + /* Clipboard format */ 613 4; /* Clipboard data length */ 614 uint32_t max_length = USYNERGY_REPLY_BUFFER_SIZE - overhead_size; 615 616 // Clip text to max length 617 uint32_t text_length = (uint32_t)strlen(text); 618 if (text_length > max_length) 619 { 620 char buffer[128]; 621 sprintf(buffer, "Clipboard buffer too small, clipboard truncated at %d characters", max_length); 622 sTrace(context, buffer); 623 text_length = max_length; 624 } 625 626 // Assemble packet 627 sAddString(context, "DCLP"); 628 sAddUInt8(context, 0); /* Clipboard index */ 629 sAddUInt32(context, context->m_sequenceNumber); 630 sAddUInt32(context, 4+4+4+text_length); /* Rest of message size: numFormats, format, length, data */ 631 sAddUInt32(context, 1); /* Number of formats (only text for now) */ 632 sAddUInt32(context, USYNERGY_CLIPBOARD_FORMAT_TEXT); 633 sAddUInt32(context, text_length); 634 sAddString(context, text); 635 sSendReply(context); 636 }