blob: d73d753e2cbd72a53007d650c5da40534f78dbc6 [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossmana3ac01e2011-11-07 21:13:54 +00002 * Copyright 2009-2011 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)
Pierre Ossmana3ac01e2011-11-07 21:13:54 +000037 : SConnection(reverse), sock(s), inProcessMessages(false), server(server_),
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000038 updates(false), image_getter(server->useEconomicTranslate),
39 drawRenderedCursor(false), removeRenderedCursor(false),
Pierre Ossman1bb8b6c2011-10-25 15:20:05 +000040 updateTimer(this), pointerEventTime(0),
41 accessRights(AccessDefault), 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();
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000114
115 inProcessMessages = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000116
117 while (getInStream()->checkNoWait(1)) {
118 processMsg();
119 }
120
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000121 inProcessMessages = false;
Constantin Kaplinsky2ef66952008-08-29 11:33:46 +0000122
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000123 // If there were anything requiring an update, try to send it here.
124 // We wait until now with this to aggregate responses and to give
125 // higher priority to user actions such as keyboard and pointer events.
126 writeFramebufferUpdate();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000127 } catch (rdr::EndOfStream&) {
128 close("Clean disconnection");
129 } catch (rdr::Exception &e) {
130 close(e.str());
131 }
132}
133
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000134void VNCSConnectionST::pixelBufferChange()
135{
136 try {
137 if (!authenticated()) return;
138 if (cp.width && cp.height && (server->pb->width() != cp.width ||
139 server->pb->height() != cp.height))
140 {
141 // We need to clip the next update to the new size, but also add any
142 // extra bits if it's bigger. If we wanted to do this exactly, something
143 // like the code below would do it, but at the moment we just update the
144 // entire new size. However, we do need to clip the renderedCursorRect
145 // because that might be added to updates in writeFramebufferUpdate().
146
147 //updates.intersect(server->pb->getRect());
148 //
149 //if (server->pb->width() > cp.width)
150 // updates.add_changed(Rect(cp.width, 0, server->pb->width(),
151 // server->pb->height()));
152 //if (server->pb->height() > cp.height)
153 // updates.add_changed(Rect(0, cp.height, cp.width,
154 // server->pb->height()));
155
156 renderedCursorRect = renderedCursorRect.intersect(server->pb->getRect());
157
158 cp.width = server->pb->width();
159 cp.height = server->pb->height();
Pierre Ossman34e62f32009-03-20 21:46:12 +0000160 cp.screenLayout = server->screenLayout;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000161 if (state() == RFBSTATE_NORMAL) {
Pierre Ossman2ee430a2009-05-28 12:54:24 +0000162 // We should only send EDS to client asking for both
163 if (!writer()->writeExtendedDesktopSize()) {
164 if (!writer()->writeSetDesktopSize()) {
165 close("Client does not support desktop resize");
166 return;
167 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000168 }
169 }
170 }
171 // Just update the whole screen at the moment because we're too lazy to
172 // work out what's actually changed.
173 updates.clear();
174 updates.add_changed(server->pb->getRect());
175 vlog.debug("pixel buffer changed - re-initialising image getter");
176 image_getter.init(server->pb, cp.pf(), writer());
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000177 writeFramebufferUpdate();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000178 } catch(rdr::Exception &e) {
179 close(e.str());
180 }
181}
182
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000183void VNCSConnectionST::writeFramebufferUpdateOrClose()
Pierre Ossman04e62db2009-03-23 16:57:07 +0000184{
185 try {
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000186 writeFramebufferUpdate();
187 } catch(rdr::Exception &e) {
188 close(e.str());
189 }
190}
Pierre Ossman04e62db2009-03-23 16:57:07 +0000191
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000192void VNCSConnectionST::screenLayoutChangeOrClose(rdr::U16 reason)
193{
194 try {
195 screenLayoutChange(reason);
Pierre Ossman04e62db2009-03-23 16:57:07 +0000196 } catch(rdr::Exception &e) {
197 close(e.str());
198 }
199}
200
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000201void VNCSConnectionST::setColourMapEntriesOrClose(int firstColour,int nColours)
202{
203 try {
204 setColourMapEntries(firstColour, nColours);
205 } catch(rdr::Exception& e) {
206 close(e.str());
207 }
208}
209
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000210void VNCSConnectionST::bellOrClose()
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000211{
212 try {
213 if (state() == RFBSTATE_NORMAL) writer()->writeBell();
214 } catch(rdr::Exception& e) {
215 close(e.str());
216 }
217}
218
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000219void VNCSConnectionST::serverCutTextOrClose(const char *str, int len)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000220{
221 try {
222 if (!(accessRights & AccessCutText)) return;
223 if (!rfb::Server::sendCutText) return;
224 if (state() == RFBSTATE_NORMAL)
225 writer()->writeServerCutText(str, len);
226 } catch(rdr::Exception& e) {
227 close(e.str());
228 }
229}
230
Peter Ã…strandc39e0782009-01-15 12:21:42 +0000231
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000232void VNCSConnectionST::setDesktopNameOrClose(const char *name)
Peter Ã…strandc39e0782009-01-15 12:21:42 +0000233{
Peter Ã…strandc39e0782009-01-15 12:21:42 +0000234 try {
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000235 setDesktopName(name);
Peter Ã…strandc39e0782009-01-15 12:21:42 +0000236 } catch(rdr::Exception& e) {
237 close(e.str());
238 }
239}
240
241
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000242void VNCSConnectionST::setCursorOrClose()
243{
244 try {
245 setCursor();
246 } catch(rdr::Exception& e) {
247 close(e.str());
248 }
249}
250
251
252int VNCSConnectionST::checkIdleTimeout()
253{
254 int idleTimeout = rfb::Server::idleTimeout;
255 if (idleTimeout == 0) return 0;
256 if (state() != RFBSTATE_NORMAL && idleTimeout < 15)
257 idleTimeout = 15; // minimum of 15 seconds while authenticating
258 time_t now = time(0);
259 if (now < lastEventTime) {
260 // Someone must have set the time backwards. Set lastEventTime so that the
261 // idleTimeout will count from now.
262 vlog.info("Time has gone backwards - resetting idle timeout");
263 lastEventTime = now;
264 }
265 int timeLeft = lastEventTime + idleTimeout - now;
266 if (timeLeft < -60) {
267 // Our callback is over a minute late - someone must have set the time
268 // forwards. Set lastEventTime so that the idleTimeout will count from
269 // now.
270 vlog.info("Time has gone forwards - resetting idle timeout");
271 lastEventTime = now;
272 return secsToMillis(idleTimeout);
273 }
274 if (timeLeft <= 0) {
275 close("Idle timeout");
276 return 0;
277 }
278 return secsToMillis(timeLeft);
279}
280
281// renderedCursorChange() is called whenever the server-side rendered cursor
282// changes shape or position. It ensures that the next update will clean up
283// the old rendered cursor and if necessary draw the new rendered cursor.
284
285void VNCSConnectionST::renderedCursorChange()
286{
287 if (state() != RFBSTATE_NORMAL) return;
Pierre Ossman5c9e1e52011-11-08 10:32:05 +0000288 if (!renderedCursorRect.is_empty())
289 removeRenderedCursor = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000290 if (needRenderedCursor())
291 drawRenderedCursor = true;
292}
293
294// needRenderedCursor() returns true if this client needs the server-side
295// rendered cursor. This may be because it does not support local cursor or
296// because the current cursor position has not been set by this client.
297// Unfortunately we can't know for sure when the current cursor position has
298// been set by this client. We guess that this is the case when the current
299// cursor position is the same as the last pointer event from this client, or
300// if it is a very short time since this client's last pointer event (up to a
301// second). [ Ideally we should do finer-grained timing here and make the time
302// configurable, but I don't think it's that important. ]
303
304bool VNCSConnectionST::needRenderedCursor()
305{
Peter Ã…strandffeeb262010-02-10 09:29:00 +0000306 bool pointerpos = (!server->cursorPos.equals(pointerEventPos) && (time(0) - pointerEventTime) > 0);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000307 return (state() == RFBSTATE_NORMAL
Peter Ã…strandffeeb262010-02-10 09:29:00 +0000308 && ((!cp.supportsLocalCursor && !cp.supportsLocalXCursor) || pointerpos));
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000309}
310
311
312void VNCSConnectionST::approveConnectionOrClose(bool accept,
313 const char* reason)
314{
315 try {
316 approveConnection(accept, reason);
317 } catch (rdr::Exception& e) {
318 close(e.str());
319 }
320}
321
322
323
324// -=- Callbacks from SConnection
325
326void VNCSConnectionST::authSuccess()
327{
328 lastEventTime = time(0);
329
330 server->startDesktop();
331
332 // - Set the connection parameters appropriately
333 cp.width = server->pb->width();
334 cp.height = server->pb->height();
Pierre Ossman34e62f32009-03-20 21:46:12 +0000335 cp.screenLayout = server->screenLayout;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000336 cp.setName(server->getName());
337
338 // - Set the default pixel format
339 cp.setPF(server->pb->getPF());
340 char buffer[256];
341 cp.pf().print(buffer, 256);
342 vlog.info("Server default pixel format %s", buffer);
343 image_getter.init(server->pb, cp.pf(), 0);
344
345 // - Mark the entire display as "dirty"
346 updates.add_changed(server->pb->getRect());
347 startTime = time(0);
348}
349
350void VNCSConnectionST::queryConnection(const char* userName)
351{
352 // - Authentication succeeded - clear from blacklist
353 CharArray name; name.buf = sock->getPeerAddress();
354 server->blHosts->clearBlackmark(name.buf);
355
356 // - Special case to provide a more useful error message
357 if (rfb::Server::neverShared && !rfb::Server::disconnectClients &&
358 server->authClientCount() > 0) {
359 approveConnection(false, "The server is already in use");
360 return;
361 }
362
363 // - Does the client have the right to bypass the query?
364 if (reverseConnection ||
365 !(rfb::Server::queryConnect || sock->requiresQuery()) ||
366 (accessRights & AccessNoQuery))
367 {
368 approveConnection(true);
369 return;
370 }
371
372 // - Get the server to display an Accept/Reject dialog, if required
373 // If a dialog is displayed, the result will be PENDING, and the
374 // server will call approveConnection at a later time
375 CharArray reason;
376 VNCServerST::queryResult qr = server->queryConnection(sock, userName,
377 &reason.buf);
378 if (qr == VNCServerST::PENDING)
379 return;
380
381 // - If server returns ACCEPT/REJECT then pass result to SConnection
382 approveConnection(qr == VNCServerST::ACCEPT, reason.buf);
383}
384
385void VNCSConnectionST::clientInit(bool shared)
386{
387 lastEventTime = time(0);
388 if (rfb::Server::alwaysShared || reverseConnection) shared = true;
389 if (rfb::Server::neverShared) shared = false;
390 if (!shared) {
391 if (rfb::Server::disconnectClients) {
392 // - Close all the other connected clients
393 vlog.debug("non-shared connection - closing clients");
394 server->closeClients("Non-shared connection requested", getSock());
395 } else {
396 // - Refuse this connection if there are existing clients, in addition to
397 // this one
398 if (server->authClientCount() > 1) {
399 close("Server is already in use");
400 return;
401 }
402 }
403 }
404 SConnection::clientInit(shared);
405}
406
407void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
408{
409 SConnection::setPixelFormat(pf);
410 char buffer[256];
411 pf.print(buffer, 256);
412 vlog.info("Client pixel format %s", buffer);
413 image_getter.init(server->pb, pf, writer());
414 setCursor();
415}
416
417void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
418{
419 pointerEventTime = lastEventTime = time(0);
420 server->lastUserInputTime = lastEventTime;
421 if (!(accessRights & AccessPtrEvents)) return;
422 if (!rfb::Server::acceptPointerEvents) return;
423 if (!server->pointerClient || server->pointerClient == this) {
424 pointerEventPos = pos;
425 if (buttonMask)
426 server->pointerClient = this;
427 else
428 server->pointerClient = 0;
429 server->desktop->pointerEvent(pointerEventPos, buttonMask);
430 }
431}
432
433
434class VNCSConnectionSTShiftPresser {
435public:
436 VNCSConnectionSTShiftPresser(SDesktop* desktop_)
437 : desktop(desktop_), pressed(false) {}
438 ~VNCSConnectionSTShiftPresser() {
439 if (pressed) { desktop->keyEvent(XK_Shift_L, false); }
440 }
441 void press() {
442 desktop->keyEvent(XK_Shift_L, true);
443 pressed = true;
444 }
445 SDesktop* desktop;
446 bool pressed;
447};
448
449// keyEvent() - record in the pressedKeys which keys were pressed. Allow
450// multiple down events (for autorepeat), but only allow a single up event.
451void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) {
452 lastEventTime = time(0);
453 server->lastUserInputTime = lastEventTime;
454 if (!(accessRights & AccessKeyEvents)) return;
455 if (!rfb::Server::acceptKeyEvents) return;
456
457 // Remap the key if required
458 if (server->keyRemapper)
459 key = server->keyRemapper->remapKey(key);
460
461 // Turn ISO_Left_Tab into shifted Tab.
462 VNCSConnectionSTShiftPresser shiftPresser(server->desktop);
463 if (key == XK_ISO_Left_Tab) {
464 if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() &&
465 pressedKeys.find(XK_Shift_R) == pressedKeys.end())
466 shiftPresser.press();
467 key = XK_Tab;
468 }
469
470 if (down) {
471 pressedKeys.insert(key);
472 } else {
473 if (!pressedKeys.erase(key)) return;
474 }
475 server->desktop->keyEvent(key, down);
476}
477
478void VNCSConnectionST::clientCutText(const char* str, int len)
479{
480 if (!(accessRights & AccessCutText)) return;
481 if (!rfb::Server::acceptCutText) return;
482 server->desktop->clientCutText(str, len);
483}
484
485void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
486{
Pierre Ossmane9962f72009-04-23 12:31:42 +0000487 Rect safeRect;
488
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000489 if (!(accessRights & AccessView)) return;
490
491 SConnection::framebufferUpdateRequest(r, incremental);
492
Pierre Ossmand9a59ba2009-03-20 15:55:37 +0000493 // Check that the client isn't sending crappy requests
494 if (!r.enclosed_by(Rect(0, 0, cp.width, cp.height))) {
495 vlog.error("FramebufferUpdateRequest %dx%d at %d,%d exceeds framebuffer %dx%d",
496 r.width(), r.height(), r.tl.x, r.tl.y, cp.width, cp.height);
Pierre Ossmane9962f72009-04-23 12:31:42 +0000497 safeRect = r.intersect(Rect(0, 0, cp.width, cp.height));
498 } else {
499 safeRect = r;
Pierre Ossmand9a59ba2009-03-20 15:55:37 +0000500 }
501
Constantin Kaplinsky2ef66952008-08-29 11:33:46 +0000502 // Just update the requested region.
503 // Framebuffer update will be sent a bit later, see processMessages().
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000504 Region reqRgn(r);
505 requested.assign_union(reqRgn);
506
507 if (!incremental) {
508 // Non-incremental update - treat as if area requested has changed
509 updates.add_changed(reqRgn);
510 server->comparer->add_changed(reqRgn);
Pierre Ossman53125a72009-04-22 15:37:51 +0000511
512 // And send the screen layout to the client (which, unlike the
513 // framebuffer dimensions, the client doesn't get during init)
Pierre Ossmanc5e25602009-03-20 12:59:05 +0000514 writer()->writeExtendedDesktopSize();
Pierre Ossman53125a72009-04-22 15:37:51 +0000515
516 // We do not send a DesktopSize since it only contains the
517 // framebuffer size (which the client already should know) and
518 // because some clients don't handle extra DesktopSize events
519 // very well.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000520 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000521}
522
Pierre Ossman34bb0612009-03-21 21:16:14 +0000523void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height,
524 const ScreenSet& layout)
Pierre Ossmanc5e25602009-03-20 12:59:05 +0000525{
Pierre Ossman04e62db2009-03-23 16:57:07 +0000526 unsigned int result;
527
528 // Don't bother the desktop with an invalid configuration
529 if (!layout.validate(fb_width, fb_height)) {
530 writer()->writeExtendedDesktopSize(reasonClient, resultInvalid,
531 fb_width, fb_height, layout);
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000532 writeFramebufferUpdate();
Pierre Ossman04e62db2009-03-23 16:57:07 +0000533 return;
534 }
535
536 // FIXME: the desktop will call back to VNCServerST and an extra set
537 // of ExtendedDesktopSize messages will be sent. This is okay
538 // protocol-wise, but unnecessary.
539 result = server->desktop->setScreenLayout(fb_width, fb_height, layout);
540
Pierre Ossman04e62db2009-03-23 16:57:07 +0000541 writer()->writeExtendedDesktopSize(reasonClient, result,
542 fb_width, fb_height, layout);
Pierre Ossman04e62db2009-03-23 16:57:07 +0000543
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000544 // Only notify other clients on success
Pierre Ossman04e62db2009-03-23 16:57:07 +0000545 if (result == resultSuccess) {
546 if (server->screenLayout != layout)
547 throw Exception("Desktop configured a different screen layout than requested");
548 server->notifyScreenLayoutChange(this);
549 }
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000550
551 // but always send back a reply to the requesting client
552 // (do this last as it might throw an exception on socket errors)
553 writeFramebufferUpdate();
Pierre Ossmanc5e25602009-03-20 12:59:05 +0000554}
555
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000556void VNCSConnectionST::setInitialColourMap()
557{
558 setColourMapEntries(0, 0);
559}
560
561// supportsLocalCursor() is called whenever the status of
562// cp.supportsLocalCursor has changed. If the client does now support local
563// cursor, we make sure that the old server-side rendered cursor is cleaned up
564// and the cursor is sent to the client.
565
566void VNCSConnectionST::supportsLocalCursor()
567{
568 if (cp.supportsLocalCursor || cp.supportsLocalXCursor) {
Pierre Ossman5c9e1e52011-11-08 10:32:05 +0000569 if (!renderedCursorRect.is_empty())
570 removeRenderedCursor = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000571 drawRenderedCursor = false;
572 setCursor();
573 }
574}
575
576void VNCSConnectionST::writeSetCursorCallback()
577{
578 if (cp.supportsLocalXCursor) {
579 Pixel pix0, pix1;
580 rdr::U8Array bitmap(server->cursor.getBitmap(&pix0, &pix1));
581 if (bitmap.buf) {
582 // The client supports XCursor and the cursor only has two
583 // colors. Use the XCursor encoding.
584 writer()->writeSetXCursor(server->cursor.width(),
585 server->cursor.height(),
586 server->cursor.hotspot.x,
587 server->cursor.hotspot.y,
588 bitmap.buf, server->cursor.mask.buf);
589 return;
590 } else {
591 // More than two colors
592 if (!cp.supportsLocalCursor) {
593 // FIXME: We could reduce to two colors.
594 vlog.info("Unable to send multicolor cursor: RichCursor not supported by client");
595 return;
596 }
597 }
598 }
599
600 // Use RichCursor
601 rdr::U8* transData = writer()->getImageBuf(server->cursor.area());
602 image_getter.translatePixels(server->cursor.data, transData,
603 server->cursor.area());
604 writer()->writeSetCursor(server->cursor.width(),
605 server->cursor.height(),
606 server->cursor.hotspot,
607 transData, server->cursor.mask.buf);
608}
609
610
Pierre Ossman1bb8b6c2011-10-25 15:20:05 +0000611bool VNCSConnectionST::handleTimeout(Timer* t)
612{
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000613 try {
614 if (t == &updateTimer)
615 writeFramebufferUpdate();
616 } catch (rdr::Exception& e) {
617 close(e.str());
618 }
Pierre Ossman1bb8b6c2011-10-25 15:20:05 +0000619
620 return false;
621}
622
623
624bool VNCSConnectionST::isCongested()
625{
Pierre Ossman1bb8b6c2011-10-25 15:20:05 +0000626 if (sock->outStream().bufferUsage() > 0)
627 return true;
628
629 return false;
630}
631
632
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000633void VNCSConnectionST::writeFramebufferUpdate()
634{
Pierre Ossman1bb8b6c2011-10-25 15:20:05 +0000635 updateTimer.stop();
636
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000637 // We try to aggregate responses, so don't send out anything whilst we
638 // still have incoming messages. processMessages() will give us another
639 // chance to run once things are idle.
640 if (inProcessMessages)
641 return;
642
Pierre Ossmane9962f72009-04-23 12:31:42 +0000643 if (state() != RFBSTATE_NORMAL || requested.is_empty())
644 return;
Pierre Ossmand9a59ba2009-03-20 15:55:37 +0000645
Pierre Ossman1bb8b6c2011-10-25 15:20:05 +0000646 // Check that we actually have some space on the link and retry in a
647 // bit if things are congested.
648 if (isCongested()) {
649 updateTimer.start(50);
650 return;
651 }
652
Pierre Ossmane9962f72009-04-23 12:31:42 +0000653 // First take care of any updates that cannot contain framebuffer data
654 // changes.
655 if (writer()->needNoDataUpdate()) {
656 writer()->writeNoDataUpdate();
657 requested.clear();
658 return;
659 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000660
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000661 updates.enable_copyrect(cp.useCopyRect);
662
Pierre Ossman02e43d72009-03-05 11:57:11 +0000663 server->checkUpdate();
Constantin Kaplinskya09dc142008-12-18 12:08:15 +0000664
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000665 // Get the lists of updates. Prior to exporting the data to the `ui' object,
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000666 // getUpdateInfo() will normalize the `updates' object such way that its
Pierre Ossman02e43d72009-03-05 11:57:11 +0000667 // `changed' and `copied' regions would not intersect.
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000668
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000669 UpdateInfo ui;
670 updates.getUpdateInfo(&ui, requested);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000671 bool needNewUpdateInfo = false;
672
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000673 // If the previous position of the rendered cursor overlaps the source of the
674 // copy, then when the copy happens the corresponding rectangle in the
675 // destination will be wrong, so add it to the changed region.
676
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000677 if (!ui.copied.is_empty() && !renderedCursorRect.is_empty()) {
678 Rect bogusCopiedCursor = (renderedCursorRect.translate(ui.copy_delta)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000679 .intersect(server->pb->getRect()));
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000680 if (!ui.copied.intersect(bogusCopiedCursor).is_empty()) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000681 updates.add_changed(bogusCopiedCursor);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000682 needNewUpdateInfo = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000683 }
684 }
685
686 // If we need to remove the old rendered cursor, just add the rectangle to
687 // the changed region.
688
689 if (removeRenderedCursor) {
690 updates.add_changed(renderedCursorRect);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000691 needNewUpdateInfo = true;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000692 renderedCursorRect.clear();
693 removeRenderedCursor = false;
694 }
695
696 // Return if there is nothing to send the client.
697
698 if (updates.is_empty() && !writer()->needFakeUpdate() && !drawRenderedCursor)
699 return;
700
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000701 // The `updates' object could change, make sure we have valid update info.
702
703 if (needNewUpdateInfo)
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000704 updates.getUpdateInfo(&ui, requested);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000705
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000706 // If the client needs a server-side rendered cursor, work out the cursor
707 // rectangle. If it's empty then don't bother drawing it, but if it overlaps
708 // with the update region, we need to draw the rendered cursor regardless of
709 // whether it has changed.
710
711 if (needRenderedCursor()) {
712 renderedCursorRect
713 = (server->renderedCursor.getRect(server->renderedCursorTL)
714 .intersect(requested.get_bounding_rect()));
715
716 if (renderedCursorRect.is_empty()) {
717 drawRenderedCursor = false;
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000718 } else if (!ui.changed.union_(ui.copied)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000719 .intersect(renderedCursorRect).is_empty()) {
720 drawRenderedCursor = true;
721 }
722
723 // We could remove the new cursor rect from updates here. It's not clear
724 // whether this is worth it. If we do remove it, then we won't draw over
725 // the same bit of screen twice, but we have the overhead of a more complex
726 // region.
727
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000728 //if (drawRenderedCursor) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000729 // updates.subtract(renderedCursorRect);
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000730 // updates.getUpdateInfo(&ui, requested);
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000731 //}
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000732 }
733
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000734 if (!ui.is_empty() || writer()->needFakeUpdate() || drawRenderedCursor) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000735 // Compute the number of rectangles. Tight encoder makes the things more
Constantin Kaplinsky604d7812007-08-31 15:50:37 +0000736 // complicated as compared to the original VNC4.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000737 writer()->setupCurrentEncoder();
Constantin Kaplinsky1a845d02007-08-31 21:06:53 +0000738 int nRects = (ui.copied.numRects() +
Constantin Kaplinsky1a845d02007-08-31 21:06:53 +0000739 (drawRenderedCursor ? 1 : 0));
Constantin Kaplinsky651606d2007-10-17 17:40:23 +0000740
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000741 std::vector<Rect> rects;
742 std::vector<Rect>::const_iterator i;
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000743 ui.changed.get_rects(&rects);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000744 for (i = rects.begin(); i != rects.end(); i++) {
DRCcd2c5d42011-08-11 11:18:34 +0000745 if (i->width() && i->height()) {
746 int nUpdateRects = writer()->getNumRects(*i);
747 if (nUpdateRects == 0 && cp.currentEncoding() == encodingTight) {
748 nRects = 0xFFFF; break;
749 }
750 else
751 nRects += nUpdateRects;
752 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000753 }
754
755 writer()->writeFramebufferUpdateStart(nRects);
756 Region updatedRegion;
Constantin Kaplinsky45517c82007-08-31 15:56:33 +0000757 writer()->writeRects(ui, &image_getter, &updatedRegion);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000758 updates.subtract(updatedRegion);
759 if (drawRenderedCursor)
760 writeRenderedCursorRect();
761 writer()->writeFramebufferUpdateEnd();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000762 requested.clear();
763 }
764}
765
766
767// writeRenderedCursorRect() writes a single rectangle drawing the rendered
768// cursor on the client.
769
770void VNCSConnectionST::writeRenderedCursorRect()
771{
772 image_getter.setPixelBuffer(&server->renderedCursor);
773 image_getter.setOffset(server->renderedCursorTL);
774
775 Rect actual;
776 writer()->writeRect(renderedCursorRect, &image_getter, &actual);
777
778 image_getter.setPixelBuffer(server->pb);
779 image_getter.setOffset(Point(0,0));
780
781 drawRenderedCursor = false;
782}
783
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000784void VNCSConnectionST::screenLayoutChange(rdr::U16 reason)
785{
786 if (!authenticated())
787 return;
788
789 cp.screenLayout = server->screenLayout;
790
791 if (state() != RFBSTATE_NORMAL)
792 return;
793
794 writer()->writeExtendedDesktopSize(reason, 0, cp.width, cp.height,
795 cp.screenLayout);
796 writeFramebufferUpdate();
797}
798
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000799void VNCSConnectionST::setColourMapEntries(int firstColour, int nColours)
800{
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000801 if (!readyForSetColourMapEntries)
802 return;
803 if (server->pb->getPF().trueColour)
804 return;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000805
Pierre Ossmana2739342011-03-08 16:53:07 +0000806 image_getter.setColourMapEntries(firstColour, nColours);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000807
808 if (cp.pf().trueColour) {
809 updates.add_changed(server->pb->getRect());
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000810 writeFramebufferUpdate();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000811 }
812}
813
814
815// setCursor() is called whenever the cursor has changed shape or pixel format.
816// If the client supports local cursor then it will arrange for the cursor to
817// be sent to the client.
818
819void VNCSConnectionST::setCursor()
820{
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000821 if (state() != RFBSTATE_NORMAL)
822 return;
823 if (!cp.supportsLocalCursor)
824 return;
825
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000826 writer()->cursorChange(this);
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000827 writeFramebufferUpdate();
828}
829
830void VNCSConnectionST::setDesktopName(const char *name)
831{
832 cp.setName(name);
833
834 if (state() != RFBSTATE_NORMAL)
835 return;
836
837 if (!writer()->writeSetDesktopName()) {
838 fprintf(stderr, "Client does not support desktop rename\n");
839 return;
840 }
841
842 writeFramebufferUpdate();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000843}
844
845void VNCSConnectionST::setSocketTimeouts()
846{
847 int timeoutms = rfb::Server::clientWaitTimeMillis;
848 soonestTimeout(&timeoutms, secsToMillis(rfb::Server::idleTimeout));
849 if (timeoutms == 0)
850 timeoutms = -1;
851 sock->inStream().setTimeout(timeoutms);
852 sock->outStream().setTimeout(timeoutms);
853}
854
855char* VNCSConnectionST::getStartTime()
856{
857 char* result = ctime(&startTime);
858 result[24] = '\0';
859 return result;
860}
861
862void VNCSConnectionST::setStatus(int status)
863{
864 switch (status) {
865 case 0:
866 accessRights = accessRights | AccessPtrEvents | AccessKeyEvents | AccessView;
867 break;
868 case 1:
Adam Tkac8e985062011-02-07 11:33:57 +0000869 accessRights = accessRights & ~(AccessPtrEvents | AccessKeyEvents) | AccessView;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000870 break;
871 case 2:
Adam Tkac8e985062011-02-07 11:33:57 +0000872 accessRights = accessRights & ~(AccessPtrEvents | AccessKeyEvents | AccessView);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000873 break;
874 }
875 framebufferUpdateRequest(server->pb->getRect(), false);
876}
877int VNCSConnectionST::getStatus()
878{
879 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0007)
880 return 0;
881 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0001)
882 return 1;
883 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0000)
884 return 2;
885 return 4;
886}
887