blob: 5dc7c22f04d942a56952ae306f84a3f49287b0be [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),
39 wsccb(0), needSetDesktopSize(false),
40 needExtendedDesktopSize(false), needSetDesktopName(false),
41 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 encoders[i] = 0;
46 bytesSent[i] = 0;
47 rectsSent[i] = 0;
48 }
49}
50
51SMsgWriter::~SMsgWriter()
52{
53 vlog.info("framebuffer updates %d",updatesSent);
54 int bytes = 0;
Peter Åstrand98fe98c2010-02-10 07:43:02 +000055 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000056 delete encoders[i];
57 if (i != encodingCopyRect)
58 bytes += bytesSent[i];
59 if (rectsSent[i])
60 vlog.info(" %s rects %d, bytes %d",
61 encodingName(i), rectsSent[i], bytesSent[i]);
62 }
DRC887c5fd2011-08-19 03:13:47 +000063 vlog.info(" raw bytes equivalent %llu, compression ratio %f",
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000064 rawBytesEquivalent, (double)rawBytesEquivalent / bytes);
65 delete [] imageBuf;
66}
67
Pierre Ossman7638e9c2014-01-16 13:12:40 +010068void SMsgWriter::writeServerInit()
69{
70 os->writeU16(cp->width);
71 os->writeU16(cp->height);
72 cp->pf().write(os);
73 os->writeString(cp->name());
74 endMsg();
75}
76
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000077void SMsgWriter::writeSetColourMapEntries(int firstColour, int nColours,
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010078 const rdr::U16 red[],
79 const rdr::U16 green[],
80 const rdr::U16 blue[])
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000081{
82 startMsg(msgTypeSetColourMapEntries);
83 os->pad(1);
84 os->writeU16(firstColour);
85 os->writeU16(nColours);
86 for (int i = firstColour; i < firstColour+nColours; i++) {
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010087 os->writeU16(red[i]);
88 os->writeU16(green[i]);
89 os->writeU16(blue[i]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000090 }
91 endMsg();
92}
93
94void SMsgWriter::writeBell()
95{
96 startMsg(msgTypeBell);
97 endMsg();
98}
99
100void SMsgWriter::writeServerCutText(const char* str, int len)
101{
102 startMsg(msgTypeServerCutText);
103 os->pad(3);
104 os->writeU32(len);
105 os->writeBytes(str, len);
106 endMsg();
107}
108
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100109void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
110{
111 if (!cp->supportsFence)
112 throw Exception("Client does not support fences");
113 if (len > 64)
114 throw Exception("Too large fence payload");
115 if ((flags & ~fenceFlagsSupported) != 0)
116 throw Exception("Unknown fence flags");
117
118 startMsg(msgTypeServerFence);
119 os->pad(3);
120
121 os->writeU32(flags);
122
123 os->writeU8(len);
124 os->writeBytes(data, len);
125
126 endMsg();
127}
128
129void SMsgWriter::writeEndOfContinuousUpdates()
130{
131 if (!cp->supportsContinuousUpdates)
132 throw Exception("Client does not support continuous updates");
133
134 startMsg(msgTypeEndOfContinuousUpdates);
135 endMsg();
136}
137
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000138void SMsgWriter::setupCurrentEncoder()
139{
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000140 int encoding = cp->currentEncoding();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000141
142 // FIXME: Code duplication, see writeRect().
143 if (!encoders[encoding]) {
144 encoders[encoding] = Encoder::createEncoder(encoding, this);
145 assert(encoders[encoding]);
146 }
147
148 encoders[encoding]->setCompressLevel(cp->compressLevel);
149 encoders[encoding]->setQualityLevel(cp->qualityLevel);
DRCb4a83232011-08-19 04:57:18 +0000150 encoders[encoding]->setFineQualityLevel(cp->fineQualityLevel,
151 cp->subsampling);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000152}
153
154int SMsgWriter::getNumRects(const Rect &r)
155{
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000156 int encoding = cp->currentEncoding();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000157
158 if (!encoders[encoding])
159 setupCurrentEncoder();
160
161 return encoders[encoding]->getNumRects(r);
162}
163
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100164bool SMsgWriter::writeSetDesktopSize() {
165 if (!cp->supportsDesktopResize)
166 return false;
167
168 needSetDesktopSize = true;
169
170 return true;
171}
172
173bool SMsgWriter::writeExtendedDesktopSize() {
174 if (!cp->supportsExtendedDesktopSize)
175 return false;
176
177 needExtendedDesktopSize = true;
178
179 return true;
180}
181
182bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
183 int fb_width, int fb_height,
184 const ScreenSet& layout) {
185 ExtendedDesktopSizeMsg msg;
186
187 if (!cp->supportsExtendedDesktopSize)
188 return false;
189
190 msg.reason = reason;
191 msg.result = result;
192 msg.fb_width = fb_width;
193 msg.fb_height = fb_height;
194 msg.layout = layout;
195
196 extendedDesktopSizeMsgs.push_back(msg);
197
198 return true;
199}
200
201bool SMsgWriter::writeSetDesktopName() {
202 if (!cp->supportsDesktopRename)
203 return false;
204
205 needSetDesktopName = true;
206
207 return true;
208}
209
210void SMsgWriter::cursorChange(WriteSetCursorCallback* cb)
211{
212 wsccb = cb;
213}
214
215void SMsgWriter::writeSetCursor(int width, int height, const Point& hotspot,
216 void* data, void* mask)
217{
218 if (!wsccb)
219 return;
220
221 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
222 throw Exception("SMsgWriter::writeSetCursor: nRects out of sync");
223
224 os->writeS16(hotspot.x);
225 os->writeS16(hotspot.y);
226 os->writeU16(width);
227 os->writeU16(height);
228 os->writeU32(pseudoEncodingCursor);
229 os->writeBytes(data, width * height * (cp->pf().bpp/8));
230 os->writeBytes(mask, (width+7)/8 * height);
231}
232
233void SMsgWriter::writeSetXCursor(int width, int height, int hotspotX,
234 int hotspotY, void* data, void* mask)
235{
236 if (!wsccb)
237 return;
238
239 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
240 throw Exception("SMsgWriter::writeSetXCursor: nRects out of sync");
241
242 os->writeS16(hotspotX);
243 os->writeS16(hotspotY);
244 os->writeU16(width);
245 os->writeU16(height);
246 os->writeU32(pseudoEncodingXCursor);
247 // FIXME: We only support black and white cursors, currently. We
248 // could pass the correct color by using the pix0/pix1 values
249 // returned from getBitmap, in writeSetCursorCallback. However, we
250 // would then need to undo the conversion from rgb to Pixel that is
251 // done by FakeAllocColor.
252 if (width * height) {
253 os->writeU8(0);
254 os->writeU8(0);
255 os->writeU8(0);
256 os->writeU8(255);
257 os->writeU8(255);
258 os->writeU8(255);
259 os->writeBytes(data, (width+7)/8 * height);
260 os->writeBytes(mask, (width+7)/8 * height);
261 }
262}
263
Pierre Ossmane9962f72009-04-23 12:31:42 +0000264bool SMsgWriter::needFakeUpdate()
265{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100266 return wsccb || needSetDesktopName || needNoDataUpdate();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000267}
268
Pierre Ossmane9962f72009-04-23 12:31:42 +0000269bool SMsgWriter::needNoDataUpdate()
270{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100271 return needSetDesktopSize || needExtendedDesktopSize ||
272 !extendedDesktopSizeMsgs.empty();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000273}
274
275void SMsgWriter::writeNoDataUpdate()
276{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100277 int nRects;
278
279 nRects = 0;
280
281 if (needSetDesktopSize)
282 nRects++;
283 if (needExtendedDesktopSize)
284 nRects++;
285 if (!extendedDesktopSizeMsgs.empty())
286 nRects += extendedDesktopSizeMsgs.size();
287
288 writeFramebufferUpdateStart(nRects);
289 writeNoDataRects();
290 writeFramebufferUpdateEnd();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000291}
292
Pierre Ossman717c07b2014-01-21 14:45:10 +0100293void SMsgWriter::writeRects(const UpdateInfo& ui, TransImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000294{
295 std::vector<Rect> rects;
296 std::vector<Rect>::const_iterator i;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000297
298 ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0);
299 for (i = rects.begin(); i != rects.end(); i++)
300 writeCopyRect(*i, i->tl.x - ui.copy_delta.x, i->tl.y - ui.copy_delta.y);
301
302 ui.changed.get_rects(&rects);
Pierre Ossman717c07b2014-01-21 14:45:10 +0100303 for (i = rects.begin(); i != rects.end(); i++)
304 writeRect(*i, ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000305}
306
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100307void SMsgWriter::writeFramebufferUpdateStart(int nRects)
308{
309 startMsg(msgTypeFramebufferUpdate);
310 os->pad(1);
311
312 if (nRects != 0xFFFF) {
313 if (wsccb)
314 nRects++;
315 if (needSetDesktopName)
316 nRects++;
317 }
318
319 os->writeU16(nRects);
320
321 nRectsInUpdate = 0;
322 if (nRects == 0xFFFF)
323 nRectsInHeader = 0;
324 else
325 nRectsInHeader = nRects;
326
327 writePseudoRects();
328}
329
330void SMsgWriter::writeFramebufferUpdateEnd()
331{
332 if (nRectsInUpdate != nRectsInHeader && nRectsInHeader)
333 throw Exception("SMsgWriter::writeFramebufferUpdateEnd: "
334 "nRects out of sync");
335
336 if (nRectsInHeader == 0) {
337 // Send last rect. marker
338 os->writeS16(0);
339 os->writeS16(0);
340 os->writeU16(0);
341 os->writeU16(0);
342 os->writeU32(pseudoEncodingLastRect);
343 }
344
345 updatesSent++;
346 endMsg();
347}
348
Pierre Ossman717c07b2014-01-21 14:45:10 +0100349void SMsgWriter::writeRect(const Rect& r, TransImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000350{
Pierre Ossman717c07b2014-01-21 14:45:10 +0100351 writeRect(r, cp->currentEncoding(), ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000352}
353
Pierre Ossman717c07b2014-01-21 14:45:10 +0100354void SMsgWriter::writeRect(const Rect& r, int encoding, TransImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000355{
356 if (!encoders[encoding]) {
357 encoders[encoding] = Encoder::createEncoder(encoding, this);
358 assert(encoders[encoding]);
359 }
Pierre Ossman717c07b2014-01-21 14:45:10 +0100360 encoders[encoding]->writeRect(r, ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000361}
362
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000363void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY)
364{
365 startRect(r,encodingCopyRect);
366 os->writeU16(srcX);
367 os->writeU16(srcY);
368 endRect();
369}
370
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100371void SMsgWriter::startRect(const Rect& r, int encoding)
372{
373 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
374 throw Exception("SMsgWriter::startRect: nRects out of sync");
375
376 currentEncoding = encoding;
377 lenBeforeRect = os->length();
378 if (encoding != encodingCopyRect)
379 rawBytesEquivalent += 12 + r.width() * r.height() * (bpp()/8);
380
381 os->writeS16(r.tl.x);
382 os->writeS16(r.tl.y);
383 os->writeU16(r.width());
384 os->writeU16(r.height());
385 os->writeU32(encoding);
386}
387
388void SMsgWriter::endRect()
389{
390 if (currentEncoding <= encodingMax) {
391 bytesSent[currentEncoding] += os->length() - lenBeforeRect;
392 rectsSent[currentEncoding]++;
393 }
394}
395
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000396rdr::U8* SMsgWriter::getImageBuf(int required, int requested, int* nPixels)
397{
398 int requiredBytes = required * (cp->pf().bpp / 8);
399 int requestedBytes = requested * (cp->pf().bpp / 8);
400 int size = requestedBytes;
401 if (size > imageBufIdealSize) size = imageBufIdealSize;
402
403 if (size < requiredBytes)
404 size = requiredBytes;
405
406 if (imageBufSize < size) {
407 imageBufSize = size;
408 delete [] imageBuf;
409 imageBuf = new rdr::U8[imageBufSize];
410 }
411 if (nPixels)
412 *nPixels = imageBufSize / (cp->pf().bpp / 8);
413 return imageBuf;
414}
415
416int SMsgWriter::bpp()
417{
418 return cp->pf().bpp;
419}
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100420
421void SMsgWriter::startMsg(int type)
422{
423 os->writeU8(type);
424}
425
426void SMsgWriter::endMsg()
427{
428 os->flush();
429}
430
431void SMsgWriter::writePseudoRects()
432{
433 if (wsccb) {
434 wsccb->writeSetCursorCallback();
435 wsccb = 0;
436 }
437
438 if (needSetDesktopName) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100439 writeSetDesktopNameRect(cp->name());
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100440 needSetDesktopName = false;
441 }
442}
443
444void SMsgWriter::writeNoDataRects()
445{
446 // Start with specific ExtendedDesktopSize messages
447 if (!extendedDesktopSizeMsgs.empty()) {
448 std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100449
450 for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100451 writeExtendedDesktopSizeRect(ri->reason, ri->result,
452 ri->fb_width, ri->fb_height, ri->layout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100453 }
454
455 extendedDesktopSizeMsgs.clear();
456 }
457
458 // Send this before SetDesktopSize to make life easier on the clients
459 if (needExtendedDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100460 writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height,
461 cp->screenLayout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100462 needExtendedDesktopSize = false;
463 }
464
465 // Some clients assume this is the last rectangle so don't send anything
466 // more after this
467 if (needSetDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100468 writeSetDesktopSizeRect(cp->width, cp->height);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100469 needSetDesktopSize = false;
470 }
471}
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100472
473void SMsgWriter::writeSetDesktopSizeRect(int width, int height)
474{
475 if (!cp->supportsDesktopResize)
476 throw Exception("Client does not support desktop resize");
477 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
478 throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync");
479
480 os->writeS16(0);
481 os->writeS16(0);
482 os->writeU16(width);
483 os->writeU16(height);
484 os->writeU32(pseudoEncodingDesktopSize);
485}
486
487void SMsgWriter::writeExtendedDesktopSizeRect(rdr::U16 reason,
488 rdr::U16 result,
489 int fb_width,
490 int fb_height,
491 const ScreenSet& layout)
492{
493 ScreenSet::const_iterator si;
494
495 if (!cp->supportsExtendedDesktopSize)
496 throw Exception("Client does not support extended desktop resize");
497 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
498 throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync");
499
500 os->writeU16(reason);
501 os->writeU16(result);
502 os->writeU16(fb_width);
503 os->writeU16(fb_height);
504 os->writeU32(pseudoEncodingExtendedDesktopSize);
505
506 os->writeU8(layout.num_screens());
507 os->pad(3);
508
509 for (si = layout.begin();si != layout.end();++si) {
510 os->writeU32(si->id);
511 os->writeU16(si->dimensions.tl.x);
512 os->writeU16(si->dimensions.tl.y);
513 os->writeU16(si->dimensions.width());
514 os->writeU16(si->dimensions.height());
515 os->writeU32(si->flags);
516 }
517}
518
519void SMsgWriter::writeSetDesktopNameRect(const char *name)
520{
521 if (!cp->supportsDesktopRename)
522 throw Exception("Client does not support desktop rename");
523 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
524 throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync");
525
526 os->writeS16(0);
527 os->writeS16(0);
528 os->writeU16(0);
529 os->writeU16(0);
530 os->writeU32(pseudoEncodingDesktopName);
531 os->writeString(name);
532}