blob: d8adfbc1c693b08a5701139b1fd6e8721644fe1b [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 Ossman6a1a0d02017-02-19 15:48:17 +01003 * Copyright 2009-2017 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>
Pierre Ossmanb45a84f2016-12-12 16:59:15 +010030#include <rfb/ledStates.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000031
32using namespace rfb;
33
34static LogWriter vlog("SMsgWriter");
35
36SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_)
Pierre Ossman20dd2a92015-02-11 17:43:15 +010037 : cp(cp_), os(os_),
Pierre Ossman7638e9c2014-01-16 13:12:40 +010038 nRectsInUpdate(0), nRectsInHeader(0),
Pierre Ossman126e5642014-02-13 14:40:25 +010039 needSetDesktopSize(false), needExtendedDesktopSize(false),
Pierre Ossman8053c8e2017-02-21 12:59:04 +010040 needSetDesktopName(false), needSetCursor(false),
Pierre Ossmanb45a84f2016-12-12 16:59:15 +010041 needSetXCursor(false), needSetCursorWithAlpha(false),
42 needLEDState(false)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000043{
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000044}
45
46SMsgWriter::~SMsgWriter()
47{
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000048}
49
Pierre Ossman7638e9c2014-01-16 13:12:40 +010050void SMsgWriter::writeServerInit()
51{
52 os->writeU16(cp->width);
53 os->writeU16(cp->height);
54 cp->pf().write(os);
55 os->writeString(cp->name());
56 endMsg();
57}
58
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000059void SMsgWriter::writeSetColourMapEntries(int firstColour, int nColours,
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010060 const rdr::U16 red[],
61 const rdr::U16 green[],
62 const rdr::U16 blue[])
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000063{
64 startMsg(msgTypeSetColourMapEntries);
65 os->pad(1);
66 os->writeU16(firstColour);
67 os->writeU16(nColours);
68 for (int i = firstColour; i < firstColour+nColours; i++) {
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +010069 os->writeU16(red[i]);
70 os->writeU16(green[i]);
71 os->writeU16(blue[i]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000072 }
73 endMsg();
74}
75
76void SMsgWriter::writeBell()
77{
78 startMsg(msgTypeBell);
79 endMsg();
80}
81
82void SMsgWriter::writeServerCutText(const char* str, int len)
83{
84 startMsg(msgTypeServerCutText);
85 os->pad(3);
86 os->writeU32(len);
87 os->writeBytes(str, len);
88 endMsg();
89}
90
Pierre Ossman7638e9c2014-01-16 13:12:40 +010091void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[])
92{
93 if (!cp->supportsFence)
94 throw Exception("Client does not support fences");
95 if (len > 64)
96 throw Exception("Too large fence payload");
97 if ((flags & ~fenceFlagsSupported) != 0)
98 throw Exception("Unknown fence flags");
99
100 startMsg(msgTypeServerFence);
101 os->pad(3);
102
103 os->writeU32(flags);
104
105 os->writeU8(len);
Michal Srbf3afa242017-03-27 19:02:15 +0300106
107 if (len > 0)
108 os->writeBytes(data, len);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100109
110 endMsg();
111}
112
113void SMsgWriter::writeEndOfContinuousUpdates()
114{
115 if (!cp->supportsContinuousUpdates)
116 throw Exception("Client does not support continuous updates");
117
118 startMsg(msgTypeEndOfContinuousUpdates);
119 endMsg();
120}
121
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100122bool SMsgWriter::writeSetDesktopSize() {
123 if (!cp->supportsDesktopResize)
124 return false;
125
126 needSetDesktopSize = true;
127
128 return true;
129}
130
131bool SMsgWriter::writeExtendedDesktopSize() {
132 if (!cp->supportsExtendedDesktopSize)
133 return false;
134
135 needExtendedDesktopSize = true;
136
137 return true;
138}
139
140bool SMsgWriter::writeExtendedDesktopSize(rdr::U16 reason, rdr::U16 result,
141 int fb_width, int fb_height,
142 const ScreenSet& layout) {
143 ExtendedDesktopSizeMsg msg;
144
145 if (!cp->supportsExtendedDesktopSize)
146 return false;
147
148 msg.reason = reason;
149 msg.result = result;
150 msg.fb_width = fb_width;
151 msg.fb_height = fb_height;
152 msg.layout = layout;
153
154 extendedDesktopSizeMsgs.push_back(msg);
155
156 return true;
157}
158
159bool SMsgWriter::writeSetDesktopName() {
160 if (!cp->supportsDesktopRename)
161 return false;
162
163 needSetDesktopName = true;
164
165 return true;
166}
167
Pierre Ossman126e5642014-02-13 14:40:25 +0100168bool SMsgWriter::writeSetCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100169{
Pierre Ossman126e5642014-02-13 14:40:25 +0100170 if (!cp->supportsLocalCursor)
171 return false;
172
173 needSetCursor = true;
174
175 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100176}
177
Pierre Ossman126e5642014-02-13 14:40:25 +0100178bool SMsgWriter::writeSetXCursor()
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100179{
Pierre Ossman126e5642014-02-13 14:40:25 +0100180 if (!cp->supportsLocalXCursor)
181 return false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100182
Pierre Ossman126e5642014-02-13 14:40:25 +0100183 needSetXCursor = true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100184
Pierre Ossman126e5642014-02-13 14:40:25 +0100185 return true;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100186}
187
Pierre Ossman8053c8e2017-02-21 12:59:04 +0100188bool SMsgWriter::writeSetCursorWithAlpha()
189{
190 if (!cp->supportsLocalCursorWithAlpha)
191 return false;
192
193 needSetCursorWithAlpha = true;
194
195 return true;
196}
197
Pierre Ossmanb45a84f2016-12-12 16:59:15 +0100198bool SMsgWriter::writeLEDState()
199{
200 if (!cp->supportsLEDState)
201 return false;
202 if (cp->ledState() == ledUnknown)
203 return false;
204
205 needLEDState = true;
206
207 return true;
208}
209
Pierre Ossmane9962f72009-04-23 12:31:42 +0000210bool SMsgWriter::needFakeUpdate()
211{
Pierre Ossman126e5642014-02-13 14:40:25 +0100212 if (needSetDesktopName)
213 return true;
Pierre Ossman8053c8e2017-02-21 12:59:04 +0100214 if (needSetCursor || needSetXCursor || needSetCursorWithAlpha)
Pierre Ossman126e5642014-02-13 14:40:25 +0100215 return true;
Pierre Ossmanb45a84f2016-12-12 16:59:15 +0100216 if (needLEDState)
217 return true;
Pierre Ossman126e5642014-02-13 14:40:25 +0100218 if (needNoDataUpdate())
219 return true;
220
221 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000222}
223
Pierre Ossmane9962f72009-04-23 12:31:42 +0000224bool SMsgWriter::needNoDataUpdate()
225{
Pierre Ossman126e5642014-02-13 14:40:25 +0100226 if (needSetDesktopSize)
227 return true;
228 if (needExtendedDesktopSize || !extendedDesktopSizeMsgs.empty())
229 return true;
230
231 return false;
Pierre Ossmane9962f72009-04-23 12:31:42 +0000232}
233
234void SMsgWriter::writeNoDataUpdate()
235{
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100236 int nRects;
237
238 nRects = 0;
239
240 if (needSetDesktopSize)
241 nRects++;
242 if (needExtendedDesktopSize)
243 nRects++;
244 if (!extendedDesktopSizeMsgs.empty())
245 nRects += extendedDesktopSizeMsgs.size();
246
247 writeFramebufferUpdateStart(nRects);
248 writeNoDataRects();
249 writeFramebufferUpdateEnd();
Pierre Ossmane9962f72009-04-23 12:31:42 +0000250}
251
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100252void SMsgWriter::writeFramebufferUpdateStart(int nRects)
253{
254 startMsg(msgTypeFramebufferUpdate);
255 os->pad(1);
256
257 if (nRects != 0xFFFF) {
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100258 if (needSetDesktopName)
259 nRects++;
Pierre Ossman126e5642014-02-13 14:40:25 +0100260 if (needSetCursor)
261 nRects++;
262 if (needSetXCursor)
263 nRects++;
Pierre Ossman8053c8e2017-02-21 12:59:04 +0100264 if (needSetCursorWithAlpha)
265 nRects++;
Pierre Ossmanb45a84f2016-12-12 16:59:15 +0100266 if (needLEDState)
267 nRects++;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100268 }
269
270 os->writeU16(nRects);
271
272 nRectsInUpdate = 0;
273 if (nRects == 0xFFFF)
274 nRectsInHeader = 0;
275 else
276 nRectsInHeader = nRects;
277
278 writePseudoRects();
279}
280
281void SMsgWriter::writeFramebufferUpdateEnd()
282{
283 if (nRectsInUpdate != nRectsInHeader && nRectsInHeader)
284 throw Exception("SMsgWriter::writeFramebufferUpdateEnd: "
285 "nRects out of sync");
286
287 if (nRectsInHeader == 0) {
288 // Send last rect. marker
289 os->writeS16(0);
290 os->writeS16(0);
291 os->writeU16(0);
292 os->writeU16(0);
293 os->writeU32(pseudoEncodingLastRect);
294 }
295
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100296 endMsg();
297}
298
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000299void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY)
300{
301 startRect(r,encodingCopyRect);
302 os->writeU16(srcX);
303 os->writeU16(srcY);
304 endRect();
305}
306
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100307void SMsgWriter::startRect(const Rect& r, int encoding)
308{
309 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
310 throw Exception("SMsgWriter::startRect: nRects out of sync");
311
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100312 os->writeS16(r.tl.x);
313 os->writeS16(r.tl.y);
314 os->writeU16(r.width());
315 os->writeU16(r.height());
316 os->writeU32(encoding);
317}
318
319void SMsgWriter::endRect()
320{
Pierre Ossman35294682016-04-29 14:27:08 +0200321 os->flush();
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100322}
323
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100324void SMsgWriter::startMsg(int type)
325{
326 os->writeU8(type);
327}
328
329void SMsgWriter::endMsg()
330{
331 os->flush();
332}
333
334void SMsgWriter::writePseudoRects()
335{
Pierre Ossman126e5642014-02-13 14:40:25 +0100336 if (needSetCursor) {
Pierre Ossman126e5642014-02-13 14:40:25 +0100337 const Cursor& cursor = cp->cursor();
338
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100339 rdr::U8Array data(cursor.width()*cursor.height() * cp->pf().bpp/8);
340 rdr::U8Array mask(cursor.getMask());
341
342 const rdr::U8* in;
343 rdr::U8* out;
344
345 in = cursor.getBuffer();
346 out = data.buf;
347 for (int i = 0;i < cursor.width()*cursor.height();i++) {
348 cp->pf().bufferFromRGB(out, in, 1);
349 in += 4;
350 out += cp->pf().bpp/8;
351 }
Pierre Ossman126e5642014-02-13 14:40:25 +0100352
353 writeSetCursorRect(cursor.width(), cursor.height(),
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100354 cursor.hotspot().x, cursor.hotspot().y,
355 data.buf, mask.buf);
Pierre Ossman126e5642014-02-13 14:40:25 +0100356 needSetCursor = false;
Pierre Ossman126e5642014-02-13 14:40:25 +0100357 }
358
359 if (needSetXCursor) {
360 const Cursor& cursor = cp->cursor();
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100361 rdr::U8Array bitmap(cursor.getBitmap());
362 rdr::U8Array mask(cursor.getMask());
Pierre Ossman126e5642014-02-13 14:40:25 +0100363
364 writeSetXCursorRect(cursor.width(), cursor.height(),
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100365 cursor.hotspot().x, cursor.hotspot().y,
366 bitmap.buf, mask.buf);
Pierre Ossman126e5642014-02-13 14:40:25 +0100367 needSetXCursor = false;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100368 }
369
Pierre Ossman8053c8e2017-02-21 12:59:04 +0100370 if (needSetCursorWithAlpha) {
371 const Cursor& cursor = cp->cursor();
372
373 writeSetCursorWithAlphaRect(cursor.width(), cursor.height(),
374 cursor.hotspot().x, cursor.hotspot().y,
375 cursor.getBuffer());
376 needSetCursorWithAlpha = false;
377 }
378
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100379 if (needSetDesktopName) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100380 writeSetDesktopNameRect(cp->name());
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100381 needSetDesktopName = false;
382 }
Pierre Ossmanb45a84f2016-12-12 16:59:15 +0100383
384 if (needLEDState) {
385 writeLEDStateRect(cp->ledState());
386 needLEDState = false;
387 }
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100388}
389
390void SMsgWriter::writeNoDataRects()
391{
392 // Start with specific ExtendedDesktopSize messages
393 if (!extendedDesktopSizeMsgs.empty()) {
394 std::list<ExtendedDesktopSizeMsg>::const_iterator ri;
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100395
396 for (ri = extendedDesktopSizeMsgs.begin();ri != extendedDesktopSizeMsgs.end();++ri) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100397 writeExtendedDesktopSizeRect(ri->reason, ri->result,
398 ri->fb_width, ri->fb_height, ri->layout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100399 }
400
401 extendedDesktopSizeMsgs.clear();
402 }
403
404 // Send this before SetDesktopSize to make life easier on the clients
405 if (needExtendedDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100406 writeExtendedDesktopSizeRect(0, 0, cp->width, cp->height,
407 cp->screenLayout);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100408 needExtendedDesktopSize = false;
409 }
410
411 // Some clients assume this is the last rectangle so don't send anything
412 // more after this
413 if (needSetDesktopSize) {
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100414 writeSetDesktopSizeRect(cp->width, cp->height);
Pierre Ossman7638e9c2014-01-16 13:12:40 +0100415 needSetDesktopSize = false;
416 }
417}
Pierre Ossmanc0b1aa02014-01-16 13:39:05 +0100418
419void SMsgWriter::writeSetDesktopSizeRect(int width, int height)
420{
421 if (!cp->supportsDesktopResize)
422 throw Exception("Client does not support desktop resize");
423 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
424 throw Exception("SMsgWriter::writeSetDesktopSizeRect: nRects out of sync");
425
426 os->writeS16(0);
427 os->writeS16(0);
428 os->writeU16(width);
429 os->writeU16(height);
430 os->writeU32(pseudoEncodingDesktopSize);
431}
432
433void SMsgWriter::writeExtendedDesktopSizeRect(rdr::U16 reason,
434 rdr::U16 result,
435 int fb_width,
436 int fb_height,
437 const ScreenSet& layout)
438{
439 ScreenSet::const_iterator si;
440
441 if (!cp->supportsExtendedDesktopSize)
442 throw Exception("Client does not support extended desktop resize");
443 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
444 throw Exception("SMsgWriter::writeExtendedDesktopSizeRect: nRects out of sync");
445
446 os->writeU16(reason);
447 os->writeU16(result);
448 os->writeU16(fb_width);
449 os->writeU16(fb_height);
450 os->writeU32(pseudoEncodingExtendedDesktopSize);
451
452 os->writeU8(layout.num_screens());
453 os->pad(3);
454
455 for (si = layout.begin();si != layout.end();++si) {
456 os->writeU32(si->id);
457 os->writeU16(si->dimensions.tl.x);
458 os->writeU16(si->dimensions.tl.y);
459 os->writeU16(si->dimensions.width());
460 os->writeU16(si->dimensions.height());
461 os->writeU32(si->flags);
462 }
463}
464
465void SMsgWriter::writeSetDesktopNameRect(const char *name)
466{
467 if (!cp->supportsDesktopRename)
468 throw Exception("Client does not support desktop rename");
469 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
470 throw Exception("SMsgWriter::writeSetDesktopNameRect: nRects out of sync");
471
472 os->writeS16(0);
473 os->writeS16(0);
474 os->writeU16(0);
475 os->writeU16(0);
476 os->writeU32(pseudoEncodingDesktopName);
477 os->writeString(name);
478}
Pierre Ossman126e5642014-02-13 14:40:25 +0100479
480void SMsgWriter::writeSetCursorRect(int width, int height,
481 int hotspotX, int hotspotY,
482 const void* data, const void* mask)
483{
484 if (!cp->supportsLocalCursor)
485 throw Exception("Client does not support local cursors");
486 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
487 throw Exception("SMsgWriter::writeSetCursorRect: nRects out of sync");
488
489 os->writeS16(hotspotX);
490 os->writeS16(hotspotY);
491 os->writeU16(width);
492 os->writeU16(height);
493 os->writeU32(pseudoEncodingCursor);
494 os->writeBytes(data, width * height * (cp->pf().bpp/8));
495 os->writeBytes(mask, (width+7)/8 * height);
496}
497
498void SMsgWriter::writeSetXCursorRect(int width, int height,
499 int hotspotX, int hotspotY,
Pierre Ossman126e5642014-02-13 14:40:25 +0100500 const void* data, const void* mask)
501{
502 if (!cp->supportsLocalXCursor)
503 throw Exception("Client does not support local cursors");
504 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
505 throw Exception("SMsgWriter::writeSetXCursorRect: nRects out of sync");
506
507 os->writeS16(hotspotX);
508 os->writeS16(hotspotY);
509 os->writeU16(width);
510 os->writeU16(height);
511 os->writeU32(pseudoEncodingXCursor);
512 if (width * height) {
Pierre Ossman6a1a0d02017-02-19 15:48:17 +0100513 os->writeU8(255);
514 os->writeU8(255);
515 os->writeU8(255);
516 os->writeU8(0);
517 os->writeU8(0);
518 os->writeU8(0);
Pierre Ossman126e5642014-02-13 14:40:25 +0100519 os->writeBytes(data, (width+7)/8 * height);
520 os->writeBytes(mask, (width+7)/8 * height);
521 }
522}
Pierre Ossman8053c8e2017-02-21 12:59:04 +0100523
524void SMsgWriter::writeSetCursorWithAlphaRect(int width, int height,
525 int hotspotX, int hotspotY,
526 const rdr::U8* data)
527{
528 if (!cp->supportsLocalCursorWithAlpha)
529 throw Exception("Client does not support local cursors");
530 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
531 throw Exception("SMsgWriter::writeSetCursorWithAlphaRect: nRects out of sync");
532
533 os->writeS16(hotspotX);
534 os->writeS16(hotspotY);
535 os->writeU16(width);
536 os->writeU16(height);
537 os->writeU32(pseudoEncodingCursorWithAlpha);
538
539 // FIXME: Use an encoder with compression?
540 os->writeU32(encodingRaw);
541
542 // Alpha needs to be pre-multiplied
543 for (int i = 0;i < width*height;i++) {
544 os->writeU8((unsigned)data[0] * data[3] / 255);
545 os->writeU8((unsigned)data[1] * data[3] / 255);
546 os->writeU8((unsigned)data[2] * data[3] / 255);
547 os->writeU8(data[3]);
548 data += 4;
549 }
550}
Pierre Ossmanb45a84f2016-12-12 16:59:15 +0100551
552void SMsgWriter::writeLEDStateRect(rdr::U8 state)
553{
554 if (!cp->supportsLEDState)
555 throw Exception("Client does not support LED state updates");
556 if (cp->ledState() == ledUnknown)
557 throw Exception("Server does not support LED state updates");
558 if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
559 throw Exception("SMsgWriter::writeLEDStateRect: nRects out of sync");
560
561 os->writeS16(0);
562 os->writeS16(0);
563 os->writeU16(0);
564 os->writeU16(0);
565 os->writeU32(pseudoEncodingLEDState);
566 os->writeU8(state);
567}