blob: 711bd521f25e597b0e6a389132ecb085c52ba3c2 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +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// CConn.cxx
20//
21
22#include <unistd.h>
23#include "CConn.h"
24#include <rfb/CMsgWriter.h>
25#include <rfb/encodings.h>
26#include <rfb/secTypes.h>
27#include <rfb/CSecurityNone.h>
28#include <rfb/CSecurityVncAuth.h>
29#include <rfb/Hostname.h>
30#include <rfb/LogWriter.h>
31#include <rfb/util.h>
32#include <rfb/Password.h>
33#include <network/TcpSocket.h>
34
35#include "TXViewport.h"
36#include "DesktopWindow.h"
37#include "ServerDialog.h"
38#include "PasswdDialog.h"
39#include "parameters.h"
40
41using namespace rfb;
42
43static rfb::LogWriter vlog("CConn");
44
45IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
46 "pixel data - a debugging feature", 0);
47
48StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
49 "F8");
50StringParameter windowName("name", "The X window name", "");
51
52CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
53 char* vncServerName, bool reverse)
54 : dpy(dpy_), argc(argc_),
55 argv(argv_), serverHost(0), serverPort(0), sock(sock_), viewport(0),
56 desktop(0), desktopEventHandler(0),
57 currentEncoding(encodingZRLE), lastServerEncoding((unsigned int)-1),
58 fullColour(::fullColour),
59 autoSelect(::autoSelect), shared(::shared), formatChange(false),
60 encodingChange(false), sameMachine(false), fullScreen(::fullScreen),
61 ctrlDown(false), altDown(false),
62 menuKeysym(0), menu(dpy, this), options(dpy, this), about(dpy), info(dpy),
63 reverseConnection(reverse)
64{
65 CharArray menuKeyStr(menuKey.getData());
66 menuKeysym = XStringToKeysym(menuKeyStr.buf);
67
68 setShared(shared);
69 addSecType(secTypeNone);
70 addSecType(secTypeVncAuth);
71 CharArray encStr(preferredEncoding.getData());
72 int encNum = encodingNum(encStr.buf);
73 if (encNum != -1) {
74 currentEncoding = encNum;
75 }
76 cp.supportsDesktopResize = true;
Peter Åstrandc39e0782009-01-15 12:21:42 +000077 cp.supportsDesktopRename = true;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000078 cp.supportsLocalCursor = useLocalCursor;
79 cp.customCompressLevel = customCompressLevel;
80 cp.compressLevel = compressLevel;
81 cp.noJpeg = noJpeg;
82 cp.qualityLevel = qualityLevel;
83 initMenu();
84
85 if (sock) {
86 char* name = sock->getPeerEndpoint();
87 vlog.info("Accepted connection from %s", name);
88 if (name) free(name);
89 } else {
90 if (vncServerName) {
91 getHostAndPort(vncServerName, &serverHost, &serverPort);
92 } else {
93 ServerDialog dlg(dpy, &options, &about);
94 if (!dlg.show() || dlg.entry.getText()[0] == 0) {
95 exit(1);
96 }
97 getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
98 }
99
100 sock = new network::TcpSocket(serverHost, serverPort);
101 vlog.info("connected to host %s port %d", serverHost, serverPort);
102 }
103
104 sameMachine = sock->sameMachine();
105 sock->inStream().setBlockCallback(this);
106 setServerName(sock->getPeerEndpoint());
107 setStreams(&sock->inStream(), &sock->outStream());
108 initialiseProtocol();
109}
110
111CConn::~CConn() {
112 free(serverHost);
113 delete desktop;
114 delete viewport;
115 delete sock;
116}
117
118// deleteWindow() is called when the user closes the desktop or menu windows.
119
120void CConn::deleteWindow(TXWindow* w) {
121 if (w == &menu) {
122 menu.unmap();
123 } else if (w == viewport) {
124 exit(1);
125 }
126}
127
128// handleEvent() filters all events on the desktop and menu. Most are passed
129// straight through. The exception is the F8 key. When pressed on the
130// desktop, it is used to bring up the menu. An F8 press or release on the
131// menu is passed through as if it were on the desktop.
132
133void CConn::handleEvent(TXWindow* w, XEvent* ev)
134{
135 KeySym ks;
136 char str[256];
137
138 switch (ev->type) {
139 case KeyPress:
140 case KeyRelease:
141 XLookupString(&ev->xkey, str, 256, &ks, NULL);
142 if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
143 if (w == desktop && ev->type == KeyPress) {
144 showMenu(ev->xkey.x_root, ev->xkey.y_root);
145 break;
146 } else if (w == &menu) {
147 if (ev->type == KeyPress) menu.unmap();
148 desktopEventHandler->handleEvent(w, ev);
149 break;
150 }
151 }
152 // drop through
153
154 default:
155 if (w == desktop) desktopEventHandler->handleEvent(w, ev);
156 else if (w == &menu) menuEventHandler->handleEvent(w, ev);
157 }
158}
159
160// blockCallback() is called when reading from the socket would block. We
161// process X events until the socket is ready for reading again.
162
163void CConn::blockCallback() {
164 fd_set rfds;
165 do {
166 struct timeval tv;
167 struct timeval* tvp = 0;
168
169 // Process any incoming X events
170 TXWindow::handleXEvents(dpy);
171
172 // Process expired timers and get the time until the next one
173 int timeoutMs = Timer::checkTimeouts();
174 if (timeoutMs) {
175 tv.tv_sec = timeoutMs / 1000;
176 tv.tv_usec = (timeoutMs % 1000) * 1000;
177 tvp = &tv;
178 }
179
180 // If there are X requests pending then poll, don't wait!
181 if (XPending(dpy)) {
182 tv.tv_usec = tv.tv_sec = 0;
183 tvp = &tv;
184 }
185
186 // Wait for X events, VNC traffic, or the next timer expiry
187 FD_ZERO(&rfds);
188 FD_SET(ConnectionNumber(dpy), &rfds);
189 FD_SET(sock->getFd(), &rfds);
190 int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
191 if (n < 0) throw rdr::SystemException("select",errno);
192 } while (!(FD_ISSET(sock->getFd(), &rfds)));
193}
194
195
196// getPasswd() is called by the CSecurity object when it needs us to read a
197// password from the user.
198
199void CConn::getUserPasswd(char** user, char** password)
200{
201 CharArray passwordFileStr(passwordFile.getData());
202 if (!user && passwordFileStr.buf[0]) {
203 FILE* fp = fopen(passwordFileStr.buf, "r");
204 if (!fp) throw rfb::Exception("Opening password file failed");
205 ObfuscatedPasswd obfPwd(256);
206 obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
207 fclose(fp);
208 PlainPasswd passwd(obfPwd);
209 *password = passwd.takeBuf();
210 return;
211 }
212
213 const char* secType = secTypeName(getCurrentCSecurity()->getType());
214 const char* titlePrefix = _("VNC authentication");
215 unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
216 CharArray title(titleLen);
217 snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
218 PasswdDialog dlg(dpy, title.buf, !user);
219 if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
220 if (user)
221 *user = strDup(dlg.userEntry.getText());
222 *password = strDup(dlg.passwdEntry.getText());
223}
224
225
226// CConnection callback methods
227
228// getCSecurity() gets the appropriate CSecurity object for the security
229// types which we support.
230CSecurity* CConn::getCSecurity(int secType) {
231 switch (secType) {
232 case secTypeNone:
233 return new CSecurityNone();
234 case secTypeVncAuth:
235 return new CSecurityVncAuth(this);
236 default:
237 throw rfb::Exception("Unsupported secType?");
238 }
239}
240
241// serverInit() is called when the serverInit message has been received. At
242// this point we create the desktop window and display it. We also tell the
243// server the pixel format and encodings to use and request the first update.
244void CConn::serverInit() {
245 CConnection::serverInit();
246
247 // If using AutoSelect with old servers, start in FullColor
248 // mode. See comment in autoSelectFormatAndEncoding.
249 if (cp.beforeVersion(3, 8) && autoSelect) {
250 fullColour = true;
251 }
252
253 serverPF = cp.pf();
254 desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
255 desktopEventHandler = desktop->setEventHandler(this);
256 desktop->addEventMask(KeyPressMask | KeyReleaseMask);
257 fullColourPF = desktop->getPF();
258 if (!serverPF.trueColour)
259 fullColour = true;
260 recreateViewport();
261 formatChange = encodingChange = true;
262 requestNewUpdate();
263}
264
265// setDesktopSize() is called when the desktop size changes (including when
266// it is set initially).
267void CConn::setDesktopSize(int w, int h) {
268 CConnection::setDesktopSize(w,h);
269 if (desktop) {
270 desktop->resize(w, h);
271 recreateViewport();
272 }
273}
274
Peter Åstrandc39e0782009-01-15 12:21:42 +0000275// setName() is called when the desktop name changes
276void CConn::setName(const char* name) {
277 CConnection::setName(name);
278 if (viewport) {
279 viewport->setName(name);
280 }
281}
282
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000283// framebufferUpdateEnd() is called at the end of an update.
284// For each rectangle, the FdInStream will have timed the speed
285// of the connection, allowing us to select format and encoding
286// appropriately, and then request another incremental update.
287void CConn::framebufferUpdateEnd() {
288 if (debugDelay != 0) {
289 XSync(dpy, False);
290 struct timeval tv;
291 tv.tv_sec = debugDelay / 1000;
292 tv.tv_usec = (debugDelay % 1000) * 1000;
293 select(0, 0, 0, 0, &tv);
294 std::list<rfb::Rect>::iterator i;
295 for (i = debugRects.begin(); i != debugRects.end(); i++) {
296 desktop->invertRect(*i);
297 }
298 debugRects.clear();
299 }
300 desktop->framebufferUpdateEnd();
301 if (autoSelect)
302 autoSelectFormatAndEncoding();
303 requestNewUpdate();
304}
305
306// The rest of the callbacks are fairly self-explanatory...
307
308void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
309{
310 desktop->setColourMapEntries(firstColour, nColours, rgbs);
311}
312
313void CConn::bell() { XBell(dpy, 0); }
314
315void CConn::serverCutText(const char* str, int len) {
316 desktop->serverCutText(str,len);
317}
318
319// We start timing on beginRect and stop timing on endRect, to
320// avoid skewing the bandwidth estimation as a result of the server
321// being slow or the network having high latency
322void CConn::beginRect(const Rect& r, unsigned int encoding)
323{
324 sock->inStream().startTiming();
325 if (encoding != encodingCopyRect) {
326 lastServerEncoding = encoding;
327 }
328}
329
330void CConn::endRect(const Rect& r, unsigned int encoding)
331{
332 sock->inStream().stopTiming();
333 if (debugDelay != 0) {
334 desktop->invertRect(r);
335 debugRects.push_back(r);
336 }
337}
338
339void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
340 desktop->fillRect(r,p);
341}
342void CConn::imageRect(const rfb::Rect& r, void* p) {
343 desktop->imageRect(r,p);
344}
345void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
346 desktop->copyRect(r,sx,sy);
347}
348void CConn::setCursor(int width, int height, const Point& hotspot,
349 void* data, void* mask) {
350 desktop->setCursor(width, height, hotspot, data, mask);
351}
352
353
354// Menu stuff - menuSelect() is called when the user selects a menu option.
355
356enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
357 ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
358
359void CConn::initMenu() {
360 menuEventHandler = menu.setEventHandler(this);
361 menu.addEventMask(KeyPressMask | KeyReleaseMask);
362 menu.addEntry(_("Exit viewer"), ID_EXIT);
363 menu.addEntry(0, 0);
364 menu.addEntry(_("Full screen"), ID_FULLSCREEN);
365 menu.check(ID_FULLSCREEN, fullScreen);
366 menu.addEntry(0, 0);
367 menu.addEntry(_("Ctrl"), ID_CTRL);
368 menu.addEntry(_("Alt"), ID_ALT);
369 CharArray menuKeyStr(menuKey.getData());
370 CharArray sendMenuKey(64);
371 snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
372 menu.addEntry(sendMenuKey.buf, ID_F8);
373 menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
374 menu.addEntry(0, 0);
375 menu.addEntry(_("Refresh screen"), ID_REFRESH);
376 menu.addEntry(0, 0);
377 menu.addEntry(_("New connection..."), ID_NEWCONN);
378 menu.addEntry(_("Options..."), ID_OPTIONS);
379 menu.addEntry(_("Connection info..."), ID_INFO);
380 menu.addEntry(_("About VNCviewer..."), ID_ABOUT);
381 menu.addEntry(0, 0);
382 menu.addEntry(_("Dismiss menu"), ID_DISMISS);
383 menu.toplevel(_("VNC Menu"), this);
384 menu.setBorderWidth(1);
385}
386
387void CConn::showMenu(int x, int y) {
388 menu.check(ID_FULLSCREEN, fullScreen);
389 if (x + menu.width() > viewport->width())
390 x = viewport->width() - menu.width();
391 if (y + menu.height() > viewport->height())
392 y = viewport->height() - menu.height();
393 menu.move(x, y);
394 menu.raise();
395 menu.map();
396}
397
398void CConn::menuSelect(long id, TXMenu* m) {
399 switch (id) {
400 case ID_NEWCONN:
401 {
402 menu.unmap();
403 if (fullScreen) {
404 fullScreen = false;
405 if (viewport) recreateViewport();
406 }
407 int pid = fork();
408 if (pid < 0) { perror("fork"); exit(1); }
409 if (pid == 0) {
410 delete sock;
411 close(ConnectionNumber(dpy));
412 struct timeval tv;
413 tv.tv_sec = 0;
414 tv.tv_usec = 200*1000;
415 select(0, 0, 0, 0, &tv);
Adam Tkacfee32e32008-10-10 15:48:22 +0000416 execlp(programName, programName, NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000417 perror("execlp"); exit(1);
418 }
419 break;
420 }
421 case ID_OPTIONS:
422 menu.unmap();
423 options.show();
424 break;
425 case ID_INFO:
426 {
427 menu.unmap();
428 char pfStr[100];
429 char spfStr[100];
430 cp.pf().print(pfStr, 100);
431 serverPF.print(spfStr, 100);
432 int secType = getCurrentCSecurity()->getType();
433 char infoText[1024];
434 snprintf(infoText, sizeof(infoText),
435 _("Desktop name: %.80s\n"
436 "Host: %.80s port: %d\n"
437 "Size: %d x %d\n"
438 "Pixel format: %s\n"
439 "(server default %s)\n"
440 "Requested encoding: %s\n"
441 "Last used encoding: %s\n"
442 "Line speed estimate: %d kbit/s\n"
443 "Protocol version: %d.%d\n"
444 "Security method: %s\n"),
445 cp.name(), serverHost, serverPort, cp.width, cp.height,
446 pfStr, spfStr, encodingName(currentEncoding),
447 encodingName(lastServerEncoding),
448 sock->inStream().kbitsPerSecond(),
449 cp.majorVersion, cp.minorVersion,
450 secTypeName(secType));
451 info.setText(infoText);
452 info.show();
453 break;
454 }
455 case ID_FULLSCREEN:
456 menu.unmap();
457 fullScreen = !fullScreen;
458 if (viewport) recreateViewport();
459 break;
460 case ID_REFRESH:
461 menu.unmap();
462 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
463 false);
464 break;
465 case ID_F8:
466 menu.unmap();
467 if (!viewOnly) {
468 writer()->keyEvent(menuKeysym, true);
469 writer()->keyEvent(menuKeysym, false);
470 }
471 break;
472 case ID_CTRLALTDEL:
473 menu.unmap();
474 if (!viewOnly) {
475 writer()->keyEvent(XK_Control_L, true);
476 writer()->keyEvent(XK_Alt_L, true);
477 writer()->keyEvent(XK_Delete, true);
478 writer()->keyEvent(XK_Delete, false);
479 writer()->keyEvent(XK_Alt_L, false);
480 writer()->keyEvent(XK_Control_L, false);
481 }
482 break;
483 case ID_CTRL:
484 menu.unmap();
485 if (!viewOnly) {
486 ctrlDown = !ctrlDown;
487 writer()->keyEvent(XK_Control_L, ctrlDown);
488 menu.check(ID_CTRL, ctrlDown);
489 }
490 break;
491 case ID_ALT:
492 menu.unmap();
493 if (!viewOnly) {
494 altDown = !altDown;
495 writer()->keyEvent(XK_Alt_L, altDown);
496 menu.check(ID_ALT, altDown);
497 }
498 break;
499 case ID_ABOUT:
500 menu.unmap();
501 about.show();
502 break;
503 case ID_DISMISS:
504 menu.unmap();
505 break;
506 case ID_EXIT:
507 exit(1);
508 break;
509 }
510}
511
512
513// OptionsDialogCallback. setOptions() sets the options dialog's checkboxes
514// etc to reflect our flags. getOptions() sets our flags according to the
515// options dialog's checkboxes.
516
517void CConn::setOptions() {
518 char digit[2] = "0";
519 options.autoSelect.checked(autoSelect);
520 options.fullColour.checked(fullColour);
521 options.veryLowColour.checked(!fullColour && lowColourLevel == 0);
522 options.lowColour.checked(!fullColour && lowColourLevel == 1);
523 options.mediumColour.checked(!fullColour && lowColourLevel == 2);
524 options.tight.checked(currentEncoding == encodingTight);
525 options.zrle.checked(currentEncoding == encodingZRLE);
526 options.hextile.checked(currentEncoding == encodingHextile);
527 options.raw.checked(currentEncoding == encodingRaw);
528
529 options.customCompressLevel.checked(customCompressLevel);
530 digit[0] = '0' + compressLevel;
531 options.compressLevel.setText(digit);
532 options.noJpeg.checked(!noJpeg);
533 digit[0] = '0' + qualityLevel;
534 options.qualityLevel.setText(digit);
535
536 options.viewOnly.checked(viewOnly);
537 options.acceptClipboard.checked(acceptClipboard);
538 options.sendClipboard.checked(sendClipboard);
539 options.sendPrimary.checked(sendPrimary);
540 if (state() == RFBSTATE_NORMAL)
541 options.shared.disabled(true);
542 else
543 options.shared.checked(shared);
544 options.fullScreen.checked(fullScreen);
545 options.useLocalCursor.checked(useLocalCursor);
546 options.dotWhenNoCursor.checked(dotWhenNoCursor);
547}
548
549void CConn::getOptions() {
550 autoSelect = options.autoSelect.checked();
551 if (fullColour != options.fullColour.checked())
552 formatChange = true;
553 fullColour = options.fullColour.checked();
554 if (!fullColour) {
555 int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
556 options.lowColour.checked() ? 1 : 2);
557 if (newLowColourLevel != lowColourLevel) {
558 lowColourLevel.setParam(newLowColourLevel);
559 formatChange = true;
560 }
561 }
562 unsigned int newEncoding = (options.tight.checked() ? encodingTight :
563 options.zrle.checked() ? encodingZRLE :
564 options.hextile.checked() ? encodingHextile :
565 encodingRaw);
566 if (newEncoding != currentEncoding) {
567 currentEncoding = newEncoding;
568 encodingChange = true;
569 }
570
571 customCompressLevel.setParam(options.customCompressLevel.checked());
572 if (cp.customCompressLevel != customCompressLevel) {
573 cp.customCompressLevel = customCompressLevel;
574 encodingChange = true;
575 }
576 compressLevel.setParam(options.compressLevel.getText());
577 if (cp.compressLevel != compressLevel) {
578 cp.compressLevel = compressLevel;
579 encodingChange = true;
580 }
581 noJpeg.setParam(!options.noJpeg.checked());
582 if (cp.noJpeg != noJpeg) {
583 cp.noJpeg = noJpeg;
584 encodingChange = true;
585 }
586 qualityLevel.setParam(options.qualityLevel.getText());
587 if (cp.qualityLevel != qualityLevel) {
588 cp.qualityLevel = qualityLevel;
589 encodingChange = true;
590 }
591
592 viewOnly.setParam(options.viewOnly.checked());
593 acceptClipboard.setParam(options.acceptClipboard.checked());
594 sendClipboard.setParam(options.sendClipboard.checked());
595 sendPrimary.setParam(options.sendPrimary.checked());
596 shared = options.shared.checked();
597 setShared(shared);
598 if (fullScreen != options.fullScreen.checked()) {
599 fullScreen = options.fullScreen.checked();
600 if (viewport) recreateViewport();
601 }
602 useLocalCursor.setParam(options.useLocalCursor.checked());
603 if (cp.supportsLocalCursor != useLocalCursor) {
604 cp.supportsLocalCursor = useLocalCursor;
605 encodingChange = true;
606 if (desktop)
607 desktop->resetLocalCursor();
608 }
609 dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
610 checkEncodings();
611}
612
613void CConn::recreateViewport()
614{
615 TXViewport* oldViewport = viewport;
616 viewport = new TXViewport(dpy, cp.width, cp.height);
617 desktop->setViewport(viewport);
618 CharArray windowNameStr(windowName.getData());
619 if (!windowNameStr.buf[0]) {
620 windowNameStr.replaceBuf(new char[256]);
621 snprintf(windowNameStr.buf, 256, "VNC: %.240s", cp.name());
622 }
623 viewport->toplevel(windowNameStr.buf, this, argc, argv);
624 viewport->setBumpScroll(fullScreen);
625 XSetWindowAttributes attr;
626 attr.override_redirect = fullScreen;
627 XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
628 XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
629 XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
630 XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
631 XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
632 reconfigureViewport();
633 menu.setTransientFor(viewport->win());
634 viewport->map();
635 if (fullScreen) {
636 XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
637 CurrentTime);
638 } else {
639 XUngrabKeyboard(dpy, CurrentTime);
640 }
641 if (oldViewport) delete oldViewport;
642}
643
644void CConn::reconfigureViewport()
645{
646 viewport->setMaxSize(cp.width, cp.height);
647 if (fullScreen) {
648 viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
649 DisplayHeight(dpy,DefaultScreen(dpy)));
650 } else {
651 int w = cp.width;
652 int h = cp.height;
653 if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
654 w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
655 if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
656 h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
657
658 int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
659 int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
660
661 CharArray geometryStr(geometry.getData());
662 viewport->setGeometry(geometryStr.buf, x, y, w, h);
663 }
664}
665
666// Note: The method below is duplicated in vncviewer/cview.cxx!
667
668// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
669// to the connection speed:
670//
671// Above 16Mbps (timing for at least a second), switch to hextile
672// Otherwise, switch to ZRLE
673//
674// Above 256Kbps, use full colour mode
675//
676void CConn::autoSelectFormatAndEncoding()
677{
678 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
679 unsigned int newEncoding = currentEncoding;
680 bool newFullColour = fullColour;
681 unsigned int timeWaited = sock->inStream().timeWaited();
682
683 // Select best encoding
684 if (kbitsPerSecond > 16000 && timeWaited >= 10000) {
685 newEncoding = encodingHextile;
686 } else {
687 newEncoding = encodingZRLE;
688 }
689
690 if (newEncoding != currentEncoding) {
691 vlog.info("Throughput %d kbit/s - changing to %s encoding",
692 kbitsPerSecond, encodingName(newEncoding));
693 currentEncoding = newEncoding;
694 encodingChange = true;
695 }
696
697 if (kbitsPerSecond == 0) {
698 return;
699 }
700
701 if (cp.beforeVersion(3, 8)) {
702 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
703 // cursors "asynchronously". If this happens in the middle of a
704 // pixel format change, the server will encode the cursor with
705 // the old format, but the client will try to decode it
706 // according to the new format. This will lead to a
707 // crash. Therefore, we do not allow automatic format change for
708 // old servers.
709 return;
710 }
711
712 // Select best color level
713 newFullColour = (kbitsPerSecond > 256);
714 if (newFullColour != fullColour) {
715 vlog.info("Throughput %d kbit/s - full color is now %s",
716 kbitsPerSecond,
717 newFullColour ? "enabled" : "disabled");
718 fullColour = newFullColour;
719 formatChange = true;
720 }
721}
722
723// checkEncodings() sends a setEncodings message if one is needed.
724void CConn::checkEncodings()
725{
726 if (encodingChange && writer()) {
727 vlog.info("Using %s encoding",encodingName(currentEncoding));
728 writer()->writeSetEncodings(currentEncoding, true);
729 encodingChange = false;
730 }
731}
732
733// requestNewUpdate() requests an update from the server, having set the
734// format and encoding appropriately.
735void CConn::requestNewUpdate()
736{
737 if (formatChange) {
738 if (fullColour) {
739 desktop->setPF(fullColourPF);
740 } else {
741 if (lowColourLevel == 0)
742 desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
743 else if (lowColourLevel == 1)
744 desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
745 else
746 desktop->setPF(PixelFormat(8,8,0,0));
747 }
748 char str[256];
749 desktop->getPF().print(str, 256);
750 vlog.info("Using pixel format %s",str);
751 cp.setPF(desktop->getPF());
752 writer()->writeSetPixelFormat(cp.pf());
753 }
754 checkEncodings();
755 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
756 !formatChange);
757 formatChange = false;
758}