blob: 3698cb6c83e11fb52d3718178c1948b7f055cf0a [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 Ossmanc0397262014-03-14 15:59:46 +010037 : cp(cp_), os(os_), currentEncoding(0),
Pierre Ossman7638e9c2014-01-16 13:12:40 +010038 nRectsInUpdate(0), nRectsInHeader(0),
Pierre Ossman126e5642014-02-13 14:40:25 +010039 needSetDesktopSize(false), needExtendedDesktopSize(false),
40 needSetDesktopName(false), needSetCursor(false), needSetXCursor(false),
Pierre Ossmanc0397262014-03-14 15:59:46 +010041 lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000042{
Peter Åstrand98fe98c2010-02-10 07:43:02 +000043 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000044 bytesSent[i] = 0;
45 rectsSent[i] = 0;
46 }
47}
48
49SMsgWriter::~SMsgWriter()
50{
51 vlog.info("framebuffer updates %d",updatesSent);
52 int bytes = 0;
Peter Åstrand98fe98c2010-02-10 07:43:02 +000053 for (int i = 0; i <= encodingMax; i++) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000054 if (i != encodingCopyRect)
55 bytes += bytesSent[i];
56 if (rectsSent[i])
57 vlog.info(" %s rects %d, bytes %d",
58 encodingName(i), rectsSent[i], bytesSent[i]);
59 }
DRC887c5fd2011-08-19 03:13:47 +000060 vlog.info(" raw bytes equivalent %llu, compression ratio %f",
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000061 rawBytesEquivalent, (double)rawBytesEquivalent / bytes);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000062}
63
Pierre Ossman7638e9c2014-01-16 13:12:40 +010064void SMsgWriter::writeServerInit()
65{
66 os->writeU16(cp->width);
67 os->writeU16(cp->height);
68 cp->pf().write(os);
69 os->writeString(cp->name());
70 endMsg();
71}
72
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000073void SMsgWriter::writeSetColourMapEntries(int firstColour, int nColours,
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010074 const rdr::U16 red[],
75 const rdr::U16 green[],
76 const rdr::U16 blue[])
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000077{
78 startMsg(msgTypeSetColourMapEntries);
79 os->pad(1);
80 os->writeU16(firstColour);
81 os->writeU16(nColours);
82 for (int i = firstColour; i < firstColour+nColours; i++) {
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010083 os->writeU16(red[i]);
84 os->writeU16(green[i]);
85 os->writeU16(blue[i]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000086 }
87 endMsg();
88}
89
90void SMsgWriter::writeBell()
91{
92 startMsg(msgTypeBell);
93 endMsg();
94}
95
96void SMsgWriter::writeServerCutText(const char* str, int len)
97{
98 startMsg(msgTypeServerCutText);
99 os->pad(3);
100 os->writeU32(len);
101 os->writeBytes(str, len);
102 endMsg();
103}
104
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100105void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
106{
107 if (!cp->supportsFence)
108 throw Exception("Client does not support fences");
109 if (len > 64)
110 throw Exception("Too large fence payload");
111 if ((flags & ~fenceFlagsSupported) != 0)
112 throw Exception("Unknown fence flags");
113
114 startMsg(msgTypeServerFence);
115 os->pad(3);
116
117 os->writeU32(flags);
118
119 os->writeU8(len);
120 os->writeBytes(data, len);
121
122 endMsg();
123}
124
125void SMsgWriter::writeEndOfContinuousUpdates()
126{
127 if (!cp->supportsContinuousUpdates)
128 throw Exception("Client does not support continuous updates");
129
130 startMsg(msgTypeEndOfContinuousUpdates);
131 endMsg();
132}
133
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100134bool SMsgWriter::writeSetDesktopSize() {
135 if (!cp->supportsDesktopResize)
136 return false;
137
138 needSetDesktopSize = true;
139
140 return true;
141}
142
143bool SMsgWriter::writeExtendedDesktopSize() {
144 if (!cp->supportsExtendedDesktopSize)
145 return false;
146
147 needExtendedDesktopSize = true;
148
149 return true;
150}
151
152bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
153 int fb_width, int fb_height,
154 const ScreenSet& layout) {
155 ExtendedDesktopSizeMsg msg;
156
157 if (!cp->supportsExtendedDesktopSize)
158 return false;
159
160 msg.reason = reason;
161 msg.result = result;
162 msg.fb_width = fb_width;
163 msg.fb_height = fb_height;
164 msg.layout = layout;
165
166 extendedDesktopSizeMsgs.push_back(msg);
167
168 return true;
169}
170
171bool SMsgWriter::writeSetDesktopName() {
172 if (!cp->supportsDesktopRename)
173 return false;
174
175 needSetDesktopName = true;
176
177 return true;
178}
179
Pierre Ossman126e5642014-02-13 14:40:25 +0100180bool SMsgWriter::writeSetCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100181{
Pierre Ossman126e5642014-02-13 14:40:25 +0100182 if (!cp->supportsLocalCursor)
183 return false;
184
185 needSetCursor = true;
186
187 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100188}
189
Pierre Ossman126e5642014-02-13 14:40:25 +0100190bool SMsgWriter::writeSetXCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100191{
Pierre Ossman126e5642014-02-13 14:40:25 +0100192 if (!cp->supportsLocalXCursor)
193 return false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100194
Pierre Ossman126e5642014-02-13 14:40:25 +0100195 needSetXCursor = true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100196
Pierre Ossman126e5642014-02-13 14:40:25 +0100197 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100198}
199
Pierre Ossmane9962f72009-04-23 12:31:42 +0000200bool SMsgWriter::needFakeUpdate()
201{
Pierre Ossman126e5642014-02-13 14:40:25 +0100202 if (needSetDesktopName)
203 return true;
204 if (needSetCursor || needSetXCursor)
205 return true;
206 if (needNoDataUpdate())
207 return true;
208
209 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000210}
211
Pierre Ossmane9962f72009-04-23 12:31:42 +0000212bool SMsgWriter::needNoDataUpdate()
213{
Pierre Ossman126e5642014-02-13 14:40:25 +0100214 if (needSetDesktopSize)
215 return true;
216 if (needExtendedDesktopSize || !extendedDesktopSizeMsgs.empty())
217 return true;
218
219 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000220}
221
222void SMsgWriter::writeNoDataUpdate()
223{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100224 int nRects;
225
226 nRects = 0;
227
228 if (needSetDesktopSize)
229 nRects++;
230 if (needExtendedDesktopSize)
231 nRects++;
232 if (!extendedDesktopSizeMsgs.empty())
233 nRects += extendedDesktopSizeMsgs.size();
234
235 writeFramebufferUpdateStart(nRects);
236 writeNoDataRects();
237 writeFramebufferUpdateEnd();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000238}
239
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100240void SMsgWriter::writeFramebufferUpdateStart(int nRects)
241{
242 startMsg(msgTypeFramebufferUpdate);
243 os->pad(1);
244
245 if (nRects != 0xFFFF) {
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100246 if (needSetDesktopName)
247 nRects++;
Pierre Ossman126e5642014-02-13 14:40:25 +0100248 if (needSetCursor)
249 nRects++;
250 if (needSetXCursor)
251 nRects++;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100252 }
253
254 os->writeU16(nRects);
255
256 nRectsInUpdate = 0;
257 if (nRects == 0xFFFF)
258 nRectsInHeader = 0;
259 else
260 nRectsInHeader = nRects;
261
262 writePseudoRects();
263}
264
265void SMsgWriter::writeFramebufferUpdateEnd()
266{
267 if (nRectsInUpdate != nRectsInHeader && nRectsInHeader)
268 throw Exception("SMsgWriter::writeFramebufferUpdateEnd: "
269 "nRects out of sync");
270
271 if (nRectsInHeader == 0) {
272 // Send last rect. marker
273 os->writeS16(0);
274 os->writeS16(0);
275 os->writeU16(0);
276 os->writeU16(0);
277 os->writeU32(pseudoEncodingLastRect);
278 }
279
280 updatesSent++;
281 endMsg();
282}
283
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000284void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY)
285{
286 startRect(r,encodingCopyRect);
287 os->writeU16(srcX);
288 os->writeU16(srcY);
289 endRect();
290}
291
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100292void SMsgWriter::startRect(const Rect& r, int encoding)
293{
294 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
295 throw Exception("SMsgWriter::startRect: nRects out of sync");
296
297 currentEncoding = encoding;
298 lenBeforeRect = os->length();
299 if (encoding != encodingCopyRect)
Pierre Ossman668468b2014-01-31 12:37:32 +0100300 rawBytesEquivalent += 12 + r.width() * r.height() * (cp->pf().bpp/8);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100301
302 os->writeS16(r.tl.x);
303 os->writeS16(r.tl.y);
304 os->writeU16(r.width());
305 os->writeU16(r.height());
306 os->writeU32(encoding);
307}
308
309void SMsgWriter::endRect()
310{
311 if (currentEncoding <= encodingMax) {
312 bytesSent[currentEncoding] += os->length() - lenBeforeRect;
313 rectsSent[currentEncoding]++;
314 }
315}
316
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100317void SMsgWriter::startMsg(int type)
318{
319 os->writeU8(type);
320}
321
322void SMsgWriter::endMsg()
323{
324 os->flush();
325}
326
327void SMsgWriter::writePseudoRects()
328{
Pierre Ossman126e5642014-02-13 14:40:25 +0100329 if (needSetCursor) {
330 rdr::U8* data;
331 int stride;
332
333 const Cursor& cursor = cp->cursor();
334
335 data = new rdr::U8[cursor.area() * cp->pf().bpp/8];
336 cursor.getImage(cp->pf(), data, cursor.getRect());
337
338 writeSetCursorRect(cursor.width(), cursor.height(),
339 cursor.hotspot.x, cursor.hotspot.y,
340 data, cursor.mask.buf);
341 needSetCursor = false;
342
343 delete [] data;
344 }
345
346 if (needSetXCursor) {
347 const Cursor& cursor = cp->cursor();
348 Pixel pix0, pix1;
349 rdr::U8 rgb0[3], rgb1[3];
350 rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1));
351
352 if (!bitmap.buf) {
353 // FIXME: We could reduce to two colors.
354 throw Exception("SMsgWriter::writePseudoRects: Unable to send multicolor cursor: RichCursor not supported by client");
355 }
356
357 cp->pf().rgbFromPixel(pix0, &rgb0[0], &rgb0[1], &rgb0[2]);
358 cp->pf().rgbFromPixel(pix1, &rgb1[0], &rgb1[1], &rgb1[2]);
359
360 writeSetXCursorRect(cursor.width(), cursor.height(),
361 cursor.hotspot.x, cursor.hotspot.y,
362 rgb0, rgb1, bitmap.buf, cursor.mask.buf);
363 needSetXCursor = false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100364 }
365
366 if (needSetDesktopName) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100367 writeSetDesktopNameRect(cp->name());
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100368 needSetDesktopName = false;
369 }
370}
371
372void SMsgWriter::writeNoDataRects()
373{
374 // Start with specific ExtendedDesktopSize messages
375 if (!extendedDesktopSizeMsgs.empty()) {
376 std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100377
378 for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100379 writeExtendedDesktopSizeRect(ri->reason, ri->result,
380 ri->fb_width, ri->fb_height, ri->layout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100381 }
382
383 extendedDesktopSizeMsgs.clear();
384 }
385
386 // Send this before SetDesktopSize to make life easier on the clients
387 if (needExtendedDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100388 writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height,
389 cp->screenLayout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100390 needExtendedDesktopSize = false;
391 }
392
393 // Some clients assume this is the last rectangle so don't send anything
394 // more after this
395 if (needSetDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100396 writeSetDesktopSizeRect(cp->width, cp->height);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100397 needSetDesktopSize = false;
398 }
399}
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100400
401void SMsgWriter::writeSetDesktopSizeRect(int width, int height)
402{
403 if (!cp->supportsDesktopResize)
404 throw Exception("Client does not support desktop resize");
405 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
406 throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync");
407
408 os->writeS16(0);
409 os->writeS16(0);
410 os->writeU16(width);
411 os->writeU16(height);
412 os->writeU32(pseudoEncodingDesktopSize);
413}
414
415void SMsgWriter::writeExtendedDesktopSizeRect(rdr::U16 reason,
416 rdr::U16 result,
417 int fb_width,
418 int fb_height,
419 const ScreenSet& layout)
420{
421 ScreenSet::const_iterator si;
422
423 if (!cp->supportsExtendedDesktopSize)
424 throw Exception("Client does not support extended desktop resize");
425 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
426 throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync");
427
428 os->writeU16(reason);
429 os->writeU16(result);
430 os->writeU16(fb_width);
431 os->writeU16(fb_height);
432 os->writeU32(pseudoEncodingExtendedDesktopSize);
433
434 os->writeU8(layout.num_screens());
435 os->pad(3);
436
437 for (si = layout.begin();si != layout.end();++si) {
438 os->writeU32(si->id);
439 os->writeU16(si->dimensions.tl.x);
440 os->writeU16(si->dimensions.tl.y);
441 os->writeU16(si->dimensions.width());
442 os->writeU16(si->dimensions.height());
443 os->writeU32(si->flags);
444 }
445}
446
447void SMsgWriter::writeSetDesktopNameRect(const char *name)
448{
449 if (!cp->supportsDesktopRename)
450 throw Exception("Client does not support desktop rename");
451 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
452 throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync");
453
454 os->writeS16(0);
455 os->writeS16(0);
456 os->writeU16(0);
457 os->writeU16(0);
458 os->writeU32(pseudoEncodingDesktopName);
459 os->writeString(name);
460}
Pierre Ossman126e5642014-02-13 14:40:25 +0100461
462void SMsgWriter::writeSetCursorRect(int width, int height,
463 int hotspotX, int hotspotY,
464 const void* data, const void* mask)
465{
466 if (!cp->supportsLocalCursor)
467 throw Exception("Client does not support local cursors");
468 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
469 throw Exception("SMsgWriter::writeSetCursorRect: nRects out of sync");
470
471 os->writeS16(hotspotX);
472 os->writeS16(hotspotY);
473 os->writeU16(width);
474 os->writeU16(height);
475 os->writeU32(pseudoEncodingCursor);
476 os->writeBytes(data, width * height * (cp->pf().bpp/8));
477 os->writeBytes(mask, (width+7)/8 * height);
478}
479
480void SMsgWriter::writeSetXCursorRect(int width, int height,
481 int hotspotX, int hotspotY,
482 const rdr::U8 pix0[],
483 const rdr::U8 pix1[],
484 const void* data, const void* mask)
485{
486 if (!cp->supportsLocalXCursor)
487 throw Exception("Client does not support local cursors");
488 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
489 throw Exception("SMsgWriter::writeSetXCursorRect: nRects out of sync");
490
491 os->writeS16(hotspotX);
492 os->writeS16(hotspotY);
493 os->writeU16(width);
494 os->writeU16(height);
495 os->writeU32(pseudoEncodingXCursor);
496 if (width * height) {
497 os->writeU8(pix0[0]);
498 os->writeU8(pix0[1]);
499 os->writeU8(pix0[2]);
500 os->writeU8(pix1[0]);
501 os->writeU8(pix1[1]);
502 os->writeU8(pix1[2]);
503 os->writeBytes(data, (width+7)/8 * height);
504 os->writeBytes(mask, (width+7)/8 * height);
505 }
506}