blob: 5fc809f50728093467f92808656de037ba4f9b27 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossman49f88222009-03-20 13:02:50 +00002 * Copyright 2009 Pierre Ossman for Cendio AB
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +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// CConn.cxx
21//
22
23#include <unistd.h>
24#include "CConn.h"
25#include <rfb/CMsgWriter.h>
26#include <rfb/encodings.h>
27#include <rfb/secTypes.h>
28#include <rfb/CSecurityNone.h>
29#include <rfb/CSecurityVncAuth.h>
30#include <rfb/Hostname.h>
31#include <rfb/LogWriter.h>
32#include <rfb/util.h>
33#include <rfb/Password.h>
Pierre Ossman49f88222009-03-20 13:02:50 +000034#include <rfb/screenTypes.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000035#include <network/TcpSocket.h>
36
37#include "TXViewport.h"
38#include "DesktopWindow.h"
39#include "ServerDialog.h"
40#include "PasswdDialog.h"
41#include "parameters.h"
42
43using namespace rfb;
44
45static rfb::LogWriter vlog("CConn");
46
47IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
48 "pixel data - a debugging feature", 0);
49
50StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
51 "F8");
52StringParameter windowName("name", "The X window name", "");
53
54CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
55 char* vncServerName, bool reverse)
56 : dpy(dpy_), argc(argc_),
57 argv(argv_), serverHost(0), serverPort(0), sock(sock_), viewport(0),
58 desktop(0), desktopEventHandler(0),
Pierre Ossman7dfa22e2009-03-12 13:03:22 +000059 currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000060 fullColour(::fullColour),
61 autoSelect(::autoSelect), shared(::shared), formatChange(false),
62 encodingChange(false), sameMachine(false), fullScreen(::fullScreen),
63 ctrlDown(false), altDown(false),
64 menuKeysym(0), menu(dpy, this), options(dpy, this), about(dpy), info(dpy),
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +000065 reverseConnection(reverse), firstUpdate(true)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000066{
67 CharArray menuKeyStr(menuKey.getData());
68 menuKeysym = XStringToKeysym(menuKeyStr.buf);
69
70 setShared(shared);
71 addSecType(secTypeNone);
72 addSecType(secTypeVncAuth);
73 CharArray encStr(preferredEncoding.getData());
74 int encNum = encodingNum(encStr.buf);
75 if (encNum != -1) {
76 currentEncoding = encNum;
77 }
78 cp.supportsDesktopResize = true;
Pierre Ossman49f88222009-03-20 13:02:50 +000079 cp.supportsExtendedDesktopSize = true;
Peter Åstrandc39e0782009-01-15 12:21:42 +000080 cp.supportsDesktopRename = true;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000081 cp.supportsLocalCursor = useLocalCursor;
82 cp.customCompressLevel = customCompressLevel;
83 cp.compressLevel = compressLevel;
84 cp.noJpeg = noJpeg;
85 cp.qualityLevel = qualityLevel;
86 initMenu();
87
88 if (sock) {
89 char* name = sock->getPeerEndpoint();
90 vlog.info("Accepted connection from %s", name);
91 if (name) free(name);
92 } else {
93 if (vncServerName) {
94 getHostAndPort(vncServerName, &serverHost, &serverPort);
95 } else {
96 ServerDialog dlg(dpy, &options, &about);
97 if (!dlg.show() || dlg.entry.getText()[0] == 0) {
98 exit(1);
99 }
100 getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
101 }
102
103 sock = new network::TcpSocket(serverHost, serverPort);
104 vlog.info("connected to host %s port %d", serverHost, serverPort);
105 }
106
107 sameMachine = sock->sameMachine();
108 sock->inStream().setBlockCallback(this);
109 setServerName(sock->getPeerEndpoint());
110 setStreams(&sock->inStream(), &sock->outStream());
111 initialiseProtocol();
112}
113
114CConn::~CConn() {
115 free(serverHost);
116 delete desktop;
117 delete viewport;
118 delete sock;
119}
120
121// deleteWindow() is called when the user closes the desktop or menu windows.
122
123void CConn::deleteWindow(TXWindow* w) {
124 if (w == &menu) {
125 menu.unmap();
126 } else if (w == viewport) {
127 exit(1);
128 }
129}
130
131// handleEvent() filters all events on the desktop and menu. Most are passed
132// straight through. The exception is the F8 key. When pressed on the
133// desktop, it is used to bring up the menu. An F8 press or release on the
134// menu is passed through as if it were on the desktop.
135
136void CConn::handleEvent(TXWindow* w, XEvent* ev)
137{
138 KeySym ks;
139 char str[256];
140
141 switch (ev->type) {
142 case KeyPress:
143 case KeyRelease:
144 XLookupString(&ev->xkey, str, 256, &ks, NULL);
145 if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
146 if (w == desktop && ev->type == KeyPress) {
147 showMenu(ev->xkey.x_root, ev->xkey.y_root);
148 break;
149 } else if (w == &menu) {
150 if (ev->type == KeyPress) menu.unmap();
151 desktopEventHandler->handleEvent(w, ev);
152 break;
153 }
154 }
155 // drop through
156
157 default:
158 if (w == desktop) desktopEventHandler->handleEvent(w, ev);
159 else if (w == &menu) menuEventHandler->handleEvent(w, ev);
160 }
161}
162
163// blockCallback() is called when reading from the socket would block. We
164// process X events until the socket is ready for reading again.
165
166void CConn::blockCallback() {
167 fd_set rfds;
168 do {
169 struct timeval tv;
170 struct timeval* tvp = 0;
171
172 // Process any incoming X events
173 TXWindow::handleXEvents(dpy);
174
175 // Process expired timers and get the time until the next one
176 int timeoutMs = Timer::checkTimeouts();
177 if (timeoutMs) {
178 tv.tv_sec = timeoutMs / 1000;
179 tv.tv_usec = (timeoutMs % 1000) * 1000;
180 tvp = &tv;
181 }
182
183 // If there are X requests pending then poll, don't wait!
184 if (XPending(dpy)) {
185 tv.tv_usec = tv.tv_sec = 0;
186 tvp = &tv;
187 }
188
189 // Wait for X events, VNC traffic, or the next timer expiry
190 FD_ZERO(&rfds);
191 FD_SET(ConnectionNumber(dpy), &rfds);
192 FD_SET(sock->getFd(), &rfds);
193 int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
194 if (n < 0) throw rdr::SystemException("select",errno);
195 } while (!(FD_ISSET(sock->getFd(), &rfds)));
196}
197
198
199// getPasswd() is called by the CSecurity object when it needs us to read a
200// password from the user.
201
202void CConn::getUserPasswd(char** user, char** password)
203{
204 CharArray passwordFileStr(passwordFile.getData());
205 if (!user && passwordFileStr.buf[0]) {
206 FILE* fp = fopen(passwordFileStr.buf, "r");
207 if (!fp) throw rfb::Exception("Opening password file failed");
208 ObfuscatedPasswd obfPwd(256);
209 obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
210 fclose(fp);
211 PlainPasswd passwd(obfPwd);
212 *password = passwd.takeBuf();
213 return;
214 }
215
216 const char* secType = secTypeName(getCurrentCSecurity()->getType());
217 const char* titlePrefix = _("VNC authentication");
218 unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
219 CharArray title(titleLen);
220 snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
221 PasswdDialog dlg(dpy, title.buf, !user);
222 if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
223 if (user)
Adam Tkacd36b6262009-09-04 10:57:20 +0000224 *user = strDup(dlg.userEntry.getText());
225 *password = strDup(dlg.passwdEntry.getText());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000226}
227
228
229// CConnection callback methods
230
231// getCSecurity() gets the appropriate CSecurity object for the security
232// types which we support.
233CSecurity* CConn::getCSecurity(int secType) {
234 switch (secType) {
235 case secTypeNone:
236 return new CSecurityNone();
237 case secTypeVncAuth:
238 return new CSecurityVncAuth(this);
239 default:
240 throw rfb::Exception("Unsupported secType?");
241 }
242}
243
244// serverInit() is called when the serverInit message has been received. At
245// this point we create the desktop window and display it. We also tell the
246// server the pixel format and encodings to use and request the first update.
247void CConn::serverInit() {
248 CConnection::serverInit();
249
250 // If using AutoSelect with old servers, start in FullColor
251 // mode. See comment in autoSelectFormatAndEncoding.
252 if (cp.beforeVersion(3, 8) && autoSelect) {
253 fullColour = true;
254 }
255
256 serverPF = cp.pf();
257 desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
258 desktopEventHandler = desktop->setEventHandler(this);
259 desktop->addEventMask(KeyPressMask | KeyReleaseMask);
260 fullColourPF = desktop->getPF();
261 if (!serverPF.trueColour)
262 fullColour = true;
263 recreateViewport();
264 formatChange = encodingChange = true;
265 requestNewUpdate();
266}
267
268// setDesktopSize() is called when the desktop size changes (including when
269// it is set initially).
270void CConn::setDesktopSize(int w, int h) {
271 CConnection::setDesktopSize(w,h);
Pierre Ossman49f88222009-03-20 13:02:50 +0000272 resizeFramebuffer();
273}
274
275// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
Pierre Ossmancbd1b2c2009-03-20 16:05:04 +0000276void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
277 const rfb::ScreenSet& layout) {
278 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
Pierre Ossman49f88222009-03-20 13:02:50 +0000279
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000280 if ((reason == reasonClient) && (result != resultSuccess)) {
281 vlog.error("SetDesktopSize failed: %d", result);
Pierre Ossman49f88222009-03-20 13:02:50 +0000282 return;
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000283 }
Pierre Ossman49f88222009-03-20 13:02:50 +0000284
285 resizeFramebuffer();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000286}
287
Peter Åstrandc39e0782009-01-15 12:21:42 +0000288// setName() is called when the desktop name changes
289void CConn::setName(const char* name) {
290 CConnection::setName(name);
Peter Åstrand051a83a2009-01-15 13:36:03 +0000291
292 CharArray windowNameStr(windowName.getData());
293 if (!windowNameStr.buf[0]) {
294 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000295 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Peter Åstrand051a83a2009-01-15 13:36:03 +0000296 }
297
Peter Åstrandc39e0782009-01-15 12:21:42 +0000298 if (viewport) {
Peter Åstrand051a83a2009-01-15 13:36:03 +0000299 viewport->setName(windowNameStr.buf);
Peter Åstrandc39e0782009-01-15 12:21:42 +0000300 }
301}
302
Pierre Ossman42d20e72009-04-01 14:42:34 +0000303// framebufferUpdateStart() is called at the beginning of an update.
304// Here we try to send out a new framebuffer update request so that the
305// next update can be sent out in parallel with us decoding the current
306// one. We cannot do this if we're in the middle of a format change
307// though.
308void CConn::framebufferUpdateStart() {
309 if (!formatChange)
310 requestNewUpdate();
311}
312
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000313// framebufferUpdateEnd() is called at the end of an update.
314// For each rectangle, the FdInStream will have timed the speed
315// of the connection, allowing us to select format and encoding
316// appropriately, and then request another incremental update.
317void CConn::framebufferUpdateEnd() {
318 if (debugDelay != 0) {
319 XSync(dpy, False);
320 struct timeval tv;
321 tv.tv_sec = debugDelay / 1000;
322 tv.tv_usec = (debugDelay % 1000) * 1000;
323 select(0, 0, 0, 0, &tv);
324 std::list<rfb::Rect>::iterator i;
325 for (i = debugRects.begin(); i != debugRects.end(); i++) {
326 desktop->invertRect(*i);
327 }
328 debugRects.clear();
329 }
330 desktop->framebufferUpdateEnd();
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000331
332 if (firstUpdate) {
333 int width, height;
334
335 if (cp.supportsSetDesktopSize &&
336 sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
337 ScreenSet layout;
338
339 layout = cp.screenLayout;
340
341 if (layout.num_screens() == 0)
342 layout.add_screen(rfb::Screen());
343 else if (layout.num_screens() != 1) {
344 ScreenSet::iterator iter;
345
346 while (true) {
347 iter = layout.begin();
348 ++iter;
349
350 if (iter == layout.end())
351 break;
352
353 layout.remove_screen(iter->id);
354 }
355 }
356
357 layout.begin()->dimensions.tl.x = 0;
358 layout.begin()->dimensions.tl.y = 0;
359 layout.begin()->dimensions.br.x = width;
360 layout.begin()->dimensions.br.y = height;
361
362 writer()->writeSetDesktopSize(width, height, layout);
363 }
364
365 firstUpdate = false;
366 }
367
Pierre Ossman42d20e72009-04-01 14:42:34 +0000368 // A format change prevented us from sending this before the update,
369 // so make sure to send it now.
370 if (formatChange)
371 requestNewUpdate();
372
373 // Compute new settings based on updated bandwidth values
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000374 if (autoSelect)
375 autoSelectFormatAndEncoding();
Pierre Ossman42d20e72009-04-01 14:42:34 +0000376
377 // Make sure that the X11 handling and the timers gets some CPU time
378 // in case of back to back framebuffer updates.
379 TXWindow::handleXEvents(dpy);
380 Timer::checkTimeouts();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000381}
382
383// The rest of the callbacks are fairly self-explanatory...
384
385void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
386{
387 desktop->setColourMapEntries(firstColour, nColours, rgbs);
388}
389
390void CConn::bell() { XBell(dpy, 0); }
391
Adam Tkac141c1722009-06-16 10:43:59 +0000392void CConn::serverCutText(const char* str, rdr::U32 len) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000393 desktop->serverCutText(str,len);
394}
395
396// We start timing on beginRect and stop timing on endRect, to
397// avoid skewing the bandwidth estimation as a result of the server
398// being slow or the network having high latency
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000399void CConn::beginRect(const Rect& r, int encoding)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000400{
401 sock->inStream().startTiming();
402 if (encoding != encodingCopyRect) {
403 lastServerEncoding = encoding;
404 }
405}
406
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000407void CConn::endRect(const Rect& r, int encoding)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000408{
409 sock->inStream().stopTiming();
410 if (debugDelay != 0) {
411 desktop->invertRect(r);
412 debugRects.push_back(r);
413 }
414}
415
416void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
417 desktop->fillRect(r,p);
418}
419void CConn::imageRect(const rfb::Rect& r, void* p) {
420 desktop->imageRect(r,p);
421}
422void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
423 desktop->copyRect(r,sx,sy);
424}
425void CConn::setCursor(int width, int height, const Point& hotspot,
426 void* data, void* mask) {
427 desktop->setCursor(width, height, hotspot, data, mask);
428}
429
430
431// Menu stuff - menuSelect() is called when the user selects a menu option.
432
433enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
434 ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
435
436void CConn::initMenu() {
437 menuEventHandler = menu.setEventHandler(this);
438 menu.addEventMask(KeyPressMask | KeyReleaseMask);
439 menu.addEntry(_("Exit viewer"), ID_EXIT);
440 menu.addEntry(0, 0);
441 menu.addEntry(_("Full screen"), ID_FULLSCREEN);
442 menu.check(ID_FULLSCREEN, fullScreen);
443 menu.addEntry(0, 0);
444 menu.addEntry(_("Ctrl"), ID_CTRL);
445 menu.addEntry(_("Alt"), ID_ALT);
446 CharArray menuKeyStr(menuKey.getData());
447 CharArray sendMenuKey(64);
448 snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
449 menu.addEntry(sendMenuKey.buf, ID_F8);
450 menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
451 menu.addEntry(0, 0);
452 menu.addEntry(_("Refresh screen"), ID_REFRESH);
453 menu.addEntry(0, 0);
454 menu.addEntry(_("New connection..."), ID_NEWCONN);
455 menu.addEntry(_("Options..."), ID_OPTIONS);
456 menu.addEntry(_("Connection info..."), ID_INFO);
Peter Åstrandf55ca172009-08-27 12:22:10 +0000457 menu.addEntry(_("About TigerVNC viewer..."), ID_ABOUT);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000458 menu.addEntry(0, 0);
459 menu.addEntry(_("Dismiss menu"), ID_DISMISS);
460 menu.toplevel(_("VNC Menu"), this);
461 menu.setBorderWidth(1);
462}
463
464void CConn::showMenu(int x, int y) {
465 menu.check(ID_FULLSCREEN, fullScreen);
466 if (x + menu.width() > viewport->width())
467 x = viewport->width() - menu.width();
468 if (y + menu.height() > viewport->height())
469 y = viewport->height() - menu.height();
470 menu.move(x, y);
471 menu.raise();
472 menu.map();
473}
474
475void CConn::menuSelect(long id, TXMenu* m) {
476 switch (id) {
477 case ID_NEWCONN:
478 {
479 menu.unmap();
480 if (fullScreen) {
481 fullScreen = false;
482 if (viewport) recreateViewport();
483 }
484 int pid = fork();
485 if (pid < 0) { perror("fork"); exit(1); }
486 if (pid == 0) {
487 delete sock;
488 close(ConnectionNumber(dpy));
489 struct timeval tv;
490 tv.tv_sec = 0;
491 tv.tv_usec = 200*1000;
492 select(0, 0, 0, 0, &tv);
Adam Tkacfee32e32008-10-10 15:48:22 +0000493 execlp(programName, programName, NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000494 perror("execlp"); exit(1);
495 }
496 break;
497 }
498 case ID_OPTIONS:
499 menu.unmap();
500 options.show();
501 break;
502 case ID_INFO:
503 {
504 menu.unmap();
505 char pfStr[100];
506 char spfStr[100];
507 cp.pf().print(pfStr, 100);
508 serverPF.print(spfStr, 100);
509 int secType = getCurrentCSecurity()->getType();
510 char infoText[1024];
511 snprintf(infoText, sizeof(infoText),
512 _("Desktop name: %.80s\n"
513 "Host: %.80s port: %d\n"
514 "Size: %d x %d\n"
515 "Pixel format: %s\n"
516 "(server default %s)\n"
517 "Requested encoding: %s\n"
518 "Last used encoding: %s\n"
519 "Line speed estimate: %d kbit/s\n"
520 "Protocol version: %d.%d\n"
521 "Security method: %s\n"),
522 cp.name(), serverHost, serverPort, cp.width, cp.height,
523 pfStr, spfStr, encodingName(currentEncoding),
524 encodingName(lastServerEncoding),
525 sock->inStream().kbitsPerSecond(),
526 cp.majorVersion, cp.minorVersion,
527 secTypeName(secType));
528 info.setText(infoText);
529 info.show();
530 break;
531 }
532 case ID_FULLSCREEN:
533 menu.unmap();
534 fullScreen = !fullScreen;
535 if (viewport) recreateViewport();
536 break;
537 case ID_REFRESH:
538 menu.unmap();
539 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
540 false);
541 break;
542 case ID_F8:
543 menu.unmap();
544 if (!viewOnly) {
545 writer()->keyEvent(menuKeysym, true);
546 writer()->keyEvent(menuKeysym, false);
547 }
548 break;
549 case ID_CTRLALTDEL:
550 menu.unmap();
551 if (!viewOnly) {
552 writer()->keyEvent(XK_Control_L, true);
553 writer()->keyEvent(XK_Alt_L, true);
554 writer()->keyEvent(XK_Delete, true);
555 writer()->keyEvent(XK_Delete, false);
556 writer()->keyEvent(XK_Alt_L, false);
557 writer()->keyEvent(XK_Control_L, false);
558 }
559 break;
560 case ID_CTRL:
561 menu.unmap();
562 if (!viewOnly) {
563 ctrlDown = !ctrlDown;
564 writer()->keyEvent(XK_Control_L, ctrlDown);
565 menu.check(ID_CTRL, ctrlDown);
566 }
567 break;
568 case ID_ALT:
569 menu.unmap();
570 if (!viewOnly) {
571 altDown = !altDown;
572 writer()->keyEvent(XK_Alt_L, altDown);
573 menu.check(ID_ALT, altDown);
574 }
575 break;
576 case ID_ABOUT:
577 menu.unmap();
578 about.show();
579 break;
580 case ID_DISMISS:
581 menu.unmap();
582 break;
583 case ID_EXIT:
584 exit(1);
585 break;
586 }
587}
588
589
590// OptionsDialogCallback. setOptions() sets the options dialog's checkboxes
591// etc to reflect our flags. getOptions() sets our flags according to the
592// options dialog's checkboxes.
593
594void CConn::setOptions() {
595 char digit[2] = "0";
596 options.autoSelect.checked(autoSelect);
597 options.fullColour.checked(fullColour);
598 options.veryLowColour.checked(!fullColour && lowColourLevel == 0);
599 options.lowColour.checked(!fullColour && lowColourLevel == 1);
600 options.mediumColour.checked(!fullColour && lowColourLevel == 2);
601 options.tight.checked(currentEncoding == encodingTight);
602 options.zrle.checked(currentEncoding == encodingZRLE);
603 options.hextile.checked(currentEncoding == encodingHextile);
604 options.raw.checked(currentEncoding == encodingRaw);
605
606 options.customCompressLevel.checked(customCompressLevel);
607 digit[0] = '0' + compressLevel;
608 options.compressLevel.setText(digit);
609 options.noJpeg.checked(!noJpeg);
610 digit[0] = '0' + qualityLevel;
611 options.qualityLevel.setText(digit);
612
613 options.viewOnly.checked(viewOnly);
614 options.acceptClipboard.checked(acceptClipboard);
615 options.sendClipboard.checked(sendClipboard);
616 options.sendPrimary.checked(sendPrimary);
617 if (state() == RFBSTATE_NORMAL)
618 options.shared.disabled(true);
619 else
620 options.shared.checked(shared);
621 options.fullScreen.checked(fullScreen);
622 options.useLocalCursor.checked(useLocalCursor);
623 options.dotWhenNoCursor.checked(dotWhenNoCursor);
624}
625
626void CConn::getOptions() {
627 autoSelect = options.autoSelect.checked();
628 if (fullColour != options.fullColour.checked())
629 formatChange = true;
630 fullColour = options.fullColour.checked();
631 if (!fullColour) {
632 int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
633 options.lowColour.checked() ? 1 : 2);
634 if (newLowColourLevel != lowColourLevel) {
635 lowColourLevel.setParam(newLowColourLevel);
636 formatChange = true;
637 }
638 }
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000639 int newEncoding = (options.tight.checked() ? encodingTight :
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000640 options.zrle.checked() ? encodingZRLE :
641 options.hextile.checked() ? encodingHextile :
642 encodingRaw);
643 if (newEncoding != currentEncoding) {
644 currentEncoding = newEncoding;
645 encodingChange = true;
646 }
647
648 customCompressLevel.setParam(options.customCompressLevel.checked());
649 if (cp.customCompressLevel != customCompressLevel) {
650 cp.customCompressLevel = customCompressLevel;
651 encodingChange = true;
652 }
653 compressLevel.setParam(options.compressLevel.getText());
654 if (cp.compressLevel != compressLevel) {
655 cp.compressLevel = compressLevel;
656 encodingChange = true;
657 }
658 noJpeg.setParam(!options.noJpeg.checked());
659 if (cp.noJpeg != noJpeg) {
660 cp.noJpeg = noJpeg;
661 encodingChange = true;
662 }
663 qualityLevel.setParam(options.qualityLevel.getText());
664 if (cp.qualityLevel != qualityLevel) {
665 cp.qualityLevel = qualityLevel;
666 encodingChange = true;
667 }
668
669 viewOnly.setParam(options.viewOnly.checked());
670 acceptClipboard.setParam(options.acceptClipboard.checked());
671 sendClipboard.setParam(options.sendClipboard.checked());
672 sendPrimary.setParam(options.sendPrimary.checked());
673 shared = options.shared.checked();
674 setShared(shared);
675 if (fullScreen != options.fullScreen.checked()) {
676 fullScreen = options.fullScreen.checked();
677 if (viewport) recreateViewport();
678 }
679 useLocalCursor.setParam(options.useLocalCursor.checked());
680 if (cp.supportsLocalCursor != useLocalCursor) {
681 cp.supportsLocalCursor = useLocalCursor;
682 encodingChange = true;
683 if (desktop)
684 desktop->resetLocalCursor();
685 }
686 dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
Adam Tkaccc247c92010-01-11 13:46:48 +0000687 if (desktop)
688 desktop->setNoCursor();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000689 checkEncodings();
690}
691
Pierre Ossman49f88222009-03-20 13:02:50 +0000692void CConn::resizeFramebuffer()
693{
694 if (!desktop)
695 return;
696 if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
697 return;
698
699 desktop->resize(cp.width, cp.height);
700 recreateViewport();
701}
702
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000703void CConn::recreateViewport()
704{
705 TXViewport* oldViewport = viewport;
706 viewport = new TXViewport(dpy, cp.width, cp.height);
707 desktop->setViewport(viewport);
708 CharArray windowNameStr(windowName.getData());
709 if (!windowNameStr.buf[0]) {
710 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000711 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000712 }
713 viewport->toplevel(windowNameStr.buf, this, argc, argv);
714 viewport->setBumpScroll(fullScreen);
715 XSetWindowAttributes attr;
716 attr.override_redirect = fullScreen;
717 XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
718 XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
719 XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
720 XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
721 XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
722 reconfigureViewport();
723 menu.setTransientFor(viewport->win());
724 viewport->map();
725 if (fullScreen) {
726 XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
727 CurrentTime);
728 } else {
729 XUngrabKeyboard(dpy, CurrentTime);
730 }
731 if (oldViewport) delete oldViewport;
732}
733
734void CConn::reconfigureViewport()
735{
736 viewport->setMaxSize(cp.width, cp.height);
737 if (fullScreen) {
738 viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
739 DisplayHeight(dpy,DefaultScreen(dpy)));
740 } else {
741 int w = cp.width;
742 int h = cp.height;
743 if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
744 w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
745 if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
746 h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
747
748 int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
749 int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
750
751 CharArray geometryStr(geometry.getData());
752 viewport->setGeometry(geometryStr.buf, x, y, w, h);
753 }
754}
755
Pierre Ossman78b23592009-03-12 12:25:11 +0000756// Note: The method below is duplicated in win/vncviewer/CConn.cxx!
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000757
758// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
759// to the connection speed:
760//
Pierre Ossman78b23592009-03-12 12:25:11 +0000761// First we wait for at least one second of bandwidth measurement.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000762//
Pierre Ossman78b23592009-03-12 12:25:11 +0000763// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
764// which should be perceptually lossless.
765//
766// If the bandwidth is below that, we choose a more lossy JPEG quality.
767//
768// If the bandwidth drops below 256 Kbps, we switch to palette mode.
769//
770// Note: The system here is fairly arbitrary and should be replaced
771// with something more intelligent at the server end.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000772//
773void CConn::autoSelectFormatAndEncoding()
774{
775 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000776 unsigned int timeWaited = sock->inStream().timeWaited();
Pierre Ossman78b23592009-03-12 12:25:11 +0000777 bool newFullColour = fullColour;
778 int newQualityLevel = qualityLevel;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000779
Pierre Ossman78b23592009-03-12 12:25:11 +0000780 // Always use Tight
781 currentEncoding = encodingTight;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000782
Pierre Ossman78b23592009-03-12 12:25:11 +0000783 // Check that we have a decent bandwidth measurement
784 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000785 return;
Pierre Ossman78b23592009-03-12 12:25:11 +0000786
787 // Select appropriate quality level
788 if (!noJpeg) {
789 if (kbitsPerSecond > 16000)
790 newQualityLevel = 8;
791 else
792 newQualityLevel = 6;
793
794 if (newQualityLevel != qualityLevel) {
795 vlog.info("Throughput %d kbit/s - changing to quality %d ",
796 kbitsPerSecond, newQualityLevel);
797 cp.qualityLevel = newQualityLevel;
798 qualityLevel.setParam(newQualityLevel);
799 encodingChange = true;
800 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000801 }
802
803 if (cp.beforeVersion(3, 8)) {
804 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
805 // cursors "asynchronously". If this happens in the middle of a
806 // pixel format change, the server will encode the cursor with
807 // the old format, but the client will try to decode it
808 // according to the new format. This will lead to a
809 // crash. Therefore, we do not allow automatic format change for
810 // old servers.
811 return;
812 }
813
814 // Select best color level
815 newFullColour = (kbitsPerSecond > 256);
816 if (newFullColour != fullColour) {
817 vlog.info("Throughput %d kbit/s - full color is now %s",
818 kbitsPerSecond,
819 newFullColour ? "enabled" : "disabled");
820 fullColour = newFullColour;
821 formatChange = true;
822 }
823}
824
825// checkEncodings() sends a setEncodings message if one is needed.
826void CConn::checkEncodings()
827{
828 if (encodingChange && writer()) {
829 vlog.info("Using %s encoding",encodingName(currentEncoding));
830 writer()->writeSetEncodings(currentEncoding, true);
831 encodingChange = false;
832 }
833}
834
835// requestNewUpdate() requests an update from the server, having set the
836// format and encoding appropriately.
837void CConn::requestNewUpdate()
838{
839 if (formatChange) {
840 if (fullColour) {
841 desktop->setPF(fullColourPF);
842 } else {
843 if (lowColourLevel == 0)
844 desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
845 else if (lowColourLevel == 1)
846 desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
847 else
848 desktop->setPF(PixelFormat(8,8,0,0));
849 }
850 char str[256];
851 desktop->getPF().print(str, 256);
852 vlog.info("Using pixel format %s",str);
853 cp.setPF(desktop->getPF());
854 writer()->writeSetPixelFormat(cp.pf());
855 }
856 checkEncodings();
857 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
858 !formatChange);
859 formatChange = false;
860}