blob: f93a3d60da1294771395b350c6d39a8da5db298e [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>
Adam Tkac5a0caed2010-04-23 13:58:10 +000027#include <rfb/Security.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000028#include <rfb/CSecurityNone.h>
29#include <rfb/CSecurityVncAuth.h>
Pierre Ossmanfa955d22010-09-29 14:10:04 +000030#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +000031#include <rfb/CSecurityTLS.h>
Pierre Ossmanfa955d22010-09-29 14:10:04 +000032#endif
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000033#include <rfb/Hostname.h>
34#include <rfb/LogWriter.h>
35#include <rfb/util.h>
36#include <rfb/Password.h>
Pierre Ossman49f88222009-03-20 13:02:50 +000037#include <rfb/screenTypes.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000038#include <network/TcpSocket.h>
Adam Tkac12c0fc32010-03-04 15:33:11 +000039#include <cassert>
Adam Tkacb7bafc52010-09-15 14:13:17 +000040#include <list>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000041
42#include "TXViewport.h"
43#include "DesktopWindow.h"
44#include "ServerDialog.h"
45#include "PasswdDialog.h"
46#include "parameters.h"
47
Adam Tkacb7bafc52010-09-15 14:13:17 +000048using namespace rdr;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000049using namespace rfb;
Adam Tkacb7bafc52010-09-15 14:13:17 +000050using namespace std;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000051
52static rfb::LogWriter vlog("CConn");
53
54IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
55 "pixel data - a debugging feature", 0);
56
57StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
58 "F8");
59StringParameter windowName("name", "The X window name", "");
60
61CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
62 char* vncServerName, bool reverse)
63 : dpy(dpy_), argc(argc_),
64 argv(argv_), serverHost(0), serverPort(0), sock(sock_), viewport(0),
65 desktop(0), desktopEventHandler(0),
Pierre Ossman7dfa22e2009-03-12 13:03:22 +000066 currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000067 fullColour(::fullColour),
68 autoSelect(::autoSelect), shared(::shared), formatChange(false),
69 encodingChange(false), sameMachine(false), fullScreen(::fullScreen),
70 ctrlDown(false), altDown(false),
71 menuKeysym(0), menu(dpy, this), options(dpy, this), about(dpy), info(dpy),
Adam Tkac12c0fc32010-03-04 15:33:11 +000072 reverseConnection(reverse), firstUpdate(true), pendingUpdate(false)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000073{
74 CharArray menuKeyStr(menuKey.getData());
75 menuKeysym = XStringToKeysym(menuKeyStr.buf);
76
77 setShared(shared);
Adam Tkac27b2f772010-11-18 13:33:57 +000078 CSecurity::upg = this; /* Security instance is created in CConnection constructor. */
79#ifdef HAVE_GNUTLS
80 CSecurityTLS::msg = this;
81#endif
Adam Tkacf324dc42010-04-23 14:10:17 +000082
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000083 CharArray encStr(preferredEncoding.getData());
84 int encNum = encodingNum(encStr.buf);
85 if (encNum != -1) {
86 currentEncoding = encNum;
87 }
88 cp.supportsDesktopResize = true;
Pierre Ossman49f88222009-03-20 13:02:50 +000089 cp.supportsExtendedDesktopSize = true;
Peter Åstrandc39e0782009-01-15 12:21:42 +000090 cp.supportsDesktopRename = true;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000091 cp.supportsLocalCursor = useLocalCursor;
92 cp.customCompressLevel = customCompressLevel;
93 cp.compressLevel = compressLevel;
94 cp.noJpeg = noJpeg;
95 cp.qualityLevel = qualityLevel;
96 initMenu();
97
98 if (sock) {
99 char* name = sock->getPeerEndpoint();
100 vlog.info("Accepted connection from %s", name);
101 if (name) free(name);
102 } else {
103 if (vncServerName) {
104 getHostAndPort(vncServerName, &serverHost, &serverPort);
105 } else {
106 ServerDialog dlg(dpy, &options, &about);
107 if (!dlg.show() || dlg.entry.getText()[0] == 0) {
108 exit(1);
109 }
110 getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
111 }
112
113 sock = new network::TcpSocket(serverHost, serverPort);
114 vlog.info("connected to host %s port %d", serverHost, serverPort);
115 }
116
117 sameMachine = sock->sameMachine();
118 sock->inStream().setBlockCallback(this);
Adam Tkac0b428712011-02-01 15:06:03 +0000119 setServerName(serverHost);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000120 setStreams(&sock->inStream(), &sock->outStream());
121 initialiseProtocol();
122}
123
124CConn::~CConn() {
125 free(serverHost);
126 delete desktop;
127 delete viewport;
128 delete sock;
129}
130
Adam Tkac27b2f772010-11-18 13:33:57 +0000131bool CConn::showMsgBox(int flags, const char* title, const char* text)
132{
Adam Tkac1e3c6642010-12-08 14:42:16 +0000133 CharArray titleText(12 + strlen(title) + 1);
Adam Tkac27b2f772010-11-18 13:33:57 +0000134 sprintf(titleText.buf, "VNC Viewer: %s", title);
135
136 TXMsgBox msgBox(dpy,text,flags,titleText.buf);
137 return msgBox.show();
138}
139
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000140// deleteWindow() is called when the user closes the desktop or menu windows.
141
142void CConn::deleteWindow(TXWindow* w) {
143 if (w == &menu) {
144 menu.unmap();
145 } else if (w == viewport) {
146 exit(1);
147 }
148}
149
150// handleEvent() filters all events on the desktop and menu. Most are passed
151// straight through. The exception is the F8 key. When pressed on the
152// desktop, it is used to bring up the menu. An F8 press or release on the
153// menu is passed through as if it were on the desktop.
154
155void CConn::handleEvent(TXWindow* w, XEvent* ev)
156{
157 KeySym ks;
158 char str[256];
159
160 switch (ev->type) {
161 case KeyPress:
162 case KeyRelease:
163 XLookupString(&ev->xkey, str, 256, &ks, NULL);
164 if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
165 if (w == desktop && ev->type == KeyPress) {
166 showMenu(ev->xkey.x_root, ev->xkey.y_root);
167 break;
168 } else if (w == &menu) {
169 if (ev->type == KeyPress) menu.unmap();
170 desktopEventHandler->handleEvent(w, ev);
171 break;
172 }
173 }
174 // drop through
175
176 default:
177 if (w == desktop) desktopEventHandler->handleEvent(w, ev);
178 else if (w == &menu) menuEventHandler->handleEvent(w, ev);
179 }
180}
181
182// blockCallback() is called when reading from the socket would block. We
183// process X events until the socket is ready for reading again.
184
185void CConn::blockCallback() {
186 fd_set rfds;
187 do {
188 struct timeval tv;
189 struct timeval* tvp = 0;
190
191 // Process any incoming X events
192 TXWindow::handleXEvents(dpy);
193
194 // Process expired timers and get the time until the next one
195 int timeoutMs = Timer::checkTimeouts();
196 if (timeoutMs) {
197 tv.tv_sec = timeoutMs / 1000;
198 tv.tv_usec = (timeoutMs % 1000) * 1000;
199 tvp = &tv;
200 }
201
202 // If there are X requests pending then poll, don't wait!
203 if (XPending(dpy)) {
204 tv.tv_usec = tv.tv_sec = 0;
205 tvp = &tv;
206 }
207
208 // Wait for X events, VNC traffic, or the next timer expiry
209 FD_ZERO(&rfds);
210 FD_SET(ConnectionNumber(dpy), &rfds);
211 FD_SET(sock->getFd(), &rfds);
212 int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
213 if (n < 0) throw rdr::SystemException("select",errno);
214 } while (!(FD_ISSET(sock->getFd(), &rfds)));
215}
216
217
218// getPasswd() is called by the CSecurity object when it needs us to read a
219// password from the user.
220
221void CConn::getUserPasswd(char** user, char** password)
222{
223 CharArray passwordFileStr(passwordFile.getData());
224 if (!user && passwordFileStr.buf[0]) {
225 FILE* fp = fopen(passwordFileStr.buf, "r");
226 if (!fp) throw rfb::Exception("Opening password file failed");
227 ObfuscatedPasswd obfPwd(256);
228 obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
229 fclose(fp);
230 PlainPasswd passwd(obfPwd);
231 *password = passwd.takeBuf();
232 return;
233 }
234
Adam Tkacf324dc42010-04-23 14:10:17 +0000235 const char* secType = secTypeName(csecurity->getType());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000236 const char* titlePrefix = _("VNC authentication");
237 unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
238 CharArray title(titleLen);
239 snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
240 PasswdDialog dlg(dpy, title.buf, !user);
241 if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
242 if (user)
Adam Tkacd36b6262009-09-04 10:57:20 +0000243 *user = strDup(dlg.userEntry.getText());
244 *password = strDup(dlg.passwdEntry.getText());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000245}
246
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000247// CConnection callback methods
248
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000249// serverInit() is called when the serverInit message has been received. At
250// this point we create the desktop window and display it. We also tell the
251// server the pixel format and encodings to use and request the first update.
252void CConn::serverInit() {
253 CConnection::serverInit();
254
255 // If using AutoSelect with old servers, start in FullColor
256 // mode. See comment in autoSelectFormatAndEncoding.
257 if (cp.beforeVersion(3, 8) && autoSelect) {
258 fullColour = true;
259 }
260
261 serverPF = cp.pf();
262 desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
263 desktopEventHandler = desktop->setEventHandler(this);
264 desktop->addEventMask(KeyPressMask | KeyReleaseMask);
265 fullColourPF = desktop->getPF();
266 if (!serverPF.trueColour)
267 fullColour = true;
268 recreateViewport();
269 formatChange = encodingChange = true;
270 requestNewUpdate();
271}
272
273// setDesktopSize() is called when the desktop size changes (including when
274// it is set initially).
275void CConn::setDesktopSize(int w, int h) {
276 CConnection::setDesktopSize(w,h);
Pierre Ossman49f88222009-03-20 13:02:50 +0000277 resizeFramebuffer();
278}
279
280// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
Pierre Ossmancbd1b2c2009-03-20 16:05:04 +0000281void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
282 const rfb::ScreenSet& layout) {
283 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
Pierre Ossman49f88222009-03-20 13:02:50 +0000284
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000285 if ((reason == reasonClient) && (result != resultSuccess)) {
286 vlog.error("SetDesktopSize failed: %d", result);
Pierre Ossman49f88222009-03-20 13:02:50 +0000287 return;
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000288 }
Pierre Ossman49f88222009-03-20 13:02:50 +0000289
290 resizeFramebuffer();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000291}
292
Peter Åstrandc39e0782009-01-15 12:21:42 +0000293// setName() is called when the desktop name changes
294void CConn::setName(const char* name) {
295 CConnection::setName(name);
Peter Åstrand051a83a2009-01-15 13:36:03 +0000296
297 CharArray windowNameStr(windowName.getData());
298 if (!windowNameStr.buf[0]) {
299 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000300 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Peter Åstrand051a83a2009-01-15 13:36:03 +0000301 }
302
Peter Åstrandc39e0782009-01-15 12:21:42 +0000303 if (viewport) {
Peter Åstrand051a83a2009-01-15 13:36:03 +0000304 viewport->setName(windowNameStr.buf);
Peter Åstrandc39e0782009-01-15 12:21:42 +0000305 }
306}
307
Pierre Ossman42d20e72009-04-01 14:42:34 +0000308// framebufferUpdateStart() is called at the beginning of an update.
309// Here we try to send out a new framebuffer update request so that the
310// next update can be sent out in parallel with us decoding the current
311// one. We cannot do this if we're in the middle of a format change
312// though.
313void CConn::framebufferUpdateStart() {
Adam Tkac12c0fc32010-03-04 15:33:11 +0000314 if (!formatChange) {
315 pendingUpdate = true;
Pierre Ossman42d20e72009-04-01 14:42:34 +0000316 requestNewUpdate();
Adam Tkac12c0fc32010-03-04 15:33:11 +0000317 } else
318 pendingUpdate = false;
Pierre Ossman42d20e72009-04-01 14:42:34 +0000319}
320
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000321// framebufferUpdateEnd() is called at the end of an update.
322// For each rectangle, the FdInStream will have timed the speed
323// of the connection, allowing us to select format and encoding
324// appropriately, and then request another incremental update.
325void CConn::framebufferUpdateEnd() {
326 if (debugDelay != 0) {
327 XSync(dpy, False);
328 struct timeval tv;
329 tv.tv_sec = debugDelay / 1000;
330 tv.tv_usec = (debugDelay % 1000) * 1000;
331 select(0, 0, 0, 0, &tv);
332 std::list<rfb::Rect>::iterator i;
333 for (i = debugRects.begin(); i != debugRects.end(); i++) {
334 desktop->invertRect(*i);
335 }
336 debugRects.clear();
337 }
338 desktop->framebufferUpdateEnd();
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000339
340 if (firstUpdate) {
341 int width, height;
342
343 if (cp.supportsSetDesktopSize &&
344 sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
345 ScreenSet layout;
346
347 layout = cp.screenLayout;
348
349 if (layout.num_screens() == 0)
350 layout.add_screen(rfb::Screen());
351 else if (layout.num_screens() != 1) {
352 ScreenSet::iterator iter;
353
354 while (true) {
355 iter = layout.begin();
356 ++iter;
357
358 if (iter == layout.end())
359 break;
360
361 layout.remove_screen(iter->id);
362 }
363 }
364
365 layout.begin()->dimensions.tl.x = 0;
366 layout.begin()->dimensions.tl.y = 0;
367 layout.begin()->dimensions.br.x = width;
368 layout.begin()->dimensions.br.y = height;
369
370 writer()->writeSetDesktopSize(width, height, layout);
371 }
372
373 firstUpdate = false;
374 }
375
Pierre Ossman42d20e72009-04-01 14:42:34 +0000376 // A format change prevented us from sending this before the update,
377 // so make sure to send it now.
Adam Tkac12c0fc32010-03-04 15:33:11 +0000378 if (formatChange && !pendingUpdate)
Pierre Ossman42d20e72009-04-01 14:42:34 +0000379 requestNewUpdate();
380
381 // Compute new settings based on updated bandwidth values
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000382 if (autoSelect)
383 autoSelectFormatAndEncoding();
Pierre Ossman42d20e72009-04-01 14:42:34 +0000384
385 // Make sure that the X11 handling and the timers gets some CPU time
386 // in case of back to back framebuffer updates.
387 TXWindow::handleXEvents(dpy);
388 Timer::checkTimeouts();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000389}
390
391// The rest of the callbacks are fairly self-explanatory...
392
393void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
394{
395 desktop->setColourMapEntries(firstColour, nColours, rgbs);
396}
397
398void CConn::bell() { XBell(dpy, 0); }
399
Adam Tkac141c1722009-06-16 10:43:59 +0000400void CConn::serverCutText(const char* str, rdr::U32 len) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000401 desktop->serverCutText(str,len);
402}
403
404// We start timing on beginRect and stop timing on endRect, to
405// avoid skewing the bandwidth estimation as a result of the server
406// being slow or the network having high latency
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000407void CConn::beginRect(const Rect& r, int encoding)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000408{
409 sock->inStream().startTiming();
410 if (encoding != encodingCopyRect) {
411 lastServerEncoding = encoding;
412 }
413}
414
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000415void CConn::endRect(const Rect& r, int encoding)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000416{
417 sock->inStream().stopTiming();
418 if (debugDelay != 0) {
419 desktop->invertRect(r);
420 debugRects.push_back(r);
421 }
422}
423
424void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
425 desktop->fillRect(r,p);
426}
427void CConn::imageRect(const rfb::Rect& r, void* p) {
428 desktop->imageRect(r,p);
429}
430void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
431 desktop->copyRect(r,sx,sy);
432}
433void CConn::setCursor(int width, int height, const Point& hotspot,
434 void* data, void* mask) {
435 desktop->setCursor(width, height, hotspot, data, mask);
436}
437
438
439// Menu stuff - menuSelect() is called when the user selects a menu option.
440
441enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
442 ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
443
444void CConn::initMenu() {
445 menuEventHandler = menu.setEventHandler(this);
446 menu.addEventMask(KeyPressMask | KeyReleaseMask);
447 menu.addEntry(_("Exit viewer"), ID_EXIT);
448 menu.addEntry(0, 0);
449 menu.addEntry(_("Full screen"), ID_FULLSCREEN);
450 menu.check(ID_FULLSCREEN, fullScreen);
451 menu.addEntry(0, 0);
452 menu.addEntry(_("Ctrl"), ID_CTRL);
453 menu.addEntry(_("Alt"), ID_ALT);
454 CharArray menuKeyStr(menuKey.getData());
455 CharArray sendMenuKey(64);
456 snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
457 menu.addEntry(sendMenuKey.buf, ID_F8);
458 menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
459 menu.addEntry(0, 0);
460 menu.addEntry(_("Refresh screen"), ID_REFRESH);
461 menu.addEntry(0, 0);
462 menu.addEntry(_("New connection..."), ID_NEWCONN);
463 menu.addEntry(_("Options..."), ID_OPTIONS);
464 menu.addEntry(_("Connection info..."), ID_INFO);
Peter Åstrandf55ca172009-08-27 12:22:10 +0000465 menu.addEntry(_("About TigerVNC viewer..."), ID_ABOUT);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000466 menu.addEntry(0, 0);
467 menu.addEntry(_("Dismiss menu"), ID_DISMISS);
468 menu.toplevel(_("VNC Menu"), this);
469 menu.setBorderWidth(1);
470}
471
472void CConn::showMenu(int x, int y) {
473 menu.check(ID_FULLSCREEN, fullScreen);
474 if (x + menu.width() > viewport->width())
475 x = viewport->width() - menu.width();
476 if (y + menu.height() > viewport->height())
477 y = viewport->height() - menu.height();
478 menu.move(x, y);
479 menu.raise();
480 menu.map();
481}
482
483void CConn::menuSelect(long id, TXMenu* m) {
484 switch (id) {
485 case ID_NEWCONN:
486 {
487 menu.unmap();
488 if (fullScreen) {
489 fullScreen = false;
490 if (viewport) recreateViewport();
491 }
492 int pid = fork();
493 if (pid < 0) { perror("fork"); exit(1); }
494 if (pid == 0) {
495 delete sock;
496 close(ConnectionNumber(dpy));
497 struct timeval tv;
498 tv.tv_sec = 0;
499 tv.tv_usec = 200*1000;
500 select(0, 0, 0, 0, &tv);
Adam Tkacfee32e32008-10-10 15:48:22 +0000501 execlp(programName, programName, NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000502 perror("execlp"); exit(1);
503 }
504 break;
505 }
506 case ID_OPTIONS:
507 menu.unmap();
508 options.show();
509 break;
510 case ID_INFO:
511 {
512 menu.unmap();
513 char pfStr[100];
514 char spfStr[100];
515 cp.pf().print(pfStr, 100);
516 serverPF.print(spfStr, 100);
Adam Tkacf324dc42010-04-23 14:10:17 +0000517 int secType = csecurity->getType();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000518 char infoText[1024];
519 snprintf(infoText, sizeof(infoText),
520 _("Desktop name: %.80s\n"
521 "Host: %.80s port: %d\n"
522 "Size: %d x %d\n"
523 "Pixel format: %s\n"
524 "(server default %s)\n"
525 "Requested encoding: %s\n"
526 "Last used encoding: %s\n"
527 "Line speed estimate: %d kbit/s\n"
528 "Protocol version: %d.%d\n"
529 "Security method: %s\n"),
530 cp.name(), serverHost, serverPort, cp.width, cp.height,
531 pfStr, spfStr, encodingName(currentEncoding),
532 encodingName(lastServerEncoding),
533 sock->inStream().kbitsPerSecond(),
534 cp.majorVersion, cp.minorVersion,
535 secTypeName(secType));
536 info.setText(infoText);
537 info.show();
538 break;
539 }
540 case ID_FULLSCREEN:
541 menu.unmap();
542 fullScreen = !fullScreen;
543 if (viewport) recreateViewport();
544 break;
545 case ID_REFRESH:
546 menu.unmap();
Adam Tkac12c0fc32010-03-04 15:33:11 +0000547 if (!formatChange) {
548 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
549 false);
550 pendingUpdate = true;
551 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000552 break;
553 case ID_F8:
554 menu.unmap();
555 if (!viewOnly) {
556 writer()->keyEvent(menuKeysym, true);
557 writer()->keyEvent(menuKeysym, false);
558 }
559 break;
560 case ID_CTRLALTDEL:
561 menu.unmap();
562 if (!viewOnly) {
563 writer()->keyEvent(XK_Control_L, true);
564 writer()->keyEvent(XK_Alt_L, true);
565 writer()->keyEvent(XK_Delete, true);
566 writer()->keyEvent(XK_Delete, false);
567 writer()->keyEvent(XK_Alt_L, false);
568 writer()->keyEvent(XK_Control_L, false);
569 }
570 break;
571 case ID_CTRL:
572 menu.unmap();
573 if (!viewOnly) {
574 ctrlDown = !ctrlDown;
575 writer()->keyEvent(XK_Control_L, ctrlDown);
576 menu.check(ID_CTRL, ctrlDown);
577 }
578 break;
579 case ID_ALT:
580 menu.unmap();
581 if (!viewOnly) {
582 altDown = !altDown;
583 writer()->keyEvent(XK_Alt_L, altDown);
584 menu.check(ID_ALT, altDown);
585 }
586 break;
587 case ID_ABOUT:
588 menu.unmap();
589 about.show();
590 break;
591 case ID_DISMISS:
592 menu.unmap();
593 break;
594 case ID_EXIT:
595 exit(1);
596 break;
597 }
598}
599
600
601// OptionsDialogCallback. setOptions() sets the options dialog's checkboxes
602// etc to reflect our flags. getOptions() sets our flags according to the
603// options dialog's checkboxes.
604
605void CConn::setOptions() {
606 char digit[2] = "0";
607 options.autoSelect.checked(autoSelect);
608 options.fullColour.checked(fullColour);
609 options.veryLowColour.checked(!fullColour && lowColourLevel == 0);
610 options.lowColour.checked(!fullColour && lowColourLevel == 1);
611 options.mediumColour.checked(!fullColour && lowColourLevel == 2);
612 options.tight.checked(currentEncoding == encodingTight);
613 options.zrle.checked(currentEncoding == encodingZRLE);
614 options.hextile.checked(currentEncoding == encodingHextile);
615 options.raw.checked(currentEncoding == encodingRaw);
616
617 options.customCompressLevel.checked(customCompressLevel);
618 digit[0] = '0' + compressLevel;
619 options.compressLevel.setText(digit);
620 options.noJpeg.checked(!noJpeg);
621 digit[0] = '0' + qualityLevel;
622 options.qualityLevel.setText(digit);
623
624 options.viewOnly.checked(viewOnly);
625 options.acceptClipboard.checked(acceptClipboard);
626 options.sendClipboard.checked(sendClipboard);
627 options.sendPrimary.checked(sendPrimary);
Adam Tkacb7bafc52010-09-15 14:13:17 +0000628 if (state() == RFBSTATE_NORMAL) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000629 options.shared.disabled(true);
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000630#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +0000631 options.secVeNCrypt.disabled(true);
632 options.encNone.disabled(true);
633 options.encTLS.disabled(true);
634 options.encX509.disabled(true);
635 options.ca.disabled(true);
636 options.crl.disabled(true);
637 options.secNone.disabled(true);
638 options.secVnc.disabled(true);
639 options.secPlain.disabled(true);
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000640#endif
Adam Tkacb7bafc52010-09-15 14:13:17 +0000641 } else {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000642 options.shared.checked(shared);
Adam Tkacb7bafc52010-09-15 14:13:17 +0000643
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000644#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +0000645 /* Process non-VeNCrypt sectypes */
646 list<U8> secTypes = security->GetEnabledSecTypes();
647 list<U8>::iterator i;
648 for (i = secTypes.begin(); i != secTypes.end(); i++) {
649 switch (*i) {
650 case secTypeVeNCrypt:
651 options.secVeNCrypt.checked(true);
652 break;
653 case secTypeNone:
654 options.encNone.checked(true);
655 options.secNone.checked(true);
656 break;
657 case secTypeVncAuth:
658 options.encNone.checked(true);
659 options.secVnc.checked(true);
660 break;
661 }
662 }
663
664 /* Process VeNCrypt subtypes */
665 if (options.secVeNCrypt.checked()) {
666 list<U32> secTypesExt = security->GetEnabledExtSecTypes();
667 list<U32>::iterator iext;
668 for (iext = secTypesExt.begin(); iext != secTypesExt.end(); iext++) {
669 switch (*iext) {
670 case secTypePlain:
671 options.encNone.checked(true);
672 options.secPlain.checked(true);
673 break;
674 case secTypeTLSNone:
675 options.encTLS.checked(true);
676 options.secNone.checked(true);
677 break;
678 case secTypeTLSVnc:
679 options.encTLS.checked(true);
680 options.secVnc.checked(true);
681 break;
682 case secTypeTLSPlain:
683 options.encTLS.checked(true);
684 options.secPlain.checked(true);
685 break;
686 case secTypeX509None:
687 options.encX509.checked(true);
688 options.secNone.checked(true);
689 break;
690 case secTypeX509Vnc:
691 options.encX509.checked(true);
692 options.secVnc.checked(true);
693 break;
694 case secTypeX509Plain:
695 options.encX509.checked(true);
696 options.secPlain.checked(true);
697 break;
698 }
699 }
700 }
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000701#endif
Adam Tkacb7bafc52010-09-15 14:13:17 +0000702 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000703 options.fullScreen.checked(fullScreen);
704 options.useLocalCursor.checked(useLocalCursor);
705 options.dotWhenNoCursor.checked(dotWhenNoCursor);
706}
707
708void CConn::getOptions() {
709 autoSelect = options.autoSelect.checked();
710 if (fullColour != options.fullColour.checked())
711 formatChange = true;
712 fullColour = options.fullColour.checked();
713 if (!fullColour) {
714 int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
715 options.lowColour.checked() ? 1 : 2);
716 if (newLowColourLevel != lowColourLevel) {
717 lowColourLevel.setParam(newLowColourLevel);
718 formatChange = true;
719 }
720 }
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000721 int newEncoding = (options.tight.checked() ? encodingTight :
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000722 options.zrle.checked() ? encodingZRLE :
723 options.hextile.checked() ? encodingHextile :
724 encodingRaw);
725 if (newEncoding != currentEncoding) {
726 currentEncoding = newEncoding;
727 encodingChange = true;
728 }
729
730 customCompressLevel.setParam(options.customCompressLevel.checked());
731 if (cp.customCompressLevel != customCompressLevel) {
732 cp.customCompressLevel = customCompressLevel;
733 encodingChange = true;
734 }
735 compressLevel.setParam(options.compressLevel.getText());
736 if (cp.compressLevel != compressLevel) {
737 cp.compressLevel = compressLevel;
738 encodingChange = true;
739 }
740 noJpeg.setParam(!options.noJpeg.checked());
741 if (cp.noJpeg != noJpeg) {
742 cp.noJpeg = noJpeg;
743 encodingChange = true;
744 }
745 qualityLevel.setParam(options.qualityLevel.getText());
746 if (cp.qualityLevel != qualityLevel) {
747 cp.qualityLevel = qualityLevel;
748 encodingChange = true;
749 }
750
751 viewOnly.setParam(options.viewOnly.checked());
752 acceptClipboard.setParam(options.acceptClipboard.checked());
753 sendClipboard.setParam(options.sendClipboard.checked());
754 sendPrimary.setParam(options.sendPrimary.checked());
755 shared = options.shared.checked();
756 setShared(shared);
757 if (fullScreen != options.fullScreen.checked()) {
758 fullScreen = options.fullScreen.checked();
759 if (viewport) recreateViewport();
760 }
761 useLocalCursor.setParam(options.useLocalCursor.checked());
762 if (cp.supportsLocalCursor != useLocalCursor) {
763 cp.supportsLocalCursor = useLocalCursor;
764 encodingChange = true;
765 if (desktop)
766 desktop->resetLocalCursor();
767 }
768 dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
Adam Tkaccc247c92010-01-11 13:46:48 +0000769 if (desktop)
770 desktop->setNoCursor();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000771 checkEncodings();
Adam Tkacb7bafc52010-09-15 14:13:17 +0000772
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000773#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +0000774 /* Process security types which don't use encryption */
775 if (options.encNone.checked()) {
776 if (options.secNone.checked())
777 security->EnableSecType(secTypeNone);
778 if (options.secVnc.checked())
779 security->EnableSecType(secTypeVncAuth);
780 if (options.secPlain.checked())
781 security->EnableSecType(secTypePlain);
782 } else {
783 security->DisableSecType(secTypeNone);
784 security->DisableSecType(secTypeVncAuth);
785 security->DisableSecType(secTypePlain);
786 }
787
788 /* Process security types which use TLS encryption */
789 if (options.encTLS.checked()) {
790 if (options.secNone.checked())
791 security->EnableSecType(secTypeTLSNone);
792 if (options.secVnc.checked())
793 security->EnableSecType(secTypeTLSVnc);
794 if (options.secPlain.checked())
795 security->EnableSecType(secTypeTLSPlain);
796 } else {
797 security->DisableSecType(secTypeTLSNone);
798 security->DisableSecType(secTypeTLSVnc);
799 security->DisableSecType(secTypeTLSPlain);
800 }
801
802 /* Process security types which use X509 encryption */
803 if (options.encX509.checked()) {
804 if (options.secNone.checked())
805 security->EnableSecType(secTypeX509None);
806 if (options.secVnc.checked())
807 security->EnableSecType(secTypeX509Vnc);
808 if (options.secPlain.checked())
809 security->EnableSecType(secTypeX509Plain);
810 } else {
811 security->DisableSecType(secTypeX509None);
812 security->DisableSecType(secTypeX509Vnc);
813 security->DisableSecType(secTypeX509Plain);
814 }
815
816 /* Process *None security types */
817 if (options.secNone.checked()) {
818 if (options.encNone.checked())
819 security->EnableSecType(secTypeNone);
820 if (options.encTLS.checked())
821 security->EnableSecType(secTypeTLSNone);
822 if (options.encX509.checked())
823 security->EnableSecType(secTypeX509None);
824 } else {
825 security->DisableSecType(secTypeNone);
826 security->DisableSecType(secTypeTLSNone);
827 security->DisableSecType(secTypeX509None);
828 }
829
830 /* Process *Vnc security types */
831 if (options.secVnc.checked()) {
832 if (options.encNone.checked())
833 security->EnableSecType(secTypeVncAuth);
834 if (options.encTLS.checked())
835 security->EnableSecType(secTypeTLSVnc);
836 if (options.encX509.checked())
837 security->EnableSecType(secTypeX509Vnc);
838 } else {
839 security->DisableSecType(secTypeVncAuth);
840 security->DisableSecType(secTypeTLSVnc);
841 security->DisableSecType(secTypeX509Vnc);
842 }
843
844 /* Process *Plain security types */
845 if (options.secPlain.checked()) {
846 if (options.encNone.checked())
847 security->EnableSecType(secTypePlain);
848 if (options.encTLS.checked())
849 security->EnableSecType(secTypeTLSPlain);
850 if (options.encX509.checked())
851 security->EnableSecType(secTypeX509Plain);
852 } else {
853 security->DisableSecType(secTypePlain);
854 security->DisableSecType(secTypeTLSPlain);
855 security->DisableSecType(secTypeX509Plain);
856 }
857
858 CSecurityTLS::x509ca.setParam(options.ca.getText());
859 CSecurityTLS::x509crl.setParam(options.crl.getText());
Pierre Ossmanfa955d22010-09-29 14:10:04 +0000860#endif
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000861}
862
Pierre Ossman49f88222009-03-20 13:02:50 +0000863void CConn::resizeFramebuffer()
864{
865 if (!desktop)
866 return;
867 if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
868 return;
869
870 desktop->resize(cp.width, cp.height);
871 recreateViewport();
872}
873
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000874void CConn::recreateViewport()
875{
876 TXViewport* oldViewport = viewport;
877 viewport = new TXViewport(dpy, cp.width, cp.height);
878 desktop->setViewport(viewport);
879 CharArray windowNameStr(windowName.getData());
880 if (!windowNameStr.buf[0]) {
881 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000882 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000883 }
884 viewport->toplevel(windowNameStr.buf, this, argc, argv);
885 viewport->setBumpScroll(fullScreen);
886 XSetWindowAttributes attr;
887 attr.override_redirect = fullScreen;
888 XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
889 XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
890 XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
891 XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
892 XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
893 reconfigureViewport();
894 menu.setTransientFor(viewport->win());
895 viewport->map();
896 if (fullScreen) {
897 XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
898 CurrentTime);
899 } else {
900 XUngrabKeyboard(dpy, CurrentTime);
901 }
902 if (oldViewport) delete oldViewport;
903}
904
905void CConn::reconfigureViewport()
906{
907 viewport->setMaxSize(cp.width, cp.height);
908 if (fullScreen) {
909 viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
910 DisplayHeight(dpy,DefaultScreen(dpy)));
911 } else {
912 int w = cp.width;
913 int h = cp.height;
914 if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
915 w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
916 if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
917 h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
918
919 int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
920 int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
921
922 CharArray geometryStr(geometry.getData());
923 viewport->setGeometry(geometryStr.buf, x, y, w, h);
924 }
925}
926
Pierre Ossman78b23592009-03-12 12:25:11 +0000927// Note: The method below is duplicated in win/vncviewer/CConn.cxx!
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000928
929// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
930// to the connection speed:
931//
Pierre Ossman78b23592009-03-12 12:25:11 +0000932// First we wait for at least one second of bandwidth measurement.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000933//
Pierre Ossman78b23592009-03-12 12:25:11 +0000934// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
935// which should be perceptually lossless.
936//
937// If the bandwidth is below that, we choose a more lossy JPEG quality.
938//
939// If the bandwidth drops below 256 Kbps, we switch to palette mode.
940//
941// Note: The system here is fairly arbitrary and should be replaced
942// with something more intelligent at the server end.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000943//
944void CConn::autoSelectFormatAndEncoding()
945{
946 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000947 unsigned int timeWaited = sock->inStream().timeWaited();
Pierre Ossman78b23592009-03-12 12:25:11 +0000948 bool newFullColour = fullColour;
949 int newQualityLevel = qualityLevel;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000950
Pierre Ossman78b23592009-03-12 12:25:11 +0000951 // Always use Tight
Pierre Ossman315b9992010-03-03 16:24:36 +0000952 if (currentEncoding != encodingTight) {
953 currentEncoding = encodingTight;
954 encodingChange = true;
955 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000956
Pierre Ossman78b23592009-03-12 12:25:11 +0000957 // Check that we have a decent bandwidth measurement
958 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000959 return;
Pierre Ossman78b23592009-03-12 12:25:11 +0000960
961 // Select appropriate quality level
962 if (!noJpeg) {
963 if (kbitsPerSecond > 16000)
964 newQualityLevel = 8;
965 else
966 newQualityLevel = 6;
967
968 if (newQualityLevel != qualityLevel) {
969 vlog.info("Throughput %d kbit/s - changing to quality %d ",
970 kbitsPerSecond, newQualityLevel);
971 cp.qualityLevel = newQualityLevel;
972 qualityLevel.setParam(newQualityLevel);
973 encodingChange = true;
974 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000975 }
976
977 if (cp.beforeVersion(3, 8)) {
978 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
979 // cursors "asynchronously". If this happens in the middle of a
980 // pixel format change, the server will encode the cursor with
981 // the old format, but the client will try to decode it
982 // according to the new format. This will lead to a
983 // crash. Therefore, we do not allow automatic format change for
984 // old servers.
985 return;
986 }
987
988 // Select best color level
989 newFullColour = (kbitsPerSecond > 256);
990 if (newFullColour != fullColour) {
991 vlog.info("Throughput %d kbit/s - full color is now %s",
992 kbitsPerSecond,
993 newFullColour ? "enabled" : "disabled");
994 fullColour = newFullColour;
995 formatChange = true;
996 }
997}
998
999// checkEncodings() sends a setEncodings message if one is needed.
1000void CConn::checkEncodings()
1001{
1002 if (encodingChange && writer()) {
1003 vlog.info("Using %s encoding",encodingName(currentEncoding));
1004 writer()->writeSetEncodings(currentEncoding, true);
1005 encodingChange = false;
1006 }
1007}
1008
1009// requestNewUpdate() requests an update from the server, having set the
1010// format and encoding appropriately.
1011void CConn::requestNewUpdate()
1012{
1013 if (formatChange) {
Adam Tkac12c0fc32010-03-04 15:33:11 +00001014
1015 /* Catch incorrect requestNewUpdate calls */
1016 assert(pendingUpdate == false);
1017
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001018 if (fullColour) {
1019 desktop->setPF(fullColourPF);
1020 } else {
1021 if (lowColourLevel == 0)
1022 desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
1023 else if (lowColourLevel == 1)
1024 desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
1025 else
1026 desktop->setPF(PixelFormat(8,8,0,0));
1027 }
1028 char str[256];
1029 desktop->getPF().print(str, 256);
1030 vlog.info("Using pixel format %s",str);
1031 cp.setPF(desktop->getPF());
1032 writer()->writeSetPixelFormat(cp.pf());
1033 }
1034 checkEncodings();
1035 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
1036 !formatChange);
1037 formatChange = false;
1038}