You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CMsgReader.cxx 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright 2009-2019 Pierre Ossman for Cendio AB
  3. *
  4. * This is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This software is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this software; if not, write to the Free Software
  16. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
  17. * USA.
  18. */
  19. #include <assert.h>
  20. #include <stdio.h>
  21. #include <rdr/InStream.h>
  22. #include <rdr/ZlibInStream.h>
  23. #include <rfb/msgTypes.h>
  24. #include <rfb/clipboardTypes.h>
  25. #include <rfb/Exception.h>
  26. #include <rfb/LogWriter.h>
  27. #include <rfb/util.h>
  28. #include <rfb/CMsgHandler.h>
  29. #include <rfb/CMsgReader.h>
  30. static rfb::LogWriter vlog("CMsgReader");
  31. static rfb::IntParameter maxCutText("MaxCutText", "Maximum permitted length of an incoming clipboard update", 256*1024);
  32. using namespace rfb;
  33. CMsgReader::CMsgReader(CMsgHandler* handler_, rdr::InStream* is_)
  34. : imageBufIdealSize(0), handler(handler_), is(is_),
  35. state(MSGSTATE_IDLE), cursorEncoding(-1)
  36. {
  37. }
  38. CMsgReader::~CMsgReader()
  39. {
  40. }
  41. bool CMsgReader::readServerInit()
  42. {
  43. int width, height;
  44. rdr::U32 len;
  45. if (!is->hasData(2 + 2 + 16 + 4))
  46. return false;
  47. is->setRestorePoint();
  48. width = is->readU16();
  49. height = is->readU16();
  50. PixelFormat pf;
  51. pf.read(is);
  52. len = is->readU32();
  53. if (!is->hasDataOrRestore(len))
  54. return false;
  55. is->clearRestorePoint();
  56. CharArray name(len + 1);
  57. is->readBytes(name.buf, len);
  58. name.buf[len] = '\0';
  59. handler->serverInit(width, height, pf, name.buf);
  60. return true;
  61. }
  62. bool CMsgReader::readMsg()
  63. {
  64. if (state == MSGSTATE_IDLE) {
  65. if (!is->hasData(1))
  66. return false;
  67. currentMsgType = is->readU8();
  68. state = MSGSTATE_MESSAGE;
  69. }
  70. if (currentMsgType != msgTypeFramebufferUpdate) {
  71. bool ret;
  72. switch (currentMsgType) {
  73. case msgTypeSetColourMapEntries:
  74. ret = readSetColourMapEntries();
  75. break;
  76. case msgTypeBell:
  77. ret = readBell();
  78. break;
  79. case msgTypeServerCutText:
  80. ret = readServerCutText();
  81. break;
  82. case msgTypeFramebufferUpdate:
  83. ret = readFramebufferUpdate();
  84. break;
  85. case msgTypeServerFence:
  86. ret = readFence();
  87. break;
  88. case msgTypeEndOfContinuousUpdates:
  89. ret = readEndOfContinuousUpdates();
  90. break;
  91. default:
  92. throw Exception("Unknown message type %d", currentMsgType);
  93. }
  94. if (ret)
  95. state = MSGSTATE_IDLE;
  96. return ret;
  97. } else {
  98. if (state == MSGSTATE_MESSAGE) {
  99. if (!readFramebufferUpdate())
  100. return false;
  101. // Empty update?
  102. if (nUpdateRectsLeft == 0) {
  103. state = MSGSTATE_IDLE;
  104. handler->framebufferUpdateEnd();
  105. return true;
  106. }
  107. state = MSGSTATE_RECT_HEADER;
  108. }
  109. if (state == MSGSTATE_RECT_HEADER) {
  110. if (!is->hasData(12))
  111. return false;
  112. int x = is->readU16();
  113. int y = is->readU16();
  114. int w = is->readU16();
  115. int h = is->readU16();
  116. dataRect.setXYWH(x, y, w, h);
  117. rectEncoding = is->readS32();
  118. state = MSGSTATE_RECT_DATA;
  119. }
  120. bool ret;
  121. switch (rectEncoding) {
  122. case pseudoEncodingLastRect:
  123. nUpdateRectsLeft = 1; // this rectangle is the last one
  124. ret = true;
  125. break;
  126. case pseudoEncodingXCursor:
  127. ret = readSetXCursor(dataRect.width(), dataRect.height(), dataRect.tl);
  128. break;
  129. case pseudoEncodingCursor:
  130. ret = readSetCursor(dataRect.width(), dataRect.height(), dataRect.tl);
  131. break;
  132. case pseudoEncodingCursorWithAlpha:
  133. ret = readSetCursorWithAlpha(dataRect.width(), dataRect.height(), dataRect.tl);
  134. break;
  135. case pseudoEncodingVMwareCursor:
  136. ret = readSetVMwareCursor(dataRect.width(), dataRect.height(), dataRect.tl);
  137. break;
  138. case pseudoEncodingDesktopName:
  139. ret = readSetDesktopName(dataRect.tl.x, dataRect.tl.y,
  140. dataRect.width(), dataRect.height());
  141. break;
  142. case pseudoEncodingDesktopSize:
  143. handler->setDesktopSize(dataRect.width(), dataRect.height());
  144. ret = true;
  145. break;
  146. case pseudoEncodingExtendedDesktopSize:
  147. ret = readExtendedDesktopSize(dataRect.tl.x, dataRect.tl.y,
  148. dataRect.width(), dataRect.height());
  149. break;
  150. case pseudoEncodingLEDState:
  151. ret = readLEDState();
  152. break;
  153. case pseudoEncodingVMwareLEDState:
  154. ret = readVMwareLEDState();
  155. break;
  156. case pseudoEncodingQEMUKeyEvent:
  157. handler->supportsQEMUKeyEvent();
  158. ret = true;
  159. break;
  160. default:
  161. ret = readRect(dataRect, rectEncoding);
  162. break;
  163. };
  164. if (ret) {
  165. state = MSGSTATE_RECT_HEADER;
  166. nUpdateRectsLeft--;
  167. if (nUpdateRectsLeft == 0) {
  168. state = MSGSTATE_IDLE;
  169. handler->framebufferUpdateEnd();
  170. }
  171. }
  172. return ret;
  173. }
  174. }
  175. bool CMsgReader::readSetColourMapEntries()
  176. {
  177. if (!is->hasData(5))
  178. return false;
  179. is->setRestorePoint();
  180. is->skip(1);
  181. int firstColour = is->readU16();
  182. int nColours = is->readU16();
  183. if (!is->hasDataOrRestore(nColours * 3 * 2))
  184. return false;
  185. is->clearRestorePoint();
  186. rdr::U16Array rgbs(nColours * 3);
  187. for (int i = 0; i < nColours * 3; i++)
  188. rgbs.buf[i] = is->readU16();
  189. handler->setColourMapEntries(firstColour, nColours, rgbs.buf);
  190. return true;
  191. }
  192. bool CMsgReader::readBell()
  193. {
  194. handler->bell();
  195. return true;
  196. }
  197. bool CMsgReader::readServerCutText()
  198. {
  199. if (!is->hasData(7))
  200. return false;
  201. is->setRestorePoint();
  202. is->skip(3);
  203. rdr::U32 len = is->readU32();
  204. if (len & 0x80000000) {
  205. rdr::S32 slen = len;
  206. slen = -slen;
  207. if (readExtendedClipboard(slen)) {
  208. is->clearRestorePoint();
  209. return true;
  210. } else {
  211. is->gotoRestorePoint();
  212. return false;
  213. }
  214. }
  215. if (!is->hasDataOrRestore(len))
  216. return false;
  217. is->clearRestorePoint();
  218. if (len > (size_t)maxCutText) {
  219. is->skip(len);
  220. vlog.error("cut text too long (%d bytes) - ignoring",len);
  221. return true;
  222. }
  223. CharArray ca(len);
  224. is->readBytes(ca.buf, len);
  225. CharArray filtered(convertLF(ca.buf, len));
  226. handler->serverCutText(filtered.buf);
  227. return true;
  228. }
  229. bool CMsgReader::readExtendedClipboard(rdr::S32 len)
  230. {
  231. rdr::U32 flags;
  232. rdr::U32 action;
  233. if (!is->hasData(len))
  234. return false;
  235. if (len < 4)
  236. throw Exception("Invalid extended clipboard message");
  237. if (len > maxCutText) {
  238. vlog.error("Extended clipboard message too long (%d bytes) - ignoring", len);
  239. is->skip(len);
  240. return true;
  241. }
  242. flags = is->readU32();
  243. action = flags & clipboardActionMask;
  244. if (action & clipboardCaps) {
  245. int i;
  246. size_t num;
  247. rdr::U32 lengths[16];
  248. num = 0;
  249. for (i = 0;i < 16;i++) {
  250. if (flags & (1 << i))
  251. num++;
  252. }
  253. if (len < (rdr::S32)(4 + 4*num))
  254. throw Exception("Invalid extended clipboard message");
  255. num = 0;
  256. for (i = 0;i < 16;i++) {
  257. if (flags & (1 << i))
  258. lengths[num++] = is->readU32();
  259. }
  260. handler->handleClipboardCaps(flags, lengths);
  261. } else if (action == clipboardProvide) {
  262. rdr::ZlibInStream zis;
  263. int i;
  264. size_t num;
  265. size_t lengths[16];
  266. rdr::U8* buffers[16];
  267. zis.setUnderlying(is, len - 4);
  268. num = 0;
  269. for (i = 0;i < 16;i++) {
  270. if (!(flags & 1 << i))
  271. continue;
  272. if (!zis.hasData(4))
  273. throw Exception("Extended clipboard decode error");
  274. lengths[num] = zis.readU32();
  275. if (!zis.hasData(lengths[num]))
  276. throw Exception("Extended clipboard decode error");
  277. if (lengths[num] > (size_t)maxCutText) {
  278. vlog.error("Extended clipboard data too long (%d bytes) - ignoring",
  279. (unsigned)lengths[num]);
  280. zis.skip(lengths[num]);
  281. flags &= ~(1 << i);
  282. continue;
  283. }
  284. buffers[num] = new rdr::U8[lengths[num]];
  285. zis.readBytes(buffers[num], lengths[num]);
  286. num++;
  287. }
  288. zis.flushUnderlying();
  289. zis.setUnderlying(NULL, 0);
  290. handler->handleClipboardProvide(flags, lengths, buffers);
  291. num = 0;
  292. for (i = 0;i < 16;i++) {
  293. if (!(flags & 1 << i))
  294. continue;
  295. delete [] buffers[num++];
  296. }
  297. } else {
  298. switch (action) {
  299. case clipboardRequest:
  300. handler->handleClipboardRequest(flags);
  301. break;
  302. case clipboardPeek:
  303. handler->handleClipboardPeek(flags);
  304. break;
  305. case clipboardNotify:
  306. handler->handleClipboardNotify(flags);
  307. break;
  308. default:
  309. throw Exception("Invalid extended clipboard action");
  310. }
  311. }
  312. return true;
  313. }
  314. bool CMsgReader::readFence()
  315. {
  316. rdr::U32 flags;
  317. rdr::U8 len;
  318. char data[64];
  319. if (!is->hasData(8))
  320. return false;
  321. is->setRestorePoint();
  322. is->skip(3);
  323. flags = is->readU32();
  324. len = is->readU8();
  325. if (!is->hasDataOrRestore(len))
  326. return false;
  327. is->clearRestorePoint();
  328. if (len > sizeof(data)) {
  329. vlog.error("Ignoring fence with too large payload");
  330. is->skip(len);
  331. return true;
  332. }
  333. is->readBytes(data, len);
  334. handler->fence(flags, len, data);
  335. return true;
  336. }
  337. bool CMsgReader::readEndOfContinuousUpdates()
  338. {
  339. handler->endOfContinuousUpdates();
  340. return true;
  341. }
  342. bool CMsgReader::readFramebufferUpdate()
  343. {
  344. if (!is->hasData(3))
  345. return false;
  346. is->skip(1);
  347. nUpdateRectsLeft = is->readU16();
  348. handler->framebufferUpdateStart();
  349. return true;
  350. }
  351. bool CMsgReader::readRect(const Rect& r, int encoding)
  352. {
  353. if ((r.br.x > handler->server.width()) ||
  354. (r.br.y > handler->server.height())) {
  355. vlog.error("Rect too big: %dx%d at %d,%d exceeds %dx%d",
  356. r.width(), r.height(), r.tl.x, r.tl.y,
  357. handler->server.width(), handler->server.height());
  358. throw Exception("Rect too big");
  359. }
  360. if (r.is_empty())
  361. vlog.error("zero size rect");
  362. return handler->dataRect(r, encoding);
  363. }
  364. bool CMsgReader::readSetXCursor(int width, int height, const Point& hotspot)
  365. {
  366. if (width > maxCursorSize || height > maxCursorSize)
  367. throw Exception("Too big cursor");
  368. rdr::U8Array rgba(width*height*4);
  369. if (width * height > 0) {
  370. rdr::U8 pr, pg, pb;
  371. rdr::U8 sr, sg, sb;
  372. int data_len = ((width+7)/8) * height;
  373. int mask_len = ((width+7)/8) * height;
  374. rdr::U8Array data(data_len);
  375. rdr::U8Array mask(mask_len);
  376. int x, y;
  377. rdr::U8* out;
  378. if (!is->hasData(3 + 3 + data_len + mask_len))
  379. return false;
  380. pr = is->readU8();
  381. pg = is->readU8();
  382. pb = is->readU8();
  383. sr = is->readU8();
  384. sg = is->readU8();
  385. sb = is->readU8();
  386. is->readBytes(data.buf, data_len);
  387. is->readBytes(mask.buf, mask_len);
  388. int maskBytesPerRow = (width+7)/8;
  389. out = rgba.buf;
  390. for (y = 0;y < height;y++) {
  391. for (x = 0;x < width;x++) {
  392. int byte = y * maskBytesPerRow + x / 8;
  393. int bit = 7 - x % 8;
  394. if (data.buf[byte] & (1 << bit)) {
  395. out[0] = pr;
  396. out[1] = pg;
  397. out[2] = pb;
  398. } else {
  399. out[0] = sr;
  400. out[1] = sg;
  401. out[2] = sb;
  402. }
  403. if (mask.buf[byte] & (1 << bit))
  404. out[3] = 255;
  405. else
  406. out[3] = 0;
  407. out += 4;
  408. }
  409. }
  410. }
  411. handler->setCursor(width, height, hotspot, rgba.buf);
  412. return true;
  413. }
  414. bool CMsgReader::readSetCursor(int width, int height, const Point& hotspot)
  415. {
  416. if (width > maxCursorSize || height > maxCursorSize)
  417. throw Exception("Too big cursor");
  418. int data_len = width * height * (handler->server.pf().bpp/8);
  419. int mask_len = ((width+7)/8) * height;
  420. rdr::U8Array data(data_len);
  421. rdr::U8Array mask(mask_len);
  422. int x, y;
  423. rdr::U8Array rgba(width*height*4);
  424. rdr::U8* in;
  425. rdr::U8* out;
  426. if (!is->hasData(data_len + mask_len))
  427. return false;
  428. is->readBytes(data.buf, data_len);
  429. is->readBytes(mask.buf, mask_len);
  430. int maskBytesPerRow = (width+7)/8;
  431. in = data.buf;
  432. out = rgba.buf;
  433. for (y = 0;y < height;y++) {
  434. for (x = 0;x < width;x++) {
  435. int byte = y * maskBytesPerRow + x / 8;
  436. int bit = 7 - x % 8;
  437. handler->server.pf().rgbFromBuffer(out, in, 1);
  438. if (mask.buf[byte] & (1 << bit))
  439. out[3] = 255;
  440. else
  441. out[3] = 0;
  442. in += handler->server.pf().bpp/8;
  443. out += 4;
  444. }
  445. }
  446. handler->setCursor(width, height, hotspot, rgba.buf);
  447. return true;
  448. }
  449. bool CMsgReader::readSetCursorWithAlpha(int width, int height, const Point& hotspot)
  450. {
  451. if (width > maxCursorSize || height > maxCursorSize)
  452. throw Exception("Too big cursor");
  453. const PixelFormat rgbaPF(32, 32, false, true, 255, 255, 255, 16, 8, 0);
  454. ManagedPixelBuffer pb(rgbaPF, width, height);
  455. PixelFormat origPF;
  456. bool ret;
  457. rdr::U8* buf;
  458. int stride;
  459. // We can't use restore points as the decoder likely wants to as well, so
  460. // we need to keep track of the read encoding
  461. if (cursorEncoding == -1) {
  462. if (!is->hasData(4))
  463. return false;
  464. cursorEncoding = is->readS32();
  465. }
  466. origPF = handler->server.pf();
  467. handler->server.setPF(rgbaPF);
  468. ret = handler->readAndDecodeRect(pb.getRect(), cursorEncoding, &pb);
  469. handler->server.setPF(origPF);
  470. if (!ret)
  471. return false;
  472. cursorEncoding = -1;
  473. // On-wire data has pre-multiplied alpha, but we store it
  474. // non-pre-multiplied
  475. buf = pb.getBufferRW(pb.getRect(), &stride);
  476. assert(stride == width);
  477. for (int i = 0;i < pb.area();i++) {
  478. rdr::U8 alpha;
  479. alpha = buf[3];
  480. if (alpha == 0)
  481. alpha = 1; // Avoid division by zero
  482. buf[0] = (unsigned)buf[0] * 255/alpha;
  483. buf[1] = (unsigned)buf[1] * 255/alpha;
  484. buf[2] = (unsigned)buf[2] * 255/alpha;
  485. buf += 4;
  486. }
  487. pb.commitBufferRW(pb.getRect());
  488. handler->setCursor(width, height, hotspot,
  489. pb.getBuffer(pb.getRect(), &stride));
  490. return true;
  491. }
  492. bool CMsgReader::readSetVMwareCursor(int width, int height, const Point& hotspot)
  493. {
  494. if (width > maxCursorSize || height > maxCursorSize)
  495. throw Exception("Too big cursor");
  496. rdr::U8 type;
  497. if (!is->hasData(2))
  498. return false;
  499. type = is->readU8();
  500. is->skip(1);
  501. is->setRestorePoint();
  502. if (type == 0) {
  503. int len = width * height * (handler->server.pf().bpp/8);
  504. rdr::U8Array andMask(len);
  505. rdr::U8Array xorMask(len);
  506. rdr::U8Array data(width*height*4);
  507. rdr::U8* andIn;
  508. rdr::U8* xorIn;
  509. rdr::U8* out;
  510. int Bpp;
  511. if (!is->hasDataOrRestore(len + len))
  512. return false;
  513. is->clearRestorePoint();
  514. is->readBytes(andMask.buf, len);
  515. is->readBytes(xorMask.buf, len);
  516. andIn = andMask.buf;
  517. xorIn = xorMask.buf;
  518. out = data.buf;
  519. Bpp = handler->server.pf().bpp/8;
  520. for (int y = 0;y < height;y++) {
  521. for (int x = 0;x < width;x++) {
  522. Pixel andPixel, xorPixel;
  523. andPixel = handler->server.pf().pixelFromBuffer(andIn);
  524. xorPixel = handler->server.pf().pixelFromBuffer(xorIn);
  525. andIn += Bpp;
  526. xorIn += Bpp;
  527. if (andPixel == 0) {
  528. rdr::U8 r, g, b;
  529. // Opaque pixel
  530. handler->server.pf().rgbFromPixel(xorPixel, &r, &g, &b);
  531. *out++ = r;
  532. *out++ = g;
  533. *out++ = b;
  534. *out++ = 0xff;
  535. } else if (xorPixel == 0) {
  536. // Fully transparent pixel
  537. *out++ = 0;
  538. *out++ = 0;
  539. *out++ = 0;
  540. *out++ = 0;
  541. } else if (andPixel == xorPixel) {
  542. // Inverted pixel
  543. // We don't really support this, so just turn the pixel black
  544. // FIXME: Do an outline like WinVNC does?
  545. *out++ = 0;
  546. *out++ = 0;
  547. *out++ = 0;
  548. *out++ = 0xff;
  549. } else {
  550. // Partially transparent/inverted pixel
  551. // We _really_ can't handle this, just make it black
  552. *out++ = 0;
  553. *out++ = 0;
  554. *out++ = 0;
  555. *out++ = 0xff;
  556. }
  557. }
  558. }
  559. handler->setCursor(width, height, hotspot, data.buf);
  560. } else if (type == 1) {
  561. rdr::U8Array data(width*height*4);
  562. if (!is->hasDataOrRestore(width*height*4))
  563. return false;
  564. is->clearRestorePoint();
  565. // FIXME: Is alpha premultiplied?
  566. is->readBytes(data.buf, width*height*4);
  567. handler->setCursor(width, height, hotspot, data.buf);
  568. } else {
  569. throw Exception("Unknown cursor type");
  570. }
  571. return true;
  572. }
  573. bool CMsgReader::readSetDesktopName(int x, int y, int w, int h)
  574. {
  575. rdr::U32 len;
  576. if (!is->hasData(4))
  577. return false;
  578. is->setRestorePoint();
  579. len = is->readU32();
  580. if (!is->hasDataOrRestore(len))
  581. return false;
  582. is->clearRestorePoint();
  583. CharArray name(len + 1);
  584. is->readBytes(name.buf, len);
  585. name.buf[len] = '\0';
  586. if (x || y || w || h) {
  587. vlog.error("Ignoring DesktopName rect with non-zero position/size");
  588. } else {
  589. handler->setName(name.buf);
  590. }
  591. return true;
  592. }
  593. bool CMsgReader::readExtendedDesktopSize(int x, int y, int w, int h)
  594. {
  595. unsigned int screens, i;
  596. rdr::U32 id, flags;
  597. int sx, sy, sw, sh;
  598. ScreenSet layout;
  599. if (!is->hasData(4))
  600. return false;
  601. is->setRestorePoint();
  602. screens = is->readU8();
  603. is->skip(3);
  604. if (!is->hasDataOrRestore(16 * screens))
  605. return false;
  606. is->clearRestorePoint();
  607. for (i = 0;i < screens;i++) {
  608. id = is->readU32();
  609. sx = is->readU16();
  610. sy = is->readU16();
  611. sw = is->readU16();
  612. sh = is->readU16();
  613. flags = is->readU32();
  614. layout.add_screen(Screen(id, sx, sy, sw, sh, flags));
  615. }
  616. handler->setExtendedDesktopSize(x, y, w, h, layout);
  617. return true;
  618. }
  619. bool CMsgReader::readLEDState()
  620. {
  621. rdr::U8 state;
  622. if (!is->hasData(1))
  623. return false;
  624. state = is->readU8();
  625. handler->setLEDState(state);
  626. return true;
  627. }
  628. bool CMsgReader::readVMwareLEDState()
  629. {
  630. rdr::U32 state;
  631. if (!is->hasData(4))
  632. return false;
  633. state = is->readU32();
  634. // As luck has it, this extension uses the same bit definitions,
  635. // so no conversion required
  636. handler->setLEDState(state);
  637. return true;
  638. }