blob: efbc0081eef88b9cf8216610b354cb3558030f16 [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossmanc5e25602009-03-20 12:59:05 +00002 * Copyright 2009 Pierre Ossman for Cendio AB
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00003 *
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
20#include <rfb/VNCSConnectionST.h>
21#include <rfb/LogWriter.h>
Adam Tkac5a0caed2010-04-23 13:58:10 +000022#include <rfb/Security.h>
Pierre Ossmanc5e25602009-03-20 12:59:05 +000023#include <rfb/screenTypes.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000024#include <rfb/ServerCore.h>
25#include <rfb/ComparingUpdateTracker.h>
26#include <rfb/KeyRemapper.h>
27#define XK_MISCELLANY
28#define XK_XKB_KEYS
29#include <rfb/keysymdef.h>
30
31using namespace rfb;
32
33static LogWriter vlog("VNCSConnST");
34
35VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
36 bool reverse)
Adam Tkaca6578bf2010-04-23 14:07:41 +000037 : SConnection(reverse), sock(s), server(server_),
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000038 updates(false), image_getter(server->useEconomicTranslate),
39 drawRenderedCursor(false), removeRenderedCursor(false),
40 pointerEventTime(0), accessRights(AccessDefault),
Pierre Ossmanf99c5712009-03-13 14:41:27 +000041 startTime(time(0))
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000042{
43 setStreams(&sock->inStream(), &sock->outStream());
44 peerEndpoint.buf = sock->getPeerEndpoint();
45 VNCServerST::connectionsLog.write(1,"accepted: %s", peerEndpoint.buf);
46
47 // Configure the socket
48 setSocketTimeouts();
49 lastEventTime = time(0);
50
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000051 server->clients.push_front(this);
52}
53
54
55VNCSConnectionST::~VNCSConnectionST()
56{
57 // If we reach here then VNCServerST is deleting us!
58 VNCServerST::connectionsLog.write(1,"closed: %s (%s)",
59 peerEndpoint.buf,
60 (closeReason.buf) ? closeReason.buf : "");
61
62 // Release any keys the client still had pressed
63 std::set<rdr::U32>::iterator i;
64 for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++)
65 server->desktop->keyEvent(*i, false);
66 if (server->pointerClient == this)
67 server->pointerClient = 0;
68
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000069 // Remove this client from the server
70 server->clients.remove(this);
71
72}
73
74
75// Methods called from VNCServerST
76
77bool VNCSConnectionST::init()
78{
79 try {
80 initialiseProtocol();
81 } catch (rdr::Exception& e) {
82 close(e.str());
83 return false;
84 }
85 return true;
86}
87
88void VNCSConnectionST::close(const char* reason)
89{
90 // Log the reason for the close
91 if (!closeReason.buf)
Adam Tkacd36b6262009-09-04 10:57:20 +000092 closeReason.buf = strDup(reason);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000093 else
94 vlog.debug("second close: %s (%s)", peerEndpoint.buf, reason);
95
96 if (authenticated()) {
97 server->lastDisconnectTime = time(0);
98 }
99
100 // Just shutdown the socket and mark our state as closing. Eventually the
101 // calling code will call VNCServerST's removeSocket() method causing us to
102 // be deleted.
103 sock->shutdown();
104 setState(RFBSTATE_CLOSING);
105}
106
107
108void VNCSConnectionST::processMessages()
109{
110 if (state() == RFBSTATE_CLOSING) return;
111 try {
112 // - Now set appropriate socket timeouts and process data
113 setSocketTimeouts();
114 bool clientsReadyBefore = server->clientsReadyForUpdate();
115
116 while (getInStream()->checkNoWait(1)) {
117 processMsg();
118 }
119
Constantin Kaplinsky2ef66952008-08-29 11:33:46 +0000120 // If there were update requests, try to send a framebuffer update.
Pierre Ossman02e43d72009-03-05 11:57:11 +0000121 // We don't send updates immediately on requests as this way, we
122 // give higher priority to user actions such as keyboard and
123 // pointer events.
Constantin Kaplinsky2ef66952008-08-29 11:33:46 +0000124 if (!requested.is_empty()) {
125 writeFramebufferUpdate();
126 }
127
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000128 if (!clientsReadyBefore && !requested.is_empty())
129 server->desktop->framebufferUpdateRequest();
130 } catch (rdr::EndOfStream&) {
131 close("Clean disconnection");
132 } catch (rdr::Exception &e) {
133 close(e.str());
134 }
135}
136
137void VNCSConnectionST::writeFramebufferUpdateOrClose()
138{
139 try {
140 writeFramebufferUpdate();
141 } catch(rdr::Exception &e) {
142 close(e.str());
143 }
144}
145
146void VNCSConnectionST::pixelBufferChange()
147{
148 try {
149 if (!authenticated()) return;
150 if (cp.width && cp.height && (server->pb->width() != cp.width ||
151 server->pb->height() != cp.height))
152 {
153 // We need to clip the next update to the new size, but also add any
154 // extra bits if it's bigger. If we wanted to do this exactly, something
155 // like the code below would do it, but at the moment we just update the
156 // entire new size. However, we do need to clip the renderedCursorRect
157 // because that might be added to updates in writeFramebufferUpdate().
158
159 //updates.intersect(server->pb->getRect());
160 //
161 //if (server->pb->width() > cp.width)
162 // updates.add_changed(Rect(cp.width, 0, server->pb->width(),
163 // server->pb->height()));
164 //if (server->pb->height() > cp.height)
165 // updates.add_changed(Rect(0, cp.height, cp.width,
166 // server->pb->height()));
167
168 renderedCursorRect = renderedCursorRect.intersect(server->pb->getRect());
169
170 cp.width = server->pb->width();
171 cp.height = server->pb->height();
Pierre Ossman34e62f32009-03-20 21:46:12 +0000172 cp.screenLayout = server->screenLayout;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000173 if (state() == RFBSTATE_NORMAL) {
Pierre Ossman2ee430a2009-05-28 12:54:24 +0000174 // We should only send EDS to client asking for both
175 if (!writer()->writeExtendedDesktopSize()) {
176 if (!writer()->writeSetDesktopSize()) {
177 close("Client does not support desktop resize");
178 return;
179 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000180 }
181 }
182 }
183 // Just update the whole screen at the moment because we're too lazy to
184 // work out what's actually changed.
185 updates.clear();
186 updates.add_changed(server->pb->getRect());
187 vlog.debug("pixel buffer changed - re-initialising image getter");
188 image_getter.init(server->pb, cp.pf(), writer());
189 if (writer()->needFakeUpdate())
190 writeFramebufferUpdate();
191 } catch(rdr::Exception &e) {
192 close(e.str());
193 }
194}
195
Pierre Ossman04e62db2009-03-23 16:57:07 +0000196void VNCSConnectionST::screenLayoutChange(rdr::U16 reason)
197{
198 try {
199 if (!authenticated())
200 return;
201
202 cp.screenLayout = server->screenLayout;
203 if (state() == RFBSTATE_NORMAL) {
204 writer()->writeExtendedDesktopSize(reason, 0, cp.width, cp.height,
205 cp.screenLayout);
206 }
207
208 if (writer()->needFakeUpdate())
209 writeFramebufferUpdate();
210 } catch(rdr::Exception &e) {
211 close(e.str());
212 }
213}
214
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000215void VNCSConnectionST::setColourMapEntriesOrClose(int firstColour,int nColours)
216{
217 try {
218 setColourMapEntries(firstColour, nColours);
219 } catch(rdr::Exception& e) {
220 close(e.str());
221 }
222}
223
224void VNCSConnectionST::bell()
225{
226 try {
227 if (state() == RFBSTATE_NORMAL) writer()->writeBell();
228 } catch(rdr::Exception& e) {
229 close(e.str());
230 }
231}
232
233void VNCSConnectionST::serverCutText(const char *str, int len)
234{
235 try {
236 if (!(accessRights & AccessCutText)) return;
237 if (!rfb::Server::sendCutText) return;
238 if (state() == RFBSTATE_NORMAL)
239 writer()->writeServerCutText(str, len);
240 } catch(rdr::Exception& e) {
241 close(e.str());
242 }
243}
244
Peter Åstrandc39e0782009-01-15 12:21:42 +0000245
246void VNCSConnectionST::setDesktopName(const char *name)
247{
248 cp.setName(name);
249 try {
250 if (state() == RFBSTATE_NORMAL) {
251 if (!writer()->writeSetDesktopName()) {
252 fprintf(stderr, "Client does not support desktop rename\n");
253 }
254 }
255 } catch(rdr::Exception& e) {
256 close(e.str());
257 }
258}
259
260
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000261void VNCSConnectionST::setCursorOrClose()
262{
263 try {
264 setCursor();
265 } catch(rdr::Exception& e) {
266 close(e.str());
267 }
268}
269
270
271int VNCSConnectionST::checkIdleTimeout()
272{
273 int idleTimeout = rfb::Server::idleTimeout;
274 if (idleTimeout == 0) return 0;
275 if (state() != RFBSTATE_NORMAL && idleTimeout < 15)
276 idleTimeout = 15; // minimum of 15 seconds while authenticating
277 time_t now = time(0);
278 if (now < lastEventTime) {
279 // Someone must have set the time backwards. Set lastEventTime so that the
280 // idleTimeout will count from now.
281 vlog.info("Time has gone backwards - resetting idle timeout");
282 lastEventTime = now;
283 }
284 int timeLeft = lastEventTime + idleTimeout - now;
285 if (timeLeft < -60) {
286 // Our callback is over a minute late - someone must have set the time
287 // forwards. Set lastEventTime so that the idleTimeout will count from
288 // now.
289 vlog.info("Time has gone forwards - resetting idle timeout");
290 lastEventTime = now;
291 return secsToMillis(idleTimeout);
292 }
293 if (timeLeft <= 0) {
294 close("Idle timeout");
295 return 0;
296 }
297 return secsToMillis(timeLeft);
298}
299
300// renderedCursorChange() is called whenever the server-side rendered cursor
301// changes shape or position. It ensures that the next update will clean up
302// the old rendered cursor and if necessary draw the new rendered cursor.
303
304void VNCSConnectionST::renderedCursorChange()
305{
306 if (state() != RFBSTATE_NORMAL) return;
307 removeRenderedCursor = true;
308 if (needRenderedCursor())
309 drawRenderedCursor = true;
310}
311
312// needRenderedCursor() returns true if this client needs the server-side
313// rendered cursor. This may be because it does not support local cursor or
314// because the current cursor position has not been set by this client.
315// Unfortunately we can't know for sure when the current cursor position has
316// been set by this client. We guess that this is the case when the current
317// cursor position is the same as the last pointer event from this client, or
318// if it is a very short time since this client's last pointer event (up to a
319// second). [ Ideally we should do finer-grained timing here and make the time
320// configurable, but I don't think it's that important. ]
321
322bool VNCSConnectionST::needRenderedCursor()
323{
Peter Åstrandffeeb262010-02-10 09:29:00 +0000324 bool pointerpos = (!server->cursorPos.equals(pointerEventPos) && (time(0) - pointerEventTime) > 0);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000325 return (state() == RFBSTATE_NORMAL
Peter Åstrandffeeb262010-02-10 09:29:00 +0000326 && ((!cp.supportsLocalCursor && !cp.supportsLocalXCursor) || pointerpos));
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000327}
328
329
330void VNCSConnectionST::approveConnectionOrClose(bool accept,
331 const char* reason)
332{
333 try {
334 approveConnection(accept, reason);
335 } catch (rdr::Exception& e) {
336 close(e.str());
337 }
338}
339
340
341
342// -=- Callbacks from SConnection
343
344void VNCSConnectionST::authSuccess()
345{
346 lastEventTime = time(0);
347
348 server->startDesktop();
349
350 // - Set the connection parameters appropriately
351 cp.width = server->pb->width();
352 cp.height = server->pb->height();
Pierre Ossman34e62f32009-03-20 21:46:12 +0000353 cp.screenLayout = server->screenLayout;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000354 cp.setName(server->getName());
355
356 // - Set the default pixel format
357 cp.setPF(server->pb->getPF());
358 char buffer[256];
359 cp.pf().print(buffer, 256);
360 vlog.info("Server default pixel format %s", buffer);
361 image_getter.init(server->pb, cp.pf(), 0);
362
363 // - Mark the entire display as "dirty"
364 updates.add_changed(server->pb->getRect());
365 startTime = time(0);
366}
367
368void VNCSConnectionST::queryConnection(const char* userName)
369{
370 // - Authentication succeeded - clear from blacklist
371 CharArray name; name.buf = sock->getPeerAddress();
372 server->blHosts->clearBlackmark(name.buf);
373
374 // - Special case to provide a more useful error message
375 if (rfb::Server::neverShared && !rfb::Server::disconnectClients &&
376 server->authClientCount() > 0) {
377 approveConnection(false, "The server is already in use");
378 return;
379 }
380
381 // - Does the client have the right to bypass the query?
382 if (reverseConnection ||
383 !(rfb::Server::queryConnect || sock->requiresQuery()) ||
384 (accessRights & AccessNoQuery))
385 {
386 approveConnection(true);
387 return;
388 }
389
390 // - Get the server to display an Accept/Reject dialog, if required
391 // If a dialog is displayed, the result will be PENDING, and the
392 // server will call approveConnection at a later time
393 CharArray reason;
394 VNCServerST::queryResult qr = server->queryConnection(sock, userName,
395 &reason.buf);
396 if (qr == VNCServerST::PENDING)
397 return;
398
399 // - If server returns ACCEPT/REJECT then pass result to SConnection
400 approveConnection(qr == VNCServerST::ACCEPT, reason.buf);
401}
402
403void VNCSConnectionST::clientInit(bool shared)
404{
405 lastEventTime = time(0);
406 if (rfb::Server::alwaysShared || reverseConnection) shared = true;
407 if (rfb::Server::neverShared) shared = false;
408 if (!shared) {
409 if (rfb::Server::disconnectClients) {
410 // - Close all the other connected clients
411 vlog.debug("non-shared connection - closing clients");
412 server->closeClients("Non-shared connection requested", getSock());
413 } else {
414 // - Refuse this connection if there are existing clients, in addition to
415 // this one
416 if (server->authClientCount() > 1) {
417 close("Server is already in use");
418 return;
419 }
420 }
421 }
422 SConnection::clientInit(shared);
423}
424
425void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
426{
427 SConnection::setPixelFormat(pf);
428 char buffer[256];
429 pf.print(buffer, 256);
430 vlog.info("Client pixel format %s", buffer);
431 image_getter.init(server->pb, pf, writer());
432 setCursor();
433}
434
435void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
436{
437 pointerEventTime = lastEventTime = time(0);
438 server->lastUserInputTime = lastEventTime;
439 if (!(accessRights & AccessPtrEvents)) return;
440 if (!rfb::Server::acceptPointerEvents) return;
441 if (!server->pointerClient || server->pointerClient == this) {
442 pointerEventPos = pos;
443 if (buttonMask)
444 server->pointerClient = this;
445 else
446 server->pointerClient = 0;
447 server->desktop->pointerEvent(pointerEventPos, buttonMask);
448 }
449}
450
451
452class VNCSConnectionSTShiftPresser {
453public:
454 VNCSConnectionSTShiftPresser(SDesktop* desktop_)
455 : desktop(desktop_), pressed(false) {}
456 ~VNCSConnectionSTShiftPresser() {
457 if (pressed) { desktop->keyEvent(XK_Shift_L, false); }
458 }
459 void press() {
460 desktop->keyEvent(XK_Shift_L, true);
461 pressed = true;
462 }
463 SDesktop* desktop;
464 bool pressed;
465};
466
467// keyEvent() - record in the pressedKeys which keys were pressed. Allow
468// multiple down events (for autorepeat), but only allow a single up event.
469void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) {
470 lastEventTime = time(0);
471 server->lastUserInputTime = lastEventTime;
472 if (!(accessRights & AccessKeyEvents)) return;
473 if (!rfb::Server::acceptKeyEvents) return;
474
475 // Remap the key if required
476 if (server->keyRemapper)
477 key = server->keyRemapper->remapKey(key);
478
479 // Turn ISO_Left_Tab into shifted Tab.
480 VNCSConnectionSTShiftPresser shiftPresser(server->desktop);
481 if (key == XK_ISO_Left_Tab) {
482 if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() &&
483 pressedKeys.find(XK_Shift_R) == pressedKeys.end())
484 shiftPresser.press();
485 key = XK_Tab;
486 }
487
488 if (down) {
489 pressedKeys.insert(key);
490 } else {
491 if (!pressedKeys.erase(key)) return;
492 }
493 server->desktop->keyEvent(key, down);
494}
495
496void VNCSConnectionST::clientCutText(const char* str, int len)
497{
498 if (!(accessRights & AccessCutText)) return;
499 if (!rfb::Server::acceptCutText) return;
500 server->desktop->clientCutText(str, len);
501}
502
503void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
504{
Pierre Ossmane9962f72009-04-23 12:31:42 +0000505 Rect safeRect;
506
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000507 if (!(accessRights & AccessView)) return;
508
509 SConnection::framebufferUpdateRequest(r, incremental);
510
Pierre Ossmand9a59ba2009-03-20 15:55:37 +0000511 // Check that the client isn't sending crappy requests
512 if (!r.enclosed_by(Rect(0, 0, cp.width, cp.height))) {
513 vlog.error("FramebufferUpdateRequest %dx%d at %d,%d exceeds framebuffer %dx%d",
514 r.width(), r.height(), r.tl.x, r.tl.y, cp.width, cp.height);
Pierre Ossmane9962f72009-04-23 12:31:42 +0000515 safeRect = r.intersect(Rect(0, 0, cp.width, cp.height));
516 } else {
517 safeRect = r;
Pierre Ossmand9a59ba2009-03-20 15:55:37 +0000518 }
519
Constantin Kaplinsky2ef66952008-08-29 11:33:46 +0000520 // Just update the requested region.
521 // Framebuffer update will be sent a bit later, see processMessages().
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000522 Region reqRgn(r);
523 requested.assign_union(reqRgn);
524
525 if (!incremental) {
526 // Non-incremental update - treat as if area requested has changed
527 updates.add_changed(reqRgn);
528 server->comparer->add_changed(reqRgn);
Pierre Ossman53125a72009-04-22 15:37:51 +0000529
530 // And send the screen layout to the client (which, unlike the
531 // framebuffer dimensions, the client doesn't get during init)
Pierre Ossmanc5e25602009-03-20 12:59:05 +0000532 writer()->writeExtendedDesktopSize();
Pierre Ossman53125a72009-04-22 15:37:51 +0000533
534 // We do not send a DesktopSize since it only contains the
535 // framebuffer size (which the client already should know) and
536 // because some clients don't handle extra DesktopSize events
537 // very well.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000538 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000539}
540
Pierre Ossman34bb0612009-03-21 21:16:14 +0000541void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height,
542 const ScreenSet& layout)
Pierre Ossmanc5e25602009-03-20 12:59:05 +0000543{
Pierre Ossman04e62db2009-03-23 16:57:07 +0000544 unsigned int result;
545
546 // Don't bother the desktop with an invalid configuration
547 if (!layout.validate(fb_width, fb_height)) {
548 writer()->writeExtendedDesktopSize(reasonClient, resultInvalid,
549 fb_width, fb_height, layout);
550 if (writer()->needFakeUpdate())
551 writeFramebufferUpdate();
552 return;
553 }
554
555 // FIXME: the desktop will call back to VNCServerST and an extra set
556 // of ExtendedDesktopSize messages will be sent. This is okay
557 // protocol-wise, but unnecessary.
558 result = server->desktop->setScreenLayout(fb_width, fb_height, layout);
559
560 // Always send back a reply to the requesting client
561 writer()->writeExtendedDesktopSize(reasonClient, result,
562 fb_width, fb_height, layout);
563 if (writer()->needFakeUpdate())
564 writeFramebufferUpdate();
565
566 // But only notify other clients on success
567 if (result == resultSuccess) {
568 if (server->screenLayout != layout)
569 throw Exception("Desktop configured a different screen layout than requested");
570 server->notifyScreenLayoutChange(this);
571 }
Pierre Ossmanc5e25602009-03-20 12:59:05 +0000572}
573
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000574void VNCSConnectionST::setInitialColourMap()
575{
576 setColourMapEntries(0, 0);
577}
578
579// supportsLocalCursor() is called whenever the status of
580// cp.supportsLocalCursor has changed. If the client does now support local
581// cursor, we make sure that the old server-side rendered cursor is cleaned up
582// and the cursor is sent to the client.
583
584void VNCSConnectionST::supportsLocalCursor()
585{
586 if (cp.supportsLocalCursor || cp.supportsLocalXCursor) {
587 removeRenderedCursor = true;
588 drawRenderedCursor = false;
589 setCursor();
590 }
591}
592
593void VNCSConnectionST::writeSetCursorCallback()
594{
595 if (cp.supportsLocalXCursor) {
596 Pixel pix0, pix1;
597 rdr::U8Array bitmap(server->cursor.getBitmap(&pix0, &pix1));
598 if (bitmap.buf) {
599 // The client supports XCursor and the cursor only has two
600 // colors. Use the XCursor encoding.
601 writer()->writeSetXCursor(server->cursor.width(),
602 server->cursor.height(),
603 server->cursor.hotspot.x,
604 server->cursor.hotspot.y,
605 bitmap.buf, server->cursor.mask.buf);
606 return;
607 } else {
608 // More than two colors
609 if (!cp.supportsLocalCursor) {
610 // FIXME: We could reduce to two colors.
611 vlog.info("Unable to send multicolor cursor: RichCursor not supported by client");
612 return;
613 }
614 }
615 }
616
617 // Use RichCursor
618 rdr::U8* transData = writer()->getImageBuf(server->cursor.area());
619 image_getter.translatePixels(server->cursor.data, transData,
620 server->cursor.area());
621 writer()->writeSetCursor(server->cursor.width(),
622 server->cursor.height(),
623 server->cursor.hotspot,
624 transData, server->cursor.mask.buf);
625}
626
627
628void VNCSConnectionST::writeFramebufferUpdate()
629{
Pierre Ossmane9962f72009-04-23 12:31:42 +0000630 if (state() != RFBSTATE_NORMAL || requested.is_empty())
631 return;
Pierre Ossmand9a59ba2009-03-20 15:55:37 +0000632
Pierre Ossmane9962f72009-04-23 12:31:42 +0000633 // First take care of any updates that cannot contain framebuffer data
634 // changes.
635 if (writer()->needNoDataUpdate()) {
636 writer()->writeNoDataUpdate();
637 requested.clear();
638 return;
639 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000640
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000641 updates.enable_copyrect(cp.useCopyRect);
642
Pierre Ossman02e43d72009-03-05 11:57:11 +0000643 server->checkUpdate();
Constantin Kaplinskya09dc142008-12-18 12:08:15 +0000644
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000645 // Get the lists of updates. Prior to exporting the data to the `ui' object,
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000646 // getUpdateInfo() will normalize the `updates' object such way that its
Pierre Ossman02e43d72009-03-05 11:57:11 +0000647 // `changed' and `copied' regions would not intersect.
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000648
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000649 UpdateInfo ui;
650 updates.getUpdateInfo(&ui, requested);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000651 bool needNewUpdateInfo = false;
652
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000653 // If the previous position of the rendered cursor overlaps the source of the
654 // copy, then when the copy happens the corresponding rectangle in the
655 // destination will be wrong, so add it to the changed region.
656
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000657 if (!ui.copied.is_empty() && !renderedCursorRect.is_empty()) {
658 Rect bogusCopiedCursor = (renderedCursorRect.translate(ui.copy_delta)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000659 .intersect(server->pb->getRect()));
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000660 if (!ui.copied.intersect(bogusCopiedCursor).is_empty()) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000661 updates.add_changed(bogusCopiedCursor);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000662 needNewUpdateInfo = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000663 }
664 }
665
666 // If we need to remove the old rendered cursor, just add the rectangle to
667 // the changed region.
668
669 if (removeRenderedCursor) {
670 updates.add_changed(renderedCursorRect);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000671 needNewUpdateInfo = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000672 renderedCursorRect.clear();
673 removeRenderedCursor = false;
674 }
675
676 // Return if there is nothing to send the client.
677
678 if (updates.is_empty() && !writer()->needFakeUpdate() && !drawRenderedCursor)
679 return;
680
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000681 // The `updates' object could change, make sure we have valid update info.
682
683 if (needNewUpdateInfo)
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000684 updates.getUpdateInfo(&ui, requested);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000685
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000686 // If the client needs a server-side rendered cursor, work out the cursor
687 // rectangle. If it's empty then don't bother drawing it, but if it overlaps
688 // with the update region, we need to draw the rendered cursor regardless of
689 // whether it has changed.
690
691 if (needRenderedCursor()) {
692 renderedCursorRect
693 = (server->renderedCursor.getRect(server->renderedCursorTL)
694 .intersect(requested.get_bounding_rect()));
695
696 if (renderedCursorRect.is_empty()) {
697 drawRenderedCursor = false;
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000698 } else if (!ui.changed.union_(ui.copied)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000699 .intersect(renderedCursorRect).is_empty()) {
700 drawRenderedCursor = true;
701 }
702
703 // We could remove the new cursor rect from updates here. It's not clear
704 // whether this is worth it. If we do remove it, then we won't draw over
705 // the same bit of screen twice, but we have the overhead of a more complex
706 // region.
707
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000708 //if (drawRenderedCursor) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000709 // updates.subtract(renderedCursorRect);
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000710 // updates.getUpdateInfo(&ui, requested);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000711 //}
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000712 }
713
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000714 if (!ui.is_empty() || writer()->needFakeUpdate() || drawRenderedCursor) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000715 // Compute the number of rectangles. Tight encoder makes the things more
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000716 // complicated as compared to the original VNC4.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000717 writer()->setupCurrentEncoder();
Constantin Kaplinsky1a845d02007-08-31 21:06:53 +0000718 int nRects = (ui.copied.numRects() +
Constantin Kaplinsky1a845d02007-08-31 21:06:53 +0000719 (drawRenderedCursor ? 1 : 0));
Constantin Kaplinsky651606d2007-10-17 17:40:23 +0000720
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000721 std::vector<Rect> rects;
722 std::vector<Rect>::const_iterator i;
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000723 ui.changed.get_rects(&rects);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000724 for (i = rects.begin(); i != rects.end(); i++) {
725 if (i->width() && i->height())
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000726 nRects += writer()->getNumRects(*i);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000727 }
728
729 writer()->writeFramebufferUpdateStart(nRects);
730 Region updatedRegion;
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000731 writer()->writeRects(ui, &image_getter, &updatedRegion);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000732 updates.subtract(updatedRegion);
733 if (drawRenderedCursor)
734 writeRenderedCursorRect();
735 writer()->writeFramebufferUpdateEnd();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000736 requested.clear();
737 }
738}
739
740
741// writeRenderedCursorRect() writes a single rectangle drawing the rendered
742// cursor on the client.
743
744void VNCSConnectionST::writeRenderedCursorRect()
745{
746 image_getter.setPixelBuffer(&server->renderedCursor);
747 image_getter.setOffset(server->renderedCursorTL);
748
749 Rect actual;
750 writer()->writeRect(renderedCursorRect, &image_getter, &actual);
751
752 image_getter.setPixelBuffer(server->pb);
753 image_getter.setOffset(Point(0,0));
754
755 drawRenderedCursor = false;
756}
757
758void VNCSConnectionST::setColourMapEntries(int firstColour, int nColours)
759{
760 if (!readyForSetColourMapEntries) return;
761 if (server->pb->getPF().trueColour) return;
762
763 image_getter.setColourMapEntries(firstColour, nColours, writer());
764
765 if (cp.pf().trueColour) {
766 updates.add_changed(server->pb->getRect());
767 }
768}
769
770
771// setCursor() is called whenever the cursor has changed shape or pixel format.
772// If the client supports local cursor then it will arrange for the cursor to
773// be sent to the client.
774
775void VNCSConnectionST::setCursor()
776{
777 if (state() != RFBSTATE_NORMAL || !cp.supportsLocalCursor) return;
778 writer()->cursorChange(this);
779 if (writer()->needFakeUpdate())
780 writeFramebufferUpdate();
781}
782
783void VNCSConnectionST::setSocketTimeouts()
784{
785 int timeoutms = rfb::Server::clientWaitTimeMillis;
786 soonestTimeout(&timeoutms, secsToMillis(rfb::Server::idleTimeout));
787 if (timeoutms == 0)
788 timeoutms = -1;
789 sock->inStream().setTimeout(timeoutms);
790 sock->outStream().setTimeout(timeoutms);
791}
792
793char* VNCSConnectionST::getStartTime()
794{
795 char* result = ctime(&startTime);
796 result[24] = '\0';
797 return result;
798}
799
800void VNCSConnectionST::setStatus(int status)
801{
802 switch (status) {
803 case 0:
804 accessRights = accessRights | AccessPtrEvents | AccessKeyEvents | AccessView;
805 break;
806 case 1:
Peter Åstrand29aba9e2010-02-10 10:05:56 +0000807 accessRights = accessRights & !(AccessPtrEvents | AccessKeyEvents) | AccessView;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000808 break;
809 case 2:
810 accessRights = accessRights & !(AccessPtrEvents | AccessKeyEvents | AccessView);
811 break;
812 }
813 framebufferUpdateRequest(server->pb->getRect(), false);
814}
815int VNCSConnectionST::getStatus()
816{
817 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0007)
818 return 0;
819 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0001)
820 return 1;
821 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0000)
822 return 2;
823 return 4;
824}
825