blob: 46c41388917d9cb95b32f73badae9e87828b0181 [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>
21#include <assert.h>
22#include <rdr/OutStream.h>
23#include <rfb/msgTypes.h>
Pierre Ossman7638e9c2014-01-16 13:12:40 +010024#include <rfb/fenceTypes.h>
25#include <rfb/Exception.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000026#include <rfb/ConnParams.h>
27#include <rfb/UpdateTracker.h>
Pierre Ossman7638e9c2014-01-16 13:12:40 +010028#include <rfb/Encoder.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000029#include <rfb/SMsgWriter.h>
30#include <rfb/LogWriter.h>
31
32using namespace rfb;
33
34static LogWriter vlog("SMsgWriter");
35
36SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_)
Pierre Ossman7638e9c2014-01-16 13:12:40 +010037 : imageBufIdealSize(0), cp(cp_), os(os_), currentEncoding(0),
38 nRectsInUpdate(0), nRectsInHeader(0),
Pierre Ossman126e5642014-02-13 14:40:25 +010039 needSetDesktopSize(false), needExtendedDesktopSize(false),
40 needSetDesktopName(false), needSetCursor(false), needSetXCursor(false),
Pierre Ossman7638e9c2014-01-16 13:12:40 +010041 lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0),
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000042 imageBuf(0), imageBufSize(0)
43{
Peter Åstrand98fe98c2010-02-10 07:43:02 +000044 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000045 bytesSent[i] = 0;
46 rectsSent[i] = 0;
47 }
48}
49
50SMsgWriter::~SMsgWriter()
51{
52 vlog.info("framebuffer updates %d",updatesSent);
53 int bytes = 0;
Peter Åstrand98fe98c2010-02-10 07:43:02 +000054 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000055 if (i != encodingCopyRect)
56 bytes += bytesSent[i];
57 if (rectsSent[i])
58 vlog.info(" %s rects %d, bytes %d",
59 encodingName(i), rectsSent[i], bytesSent[i]);
60 }
DRC887c5fd2011-08-19 03:13:47 +000061 vlog.info(" raw bytes equivalent %llu, compression ratio %f",
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000062 rawBytesEquivalent, (double)rawBytesEquivalent / bytes);
63 delete [] imageBuf;
64}
65
Pierre Ossman7638e9c2014-01-16 13:12:40 +010066void SMsgWriter::writeServerInit()
67{
68 os->writeU16(cp->width);
69 os->writeU16(cp->height);
70 cp->pf().write(os);
71 os->writeString(cp->name());
72 endMsg();
73}
74
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000075void SMsgWriter::writeSetColourMapEntries(int firstColour, int nColours,
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010076 const rdr::U16 red[],
77 const rdr::U16 green[],
78 const rdr::U16 blue[])
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000079{
80 startMsg(msgTypeSetColourMapEntries);
81 os->pad(1);
82 os->writeU16(firstColour);
83 os->writeU16(nColours);
84 for (int i = firstColour; i < firstColour+nColours; i++) {
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010085 os->writeU16(red[i]);
86 os->writeU16(green[i]);
87 os->writeU16(blue[i]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000088 }
89 endMsg();
90}
91
92void SMsgWriter::writeBell()
93{
94 startMsg(msgTypeBell);
95 endMsg();
96}
97
98void SMsgWriter::writeServerCutText(const char* str, int len)
99{
100 startMsg(msgTypeServerCutText);
101 os->pad(3);
102 os->writeU32(len);
103 os->writeBytes(str, len);
104 endMsg();
105}
106
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100107void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
108{
109 if (!cp->supportsFence)
110 throw Exception("Client does not support fences");
111 if (len > 64)
112 throw Exception("Too large fence payload");
113 if ((flags & ~fenceFlagsSupported) != 0)
114 throw Exception("Unknown fence flags");
115
116 startMsg(msgTypeServerFence);
117 os->pad(3);
118
119 os->writeU32(flags);
120
121 os->writeU8(len);
122 os->writeBytes(data, len);
123
124 endMsg();
125}
126
127void SMsgWriter::writeEndOfContinuousUpdates()
128{
129 if (!cp->supportsContinuousUpdates)
130 throw Exception("Client does not support continuous updates");
131
132 startMsg(msgTypeEndOfContinuousUpdates);
133 endMsg();
134}
135
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100136bool SMsgWriter::writeSetDesktopSize() {
137 if (!cp->supportsDesktopResize)
138 return false;
139
140 needSetDesktopSize = true;
141
142 return true;
143}
144
145bool SMsgWriter::writeExtendedDesktopSize() {
146 if (!cp->supportsExtendedDesktopSize)
147 return false;
148
149 needExtendedDesktopSize = true;
150
151 return true;
152}
153
154bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
155 int fb_width, int fb_height,
156 const ScreenSet& layout) {
157 ExtendedDesktopSizeMsg msg;
158
159 if (!cp->supportsExtendedDesktopSize)
160 return false;
161
162 msg.reason = reason;
163 msg.result = result;
164 msg.fb_width = fb_width;
165 msg.fb_height = fb_height;
166 msg.layout = layout;
167
168 extendedDesktopSizeMsgs.push_back(msg);
169
170 return true;
171}
172
173bool SMsgWriter::writeSetDesktopName() {
174 if (!cp->supportsDesktopRename)
175 return false;
176
177 needSetDesktopName = true;
178
179 return true;
180}
181
Pierre Ossman126e5642014-02-13 14:40:25 +0100182bool SMsgWriter::writeSetCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100183{
Pierre Ossman126e5642014-02-13 14:40:25 +0100184 if (!cp->supportsLocalCursor)
185 return false;
186
187 needSetCursor = true;
188
189 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100190}
191
Pierre Ossman126e5642014-02-13 14:40:25 +0100192bool SMsgWriter::writeSetXCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100193{
Pierre Ossman126e5642014-02-13 14:40:25 +0100194 if (!cp->supportsLocalXCursor)
195 return false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100196
Pierre Ossman126e5642014-02-13 14:40:25 +0100197 needSetXCursor = true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100198
Pierre Ossman126e5642014-02-13 14:40:25 +0100199 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100200}
201
Pierre Ossmane9962f72009-04-23 12:31:42 +0000202bool SMsgWriter::needFakeUpdate()
203{
Pierre Ossman126e5642014-02-13 14:40:25 +0100204 if (needSetDesktopName)
205 return true;
206 if (needSetCursor || needSetXCursor)
207 return true;
208 if (needNoDataUpdate())
209 return true;
210
211 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000212}
213
Pierre Ossmane9962f72009-04-23 12:31:42 +0000214bool SMsgWriter::needNoDataUpdate()
215{
Pierre Ossman126e5642014-02-13 14:40:25 +0100216 if (needSetDesktopSize)
217 return true;
218 if (needExtendedDesktopSize || !extendedDesktopSizeMsgs.empty())
219 return true;
220
221 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000222}
223
224void SMsgWriter::writeNoDataUpdate()
225{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100226 int nRects;
227
228 nRects = 0;
229
230 if (needSetDesktopSize)
231 nRects++;
232 if (needExtendedDesktopSize)
233 nRects++;
234 if (!extendedDesktopSizeMsgs.empty())
235 nRects += extendedDesktopSizeMsgs.size();
236
237 writeFramebufferUpdateStart(nRects);
238 writeNoDataRects();
239 writeFramebufferUpdateEnd();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000240}
241
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100242void SMsgWriter::writeFramebufferUpdateStart(int nRects)
243{
244 startMsg(msgTypeFramebufferUpdate);
245 os->pad(1);
246
247 if (nRects != 0xFFFF) {
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100248 if (needSetDesktopName)
249 nRects++;
Pierre Ossman126e5642014-02-13 14:40:25 +0100250 if (needSetCursor)
251 nRects++;
252 if (needSetXCursor)
253 nRects++;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100254 }
255
256 os->writeU16(nRects);
257
258 nRectsInUpdate = 0;
259 if (nRects == 0xFFFF)
260 nRectsInHeader = 0;
261 else
262 nRectsInHeader = nRects;
263
264 writePseudoRects();
265}
266
267void SMsgWriter::writeFramebufferUpdateEnd()
268{
269 if (nRectsInUpdate != nRectsInHeader && nRectsInHeader)
270 throw Exception("SMsgWriter::writeFramebufferUpdateEnd: "
271 "nRects out of sync");
272
273 if (nRectsInHeader == 0) {
274 // Send last rect. marker
275 os->writeS16(0);
276 os->writeS16(0);
277 os->writeU16(0);
278 os->writeU16(0);
279 os->writeU32(pseudoEncodingLastRect);
280 }
281
282 updatesSent++;
283 endMsg();
284}
285
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000286void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY)
287{
288 startRect(r,encodingCopyRect);
289 os->writeU16(srcX);
290 os->writeU16(srcY);
291 endRect();
292}
293
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100294void SMsgWriter::startRect(const Rect& r, int encoding)
295{
296 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
297 throw Exception("SMsgWriter::startRect: nRects out of sync");
298
299 currentEncoding = encoding;
300 lenBeforeRect = os->length();
301 if (encoding != encodingCopyRect)
Pierre Ossman668468b2014-01-31 12:37:32 +0100302 rawBytesEquivalent += 12 + r.width() * r.height() * (cp->pf().bpp/8);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100303
304 os->writeS16(r.tl.x);
305 os->writeS16(r.tl.y);
306 os->writeU16(r.width());
307 os->writeU16(r.height());
308 os->writeU32(encoding);
309}
310
311void SMsgWriter::endRect()
312{
313 if (currentEncoding <= encodingMax) {
314 bytesSent[currentEncoding] += os->length() - lenBeforeRect;
315 rectsSent[currentEncoding]++;
316 }
317}
318
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000319rdr::U8* SMsgWriter::getImageBuf(int required, int requested, int* nPixels)
320{
321 int requiredBytes = required * (cp->pf().bpp / 8);
322 int requestedBytes = requested * (cp->pf().bpp / 8);
323 int size = requestedBytes;
324 if (size > imageBufIdealSize) size = imageBufIdealSize;
325
326 if (size < requiredBytes)
327 size = requiredBytes;
328
329 if (imageBufSize < size) {
330 imageBufSize = size;
331 delete [] imageBuf;
332 imageBuf = new rdr::U8[imageBufSize];
333 }
334 if (nPixels)
335 *nPixels = imageBufSize / (cp->pf().bpp / 8);
336 return imageBuf;
337}
338
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100339void SMsgWriter::startMsg(int type)
340{
341 os->writeU8(type);
342}
343
344void SMsgWriter::endMsg()
345{
346 os->flush();
347}
348
349void SMsgWriter::writePseudoRects()
350{
Pierre Ossman126e5642014-02-13 14:40:25 +0100351 if (needSetCursor) {
352 rdr::U8* data;
353 int stride;
354
355 const Cursor& cursor = cp->cursor();
356
357 data = new rdr::U8[cursor.area() * cp->pf().bpp/8];
358 cursor.getImage(cp->pf(), data, cursor.getRect());
359
360 writeSetCursorRect(cursor.width(), cursor.height(),
361 cursor.hotspot.x, cursor.hotspot.y,
362 data, cursor.mask.buf);
363 needSetCursor = false;
364
365 delete [] data;
366 }
367
368 if (needSetXCursor) {
369 const Cursor& cursor = cp->cursor();
370 Pixel pix0, pix1;
371 rdr::U8 rgb0[3], rgb1[3];
372 rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1));
373
374 if (!bitmap.buf) {
375 // FIXME: We could reduce to two colors.
376 throw Exception("SMsgWriter::writePseudoRects: Unable to send multicolor cursor: RichCursor not supported by client");
377 }
378
379 cp->pf().rgbFromPixel(pix0, &rgb0[0], &rgb0[1], &rgb0[2]);
380 cp->pf().rgbFromPixel(pix1, &rgb1[0], &rgb1[1], &rgb1[2]);
381
382 writeSetXCursorRect(cursor.width(), cursor.height(),
383 cursor.hotspot.x, cursor.hotspot.y,
384 rgb0, rgb1, bitmap.buf, cursor.mask.buf);
385 needSetXCursor = false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100386 }
387
388 if (needSetDesktopName) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100389 writeSetDesktopNameRect(cp->name());
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100390 needSetDesktopName = false;
391 }
392}
393
394void SMsgWriter::writeNoDataRects()
395{
396 // Start with specific ExtendedDesktopSize messages
397 if (!extendedDesktopSizeMsgs.empty()) {
398 std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100399
400 for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100401 writeExtendedDesktopSizeRect(ri->reason, ri->result,
402 ri->fb_width, ri->fb_height, ri->layout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100403 }
404
405 extendedDesktopSizeMsgs.clear();
406 }
407
408 // Send this before SetDesktopSize to make life easier on the clients
409 if (needExtendedDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100410 writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height,
411 cp->screenLayout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100412 needExtendedDesktopSize = false;
413 }
414
415 // Some clients assume this is the last rectangle so don't send anything
416 // more after this
417 if (needSetDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100418 writeSetDesktopSizeRect(cp->width, cp->height);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100419 needSetDesktopSize = false;
420 }
421}
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100422
423void SMsgWriter::writeSetDesktopSizeRect(int width, int height)
424{
425 if (!cp->supportsDesktopResize)
426 throw Exception("Client does not support desktop resize");
427 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
428 throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync");
429
430 os->writeS16(0);
431 os->writeS16(0);
432 os->writeU16(width);
433 os->writeU16(height);
434 os->writeU32(pseudoEncodingDesktopSize);
435}
436
437void SMsgWriter::writeExtendedDesktopSizeRect(rdr::U16 reason,
438 rdr::U16 result,
439 int fb_width,
440 int fb_height,
441 const ScreenSet& layout)
442{
443 ScreenSet::const_iterator si;
444
445 if (!cp->supportsExtendedDesktopSize)
446 throw Exception("Client does not support extended desktop resize");
447 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
448 throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync");
449
450 os->writeU16(reason);
451 os->writeU16(result);
452 os->writeU16(fb_width);
453 os->writeU16(fb_height);
454 os->writeU32(pseudoEncodingExtendedDesktopSize);
455
456 os->writeU8(layout.num_screens());
457 os->pad(3);
458
459 for (si = layout.begin();si != layout.end();++si) {
460 os->writeU32(si->id);
461 os->writeU16(si->dimensions.tl.x);
462 os->writeU16(si->dimensions.tl.y);
463 os->writeU16(si->dimensions.width());
464 os->writeU16(si->dimensions.height());
465 os->writeU32(si->flags);
466 }
467}
468
469void SMsgWriter::writeSetDesktopNameRect(const char *name)
470{
471 if (!cp->supportsDesktopRename)
472 throw Exception("Client does not support desktop rename");
473 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
474 throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync");
475
476 os->writeS16(0);
477 os->writeS16(0);
478 os->writeU16(0);
479 os->writeU16(0);
480 os->writeU32(pseudoEncodingDesktopName);
481 os->writeString(name);
482}
Pierre Ossman126e5642014-02-13 14:40:25 +0100483
484void SMsgWriter::writeSetCursorRect(int width, int height,
485 int hotspotX, int hotspotY,
486 const void* data, const void* mask)
487{
488 if (!cp->supportsLocalCursor)
489 throw Exception("Client does not support local cursors");
490 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
491 throw Exception("SMsgWriter::writeSetCursorRect: nRects out of sync");
492
493 os->writeS16(hotspotX);
494 os->writeS16(hotspotY);
495 os->writeU16(width);
496 os->writeU16(height);
497 os->writeU32(pseudoEncodingCursor);
498 os->writeBytes(data, width * height * (cp->pf().bpp/8));
499 os->writeBytes(mask, (width+7)/8 * height);
500}
501
502void SMsgWriter::writeSetXCursorRect(int width, int height,
503 int hotspotX, int hotspotY,
504 const rdr::U8 pix0[],
505 const rdr::U8 pix1[],
506 const void* data, const void* mask)
507{
508 if (!cp->supportsLocalXCursor)
509 throw Exception("Client does not support local cursors");
510 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
511 throw Exception("SMsgWriter::writeSetXCursorRect: nRects out of sync");
512
513 os->writeS16(hotspotX);
514 os->writeS16(hotspotY);
515 os->writeU16(width);
516 os->writeU16(height);
517 os->writeU32(pseudoEncodingXCursor);
518 if (width * height) {
519 os->writeU8(pix0[0]);
520 os->writeU8(pix0[1]);
521 os->writeU8(pix0[2]);
522 os->writeU8(pix1[0]);
523 os->writeU8(pix1[1]);
524 os->writeU8(pix1[2]);
525 os->writeBytes(data, (width+7)/8 * height);
526 os->writeBytes(mask, (width+7)/8 * height);
527 }
528}