blob: 0d61292cbe0026ab5ac7d1289b5c4b2d48b65fac [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
DRCb4a83232011-08-19 04:57:18 +00002 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Pierre Ossman7638e9c2014-01-16 13:12:40 +01003 * Copyright 2009-2014 Pierre Ossman for Cendio AB
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00004 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20#include <stdio.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000021#include <rdr/OutStream.h>
22#include <rfb/msgTypes.h>
Pierre Ossman7638e9c2014-01-16 13:12:40 +010023#include <rfb/fenceTypes.h>
24#include <rfb/Exception.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000025#include <rfb/ConnParams.h>
26#include <rfb/UpdateTracker.h>
Pierre Ossman7638e9c2014-01-16 13:12:40 +010027#include <rfb/Encoder.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000028#include <rfb/SMsgWriter.h>
29#include <rfb/LogWriter.h>
30
31using namespace rfb;
32
33static LogWriter vlog("SMsgWriter");
34
35SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_)
Pierre Ossmanc0397262014-03-14 15:59:46 +010036 : cp(cp_), os(os_), currentEncoding(0),
Pierre Ossman7638e9c2014-01-16 13:12:40 +010037 nRectsInUpdate(0), nRectsInHeader(0),
Pierre Ossman126e5642014-02-13 14:40:25 +010038 needSetDesktopSize(false), needExtendedDesktopSize(false),
39 needSetDesktopName(false), needSetCursor(false), needSetXCursor(false),
Pierre Ossmanc0397262014-03-14 15:59:46 +010040 lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000041{
Peter Åstrand98fe98c2010-02-10 07:43:02 +000042 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000043 bytesSent[i] = 0;
44 rectsSent[i] = 0;
45 }
46}
47
48SMsgWriter::~SMsgWriter()
49{
50 vlog.info("framebuffer updates %d",updatesSent);
51 int bytes = 0;
Peter Åstrand98fe98c2010-02-10 07:43:02 +000052 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000053 if (i != encodingCopyRect)
54 bytes += bytesSent[i];
55 if (rectsSent[i])
56 vlog.info(" %s rects %d, bytes %d",
57 encodingName(i), rectsSent[i], bytesSent[i]);
58 }
DRC887c5fd2011-08-19 03:13:47 +000059 vlog.info(" raw bytes equivalent %llu, compression ratio %f",
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000060 rawBytesEquivalent, (double)rawBytesEquivalent / bytes);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000061}
62
Pierre Ossman7638e9c2014-01-16 13:12:40 +010063void SMsgWriter::writeServerInit()
64{
65 os->writeU16(cp->width);
66 os->writeU16(cp->height);
67 cp->pf().write(os);
68 os->writeString(cp->name());
69 endMsg();
70}
71
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000072void SMsgWriter::writeSetColourMapEntries(int firstColour, int nColours,
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010073 const rdr::U16 red[],
74 const rdr::U16 green[],
75 const rdr::U16 blue[])
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000076{
77 startMsg(msgTypeSetColourMapEntries);
78 os->pad(1);
79 os->writeU16(firstColour);
80 os->writeU16(nColours);
81 for (int i = firstColour; i < firstColour+nColours; i++) {
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010082 os->writeU16(red[i]);
83 os->writeU16(green[i]);
84 os->writeU16(blue[i]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000085 }
86 endMsg();
87}
88
89void SMsgWriter::writeBell()
90{
91 startMsg(msgTypeBell);
92 endMsg();
93}
94
95void SMsgWriter::writeServerCutText(const char* str, int len)
96{
97 startMsg(msgTypeServerCutText);
98 os->pad(3);
99 os->writeU32(len);
100 os->writeBytes(str, len);
101 endMsg();
102}
103
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100104void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
105{
106 if (!cp->supportsFence)
107 throw Exception("Client does not support fences");
108 if (len > 64)
109 throw Exception("Too large fence payload");
110 if ((flags & ~fenceFlagsSupported) != 0)
111 throw Exception("Unknown fence flags");
112
113 startMsg(msgTypeServerFence);
114 os->pad(3);
115
116 os->writeU32(flags);
117
118 os->writeU8(len);
119 os->writeBytes(data, len);
120
121 endMsg();
122}
123
124void SMsgWriter::writeEndOfContinuousUpdates()
125{
126 if (!cp->supportsContinuousUpdates)
127 throw Exception("Client does not support continuous updates");
128
129 startMsg(msgTypeEndOfContinuousUpdates);
130 endMsg();
131}
132
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100133bool SMsgWriter::writeSetDesktopSize() {
134 if (!cp->supportsDesktopResize)
135 return false;
136
137 needSetDesktopSize = true;
138
139 return true;
140}
141
142bool SMsgWriter::writeExtendedDesktopSize() {
143 if (!cp->supportsExtendedDesktopSize)
144 return false;
145
146 needExtendedDesktopSize = true;
147
148 return true;
149}
150
151bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
152 int fb_width, int fb_height,
153 const ScreenSet& layout) {
154 ExtendedDesktopSizeMsg msg;
155
156 if (!cp->supportsExtendedDesktopSize)
157 return false;
158
159 msg.reason = reason;
160 msg.result = result;
161 msg.fb_width = fb_width;
162 msg.fb_height = fb_height;
163 msg.layout = layout;
164
165 extendedDesktopSizeMsgs.push_back(msg);
166
167 return true;
168}
169
170bool SMsgWriter::writeSetDesktopName() {
171 if (!cp->supportsDesktopRename)
172 return false;
173
174 needSetDesktopName = true;
175
176 return true;
177}
178
Pierre Ossman126e5642014-02-13 14:40:25 +0100179bool SMsgWriter::writeSetCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100180{
Pierre Ossman126e5642014-02-13 14:40:25 +0100181 if (!cp->supportsLocalCursor)
182 return false;
183
184 needSetCursor = true;
185
186 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100187}
188
Pierre Ossman126e5642014-02-13 14:40:25 +0100189bool SMsgWriter::writeSetXCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100190{
Pierre Ossman126e5642014-02-13 14:40:25 +0100191 if (!cp->supportsLocalXCursor)
192 return false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100193
Pierre Ossman126e5642014-02-13 14:40:25 +0100194 needSetXCursor = true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100195
Pierre Ossman126e5642014-02-13 14:40:25 +0100196 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100197}
198
Pierre Ossmane9962f72009-04-23 12:31:42 +0000199bool SMsgWriter::needFakeUpdate()
200{
Pierre Ossman126e5642014-02-13 14:40:25 +0100201 if (needSetDesktopName)
202 return true;
203 if (needSetCursor || needSetXCursor)
204 return true;
205 if (needNoDataUpdate())
206 return true;
207
208 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000209}
210
Pierre Ossmane9962f72009-04-23 12:31:42 +0000211bool SMsgWriter::needNoDataUpdate()
212{
Pierre Ossman126e5642014-02-13 14:40:25 +0100213 if (needSetDesktopSize)
214 return true;
215 if (needExtendedDesktopSize || !extendedDesktopSizeMsgs.empty())
216 return true;
217
218 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000219}
220
221void SMsgWriter::writeNoDataUpdate()
222{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100223 int nRects;
224
225 nRects = 0;
226
227 if (needSetDesktopSize)
228 nRects++;
229 if (needExtendedDesktopSize)
230 nRects++;
231 if (!extendedDesktopSizeMsgs.empty())
232 nRects += extendedDesktopSizeMsgs.size();
233
234 writeFramebufferUpdateStart(nRects);
235 writeNoDataRects();
236 writeFramebufferUpdateEnd();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000237}
238
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100239void SMsgWriter::writeFramebufferUpdateStart(int nRects)
240{
241 startMsg(msgTypeFramebufferUpdate);
242 os->pad(1);
243
244 if (nRects != 0xFFFF) {
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100245 if (needSetDesktopName)
246 nRects++;
Pierre Ossman126e5642014-02-13 14:40:25 +0100247 if (needSetCursor)
248 nRects++;
249 if (needSetXCursor)
250 nRects++;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100251 }
252
253 os->writeU16(nRects);
254
255 nRectsInUpdate = 0;
256 if (nRects == 0xFFFF)
257 nRectsInHeader = 0;
258 else
259 nRectsInHeader = nRects;
260
261 writePseudoRects();
262}
263
264void SMsgWriter::writeFramebufferUpdateEnd()
265{
266 if (nRectsInUpdate != nRectsInHeader && nRectsInHeader)
267 throw Exception("SMsgWriter::writeFramebufferUpdateEnd: "
268 "nRects out of sync");
269
270 if (nRectsInHeader == 0) {
271 // Send last rect. marker
272 os->writeS16(0);
273 os->writeS16(0);
274 os->writeU16(0);
275 os->writeU16(0);
276 os->writeU32(pseudoEncodingLastRect);
277 }
278
279 updatesSent++;
280 endMsg();
281}
282
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000283void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY)
284{
285 startRect(r,encodingCopyRect);
286 os->writeU16(srcX);
287 os->writeU16(srcY);
288 endRect();
289}
290
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100291void SMsgWriter::startRect(const Rect& r, int encoding)
292{
293 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
294 throw Exception("SMsgWriter::startRect: nRects out of sync");
295
296 currentEncoding = encoding;
297 lenBeforeRect = os->length();
298 if (encoding != encodingCopyRect)
Pierre Ossman668468b2014-01-31 12:37:32 +0100299 rawBytesEquivalent += 12 + r.width() * r.height() * (cp->pf().bpp/8);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100300
301 os->writeS16(r.tl.x);
302 os->writeS16(r.tl.y);
303 os->writeU16(r.width());
304 os->writeU16(r.height());
305 os->writeU32(encoding);
306}
307
308void SMsgWriter::endRect()
309{
310 if (currentEncoding <= encodingMax) {
311 bytesSent[currentEncoding] += os->length() - lenBeforeRect;
312 rectsSent[currentEncoding]++;
313 }
314}
315
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100316void SMsgWriter::startMsg(int type)
317{
318 os->writeU8(type);
319}
320
321void SMsgWriter::endMsg()
322{
323 os->flush();
324}
325
326void SMsgWriter::writePseudoRects()
327{
Pierre Ossman126e5642014-02-13 14:40:25 +0100328 if (needSetCursor) {
329 rdr::U8* data;
330 int stride;
331
332 const Cursor& cursor = cp->cursor();
333
334 data = new rdr::U8[cursor.area() * cp->pf().bpp/8];
335 cursor.getImage(cp->pf(), data, cursor.getRect());
336
337 writeSetCursorRect(cursor.width(), cursor.height(),
338 cursor.hotspot.x, cursor.hotspot.y,
339 data, cursor.mask.buf);
340 needSetCursor = false;
341
342 delete [] data;
343 }
344
345 if (needSetXCursor) {
346 const Cursor& cursor = cp->cursor();
347 Pixel pix0, pix1;
348 rdr::U8 rgb0[3], rgb1[3];
349 rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1));
350
351 if (!bitmap.buf) {
352 // FIXME: We could reduce to two colors.
353 throw Exception("SMsgWriter::writePseudoRects: Unable to send multicolor cursor: RichCursor not supported by client");
354 }
355
356 cp->pf().rgbFromPixel(pix0, &rgb0[0], &rgb0[1], &rgb0[2]);
357 cp->pf().rgbFromPixel(pix1, &rgb1[0], &rgb1[1], &rgb1[2]);
358
359 writeSetXCursorRect(cursor.width(), cursor.height(),
360 cursor.hotspot.x, cursor.hotspot.y,
361 rgb0, rgb1, bitmap.buf, cursor.mask.buf);
362 needSetXCursor = false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100363 }
364
365 if (needSetDesktopName) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100366 writeSetDesktopNameRect(cp->name());
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100367 needSetDesktopName = false;
368 }
369}
370
371void SMsgWriter::writeNoDataRects()
372{
373 // Start with specific ExtendedDesktopSize messages
374 if (!extendedDesktopSizeMsgs.empty()) {
375 std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100376
377 for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100378 writeExtendedDesktopSizeRect(ri->reason, ri->result,
379 ri->fb_width, ri->fb_height, ri->layout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100380 }
381
382 extendedDesktopSizeMsgs.clear();
383 }
384
385 // Send this before SetDesktopSize to make life easier on the clients
386 if (needExtendedDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100387 writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height,
388 cp->screenLayout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100389 needExtendedDesktopSize = false;
390 }
391
392 // Some clients assume this is the last rectangle so don't send anything
393 // more after this
394 if (needSetDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100395 writeSetDesktopSizeRect(cp->width, cp->height);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100396 needSetDesktopSize = false;
397 }
398}
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100399
400void SMsgWriter::writeSetDesktopSizeRect(int width, int height)
401{
402 if (!cp->supportsDesktopResize)
403 throw Exception("Client does not support desktop resize");
404 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
405 throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync");
406
407 os->writeS16(0);
408 os->writeS16(0);
409 os->writeU16(width);
410 os->writeU16(height);
411 os->writeU32(pseudoEncodingDesktopSize);
412}
413
414void SMsgWriter::writeExtendedDesktopSizeRect(rdr::U16 reason,
415 rdr::U16 result,
416 int fb_width,
417 int fb_height,
418 const ScreenSet& layout)
419{
420 ScreenSet::const_iterator si;
421
422 if (!cp->supportsExtendedDesktopSize)
423 throw Exception("Client does not support extended desktop resize");
424 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
425 throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync");
426
427 os->writeU16(reason);
428 os->writeU16(result);
429 os->writeU16(fb_width);
430 os->writeU16(fb_height);
431 os->writeU32(pseudoEncodingExtendedDesktopSize);
432
433 os->writeU8(layout.num_screens());
434 os->pad(3);
435
436 for (si = layout.begin();si != layout.end();++si) {
437 os->writeU32(si->id);
438 os->writeU16(si->dimensions.tl.x);
439 os->writeU16(si->dimensions.tl.y);
440 os->writeU16(si->dimensions.width());
441 os->writeU16(si->dimensions.height());
442 os->writeU32(si->flags);
443 }
444}
445
446void SMsgWriter::writeSetDesktopNameRect(const char *name)
447{
448 if (!cp->supportsDesktopRename)
449 throw Exception("Client does not support desktop rename");
450 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
451 throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync");
452
453 os->writeS16(0);
454 os->writeS16(0);
455 os->writeU16(0);
456 os->writeU16(0);
457 os->writeU32(pseudoEncodingDesktopName);
458 os->writeString(name);
459}
Pierre Ossman126e5642014-02-13 14:40:25 +0100460
461void SMsgWriter::writeSetCursorRect(int width, int height,
462 int hotspotX, int hotspotY,
463 const void* data, const void* mask)
464{
465 if (!cp->supportsLocalCursor)
466 throw Exception("Client does not support local cursors");
467 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
468 throw Exception("SMsgWriter::writeSetCursorRect: nRects out of sync");
469
470 os->writeS16(hotspotX);
471 os->writeS16(hotspotY);
472 os->writeU16(width);
473 os->writeU16(height);
474 os->writeU32(pseudoEncodingCursor);
475 os->writeBytes(data, width * height * (cp->pf().bpp/8));
476 os->writeBytes(mask, (width+7)/8 * height);
477}
478
479void SMsgWriter::writeSetXCursorRect(int width, int height,
480 int hotspotX, int hotspotY,
481 const rdr::U8 pix0[],
482 const rdr::U8 pix1[],
483 const void* data, const void* mask)
484{
485 if (!cp->supportsLocalXCursor)
486 throw Exception("Client does not support local cursors");
487 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
488 throw Exception("SMsgWriter::writeSetXCursorRect: nRects out of sync");
489
490 os->writeS16(hotspotX);
491 os->writeS16(hotspotY);
492 os->writeU16(width);
493 os->writeU16(height);
494 os->writeU32(pseudoEncodingXCursor);
495 if (width * height) {
496 os->writeU8(pix0[0]);
497 os->writeU8(pix0[1]);
498 os->writeU8(pix0[2]);
499 os->writeU8(pix1[0]);
500 os->writeU8(pix1[1]);
501 os->writeU8(pix1[2]);
502 os->writeBytes(data, (width+7)/8 * height);
503 os->writeBytes(mask, (width+7)/8 * height);
504 }
505}