blob: fe60e431da0d2113d0b188f4ac3acf1e9c4f09a5 [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19#include <rfb/VNCSConnectionST.h>
20#include <rfb/LogWriter.h>
21#include <rfb/secTypes.h>
22#include <rfb/ServerCore.h>
23#include <rfb/ComparingUpdateTracker.h>
24#include <rfb/KeyRemapper.h>
25#define XK_MISCELLANY
26#define XK_XKB_KEYS
27#include <rfb/keysymdef.h>
28
29using namespace rfb;
30
31static LogWriter vlog("VNCSConnST");
32
33VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
34 bool reverse)
35 : SConnection(server_->securityFactory, reverse), sock(s), server(server_),
36 updates(false), image_getter(server->useEconomicTranslate),
37 drawRenderedCursor(false), removeRenderedCursor(false),
38 pointerEventTime(0), accessRights(AccessDefault),
39 startTime(time(0)), m_pFileTransfer(0)
40{
41 setStreams(&sock->inStream(), &sock->outStream());
42 peerEndpoint.buf = sock->getPeerEndpoint();
43 VNCServerST::connectionsLog.write(1,"accepted: %s", peerEndpoint.buf);
44
45 // Configure the socket
46 setSocketTimeouts();
47 lastEventTime = time(0);
48
49 // Add this client to the VNCServerST
50 if (server->m_pFTManager != NULL) {
51 SFileTransfer *pFT = server->m_pFTManager->createObject(sock);
52 if (pFT != NULL) {
53 m_pFileTransfer = pFT;
54 }
55 }
56
57 server->clients.push_front(this);
58}
59
60
61VNCSConnectionST::~VNCSConnectionST()
62{
63 // If we reach here then VNCServerST is deleting us!
64 VNCServerST::connectionsLog.write(1,"closed: %s (%s)",
65 peerEndpoint.buf,
66 (closeReason.buf) ? closeReason.buf : "");
67
68 // Release any keys the client still had pressed
69 std::set<rdr::U32>::iterator i;
70 for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++)
71 server->desktop->keyEvent(*i, false);
72 if (server->pointerClient == this)
73 server->pointerClient = 0;
74
75 if (m_pFileTransfer)
76 server->m_pFTManager->destroyObject(m_pFileTransfer);
77
78 // Remove this client from the server
79 server->clients.remove(this);
80
81}
82
83
84// Methods called from VNCServerST
85
86bool VNCSConnectionST::init()
87{
88 try {
89 initialiseProtocol();
90 } catch (rdr::Exception& e) {
91 close(e.str());
92 return false;
93 }
94 return true;
95}
96
97void VNCSConnectionST::close(const char* reason)
98{
99 // Log the reason for the close
100 if (!closeReason.buf)
101 closeReason.buf = strDup(reason);
102 else
103 vlog.debug("second close: %s (%s)", peerEndpoint.buf, reason);
104
105 if (authenticated()) {
106 server->lastDisconnectTime = time(0);
107 }
108
109 // Just shutdown the socket and mark our state as closing. Eventually the
110 // calling code will call VNCServerST's removeSocket() method causing us to
111 // be deleted.
112 sock->shutdown();
113 setState(RFBSTATE_CLOSING);
114}
115
116
117void VNCSConnectionST::processMessages()
118{
119 if (state() == RFBSTATE_CLOSING) return;
120 try {
121 // - Now set appropriate socket timeouts and process data
122 setSocketTimeouts();
123 bool clientsReadyBefore = server->clientsReadyForUpdate();
124
125 while (getInStream()->checkNoWait(1)) {
126 processMsg();
127 }
128
129 if (!clientsReadyBefore && !requested.is_empty())
130 server->desktop->framebufferUpdateRequest();
131 } catch (rdr::EndOfStream&) {
132 close("Clean disconnection");
133 } catch (rdr::Exception &e) {
134 close(e.str());
135 }
136}
137
138void VNCSConnectionST::writeFramebufferUpdateOrClose()
139{
140 try {
141 writeFramebufferUpdate();
142 } catch(rdr::Exception &e) {
143 close(e.str());
144 }
145}
146
147void VNCSConnectionST::pixelBufferChange()
148{
149 try {
150 if (!authenticated()) return;
151 if (cp.width && cp.height && (server->pb->width() != cp.width ||
152 server->pb->height() != cp.height))
153 {
154 // We need to clip the next update to the new size, but also add any
155 // extra bits if it's bigger. If we wanted to do this exactly, something
156 // like the code below would do it, but at the moment we just update the
157 // entire new size. However, we do need to clip the renderedCursorRect
158 // because that might be added to updates in writeFramebufferUpdate().
159
160 //updates.intersect(server->pb->getRect());
161 //
162 //if (server->pb->width() > cp.width)
163 // updates.add_changed(Rect(cp.width, 0, server->pb->width(),
164 // server->pb->height()));
165 //if (server->pb->height() > cp.height)
166 // updates.add_changed(Rect(0, cp.height, cp.width,
167 // server->pb->height()));
168
169 renderedCursorRect = renderedCursorRect.intersect(server->pb->getRect());
170
171 cp.width = server->pb->width();
172 cp.height = server->pb->height();
173 if (state() == RFBSTATE_NORMAL) {
174 if (!writer()->writeSetDesktopSize()) {
175 close("Client does not support desktop resize");
176 return;
177 }
178 }
179 }
180 // Just update the whole screen at the moment because we're too lazy to
181 // work out what's actually changed.
182 updates.clear();
183 updates.add_changed(server->pb->getRect());
184 vlog.debug("pixel buffer changed - re-initialising image getter");
185 image_getter.init(server->pb, cp.pf(), writer());
186 if (writer()->needFakeUpdate())
187 writeFramebufferUpdate();
188 } catch(rdr::Exception &e) {
189 close(e.str());
190 }
191}
192
193void VNCSConnectionST::setColourMapEntriesOrClose(int firstColour,int nColours)
194{
195 try {
196 setColourMapEntries(firstColour, nColours);
197 } catch(rdr::Exception& e) {
198 close(e.str());
199 }
200}
201
202void VNCSConnectionST::bell()
203{
204 try {
205 if (state() == RFBSTATE_NORMAL) writer()->writeBell();
206 } catch(rdr::Exception& e) {
207 close(e.str());
208 }
209}
210
211void VNCSConnectionST::serverCutText(const char *str, int len)
212{
213 try {
214 if (!(accessRights & AccessCutText)) return;
215 if (!rfb::Server::sendCutText) return;
216 if (state() == RFBSTATE_NORMAL)
217 writer()->writeServerCutText(str, len);
218 } catch(rdr::Exception& e) {
219 close(e.str());
220 }
221}
222
223void VNCSConnectionST::setCursorOrClose()
224{
225 try {
226 setCursor();
227 } catch(rdr::Exception& e) {
228 close(e.str());
229 }
230}
231
232
233int VNCSConnectionST::checkIdleTimeout()
234{
235 int idleTimeout = rfb::Server::idleTimeout;
236 if (idleTimeout == 0) return 0;
237 if (state() != RFBSTATE_NORMAL && idleTimeout < 15)
238 idleTimeout = 15; // minimum of 15 seconds while authenticating
239 time_t now = time(0);
240 if (now < lastEventTime) {
241 // Someone must have set the time backwards. Set lastEventTime so that the
242 // idleTimeout will count from now.
243 vlog.info("Time has gone backwards - resetting idle timeout");
244 lastEventTime = now;
245 }
246 int timeLeft = lastEventTime + idleTimeout - now;
247 if (timeLeft < -60) {
248 // Our callback is over a minute late - someone must have set the time
249 // forwards. Set lastEventTime so that the idleTimeout will count from
250 // now.
251 vlog.info("Time has gone forwards - resetting idle timeout");
252 lastEventTime = now;
253 return secsToMillis(idleTimeout);
254 }
255 if (timeLeft <= 0) {
256 close("Idle timeout");
257 return 0;
258 }
259 return secsToMillis(timeLeft);
260}
261
262// renderedCursorChange() is called whenever the server-side rendered cursor
263// changes shape or position. It ensures that the next update will clean up
264// the old rendered cursor and if necessary draw the new rendered cursor.
265
266void VNCSConnectionST::renderedCursorChange()
267{
268 if (state() != RFBSTATE_NORMAL) return;
269 removeRenderedCursor = true;
270 if (needRenderedCursor())
271 drawRenderedCursor = true;
272}
273
274// needRenderedCursor() returns true if this client needs the server-side
275// rendered cursor. This may be because it does not support local cursor or
276// because the current cursor position has not been set by this client.
277// Unfortunately we can't know for sure when the current cursor position has
278// been set by this client. We guess that this is the case when the current
279// cursor position is the same as the last pointer event from this client, or
280// if it is a very short time since this client's last pointer event (up to a
281// second). [ Ideally we should do finer-grained timing here and make the time
282// configurable, but I don't think it's that important. ]
283
284bool VNCSConnectionST::needRenderedCursor()
285{
286 return (state() == RFBSTATE_NORMAL
287 && (!cp.supportsLocalCursor && !cp.supportsLocalXCursor
288 || (!server->cursorPos.equals(pointerEventPos) &&
289 (time(0) - pointerEventTime) > 0)));
290}
291
292
293void VNCSConnectionST::approveConnectionOrClose(bool accept,
294 const char* reason)
295{
296 try {
297 approveConnection(accept, reason);
298 } catch (rdr::Exception& e) {
299 close(e.str());
300 }
301}
302
303
304
305// -=- Callbacks from SConnection
306
307void VNCSConnectionST::authSuccess()
308{
309 lastEventTime = time(0);
310
311 server->startDesktop();
312
313 // - Set the connection parameters appropriately
314 cp.width = server->pb->width();
315 cp.height = server->pb->height();
316 cp.setName(server->getName());
317
318 // - Set the default pixel format
319 cp.setPF(server->pb->getPF());
320 char buffer[256];
321 cp.pf().print(buffer, 256);
322 vlog.info("Server default pixel format %s", buffer);
323 image_getter.init(server->pb, cp.pf(), 0);
324
325 // - Mark the entire display as "dirty"
326 updates.add_changed(server->pb->getRect());
327 startTime = time(0);
328}
329
330void VNCSConnectionST::queryConnection(const char* userName)
331{
332 // - Authentication succeeded - clear from blacklist
333 CharArray name; name.buf = sock->getPeerAddress();
334 server->blHosts->clearBlackmark(name.buf);
335
336 // - Special case to provide a more useful error message
337 if (rfb::Server::neverShared && !rfb::Server::disconnectClients &&
338 server->authClientCount() > 0) {
339 approveConnection(false, "The server is already in use");
340 return;
341 }
342
343 // - Does the client have the right to bypass the query?
344 if (reverseConnection ||
345 !(rfb::Server::queryConnect || sock->requiresQuery()) ||
346 (accessRights & AccessNoQuery))
347 {
348 approveConnection(true);
349 return;
350 }
351
352 // - Get the server to display an Accept/Reject dialog, if required
353 // If a dialog is displayed, the result will be PENDING, and the
354 // server will call approveConnection at a later time
355 CharArray reason;
356 VNCServerST::queryResult qr = server->queryConnection(sock, userName,
357 &reason.buf);
358 if (qr == VNCServerST::PENDING)
359 return;
360
361 // - If server returns ACCEPT/REJECT then pass result to SConnection
362 approveConnection(qr == VNCServerST::ACCEPT, reason.buf);
363}
364
365void VNCSConnectionST::clientInit(bool shared)
366{
367 lastEventTime = time(0);
368 if (rfb::Server::alwaysShared || reverseConnection) shared = true;
369 if (rfb::Server::neverShared) shared = false;
370 if (!shared) {
371 if (rfb::Server::disconnectClients) {
372 // - Close all the other connected clients
373 vlog.debug("non-shared connection - closing clients");
374 server->closeClients("Non-shared connection requested", getSock());
375 } else {
376 // - Refuse this connection if there are existing clients, in addition to
377 // this one
378 if (server->authClientCount() > 1) {
379 close("Server is already in use");
380 return;
381 }
382 }
383 }
384 SConnection::clientInit(shared);
385}
386
387void VNCSConnectionST::setPixelFormat(const PixelFormat& pf)
388{
389 SConnection::setPixelFormat(pf);
390 char buffer[256];
391 pf.print(buffer, 256);
392 vlog.info("Client pixel format %s", buffer);
393 image_getter.init(server->pb, pf, writer());
394 setCursor();
395}
396
397void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
398{
399 pointerEventTime = lastEventTime = time(0);
400 server->lastUserInputTime = lastEventTime;
401 if (!(accessRights & AccessPtrEvents)) return;
402 if (!rfb::Server::acceptPointerEvents) return;
403 if (!server->pointerClient || server->pointerClient == this) {
404 pointerEventPos = pos;
405 if (buttonMask)
406 server->pointerClient = this;
407 else
408 server->pointerClient = 0;
409 server->desktop->pointerEvent(pointerEventPos, buttonMask);
410 }
411}
412
413
414class VNCSConnectionSTShiftPresser {
415public:
416 VNCSConnectionSTShiftPresser(SDesktop* desktop_)
417 : desktop(desktop_), pressed(false) {}
418 ~VNCSConnectionSTShiftPresser() {
419 if (pressed) { desktop->keyEvent(XK_Shift_L, false); }
420 }
421 void press() {
422 desktop->keyEvent(XK_Shift_L, true);
423 pressed = true;
424 }
425 SDesktop* desktop;
426 bool pressed;
427};
428
429// keyEvent() - record in the pressedKeys which keys were pressed. Allow
430// multiple down events (for autorepeat), but only allow a single up event.
431void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) {
432 lastEventTime = time(0);
433 server->lastUserInputTime = lastEventTime;
434 if (!(accessRights & AccessKeyEvents)) return;
435 if (!rfb::Server::acceptKeyEvents) return;
436
437 // Remap the key if required
438 if (server->keyRemapper)
439 key = server->keyRemapper->remapKey(key);
440
441 // Turn ISO_Left_Tab into shifted Tab.
442 VNCSConnectionSTShiftPresser shiftPresser(server->desktop);
443 if (key == XK_ISO_Left_Tab) {
444 if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() &&
445 pressedKeys.find(XK_Shift_R) == pressedKeys.end())
446 shiftPresser.press();
447 key = XK_Tab;
448 }
449
450 if (down) {
451 pressedKeys.insert(key);
452 } else {
453 if (!pressedKeys.erase(key)) return;
454 }
455 server->desktop->keyEvent(key, down);
456}
457
458void VNCSConnectionST::clientCutText(const char* str, int len)
459{
460 if (!(accessRights & AccessCutText)) return;
461 if (!rfb::Server::acceptCutText) return;
462 server->desktop->clientCutText(str, len);
463}
464
465void VNCSConnectionST::framebufferUpdateRequest(const Rect& r,bool incremental)
466{
467 if (!(accessRights & AccessView)) return;
468
469 SConnection::framebufferUpdateRequest(r, incremental);
470
471 Region reqRgn(r);
472 requested.assign_union(reqRgn);
473
474 if (!incremental) {
475 // Non-incremental update - treat as if area requested has changed
476 updates.add_changed(reqRgn);
477 server->comparer->add_changed(reqRgn);
478 }
479
480 writeFramebufferUpdate();
481}
482
483void VNCSConnectionST::setInitialColourMap()
484{
485 setColourMapEntries(0, 0);
486}
487
488// supportsLocalCursor() is called whenever the status of
489// cp.supportsLocalCursor has changed. If the client does now support local
490// cursor, we make sure that the old server-side rendered cursor is cleaned up
491// and the cursor is sent to the client.
492
493void VNCSConnectionST::supportsLocalCursor()
494{
495 if (cp.supportsLocalCursor || cp.supportsLocalXCursor) {
496 removeRenderedCursor = true;
497 drawRenderedCursor = false;
498 setCursor();
499 }
500}
501
502void VNCSConnectionST::writeSetCursorCallback()
503{
504 if (cp.supportsLocalXCursor) {
505 Pixel pix0, pix1;
506 rdr::U8Array bitmap(server->cursor.getBitmap(&pix0, &pix1));
507 if (bitmap.buf) {
508 // The client supports XCursor and the cursor only has two
509 // colors. Use the XCursor encoding.
510 writer()->writeSetXCursor(server->cursor.width(),
511 server->cursor.height(),
512 server->cursor.hotspot.x,
513 server->cursor.hotspot.y,
514 bitmap.buf, server->cursor.mask.buf);
515 return;
516 } else {
517 // More than two colors
518 if (!cp.supportsLocalCursor) {
519 // FIXME: We could reduce to two colors.
520 vlog.info("Unable to send multicolor cursor: RichCursor not supported by client");
521 return;
522 }
523 }
524 }
525
526 // Use RichCursor
527 rdr::U8* transData = writer()->getImageBuf(server->cursor.area());
528 image_getter.translatePixels(server->cursor.data, transData,
529 server->cursor.area());
530 writer()->writeSetCursor(server->cursor.width(),
531 server->cursor.height(),
532 server->cursor.hotspot,
533 transData, server->cursor.mask.buf);
534}
535
536
537void VNCSConnectionST::writeFramebufferUpdate()
538{
539 if (state() != RFBSTATE_NORMAL || requested.is_empty()) return;
540
541 server->checkUpdate();
542
543 // If the previous position of the rendered cursor overlaps the source of the
544 // copy, then when the copy happens the corresponding rectangle in the
545 // destination will be wrong, so add it to the changed region.
546
547 if (!updates.get_copied().is_empty() && !renderedCursorRect.is_empty()) {
548 Rect bogusCopiedCursor = (renderedCursorRect.translate(updates.get_delta())
549 .intersect(server->pb->getRect()));
550 if (!updates.get_copied().intersect(bogusCopiedCursor).is_empty()) {
551 updates.add_changed(bogusCopiedCursor);
552 }
553 }
554
555 // If we need to remove the old rendered cursor, just add the rectangle to
556 // the changed region.
557
558 if (removeRenderedCursor) {
559 updates.add_changed(renderedCursorRect);
560 renderedCursorRect.clear();
561 removeRenderedCursor = false;
562 }
563
564 // Return if there is nothing to send the client.
565
566 if (updates.is_empty() && !writer()->needFakeUpdate() && !drawRenderedCursor)
567 return;
568
569 // If the client needs a server-side rendered cursor, work out the cursor
570 // rectangle. If it's empty then don't bother drawing it, but if it overlaps
571 // with the update region, we need to draw the rendered cursor regardless of
572 // whether it has changed.
573
574 if (needRenderedCursor()) {
575 renderedCursorRect
576 = (server->renderedCursor.getRect(server->renderedCursorTL)
577 .intersect(requested.get_bounding_rect()));
578
579 if (renderedCursorRect.is_empty()) {
580 drawRenderedCursor = false;
581 } else if (!updates.get_changed().union_(updates.get_copied())
582 .intersect(renderedCursorRect).is_empty()) {
583 drawRenderedCursor = true;
584 }
585
586 // We could remove the new cursor rect from updates here. It's not clear
587 // whether this is worth it. If we do remove it, then we won't draw over
588 // the same bit of screen twice, but we have the overhead of a more complex
589 // region.
590
591 //if (drawRenderedCursor)
592 // updates.subtract(renderedCursorRect);
593 }
594
595 UpdateInfo update;
596 updates.enable_copyrect(cp.useCopyRect);
597 updates.getUpdateInfo(&update, requested);
598 if (!update.is_empty() || writer()->needFakeUpdate() || drawRenderedCursor) {
599 // Compute the number of rectangles. Tight encoder makes the things more
600 // complicated as compared to the original RealVNC.
601 writer()->setupCurrentEncoder();
602 int nRects = update.copied.numRects() + (drawRenderedCursor ? 1 : 0);
603 std::vector<Rect> rects;
604 std::vector<Rect>::const_iterator i;
605 update.changed.get_rects(&rects);
606 for (i = rects.begin(); i != rects.end(); i++) {
607 if (i->width() && i->height())
608 nRects += writer()->getNumRects(*i);
609 }
610
611 writer()->writeFramebufferUpdateStart(nRects);
612 Region updatedRegion;
613 writer()->writeRects(update, &image_getter, &updatedRegion);
614 updates.subtract(updatedRegion);
615 if (drawRenderedCursor)
616 writeRenderedCursorRect();
617 writer()->writeFramebufferUpdateEnd();
618 requested.clear();
619 }
620}
621
622
623// writeRenderedCursorRect() writes a single rectangle drawing the rendered
624// cursor on the client.
625
626void VNCSConnectionST::writeRenderedCursorRect()
627{
628 image_getter.setPixelBuffer(&server->renderedCursor);
629 image_getter.setOffset(server->renderedCursorTL);
630
631 Rect actual;
632 writer()->writeRect(renderedCursorRect, &image_getter, &actual);
633
634 image_getter.setPixelBuffer(server->pb);
635 image_getter.setOffset(Point(0,0));
636
637 drawRenderedCursor = false;
638}
639
640void VNCSConnectionST::setColourMapEntries(int firstColour, int nColours)
641{
642 if (!readyForSetColourMapEntries) return;
643 if (server->pb->getPF().trueColour) return;
644
645 image_getter.setColourMapEntries(firstColour, nColours, writer());
646
647 if (cp.pf().trueColour) {
648 updates.add_changed(server->pb->getRect());
649 }
650}
651
652
653// setCursor() is called whenever the cursor has changed shape or pixel format.
654// If the client supports local cursor then it will arrange for the cursor to
655// be sent to the client.
656
657void VNCSConnectionST::setCursor()
658{
659 if (state() != RFBSTATE_NORMAL || !cp.supportsLocalCursor) return;
660 writer()->cursorChange(this);
661 if (writer()->needFakeUpdate())
662 writeFramebufferUpdate();
663}
664
665void VNCSConnectionST::setSocketTimeouts()
666{
667 int timeoutms = rfb::Server::clientWaitTimeMillis;
668 soonestTimeout(&timeoutms, secsToMillis(rfb::Server::idleTimeout));
669 if (timeoutms == 0)
670 timeoutms = -1;
671 sock->inStream().setTimeout(timeoutms);
672 sock->outStream().setTimeout(timeoutms);
673}
674
675char* VNCSConnectionST::getStartTime()
676{
677 char* result = ctime(&startTime);
678 result[24] = '\0';
679 return result;
680}
681
682void VNCSConnectionST::setStatus(int status)
683{
684 switch (status) {
685 case 0:
686 accessRights = accessRights | AccessPtrEvents | AccessKeyEvents | AccessView;
687 break;
688 case 1:
689 accessRights = accessRights & !(AccessPtrEvents | AccessKeyEvents) | AccessView;
690 break;
691 case 2:
692 accessRights = accessRights & !(AccessPtrEvents | AccessKeyEvents | AccessView);
693 break;
694 }
695 framebufferUpdateRequest(server->pb->getRect(), false);
696}
697int VNCSConnectionST::getStatus()
698{
699 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0007)
700 return 0;
701 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0001)
702 return 1;
703 if ((accessRights & (AccessPtrEvents | AccessKeyEvents | AccessView)) == 0x0000)
704 return 2;
705 return 4;
706}
707
708bool VNCSConnectionST::processFTMsg(int type)
709{
710 if (m_pFileTransfer != NULL)
711 return m_pFileTransfer->processMessages(type);
712 else
713 return false;
714}