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
20 // -=- Single-Threaded VNC Server implementation
21
22
23 // Note about how sockets get closed:
24 //
25 // Closing sockets to clients is non-trivial because the code which calls
26 // VNCServerST must explicitly know about all the sockets (so that it can block
27 // on them appropriately). However, VNCServerST may want to close clients for
28 // a number of reasons, and from a variety of entry points. The simplest is
29 // when processSocketEvent() is called for a client, and the remote end has
30 // closed its socket. A more complex reason is when processSocketEvent() is
31 // called for a client which has just sent a ClientInit with the shared flag
32 // set to false - in this case we want to close all other clients. Yet another
33 // reason for disconnecting clients is when the desktop size has changed as a
34 // result of a call to setPixelBuffer().
35 //
36 // The responsibility for creating and deleting sockets is entirely with the
37 // calling code. When VNCServerST wants to close a connection to a client it
38 // calls the VNCSConnectionST's close() method which calls shutdown() on the
39 // socket. Eventually the calling code will notice that the socket has been
40 // shut down and call removeSocket() so that we can delete the
41 // VNCSConnectionST. Note that the socket must not be deleted by the calling
42 // code until after removeSocket() has been called.
43 //
44 // One minor complication is that we don't allocate a VNCSConnectionST object
45 // for a blacklisted host (since we want to minimise the resources used for
46 // dealing with such a connection). In order to properly implement the
47 // getSockets function, we must maintain a separate closingSockets list,
48 // otherwise blacklisted connections might be "forgotten".
49
50
51 #include <assert.h>
52 #include <stdlib.h>
53
54 #include <rfb/ComparingUpdateTracker.h>
55 #include <rfb/KeyRemapper.h>
56 #include <rfb/LogWriter.h>
57 #include <rfb/Security.h>
58 #include <rfb/ServerCore.h>
59 #include <rfb/VNCServerST.h>
60 #include <rfb/VNCSConnectionST.h>
61 #include <rfb/util.h>
62 #include <rfb/ledStates.h>
63
64 #include <rdr/types.h>
65
66 using namespace rfb;
67
68 static LogWriter slog("VNCServerST");
69 static LogWriter connectionsLog("Connections");
70
71 //
72 // -=- VNCServerST Implementation
73 //
74
75 // -=- Constructors/Destructor
76
VNCServerST(const char * name_,SDesktop * desktop_)77 VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_)
78 : blHosts(&blacklist), desktop(desktop_), desktopStarted(false),
79 blockCounter(0), pb(0), ledState(ledUnknown),
80 name(strDup(name_)), pointerClient(0), clipboardClient(0),
81 comparer(0), cursor(new Cursor(0, 0, Point(), NULL)),
82 renderedCursorInvalid(false),
83 keyRemapper(&KeyRemapper::defInstance),
84 idleTimer(this), disconnectTimer(this), connectTimer(this),
85 frameTimer(this)
86 {
87 slog.debug("creating single-threaded server %s", name.buf);
88
89 // FIXME: Do we really want to kick off these right away?
90 if (rfb::Server::maxIdleTime)
91 idleTimer.start(secsToMillis(rfb::Server::maxIdleTime));
92 if (rfb::Server::maxDisconnectionTime)
93 disconnectTimer.start(secsToMillis(rfb::Server::maxDisconnectionTime));
94 }
95
~VNCServerST()96 VNCServerST::~VNCServerST()
97 {
98 slog.debug("shutting down server %s", name.buf);
99
100 // Close any active clients, with appropriate logging & cleanup
101 closeClients("Server shutdown");
102
103 // Stop trying to render things
104 stopFrameClock();
105
106 // Delete all the clients, and their sockets, and any closing sockets
107 while (!clients.empty()) {
108 VNCSConnectionST* client;
109 client = clients.front();
110 clients.pop_front();
111 delete client;
112 }
113
114 // Stop the desktop object if active, *only* after deleting all clients!
115 stopDesktop();
116
117 if (comparer)
118 comparer->logStats();
119 delete comparer;
120
121 delete cursor;
122 }
123
124
125 // SocketServer methods
126
addSocket(network::Socket * sock,bool outgoing)127 void VNCServerST::addSocket(network::Socket* sock, bool outgoing)
128 {
129 // - Check the connection isn't black-marked
130 // *** do this in getSecurity instead?
131 CharArray address(sock->getPeerAddress());
132 if (blHosts->isBlackmarked(address.buf)) {
133 connectionsLog.error("blacklisted: %s", address.buf);
134 try {
135 rdr::OutStream& os = sock->outStream();
136
137 // Shortest possible way to tell a client it is not welcome
138 os.writeBytes("RFB 003.003\n", 12);
139 os.writeU32(0);
140 const char* reason = "Too many security failures";
141 os.writeU32(strlen(reason));
142 os.writeBytes(reason, strlen(reason));
143 os.flush();
144 } catch (rdr::Exception&) {
145 }
146 sock->shutdown();
147 closingSockets.push_back(sock);
148 return;
149 }
150
151 CharArray name;
152 name.buf = sock->getPeerEndpoint();
153 connectionsLog.status("accepted: %s", name.buf);
154
155 // Adjust the exit timers
156 if (rfb::Server::maxConnectionTime && clients.empty())
157 connectTimer.start(secsToMillis(rfb::Server::maxConnectionTime));
158 disconnectTimer.stop();
159
160 VNCSConnectionST* client = new VNCSConnectionST(this, sock, outgoing);
161 clients.push_front(client);
162 client->init();
163 }
164
removeSocket(network::Socket * sock)165 void VNCServerST::removeSocket(network::Socket* sock) {
166 // - If the socket has resources allocated to it, delete them
167 std::list<VNCSConnectionST*>::iterator ci;
168 for (ci = clients.begin(); ci != clients.end(); ci++) {
169 if ((*ci)->getSock() == sock) {
170 // - Remove any references to it
171 if (pointerClient == *ci)
172 pointerClient = NULL;
173 if (clipboardClient == *ci)
174 handleClipboardAnnounce(*ci, false);
175 clipboardRequestors.remove(*ci);
176
177 CharArray name(strDup((*ci)->getPeerEndpoint()));
178
179 // - Delete the per-Socket resources
180 delete *ci;
181
182 clients.remove(*ci);
183
184 connectionsLog.status("closed: %s", name.buf);
185
186 // - Check that the desktop object is still required
187 if (authClientCount() == 0)
188 stopDesktop();
189
190 if (comparer)
191 comparer->logStats();
192
193 // Adjust the exit timers
194 connectTimer.stop();
195 if (rfb::Server::maxDisconnectionTime && clients.empty())
196 disconnectTimer.start(secsToMillis(rfb::Server::maxDisconnectionTime));
197
198 return;
199 }
200 }
201
202 // - If the Socket has no resources, it may have been a closingSocket
203 closingSockets.remove(sock);
204 }
205
processSocketReadEvent(network::Socket * sock)206 void VNCServerST::processSocketReadEvent(network::Socket* sock)
207 {
208 // - Find the appropriate VNCSConnectionST and process the event
209 std::list<VNCSConnectionST*>::iterator ci;
210 for (ci = clients.begin(); ci != clients.end(); ci++) {
211 if ((*ci)->getSock() == sock) {
212 (*ci)->processMessages();
213 return;
214 }
215 }
216 throw rdr::Exception("invalid Socket in VNCServerST");
217 }
218
processSocketWriteEvent(network::Socket * sock)219 void VNCServerST::processSocketWriteEvent(network::Socket* sock)
220 {
221 // - Find the appropriate VNCSConnectionST and process the event
222 std::list<VNCSConnectionST*>::iterator ci;
223 for (ci = clients.begin(); ci != clients.end(); ci++) {
224 if ((*ci)->getSock() == sock) {
225 (*ci)->flushSocket();
226 return;
227 }
228 }
229 throw rdr::Exception("invalid Socket in VNCServerST");
230 }
231
232 // VNCServer methods
233
blockUpdates()234 void VNCServerST::blockUpdates()
235 {
236 blockCounter++;
237
238 stopFrameClock();
239 }
240
unblockUpdates()241 void VNCServerST::unblockUpdates()
242 {
243 assert(blockCounter > 0);
244
245 blockCounter--;
246
247 // Restart the frame clock if we have updates
248 if (blockCounter == 0) {
249 if (!comparer->is_empty())
250 startFrameClock();
251 }
252 }
253
setPixelBuffer(PixelBuffer * pb_,const ScreenSet & layout)254 void VNCServerST::setPixelBuffer(PixelBuffer* pb_, const ScreenSet& layout)
255 {
256 if (comparer)
257 comparer->logStats();
258
259 pb = pb_;
260 delete comparer;
261 comparer = 0;
262
263 if (!pb) {
264 screenLayout = ScreenSet();
265
266 if (desktopStarted)
267 throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
268
269 return;
270 }
271
272 if (!layout.validate(pb->width(), pb->height()))
273 throw Exception("setPixelBuffer: invalid screen layout");
274
275 screenLayout = layout;
276
277 // Assume the framebuffer contents wasn't saved and reset everything
278 // that tracks its contents
279 comparer = new ComparingUpdateTracker(pb);
280 renderedCursorInvalid = true;
281 add_changed(pb->getRect());
282
283 std::list<VNCSConnectionST*>::iterator ci, ci_next;
284 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
285 ci_next = ci; ci_next++;
286 (*ci)->pixelBufferChange();
287 // Since the new pixel buffer means an ExtendedDesktopSize needs to
288 // be sent anyway, we don't need to call screenLayoutChange.
289 }
290 }
291
setPixelBuffer(PixelBuffer * pb_)292 void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
293 {
294 ScreenSet layout = screenLayout;
295
296 // Check that the screen layout is still valid
297 if (pb_ && !layout.validate(pb_->width(), pb_->height())) {
298 Rect fbRect;
299 ScreenSet::iterator iter, iter_next;
300
301 fbRect.setXYWH(0, 0, pb_->width(), pb_->height());
302
303 for (iter = layout.begin();iter != layout.end();iter = iter_next) {
304 iter_next = iter; ++iter_next;
305 if (iter->dimensions.enclosed_by(fbRect))
306 continue;
307 iter->dimensions = iter->dimensions.intersect(fbRect);
308 if (iter->dimensions.is_empty()) {
309 slog.info("Removing screen %d (%x) as it is completely outside the new framebuffer",
310 (int)iter->id, (unsigned)iter->id);
311 layout.remove_screen(iter->id);
312 }
313 }
314 }
315
316 // Make sure that we have at least one screen
317 if (layout.num_screens() == 0)
318 layout.add_screen(Screen(0, 0, 0, pb_->width(), pb_->height(), 0));
319
320 setPixelBuffer(pb_, layout);
321 }
322
setScreenLayout(const ScreenSet & layout)323 void VNCServerST::setScreenLayout(const ScreenSet& layout)
324 {
325 if (!pb)
326 throw Exception("setScreenLayout: new screen layout without a PixelBuffer");
327 if (!layout.validate(pb->width(), pb->height()))
328 throw Exception("setScreenLayout: invalid screen layout");
329
330 screenLayout = layout;
331
332 std::list<VNCSConnectionST*>::iterator ci, ci_next;
333 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
334 ci_next = ci; ci_next++;
335 (*ci)->screenLayoutChangeOrClose(reasonServer);
336 }
337 }
338
requestClipboard()339 void VNCServerST::requestClipboard()
340 {
341 if (clipboardClient == NULL) {
342 slog.debug("Got request for client clipboard but no client currently owns the clipboard");
343 return;
344 }
345
346 clipboardClient->requestClipboardOrClose();
347 }
348
announceClipboard(bool available)349 void VNCServerST::announceClipboard(bool available)
350 {
351 std::list<VNCSConnectionST*>::iterator ci, ci_next;
352
353 clipboardRequestors.clear();
354
355 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
356 ci_next = ci; ci_next++;
357 (*ci)->announceClipboardOrClose(available);
358 }
359 }
360
sendClipboardData(const char * data)361 void VNCServerST::sendClipboardData(const char* data)
362 {
363 std::list<VNCSConnectionST*>::iterator ci, ci_next;
364
365 if (strchr(data, '\r') != NULL)
366 throw Exception("Invalid carriage return in clipboard data");
367
368 for (ci = clipboardRequestors.begin();
369 ci != clipboardRequestors.end(); ci = ci_next) {
370 ci_next = ci; ci_next++;
371 (*ci)->sendClipboardDataOrClose(data);
372 }
373
374 clipboardRequestors.clear();
375 }
376
bell()377 void VNCServerST::bell()
378 {
379 std::list<VNCSConnectionST*>::iterator ci, ci_next;
380 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
381 ci_next = ci; ci_next++;
382 (*ci)->bellOrClose();
383 }
384 }
385
setName(const char * name_)386 void VNCServerST::setName(const char* name_)
387 {
388 name.replaceBuf(strDup(name_));
389 std::list<VNCSConnectionST*>::iterator ci, ci_next;
390 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
391 ci_next = ci; ci_next++;
392 (*ci)->setDesktopNameOrClose(name_);
393 }
394 }
395
add_changed(const Region & region)396 void VNCServerST::add_changed(const Region& region)
397 {
398 if (comparer == NULL)
399 return;
400
401 comparer->add_changed(region);
402 startFrameClock();
403 }
404
add_copied(const Region & dest,const Point & delta)405 void VNCServerST::add_copied(const Region& dest, const Point& delta)
406 {
407 if (comparer == NULL)
408 return;
409
410 comparer->add_copied(dest, delta);
411 startFrameClock();
412 }
413
setCursor(int width,int height,const Point & newHotspot,const rdr::U8 * data)414 void VNCServerST::setCursor(int width, int height, const Point& newHotspot,
415 const rdr::U8* data)
416 {
417 delete cursor;
418 cursor = new Cursor(width, height, newHotspot, data);
419 cursor->crop();
420
421 renderedCursorInvalid = true;
422
423 std::list<VNCSConnectionST*>::iterator ci, ci_next;
424 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
425 ci_next = ci; ci_next++;
426 (*ci)->renderedCursorChange();
427 (*ci)->setCursorOrClose();
428 }
429 }
430
setCursorPos(const Point & pos,bool warped)431 void VNCServerST::setCursorPos(const Point& pos, bool warped)
432 {
433 if (!cursorPos.equals(pos)) {
434 cursorPos = pos;
435 renderedCursorInvalid = true;
436 std::list<VNCSConnectionST*>::iterator ci;
437 for (ci = clients.begin(); ci != clients.end(); ci++) {
438 (*ci)->renderedCursorChange();
439 if (warped)
440 (*ci)->cursorPositionChange();
441 }
442 }
443 }
444
setLEDState(unsigned int state)445 void VNCServerST::setLEDState(unsigned int state)
446 {
447 std::list<VNCSConnectionST*>::iterator ci, ci_next;
448
449 if (state == ledState)
450 return;
451
452 ledState = state;
453
454 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
455 ci_next = ci; ci_next++;
456 (*ci)->setLEDStateOrClose(state);
457 }
458 }
459
460 // Event handlers
461
keyEvent(rdr::U32 keysym,rdr::U32 keycode,bool down)462 void VNCServerST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
463 {
464 if (rfb::Server::maxIdleTime)
465 idleTimer.start(secsToMillis(rfb::Server::maxIdleTime));
466
467 // Remap the key if required
468 if (keyRemapper) {
469 rdr::U32 newkey;
470 newkey = keyRemapper->remapKey(keysym);
471 if (newkey != keysym) {
472 slog.debug("Key remapped to 0x%x", newkey);
473 keysym = newkey;
474 }
475 }
476
477 desktop->keyEvent(keysym, keycode, down);
478 }
479
pointerEvent(VNCSConnectionST * client,const Point & pos,int buttonMask)480 void VNCServerST::pointerEvent(VNCSConnectionST* client,
481 const Point& pos, int buttonMask)
482 {
483 if (rfb::Server::maxIdleTime)
484 idleTimer.start(secsToMillis(rfb::Server::maxIdleTime));
485
486 // Let one client own the cursor whilst buttons are pressed in order
487 // to provide a bit more sane user experience
488 if ((pointerClient != NULL) && (pointerClient != client))
489 return;
490
491 if (buttonMask)
492 pointerClient = client;
493 else
494 pointerClient = NULL;
495
496 desktop->pointerEvent(pos, buttonMask);
497 }
498
handleClipboardRequest(VNCSConnectionST * client)499 void VNCServerST::handleClipboardRequest(VNCSConnectionST* client)
500 {
501 clipboardRequestors.push_back(client);
502 if (clipboardRequestors.size() == 1)
503 desktop->handleClipboardRequest();
504 }
505
handleClipboardAnnounce(VNCSConnectionST * client,bool available)506 void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client,
507 bool available)
508 {
509 if (available)
510 clipboardClient = client;
511 else {
512 if (client != clipboardClient)
513 return;
514 clipboardClient = NULL;
515 }
516 desktop->handleClipboardAnnounce(available);
517 }
518
handleClipboardData(VNCSConnectionST * client,const char * data)519 void VNCServerST::handleClipboardData(VNCSConnectionST* client,
520 const char* data)
521 {
522 if (client != clipboardClient) {
523 slog.debug("Ignoring unexpected clipboard data");
524 return;
525 }
526 desktop->handleClipboardData(data);
527 }
528
setDesktopSize(VNCSConnectionST * requester,int fb_width,int fb_height,const ScreenSet & layout)529 unsigned int VNCServerST::setDesktopSize(VNCSConnectionST* requester,
530 int fb_width, int fb_height,
531 const ScreenSet& layout)
532 {
533 unsigned int result;
534 std::list<VNCSConnectionST*>::iterator ci, ci_next;
535
536 // We can't handle a framebuffer larger than this, so don't let a
537 // client set one (see PixelBuffer.cxx)
538 if ((fb_width > 16384) || (fb_height > 16384))
539 return resultProhibited;
540
541 // Don't bother the desktop with an invalid configuration
542 if (!layout.validate(fb_width, fb_height))
543 return resultInvalid;
544
545 // FIXME: the desktop will call back to VNCServerST and an extra set
546 // of ExtendedDesktopSize messages will be sent. This is okay
547 // protocol-wise, but unnecessary.
548 result = desktop->setScreenLayout(fb_width, fb_height, layout);
549 if (result != resultSuccess)
550 return result;
551
552 // Sanity check
553 if (screenLayout != layout)
554 throw Exception("Desktop configured a different screen layout than requested");
555
556 // Notify other clients
557 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
558 ci_next = ci; ci_next++;
559 if ((*ci) == requester)
560 continue;
561 (*ci)->screenLayoutChangeOrClose(reasonOtherClient);
562 }
563
564 return resultSuccess;
565 }
566
567 // Other public methods
568
approveConnection(network::Socket * sock,bool accept,const char * reason)569 void VNCServerST::approveConnection(network::Socket* sock, bool accept,
570 const char* reason)
571 {
572 std::list<VNCSConnectionST*>::iterator ci;
573 for (ci = clients.begin(); ci != clients.end(); ci++) {
574 if ((*ci)->getSock() == sock) {
575 (*ci)->approveConnectionOrClose(accept, reason);
576 return;
577 }
578 }
579 }
580
closeClients(const char * reason,network::Socket * except)581 void VNCServerST::closeClients(const char* reason, network::Socket* except)
582 {
583 std::list<VNCSConnectionST*>::iterator i, next_i;
584 for (i=clients.begin(); i!=clients.end(); i=next_i) {
585 next_i = i; next_i++;
586 if ((*i)->getSock() != except)
587 (*i)->close(reason);
588 }
589 }
590
getSockets(std::list<network::Socket * > * sockets)591 void VNCServerST::getSockets(std::list<network::Socket*>* sockets)
592 {
593 sockets->clear();
594 std::list<VNCSConnectionST*>::iterator ci;
595 for (ci = clients.begin(); ci != clients.end(); ci++) {
596 sockets->push_back((*ci)->getSock());
597 }
598 std::list<network::Socket*>::iterator si;
599 for (si = closingSockets.begin(); si != closingSockets.end(); si++) {
600 sockets->push_back(*si);
601 }
602 }
603
getConnection(network::Socket * sock)604 SConnection* VNCServerST::getConnection(network::Socket* sock) {
605 std::list<VNCSConnectionST*>::iterator ci;
606 for (ci = clients.begin(); ci != clients.end(); ci++) {
607 if ((*ci)->getSock() == sock)
608 return (SConnection*)*ci;
609 }
610 return 0;
611 }
612
handleTimeout(Timer * t)613 bool VNCServerST::handleTimeout(Timer* t)
614 {
615 if (t == &frameTimer) {
616 // We keep running until we go a full interval without any updates
617 if (comparer->is_empty())
618 return false;
619
620 writeUpdate();
621
622 // If this is the first iteration then we need to adjust the timeout
623 if (frameTimer.getTimeoutMs() != 1000/rfb::Server::frameRate) {
624 frameTimer.start(1000/rfb::Server::frameRate);
625 return false;
626 }
627
628 return true;
629 } else if (t == &idleTimer) {
630 slog.info("MaxIdleTime reached, exiting");
631 desktop->terminate();
632 } else if (t == &disconnectTimer) {
633 slog.info("MaxDisconnectionTime reached, exiting");
634 desktop->terminate();
635 } else if (t == &connectTimer) {
636 slog.info("MaxConnectionTime reached, exiting");
637 desktop->terminate();
638 }
639
640 return false;
641 }
642
queryConnection(VNCSConnectionST * client,const char * userName)643 void VNCServerST::queryConnection(VNCSConnectionST* client,
644 const char* userName)
645 {
646 // - Authentication succeeded - clear from blacklist
647 CharArray name;
648 name.buf = client->getSock()->getPeerAddress();
649 blHosts->clearBlackmark(name.buf);
650
651 // - Prepare the desktop for that the client will start requiring
652 // resources after this
653 startDesktop();
654
655 // - Special case to provide a more useful error message
656 if (rfb::Server::neverShared &&
657 !rfb::Server::disconnectClients &&
658 authClientCount() > 0) {
659 approveConnection(client->getSock(), false,
660 "The server is already in use");
661 return;
662 }
663
664 // - Are we configured to do queries?
665 if (!rfb::Server::queryConnect &&
666 !client->getSock()->requiresQuery()) {
667 approveConnection(client->getSock(), true, NULL);
668 return;
669 }
670
671 // - Does the client have the right to bypass the query?
672 if (client->accessCheck(SConnection::AccessNoQuery))
673 {
674 approveConnection(client->getSock(), true, NULL);
675 return;
676 }
677
678 desktop->queryConnection(client->getSock(), userName);
679 }
680
clientReady(VNCSConnectionST * client,bool shared)681 void VNCServerST::clientReady(VNCSConnectionST* client, bool shared)
682 {
683 if (!shared) {
684 if (rfb::Server::disconnectClients &&
685 client->accessCheck(SConnection::AccessNonShared)) {
686 // - Close all the other connected clients
687 slog.debug("non-shared connection - closing clients");
688 closeClients("Non-shared connection requested", client->getSock());
689 } else {
690 // - Refuse this connection if there are existing clients, in addition to
691 // this one
692 if (authClientCount() > 1) {
693 client->close("Server is already in use");
694 return;
695 }
696 }
697 }
698 }
699
700 // -=- Internal methods
701
startDesktop()702 void VNCServerST::startDesktop()
703 {
704 if (!desktopStarted) {
705 slog.debug("starting desktop");
706 desktop->start(this);
707 if (!pb)
708 throw Exception("SDesktop::start() did not set a valid PixelBuffer");
709 desktopStarted = true;
710 // The tracker might have accumulated changes whilst we were
711 // stopped, so flush those out
712 if (!comparer->is_empty())
713 writeUpdate();
714 }
715 }
716
stopDesktop()717 void VNCServerST::stopDesktop()
718 {
719 if (desktopStarted) {
720 slog.debug("stopping desktop");
721 desktopStarted = false;
722 desktop->stop();
723 stopFrameClock();
724 }
725 }
726
authClientCount()727 int VNCServerST::authClientCount() {
728 int count = 0;
729 std::list<VNCSConnectionST*>::iterator ci;
730 for (ci = clients.begin(); ci != clients.end(); ci++) {
731 if ((*ci)->authenticated())
732 count++;
733 }
734 return count;
735 }
736
needRenderedCursor()737 inline bool VNCServerST::needRenderedCursor()
738 {
739 std::list<VNCSConnectionST*>::iterator ci;
740 for (ci = clients.begin(); ci != clients.end(); ci++)
741 if ((*ci)->needRenderedCursor()) return true;
742 return false;
743 }
744
startFrameClock()745 void VNCServerST::startFrameClock()
746 {
747 if (frameTimer.isStarted())
748 return;
749 if (blockCounter > 0)
750 return;
751 if (!desktopStarted)
752 return;
753
754 // The first iteration will be just half a frame as we get a very
755 // unstable update rate if we happen to be perfectly in sync with
756 // the application's update rate
757 frameTimer.start(1000/rfb::Server::frameRate/2);
758 }
759
stopFrameClock()760 void VNCServerST::stopFrameClock()
761 {
762 frameTimer.stop();
763 }
764
msToNextUpdate()765 int VNCServerST::msToNextUpdate()
766 {
767 // FIXME: If the application is updating slower than frameRate then
768 // we could allow the clients more time here
769
770 if (!frameTimer.isStarted())
771 return 1000/rfb::Server::frameRate/2;
772 else
773 return frameTimer.getRemainingMs();
774 }
775
776 // writeUpdate() is called on a regular interval in order to see what
777 // updates are pending and propagates them to the update tracker for
778 // each client. It uses the ComparingUpdateTracker's compare() method
779 // to filter out areas of the screen which haven't actually changed. It
780 // also checks the state of the (server-side) rendered cursor, if
781 // necessary rendering it again with the correct background.
782
writeUpdate()783 void VNCServerST::writeUpdate()
784 {
785 UpdateInfo ui;
786 Region toCheck;
787
788 std::list<VNCSConnectionST*>::iterator ci, ci_next;
789
790 assert(blockCounter == 0);
791 assert(desktopStarted);
792
793 comparer->getUpdateInfo(&ui, pb->getRect());
794 toCheck = ui.changed.union_(ui.copied);
795
796 if (needRenderedCursor()) {
797 Rect clippedCursorRect = Rect(0, 0, cursor->width(), cursor->height())
798 .translate(cursorPos.subtract(cursor->hotspot()))
799 .intersect(pb->getRect());
800
801 if (!toCheck.intersect(clippedCursorRect).is_empty())
802 renderedCursorInvalid = true;
803 }
804
805 pb->grabRegion(toCheck);
806
807 if (getComparerState())
808 comparer->enable();
809 else
810 comparer->disable();
811
812 if (comparer->compare())
813 comparer->getUpdateInfo(&ui, pb->getRect());
814
815 comparer->clear();
816
817 for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
818 ci_next = ci; ci_next++;
819 (*ci)->add_copied(ui.copied, ui.copy_delta);
820 (*ci)->add_changed(ui.changed);
821 (*ci)->writeFramebufferUpdateOrClose();
822 }
823 }
824
825 // checkUpdate() is called by clients to see if it is safe to read from
826 // the framebuffer at this time.
827
getPendingRegion()828 Region VNCServerST::getPendingRegion()
829 {
830 UpdateInfo ui;
831
832 // Block clients as the frame buffer cannot be safely accessed
833 if (blockCounter > 0)
834 return pb->getRect();
835
836 // Block client from updating if there are pending updates
837 if (comparer->is_empty())
838 return Region();
839
840 comparer->getUpdateInfo(&ui, pb->getRect());
841
842 return ui.changed.union_(ui.copied);
843 }
844
getRenderedCursor()845 const RenderedCursor* VNCServerST::getRenderedCursor()
846 {
847 if (renderedCursorInvalid) {
848 renderedCursor.update(pb, cursor, cursorPos);
849 renderedCursorInvalid = false;
850 }
851
852 return &renderedCursor;
853 }
854
getComparerState()855 bool VNCServerST::getComparerState()
856 {
857 if (rfb::Server::compareFB == 0)
858 return false;
859 if (rfb::Server::compareFB != 2)
860 return true;
861
862 std::list<VNCSConnectionST*>::iterator ci, ci_next;
863 for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
864 ci_next = ci; ci_next++;
865 if ((*ci)->getComparerState())
866 return true;
867 }
868 return false;
869 }
870