blob: 615eaa9315bf2eb4366a31684c3ac030d23e949e [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/ColourMap.h>
27#include <rfb/ConnParams.h>
28#include <rfb/UpdateTracker.h>
Pierre Ossman7638e9c2014-01-16 13:12:40 +010029#include <rfb/Encoder.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000030#include <rfb/SMsgWriter.h>
31#include <rfb/LogWriter.h>
32
33using namespace rfb;
34
35static LogWriter vlog("SMsgWriter");
36
37SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_)
Pierre Ossman7638e9c2014-01-16 13:12:40 +010038 : imageBufIdealSize(0), cp(cp_), os(os_), currentEncoding(0),
39 nRectsInUpdate(0), nRectsInHeader(0),
40 wsccb(0), needSetDesktopSize(false),
41 needExtendedDesktopSize(false), needSetDesktopName(false),
42 lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0),
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000043 imageBuf(0), imageBufSize(0)
44{
Peter Åstrand98fe98c2010-02-10 07:43:02 +000045 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000046 encoders[i] = 0;
47 bytesSent[i] = 0;
48 rectsSent[i] = 0;
49 }
50}
51
52SMsgWriter::~SMsgWriter()
53{
54 vlog.info("framebuffer updates %d",updatesSent);
55 int bytes = 0;
Peter Åstrand98fe98c2010-02-10 07:43:02 +000056 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000057 delete encoders[i];
58 if (i != encodingCopyRect)
59 bytes += bytesSent[i];
60 if (rectsSent[i])
61 vlog.info(" %s rects %d, bytes %d",
62 encodingName(i), rectsSent[i], bytesSent[i]);
63 }
DRC887c5fd2011-08-19 03:13:47 +000064 vlog.info(" raw bytes equivalent %llu, compression ratio %f",
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000065 rawBytesEquivalent, (double)rawBytesEquivalent / bytes);
66 delete [] imageBuf;
67}
68
Pierre Ossman7638e9c2014-01-16 13:12:40 +010069void SMsgWriter::writeServerInit()
70{
71 os->writeU16(cp->width);
72 os->writeU16(cp->height);
73 cp->pf().write(os);
74 os->writeString(cp->name());
75 endMsg();
76}
77
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000078void SMsgWriter::writeSetColourMapEntries(int firstColour, int nColours,
79 ColourMap* cm)
80{
81 startMsg(msgTypeSetColourMapEntries);
82 os->pad(1);
83 os->writeU16(firstColour);
84 os->writeU16(nColours);
85 for (int i = firstColour; i < firstColour+nColours; i++) {
86 int r, g, b;
87 cm->lookup(i, &r, &g, &b);
88 os->writeU16(r);
89 os->writeU16(g);
90 os->writeU16(b);
91 }
92 endMsg();
93}
94
95void SMsgWriter::writeBell()
96{
97 startMsg(msgTypeBell);
98 endMsg();
99}
100
101void SMsgWriter::writeServerCutText(const char* str, int len)
102{
103 startMsg(msgTypeServerCutText);
104 os->pad(3);
105 os->writeU32(len);
106 os->writeBytes(str, len);
107 endMsg();
108}
109
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100110void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
111{
112 if (!cp->supportsFence)
113 throw Exception("Client does not support fences");
114 if (len > 64)
115 throw Exception("Too large fence payload");
116 if ((flags & ~fenceFlagsSupported) != 0)
117 throw Exception("Unknown fence flags");
118
119 startMsg(msgTypeServerFence);
120 os->pad(3);
121
122 os->writeU32(flags);
123
124 os->writeU8(len);
125 os->writeBytes(data, len);
126
127 endMsg();
128}
129
130void SMsgWriter::writeEndOfContinuousUpdates()
131{
132 if (!cp->supportsContinuousUpdates)
133 throw Exception("Client does not support continuous updates");
134
135 startMsg(msgTypeEndOfContinuousUpdates);
136 endMsg();
137}
138
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000139void SMsgWriter::setupCurrentEncoder()
140{
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000141 int encoding = cp->currentEncoding();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000142
143 // FIXME: Code duplication, see writeRect().
144 if (!encoders[encoding]) {
145 encoders[encoding] = Encoder::createEncoder(encoding, this);
146 assert(encoders[encoding]);
147 }
148
149 encoders[encoding]->setCompressLevel(cp->compressLevel);
150 encoders[encoding]->setQualityLevel(cp->qualityLevel);
DRCb4a83232011-08-19 04:57:18 +0000151 encoders[encoding]->setFineQualityLevel(cp->fineQualityLevel,
152 cp->subsampling);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000153}
154
155int SMsgWriter::getNumRects(const Rect &r)
156{
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000157 int encoding = cp->currentEncoding();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000158
159 if (!encoders[encoding])
160 setupCurrentEncoder();
161
162 return encoders[encoding]->getNumRects(r);
163}
164
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100165bool SMsgWriter::writeSetDesktopSize() {
166 if (!cp->supportsDesktopResize)
167 return false;
168
169 needSetDesktopSize = true;
170
171 return true;
172}
173
174bool SMsgWriter::writeExtendedDesktopSize() {
175 if (!cp->supportsExtendedDesktopSize)
176 return false;
177
178 needExtendedDesktopSize = true;
179
180 return true;
181}
182
183bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
184 int fb_width, int fb_height,
185 const ScreenSet& layout) {
186 ExtendedDesktopSizeMsg msg;
187
188 if (!cp->supportsExtendedDesktopSize)
189 return false;
190
191 msg.reason = reason;
192 msg.result = result;
193 msg.fb_width = fb_width;
194 msg.fb_height = fb_height;
195 msg.layout = layout;
196
197 extendedDesktopSizeMsgs.push_back(msg);
198
199 return true;
200}
201
202bool SMsgWriter::writeSetDesktopName() {
203 if (!cp->supportsDesktopRename)
204 return false;
205
206 needSetDesktopName = true;
207
208 return true;
209}
210
211void SMsgWriter::cursorChange(WriteSetCursorCallback* cb)
212{
213 wsccb = cb;
214}
215
216void SMsgWriter::writeSetCursor(int width, int height, const Point& hotspot,
217 void* data, void* mask)
218{
219 if (!wsccb)
220 return;
221
222 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
223 throw Exception("SMsgWriter::writeSetCursor: nRects out of sync");
224
225 os->writeS16(hotspot.x);
226 os->writeS16(hotspot.y);
227 os->writeU16(width);
228 os->writeU16(height);
229 os->writeU32(pseudoEncodingCursor);
230 os->writeBytes(data, width * height * (cp->pf().bpp/8));
231 os->writeBytes(mask, (width+7)/8 * height);
232}
233
234void SMsgWriter::writeSetXCursor(int width, int height, int hotspotX,
235 int hotspotY, void* data, void* mask)
236{
237 if (!wsccb)
238 return;
239
240 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
241 throw Exception("SMsgWriter::writeSetXCursor: nRects out of sync");
242
243 os->writeS16(hotspotX);
244 os->writeS16(hotspotY);
245 os->writeU16(width);
246 os->writeU16(height);
247 os->writeU32(pseudoEncodingXCursor);
248 // FIXME: We only support black and white cursors, currently. We
249 // could pass the correct color by using the pix0/pix1 values
250 // returned from getBitmap, in writeSetCursorCallback. However, we
251 // would then need to undo the conversion from rgb to Pixel that is
252 // done by FakeAllocColor.
253 if (width * height) {
254 os->writeU8(0);
255 os->writeU8(0);
256 os->writeU8(0);
257 os->writeU8(255);
258 os->writeU8(255);
259 os->writeU8(255);
260 os->writeBytes(data, (width+7)/8 * height);
261 os->writeBytes(mask, (width+7)/8 * height);
262 }
263}
264
Pierre Ossmane9962f72009-04-23 12:31:42 +0000265bool SMsgWriter::needFakeUpdate()
266{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100267 return wsccb || needSetDesktopName || needNoDataUpdate();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000268}
269
Pierre Ossmane9962f72009-04-23 12:31:42 +0000270bool SMsgWriter::needNoDataUpdate()
271{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100272 return needSetDesktopSize || needExtendedDesktopSize ||
273 !extendedDesktopSizeMsgs.empty();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000274}
275
276void SMsgWriter::writeNoDataUpdate()
277{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100278 int nRects;
279
280 nRects = 0;
281
282 if (needSetDesktopSize)
283 nRects++;
284 if (needExtendedDesktopSize)
285 nRects++;
286 if (!extendedDesktopSizeMsgs.empty())
287 nRects += extendedDesktopSizeMsgs.size();
288
289 writeFramebufferUpdateStart(nRects);
290 writeNoDataRects();
291 writeFramebufferUpdateEnd();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000292}
293
Pierre Ossman717c07b2014-01-21 14:45:10 +0100294void SMsgWriter::writeRects(const UpdateInfo& ui, TransImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000295{
296 std::vector<Rect> rects;
297 std::vector<Rect>::const_iterator i;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000298
299 ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0);
300 for (i = rects.begin(); i != rects.end(); i++)
301 writeCopyRect(*i, i->tl.x - ui.copy_delta.x, i->tl.y - ui.copy_delta.y);
302
303 ui.changed.get_rects(&rects);
Pierre Ossman717c07b2014-01-21 14:45:10 +0100304 for (i = rects.begin(); i != rects.end(); i++)
305 writeRect(*i, ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000306}
307
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100308void SMsgWriter::writeFramebufferUpdateStart(int nRects)
309{
310 startMsg(msgTypeFramebufferUpdate);
311 os->pad(1);
312
313 if (nRects != 0xFFFF) {
314 if (wsccb)
315 nRects++;
316 if (needSetDesktopName)
317 nRects++;
318 }
319
320 os->writeU16(nRects);
321
322 nRectsInUpdate = 0;
323 if (nRects == 0xFFFF)
324 nRectsInHeader = 0;
325 else
326 nRectsInHeader = nRects;
327
328 writePseudoRects();
329}
330
331void SMsgWriter::writeFramebufferUpdateEnd()
332{
333 if (nRectsInUpdate != nRectsInHeader && nRectsInHeader)
334 throw Exception("SMsgWriter::writeFramebufferUpdateEnd: "
335 "nRects out of sync");
336
337 if (nRectsInHeader == 0) {
338 // Send last rect. marker
339 os->writeS16(0);
340 os->writeS16(0);
341 os->writeU16(0);
342 os->writeU16(0);
343 os->writeU32(pseudoEncodingLastRect);
344 }
345
346 updatesSent++;
347 endMsg();
348}
349
Pierre Ossman717c07b2014-01-21 14:45:10 +0100350void SMsgWriter::writeRect(const Rect& r, TransImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000351{
Pierre Ossman717c07b2014-01-21 14:45:10 +0100352 writeRect(r, cp->currentEncoding(), ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000353}
354
Pierre Ossman717c07b2014-01-21 14:45:10 +0100355void SMsgWriter::writeRect(const Rect& r, int encoding, TransImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000356{
357 if (!encoders[encoding]) {
358 encoders[encoding] = Encoder::createEncoder(encoding, this);
359 assert(encoders[encoding]);
360 }
Pierre Ossman717c07b2014-01-21 14:45:10 +0100361 encoders[encoding]->writeRect(r, ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000362}
363
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000364void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY)
365{
366 startRect(r,encodingCopyRect);
367 os->writeU16(srcX);
368 os->writeU16(srcY);
369 endRect();
370}
371
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100372void SMsgWriter::startRect(const Rect& r, int encoding)
373{
374 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
375 throw Exception("SMsgWriter::startRect: nRects out of sync");
376
377 currentEncoding = encoding;
378 lenBeforeRect = os->length();
379 if (encoding != encodingCopyRect)
380 rawBytesEquivalent += 12 + r.width() * r.height() * (bpp()/8);
381
382 os->writeS16(r.tl.x);
383 os->writeS16(r.tl.y);
384 os->writeU16(r.width());
385 os->writeU16(r.height());
386 os->writeU32(encoding);
387}
388
389void SMsgWriter::endRect()
390{
391 if (currentEncoding <= encodingMax) {
392 bytesSent[currentEncoding] += os->length() - lenBeforeRect;
393 rectsSent[currentEncoding]++;
394 }
395}
396
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000397rdr::U8* SMsgWriter::getImageBuf(int required, int requested, int* nPixels)
398{
399 int requiredBytes = required * (cp->pf().bpp / 8);
400 int requestedBytes = requested * (cp->pf().bpp / 8);
401 int size = requestedBytes;
402 if (size > imageBufIdealSize) size = imageBufIdealSize;
403
404 if (size < requiredBytes)
405 size = requiredBytes;
406
407 if (imageBufSize < size) {
408 imageBufSize = size;
409 delete [] imageBuf;
410 imageBuf = new rdr::U8[imageBufSize];
411 }
412 if (nPixels)
413 *nPixels = imageBufSize / (cp->pf().bpp / 8);
414 return imageBuf;
415}
416
417int SMsgWriter::bpp()
418{
419 return cp->pf().bpp;
420}
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100421
422void SMsgWriter::startMsg(int type)
423{
424 os->writeU8(type);
425}
426
427void SMsgWriter::endMsg()
428{
429 os->flush();
430}
431
432void SMsgWriter::writePseudoRects()
433{
434 if (wsccb) {
435 wsccb->writeSetCursorCallback();
436 wsccb = 0;
437 }
438
439 if (needSetDesktopName) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100440 writeSetDesktopNameRect(cp->name());
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100441 needSetDesktopName = false;
442 }
443}
444
445void SMsgWriter::writeNoDataRects()
446{
447 // Start with specific ExtendedDesktopSize messages
448 if (!extendedDesktopSizeMsgs.empty()) {
449 std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100450
451 for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100452 writeExtendedDesktopSizeRect(ri->reason, ri->result,
453 ri->fb_width, ri->fb_height, ri->layout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100454 }
455
456 extendedDesktopSizeMsgs.clear();
457 }
458
459 // Send this before SetDesktopSize to make life easier on the clients
460 if (needExtendedDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100461 writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height,
462 cp->screenLayout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100463 needExtendedDesktopSize = false;
464 }
465
466 // Some clients assume this is the last rectangle so don't send anything
467 // more after this
468 if (needSetDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100469 writeSetDesktopSizeRect(cp->width, cp->height);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100470 needSetDesktopSize = false;
471 }
472}
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100473
474void SMsgWriter::writeSetDesktopSizeRect(int width, int height)
475{
476 if (!cp->supportsDesktopResize)
477 throw Exception("Client does not support desktop resize");
478 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
479 throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync");
480
481 os->writeS16(0);
482 os->writeS16(0);
483 os->writeU16(width);
484 os->writeU16(height);
485 os->writeU32(pseudoEncodingDesktopSize);
486}
487
488void SMsgWriter::writeExtendedDesktopSizeRect(rdr::U16 reason,
489 rdr::U16 result,
490 int fb_width,
491 int fb_height,
492 const ScreenSet& layout)
493{
494 ScreenSet::const_iterator si;
495
496 if (!cp->supportsExtendedDesktopSize)
497 throw Exception("Client does not support extended desktop resize");
498 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
499 throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync");
500
501 os->writeU16(reason);
502 os->writeU16(result);
503 os->writeU16(fb_width);
504 os->writeU16(fb_height);
505 os->writeU32(pseudoEncodingExtendedDesktopSize);
506
507 os->writeU8(layout.num_screens());
508 os->pad(3);
509
510 for (si = layout.begin();si != layout.end();++si) {
511 os->writeU32(si->id);
512 os->writeU16(si->dimensions.tl.x);
513 os->writeU16(si->dimensions.tl.y);
514 os->writeU16(si->dimensions.width());
515 os->writeU16(si->dimensions.height());
516 os->writeU32(si->flags);
517 }
518}
519
520void SMsgWriter::writeSetDesktopNameRect(const char *name)
521{
522 if (!cp->supportsDesktopRename)
523 throw Exception("Client does not support desktop rename");
524 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
525 throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync");
526
527 os->writeS16(0);
528 os->writeS16(0);
529 os->writeU16(0);
530 os->writeU16(0);
531 os->writeU32(pseudoEncodingDesktopName);
532 os->writeString(name);
533}