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 **/
sNetToNative16(const unsigned char * value)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 **/
sNetToNative32(const unsigned char * value)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 **/
sTrace(uSynergyContext * context,const char * text)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 **/
sAddString(uSynergyContext * context,const char * string)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 **/
sAddUInt8(uSynergyContext * context,uint8_t value)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 **/
sAddUInt16(uSynergyContext * context,uint16_t value)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 **/
sAddUInt32(uSynergyContext * context,uint32_t value)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 **/
sSendReply(uSynergyContext * context)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 **/
sSendMouseCallback(uSynergyContext * context)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 **/
sSendKeyboardCallback(uSynergyContext * context,uint16_t key,uint16_t modifiers,uSynergyBool down,uSynergyBool repeat)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 **/
sSendJoystickCallback(uSynergyContext * context,uint8_t joyNum)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
sProcessMessage(uSynergyContext * context,const uint8_t * message)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 **/
sSetDisconnected(uSynergyContext * context)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 **/
sUpdateContext(uSynergyContext * context)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 **/
uSynergyInit(uSynergyContext * context)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 **/
uSynergyUpdate(uSynergyContext * context)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 **/
uSynergySendClipboard(uSynergyContext * context,const char * text)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 }
637