blob: 1a75a11d100fdd1671a8c6141a518b96cf1844c9 [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 Tkacb10489b2010-04-23 14:16:04 +000078 CSecurity::upg = this; /* Security instance is created in CConnection costructor. */
Adam Tkacf324dc42010-04-23 14:10:17 +000079
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000080 CharArray encStr(preferredEncoding.getData());
81 int encNum = encodingNum(encStr.buf);
82 if (encNum != -1) {
83 currentEncoding = encNum;
84 }
85 cp.supportsDesktopResize = true;
Pierre Ossman49f88222009-03-20 13:02:50 +000086 cp.supportsExtendedDesktopSize = true;
Peter Åstrandc39e0782009-01-15 12:21:42 +000087 cp.supportsDesktopRename = true;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000088 cp.supportsLocalCursor = useLocalCursor;
89 cp.customCompressLevel = customCompressLevel;
90 cp.compressLevel = compressLevel;
91 cp.noJpeg = noJpeg;
92 cp.qualityLevel = qualityLevel;
93 initMenu();
94
95 if (sock) {
96 char* name = sock->getPeerEndpoint();
97 vlog.info("Accepted connection from %s", name);
98 if (name) free(name);
99 } else {
100 if (vncServerName) {
101 getHostAndPort(vncServerName, &serverHost, &serverPort);
102 } else {
103 ServerDialog dlg(dpy, &options, &about);
104 if (!dlg.show() || dlg.entry.getText()[0] == 0) {
105 exit(1);
106 }
107 getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
108 }
109
110 sock = new network::TcpSocket(serverHost, serverPort);
111 vlog.info("connected to host %s port %d", serverHost, serverPort);
112 }
113
114 sameMachine = sock->sameMachine();
115 sock->inStream().setBlockCallback(this);
116 setServerName(sock->getPeerEndpoint());
117 setStreams(&sock->inStream(), &sock->outStream());
118 initialiseProtocol();
119}
120
121CConn::~CConn() {
122 free(serverHost);
123 delete desktop;
124 delete viewport;
125 delete sock;
126}
127
128// deleteWindow() is called when the user closes the desktop or menu windows.
129
130void CConn::deleteWindow(TXWindow* w) {
131 if (w == &menu) {
132 menu.unmap();
133 } else if (w == viewport) {
134 exit(1);
135 }
136}
137
138// handleEvent() filters all events on the desktop and menu. Most are passed
139// straight through. The exception is the F8 key. When pressed on the
140// desktop, it is used to bring up the menu. An F8 press or release on the
141// menu is passed through as if it were on the desktop.
142
143void CConn::handleEvent(TXWindow* w, XEvent* ev)
144{
145 KeySym ks;
146 char str[256];
147
148 switch (ev->type) {
149 case KeyPress:
150 case KeyRelease:
151 XLookupString(&ev->xkey, str, 256, &ks, NULL);
152 if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
153 if (w == desktop && ev->type == KeyPress) {
154 showMenu(ev->xkey.x_root, ev->xkey.y_root);
155 break;
156 } else if (w == &menu) {
157 if (ev->type == KeyPress) menu.unmap();
158 desktopEventHandler->handleEvent(w, ev);
159 break;
160 }
161 }
162 // drop through
163
164 default:
165 if (w == desktop) desktopEventHandler->handleEvent(w, ev);
166 else if (w == &menu) menuEventHandler->handleEvent(w, ev);
167 }
168}
169
170// blockCallback() is called when reading from the socket would block. We
171// process X events until the socket is ready for reading again.
172
173void CConn::blockCallback() {
174 fd_set rfds;
175 do {
176 struct timeval tv;
177 struct timeval* tvp = 0;
178
179 // Process any incoming X events
180 TXWindow::handleXEvents(dpy);
181
182 // Process expired timers and get the time until the next one
183 int timeoutMs = Timer::checkTimeouts();
184 if (timeoutMs) {
185 tv.tv_sec = timeoutMs / 1000;
186 tv.tv_usec = (timeoutMs % 1000) * 1000;
187 tvp = &tv;
188 }
189
190 // If there are X requests pending then poll, don't wait!
191 if (XPending(dpy)) {
192 tv.tv_usec = tv.tv_sec = 0;
193 tvp = &tv;
194 }
195
196 // Wait for X events, VNC traffic, or the next timer expiry
197 FD_ZERO(&rfds);
198 FD_SET(ConnectionNumber(dpy), &rfds);
199 FD_SET(sock->getFd(), &rfds);
200 int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
201 if (n < 0) throw rdr::SystemException("select",errno);
202 } while (!(FD_ISSET(sock->getFd(), &rfds)));
203}
204
205
206// getPasswd() is called by the CSecurity object when it needs us to read a
207// password from the user.
208
209void CConn::getUserPasswd(char** user, char** password)
210{
211 CharArray passwordFileStr(passwordFile.getData());
212 if (!user && passwordFileStr.buf[0]) {
213 FILE* fp = fopen(passwordFileStr.buf, "r");
214 if (!fp) throw rfb::Exception("Opening password file failed");
215 ObfuscatedPasswd obfPwd(256);
216 obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
217 fclose(fp);
218 PlainPasswd passwd(obfPwd);
219 *password = passwd.takeBuf();
220 return;
221 }
222
Adam Tkacf324dc42010-04-23 14:10:17 +0000223 const char* secType = secTypeName(csecurity->getType());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000224 const char* titlePrefix = _("VNC authentication");
225 unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
226 CharArray title(titleLen);
227 snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
228 PasswdDialog dlg(dpy, title.buf, !user);
229 if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
230 if (user)
Adam Tkacd36b6262009-09-04 10:57:20 +0000231 *user = strDup(dlg.userEntry.getText());
232 *password = strDup(dlg.passwdEntry.getText());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000233}
234
235
236// CConnection callback methods
237
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000238// serverInit() is called when the serverInit message has been received. At
239// this point we create the desktop window and display it. We also tell the
240// server the pixel format and encodings to use and request the first update.
241void CConn::serverInit() {
242 CConnection::serverInit();
243
244 // If using AutoSelect with old servers, start in FullColor
245 // mode. See comment in autoSelectFormatAndEncoding.
246 if (cp.beforeVersion(3, 8) && autoSelect) {
247 fullColour = true;
248 }
249
250 serverPF = cp.pf();
251 desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
252 desktopEventHandler = desktop->setEventHandler(this);
253 desktop->addEventMask(KeyPressMask | KeyReleaseMask);
254 fullColourPF = desktop->getPF();
255 if (!serverPF.trueColour)
256 fullColour = true;
257 recreateViewport();
258 formatChange = encodingChange = true;
259 requestNewUpdate();
260}
261
262// setDesktopSize() is called when the desktop size changes (including when
263// it is set initially).
264void CConn::setDesktopSize(int w, int h) {
265 CConnection::setDesktopSize(w,h);
Pierre Ossman49f88222009-03-20 13:02:50 +0000266 resizeFramebuffer();
267}
268
269// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
Pierre Ossmancbd1b2c2009-03-20 16:05:04 +0000270void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
271 const rfb::ScreenSet& layout) {
272 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
Pierre Ossman49f88222009-03-20 13:02:50 +0000273
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000274 if ((reason == reasonClient) && (result != resultSuccess)) {
275 vlog.error("SetDesktopSize failed: %d", result);
Pierre Ossman49f88222009-03-20 13:02:50 +0000276 return;
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000277 }
Pierre Ossman49f88222009-03-20 13:02:50 +0000278
279 resizeFramebuffer();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000280}
281
Peter Åstrandc39e0782009-01-15 12:21:42 +0000282// setName() is called when the desktop name changes
283void CConn::setName(const char* name) {
284 CConnection::setName(name);
Peter Åstrand051a83a2009-01-15 13:36:03 +0000285
286 CharArray windowNameStr(windowName.getData());
287 if (!windowNameStr.buf[0]) {
288 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000289 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Peter Åstrand051a83a2009-01-15 13:36:03 +0000290 }
291
Peter Åstrandc39e0782009-01-15 12:21:42 +0000292 if (viewport) {
Peter Åstrand051a83a2009-01-15 13:36:03 +0000293 viewport->setName(windowNameStr.buf);
Peter Åstrandc39e0782009-01-15 12:21:42 +0000294 }
295}
296
Pierre Ossman42d20e72009-04-01 14:42:34 +0000297// framebufferUpdateStart() is called at the beginning of an update.
298// Here we try to send out a new framebuffer update request so that the
299// next update can be sent out in parallel with us decoding the current
300// one. We cannot do this if we're in the middle of a format change
301// though.
302void CConn::framebufferUpdateStart() {
Adam Tkac12c0fc32010-03-04 15:33:11 +0000303 if (!formatChange) {
304 pendingUpdate = true;
Pierre Ossman42d20e72009-04-01 14:42:34 +0000305 requestNewUpdate();
Adam Tkac12c0fc32010-03-04 15:33:11 +0000306 } else
307 pendingUpdate = false;
Pierre Ossman42d20e72009-04-01 14:42:34 +0000308}
309
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000310// framebufferUpdateEnd() is called at the end of an update.
311// For each rectangle, the FdInStream will have timed the speed
312// of the connection, allowing us to select format and encoding
313// appropriately, and then request another incremental update.
314void CConn::framebufferUpdateEnd() {
315 if (debugDelay != 0) {
316 XSync(dpy, False);
317 struct timeval tv;
318 tv.tv_sec = debugDelay / 1000;
319 tv.tv_usec = (debugDelay % 1000) * 1000;
320 select(0, 0, 0, 0, &tv);
321 std::list<rfb::Rect>::iterator i;
322 for (i = debugRects.begin(); i != debugRects.end(); i++) {
323 desktop->invertRect(*i);
324 }
325 debugRects.clear();
326 }
327 desktop->framebufferUpdateEnd();
Pierre Ossmaneb3cecb2009-03-23 16:49:47 +0000328
329 if (firstUpdate) {
330 int width, height;
331
332 if (cp.supportsSetDesktopSize &&
333 sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
334 ScreenSet layout;
335
336 layout = cp.screenLayout;
337
338 if (layout.num_screens() == 0)
339 layout.add_screen(rfb::Screen());
340 else if (layout.num_screens() != 1) {
341 ScreenSet::iterator iter;
342
343 while (true) {
344 iter = layout.begin();
345 ++iter;
346
347 if (iter == layout.end())
348 break;
349
350 layout.remove_screen(iter->id);
351 }
352 }
353
354 layout.begin()->dimensions.tl.x = 0;
355 layout.begin()->dimensions.tl.y = 0;
356 layout.begin()->dimensions.br.x = width;
357 layout.begin()->dimensions.br.y = height;
358
359 writer()->writeSetDesktopSize(width, height, layout);
360 }
361
362 firstUpdate = false;
363 }
364
Pierre Ossman42d20e72009-04-01 14:42:34 +0000365 // A format change prevented us from sending this before the update,
366 // so make sure to send it now.
Adam Tkac12c0fc32010-03-04 15:33:11 +0000367 if (formatChange && !pendingUpdate)
Pierre Ossman42d20e72009-04-01 14:42:34 +0000368 requestNewUpdate();
369
370 // Compute new settings based on updated bandwidth values
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000371 if (autoSelect)
372 autoSelectFormatAndEncoding();
Pierre Ossman42d20e72009-04-01 14:42:34 +0000373
374 // Make sure that the X11 handling and the timers gets some CPU time
375 // in case of back to back framebuffer updates.
376 TXWindow::handleXEvents(dpy);
377 Timer::checkTimeouts();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000378}
379
380// The rest of the callbacks are fairly self-explanatory...
381
382void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
383{
384 desktop->setColourMapEntries(firstColour, nColours, rgbs);
385}
386
387void CConn::bell() { XBell(dpy, 0); }
388
Adam Tkac141c1722009-06-16 10:43:59 +0000389void CConn::serverCutText(const char* str, rdr::U32 len) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000390 desktop->serverCutText(str,len);
391}
392
393// We start timing on beginRect and stop timing on endRect, to
394// avoid skewing the bandwidth estimation as a result of the server
395// being slow or the network having high latency
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000396void CConn::beginRect(const Rect& r, int encoding)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000397{
398 sock->inStream().startTiming();
399 if (encoding != encodingCopyRect) {
400 lastServerEncoding = encoding;
401 }
402}
403
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000404void CConn::endRect(const Rect& r, int encoding)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000405{
406 sock->inStream().stopTiming();
407 if (debugDelay != 0) {
408 desktop->invertRect(r);
409 debugRects.push_back(r);
410 }
411}
412
413void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
414 desktop->fillRect(r,p);
415}
416void CConn::imageRect(const rfb::Rect& r, void* p) {
417 desktop->imageRect(r,p);
418}
419void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
420 desktop->copyRect(r,sx,sy);
421}
422void CConn::setCursor(int width, int height, const Point& hotspot,
423 void* data, void* mask) {
424 desktop->setCursor(width, height, hotspot, data, mask);
425}
426
427
428// Menu stuff - menuSelect() is called when the user selects a menu option.
429
430enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
431 ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
432
433void CConn::initMenu() {
434 menuEventHandler = menu.setEventHandler(this);
435 menu.addEventMask(KeyPressMask | KeyReleaseMask);
436 menu.addEntry(_("Exit viewer"), ID_EXIT);
437 menu.addEntry(0, 0);
438 menu.addEntry(_("Full screen"), ID_FULLSCREEN);
439 menu.check(ID_FULLSCREEN, fullScreen);
440 menu.addEntry(0, 0);
441 menu.addEntry(_("Ctrl"), ID_CTRL);
442 menu.addEntry(_("Alt"), ID_ALT);
443 CharArray menuKeyStr(menuKey.getData());
444 CharArray sendMenuKey(64);
445 snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
446 menu.addEntry(sendMenuKey.buf, ID_F8);
447 menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
448 menu.addEntry(0, 0);
449 menu.addEntry(_("Refresh screen"), ID_REFRESH);
450 menu.addEntry(0, 0);
451 menu.addEntry(_("New connection..."), ID_NEWCONN);
452 menu.addEntry(_("Options..."), ID_OPTIONS);
453 menu.addEntry(_("Connection info..."), ID_INFO);
Peter Åstrandf55ca172009-08-27 12:22:10 +0000454 menu.addEntry(_("About TigerVNC viewer..."), ID_ABOUT);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000455 menu.addEntry(0, 0);
456 menu.addEntry(_("Dismiss menu"), ID_DISMISS);
457 menu.toplevel(_("VNC Menu"), this);
458 menu.setBorderWidth(1);
459}
460
461void CConn::showMenu(int x, int y) {
462 menu.check(ID_FULLSCREEN, fullScreen);
463 if (x + menu.width() > viewport->width())
464 x = viewport->width() - menu.width();
465 if (y + menu.height() > viewport->height())
466 y = viewport->height() - menu.height();
467 menu.move(x, y);
468 menu.raise();
469 menu.map();
470}
471
472void CConn::menuSelect(long id, TXMenu* m) {
473 switch (id) {
474 case ID_NEWCONN:
475 {
476 menu.unmap();
477 if (fullScreen) {
478 fullScreen = false;
479 if (viewport) recreateViewport();
480 }
481 int pid = fork();
482 if (pid < 0) { perror("fork"); exit(1); }
483 if (pid == 0) {
484 delete sock;
485 close(ConnectionNumber(dpy));
486 struct timeval tv;
487 tv.tv_sec = 0;
488 tv.tv_usec = 200*1000;
489 select(0, 0, 0, 0, &tv);
Adam Tkacfee32e32008-10-10 15:48:22 +0000490 execlp(programName, programName, NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000491 perror("execlp"); exit(1);
492 }
493 break;
494 }
495 case ID_OPTIONS:
496 menu.unmap();
497 options.show();
498 break;
499 case ID_INFO:
500 {
501 menu.unmap();
502 char pfStr[100];
503 char spfStr[100];
504 cp.pf().print(pfStr, 100);
505 serverPF.print(spfStr, 100);
Adam Tkacf324dc42010-04-23 14:10:17 +0000506 int secType = csecurity->getType();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000507 char infoText[1024];
508 snprintf(infoText, sizeof(infoText),
509 _("Desktop name: %.80s\n"
510 "Host: %.80s port: %d\n"
511 "Size: %d x %d\n"
512 "Pixel format: %s\n"
513 "(server default %s)\n"
514 "Requested encoding: %s\n"
515 "Last used encoding: %s\n"
516 "Line speed estimate: %d kbit/s\n"
517 "Protocol version: %d.%d\n"
518 "Security method: %s\n"),
519 cp.name(), serverHost, serverPort, cp.width, cp.height,
520 pfStr, spfStr, encodingName(currentEncoding),
521 encodingName(lastServerEncoding),
522 sock->inStream().kbitsPerSecond(),
523 cp.majorVersion, cp.minorVersion,
524 secTypeName(secType));
525 info.setText(infoText);
526 info.show();
527 break;
528 }
529 case ID_FULLSCREEN:
530 menu.unmap();
531 fullScreen = !fullScreen;
532 if (viewport) recreateViewport();
533 break;
534 case ID_REFRESH:
535 menu.unmap();
Adam Tkac12c0fc32010-03-04 15:33:11 +0000536 if (!formatChange) {
537 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
538 false);
539 pendingUpdate = true;
540 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000541 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);
Adam Tkacb7bafc52010-09-15 14:13:17 +0000617 if (state() == RFBSTATE_NORMAL) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000618 options.shared.disabled(true);
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000619#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +0000620 options.secVeNCrypt.disabled(true);
621 options.encNone.disabled(true);
622 options.encTLS.disabled(true);
623 options.encX509.disabled(true);
624 options.ca.disabled(true);
625 options.crl.disabled(true);
626 options.secNone.disabled(true);
627 options.secVnc.disabled(true);
628 options.secPlain.disabled(true);
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000629#endif
Adam Tkacb7bafc52010-09-15 14:13:17 +0000630 } else {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000631 options.shared.checked(shared);
Adam Tkacb7bafc52010-09-15 14:13:17 +0000632
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000633#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +0000634 /* Process non-VeNCrypt sectypes */
635 list<U8> secTypes = security->GetEnabledSecTypes();
636 list<U8>::iterator i;
637 for (i = secTypes.begin(); i != secTypes.end(); i++) {
638 switch (*i) {
639 case secTypeVeNCrypt:
640 options.secVeNCrypt.checked(true);
641 break;
642 case secTypeNone:
643 options.encNone.checked(true);
644 options.secNone.checked(true);
645 break;
646 case secTypeVncAuth:
647 options.encNone.checked(true);
648 options.secVnc.checked(true);
649 break;
650 }
651 }
652
653 /* Process VeNCrypt subtypes */
654 if (options.secVeNCrypt.checked()) {
655 list<U32> secTypesExt = security->GetEnabledExtSecTypes();
656 list<U32>::iterator iext;
657 for (iext = secTypesExt.begin(); iext != secTypesExt.end(); iext++) {
658 switch (*iext) {
659 case secTypePlain:
660 options.encNone.checked(true);
661 options.secPlain.checked(true);
662 break;
663 case secTypeTLSNone:
664 options.encTLS.checked(true);
665 options.secNone.checked(true);
666 break;
667 case secTypeTLSVnc:
668 options.encTLS.checked(true);
669 options.secVnc.checked(true);
670 break;
671 case secTypeTLSPlain:
672 options.encTLS.checked(true);
673 options.secPlain.checked(true);
674 break;
675 case secTypeX509None:
676 options.encX509.checked(true);
677 options.secNone.checked(true);
678 break;
679 case secTypeX509Vnc:
680 options.encX509.checked(true);
681 options.secVnc.checked(true);
682 break;
683 case secTypeX509Plain:
684 options.encX509.checked(true);
685 options.secPlain.checked(true);
686 break;
687 }
688 }
689 }
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000690#endif
Adam Tkacb7bafc52010-09-15 14:13:17 +0000691 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000692 options.fullScreen.checked(fullScreen);
693 options.useLocalCursor.checked(useLocalCursor);
694 options.dotWhenNoCursor.checked(dotWhenNoCursor);
695}
696
697void CConn::getOptions() {
698 autoSelect = options.autoSelect.checked();
699 if (fullColour != options.fullColour.checked())
700 formatChange = true;
701 fullColour = options.fullColour.checked();
702 if (!fullColour) {
703 int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
704 options.lowColour.checked() ? 1 : 2);
705 if (newLowColourLevel != lowColourLevel) {
706 lowColourLevel.setParam(newLowColourLevel);
707 formatChange = true;
708 }
709 }
Peter Åstrand98fe98c2010-02-10 07:43:02 +0000710 int newEncoding = (options.tight.checked() ? encodingTight :
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000711 options.zrle.checked() ? encodingZRLE :
712 options.hextile.checked() ? encodingHextile :
713 encodingRaw);
714 if (newEncoding != currentEncoding) {
715 currentEncoding = newEncoding;
716 encodingChange = true;
717 }
718
719 customCompressLevel.setParam(options.customCompressLevel.checked());
720 if (cp.customCompressLevel != customCompressLevel) {
721 cp.customCompressLevel = customCompressLevel;
722 encodingChange = true;
723 }
724 compressLevel.setParam(options.compressLevel.getText());
725 if (cp.compressLevel != compressLevel) {
726 cp.compressLevel = compressLevel;
727 encodingChange = true;
728 }
729 noJpeg.setParam(!options.noJpeg.checked());
730 if (cp.noJpeg != noJpeg) {
731 cp.noJpeg = noJpeg;
732 encodingChange = true;
733 }
734 qualityLevel.setParam(options.qualityLevel.getText());
735 if (cp.qualityLevel != qualityLevel) {
736 cp.qualityLevel = qualityLevel;
737 encodingChange = true;
738 }
739
740 viewOnly.setParam(options.viewOnly.checked());
741 acceptClipboard.setParam(options.acceptClipboard.checked());
742 sendClipboard.setParam(options.sendClipboard.checked());
743 sendPrimary.setParam(options.sendPrimary.checked());
744 shared = options.shared.checked();
745 setShared(shared);
746 if (fullScreen != options.fullScreen.checked()) {
747 fullScreen = options.fullScreen.checked();
748 if (viewport) recreateViewport();
749 }
750 useLocalCursor.setParam(options.useLocalCursor.checked());
751 if (cp.supportsLocalCursor != useLocalCursor) {
752 cp.supportsLocalCursor = useLocalCursor;
753 encodingChange = true;
754 if (desktop)
755 desktop->resetLocalCursor();
756 }
757 dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
Adam Tkaccc247c92010-01-11 13:46:48 +0000758 if (desktop)
759 desktop->setNoCursor();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000760 checkEncodings();
Adam Tkacb7bafc52010-09-15 14:13:17 +0000761
Pierre Ossmanfa4b3ac2010-09-30 09:06:51 +0000762#ifdef HAVE_GNUTLS
Adam Tkacb7bafc52010-09-15 14:13:17 +0000763 /* Process security types which don't use encryption */
764 if (options.encNone.checked()) {
765 if (options.secNone.checked())
766 security->EnableSecType(secTypeNone);
767 if (options.secVnc.checked())
768 security->EnableSecType(secTypeVncAuth);
769 if (options.secPlain.checked())
770 security->EnableSecType(secTypePlain);
771 } else {
772 security->DisableSecType(secTypeNone);
773 security->DisableSecType(secTypeVncAuth);
774 security->DisableSecType(secTypePlain);
775 }
776
777 /* Process security types which use TLS encryption */
778 if (options.encTLS.checked()) {
779 if (options.secNone.checked())
780 security->EnableSecType(secTypeTLSNone);
781 if (options.secVnc.checked())
782 security->EnableSecType(secTypeTLSVnc);
783 if (options.secPlain.checked())
784 security->EnableSecType(secTypeTLSPlain);
785 } else {
786 security->DisableSecType(secTypeTLSNone);
787 security->DisableSecType(secTypeTLSVnc);
788 security->DisableSecType(secTypeTLSPlain);
789 }
790
791 /* Process security types which use X509 encryption */
792 if (options.encX509.checked()) {
793 if (options.secNone.checked())
794 security->EnableSecType(secTypeX509None);
795 if (options.secVnc.checked())
796 security->EnableSecType(secTypeX509Vnc);
797 if (options.secPlain.checked())
798 security->EnableSecType(secTypeX509Plain);
799 } else {
800 security->DisableSecType(secTypeX509None);
801 security->DisableSecType(secTypeX509Vnc);
802 security->DisableSecType(secTypeX509Plain);
803 }
804
805 /* Process *None security types */
806 if (options.secNone.checked()) {
807 if (options.encNone.checked())
808 security->EnableSecType(secTypeNone);
809 if (options.encTLS.checked())
810 security->EnableSecType(secTypeTLSNone);
811 if (options.encX509.checked())
812 security->EnableSecType(secTypeX509None);
813 } else {
814 security->DisableSecType(secTypeNone);
815 security->DisableSecType(secTypeTLSNone);
816 security->DisableSecType(secTypeX509None);
817 }
818
819 /* Process *Vnc security types */
820 if (options.secVnc.checked()) {
821 if (options.encNone.checked())
822 security->EnableSecType(secTypeVncAuth);
823 if (options.encTLS.checked())
824 security->EnableSecType(secTypeTLSVnc);
825 if (options.encX509.checked())
826 security->EnableSecType(secTypeX509Vnc);
827 } else {
828 security->DisableSecType(secTypeVncAuth);
829 security->DisableSecType(secTypeTLSVnc);
830 security->DisableSecType(secTypeX509Vnc);
831 }
832
833 /* Process *Plain security types */
834 if (options.secPlain.checked()) {
835 if (options.encNone.checked())
836 security->EnableSecType(secTypePlain);
837 if (options.encTLS.checked())
838 security->EnableSecType(secTypeTLSPlain);
839 if (options.encX509.checked())
840 security->EnableSecType(secTypeX509Plain);
841 } else {
842 security->DisableSecType(secTypePlain);
843 security->DisableSecType(secTypeTLSPlain);
844 security->DisableSecType(secTypeX509Plain);
845 }
846
847 CSecurityTLS::x509ca.setParam(options.ca.getText());
848 CSecurityTLS::x509crl.setParam(options.crl.getText());
Pierre Ossmanfa955d22010-09-29 14:10:04 +0000849#endif
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000850}
851
Pierre Ossman49f88222009-03-20 13:02:50 +0000852void CConn::resizeFramebuffer()
853{
854 if (!desktop)
855 return;
856 if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
857 return;
858
859 desktop->resize(cp.width, cp.height);
860 recreateViewport();
861}
862
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000863void CConn::recreateViewport()
864{
865 TXViewport* oldViewport = viewport;
866 viewport = new TXViewport(dpy, cp.width, cp.height);
867 desktop->setViewport(viewport);
868 CharArray windowNameStr(windowName.getData());
869 if (!windowNameStr.buf[0]) {
870 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000871 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000872 }
873 viewport->toplevel(windowNameStr.buf, this, argc, argv);
874 viewport->setBumpScroll(fullScreen);
875 XSetWindowAttributes attr;
876 attr.override_redirect = fullScreen;
877 XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
878 XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
879 XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
880 XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
881 XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
882 reconfigureViewport();
883 menu.setTransientFor(viewport->win());
884 viewport->map();
885 if (fullScreen) {
886 XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
887 CurrentTime);
888 } else {
889 XUngrabKeyboard(dpy, CurrentTime);
890 }
891 if (oldViewport) delete oldViewport;
892}
893
894void CConn::reconfigureViewport()
895{
896 viewport->setMaxSize(cp.width, cp.height);
897 if (fullScreen) {
898 viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
899 DisplayHeight(dpy,DefaultScreen(dpy)));
900 } else {
901 int w = cp.width;
902 int h = cp.height;
903 if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
904 w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
905 if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
906 h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
907
908 int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
909 int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
910
911 CharArray geometryStr(geometry.getData());
912 viewport->setGeometry(geometryStr.buf, x, y, w, h);
913 }
914}
915
Pierre Ossman78b23592009-03-12 12:25:11 +0000916// Note: The method below is duplicated in win/vncviewer/CConn.cxx!
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000917
918// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
919// to the connection speed:
920//
Pierre Ossman78b23592009-03-12 12:25:11 +0000921// First we wait for at least one second of bandwidth measurement.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000922//
Pierre Ossman78b23592009-03-12 12:25:11 +0000923// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
924// which should be perceptually lossless.
925//
926// If the bandwidth is below that, we choose a more lossy JPEG quality.
927//
928// If the bandwidth drops below 256 Kbps, we switch to palette mode.
929//
930// Note: The system here is fairly arbitrary and should be replaced
931// with something more intelligent at the server end.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000932//
933void CConn::autoSelectFormatAndEncoding()
934{
935 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000936 unsigned int timeWaited = sock->inStream().timeWaited();
Pierre Ossman78b23592009-03-12 12:25:11 +0000937 bool newFullColour = fullColour;
938 int newQualityLevel = qualityLevel;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000939
Pierre Ossman78b23592009-03-12 12:25:11 +0000940 // Always use Tight
Pierre Ossman315b9992010-03-03 16:24:36 +0000941 if (currentEncoding != encodingTight) {
942 currentEncoding = encodingTight;
943 encodingChange = true;
944 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000945
Pierre Ossman78b23592009-03-12 12:25:11 +0000946 // Check that we have a decent bandwidth measurement
947 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000948 return;
Pierre Ossman78b23592009-03-12 12:25:11 +0000949
950 // Select appropriate quality level
951 if (!noJpeg) {
952 if (kbitsPerSecond > 16000)
953 newQualityLevel = 8;
954 else
955 newQualityLevel = 6;
956
957 if (newQualityLevel != qualityLevel) {
958 vlog.info("Throughput %d kbit/s - changing to quality %d ",
959 kbitsPerSecond, newQualityLevel);
960 cp.qualityLevel = newQualityLevel;
961 qualityLevel.setParam(newQualityLevel);
962 encodingChange = true;
963 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000964 }
965
966 if (cp.beforeVersion(3, 8)) {
967 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
968 // cursors "asynchronously". If this happens in the middle of a
969 // pixel format change, the server will encode the cursor with
970 // the old format, but the client will try to decode it
971 // according to the new format. This will lead to a
972 // crash. Therefore, we do not allow automatic format change for
973 // old servers.
974 return;
975 }
976
977 // Select best color level
978 newFullColour = (kbitsPerSecond > 256);
979 if (newFullColour != fullColour) {
980 vlog.info("Throughput %d kbit/s - full color is now %s",
981 kbitsPerSecond,
982 newFullColour ? "enabled" : "disabled");
983 fullColour = newFullColour;
984 formatChange = true;
985 }
986}
987
988// checkEncodings() sends a setEncodings message if one is needed.
989void CConn::checkEncodings()
990{
991 if (encodingChange && writer()) {
992 vlog.info("Using %s encoding",encodingName(currentEncoding));
993 writer()->writeSetEncodings(currentEncoding, true);
994 encodingChange = false;
995 }
996}
997
998// requestNewUpdate() requests an update from the server, having set the
999// format and encoding appropriately.
1000void CConn::requestNewUpdate()
1001{
1002 if (formatChange) {
Adam Tkac12c0fc32010-03-04 15:33:11 +00001003
1004 /* Catch incorrect requestNewUpdate calls */
1005 assert(pendingUpdate == false);
1006
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001007 if (fullColour) {
1008 desktop->setPF(fullColourPF);
1009 } else {
1010 if (lowColourLevel == 0)
1011 desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
1012 else if (lowColourLevel == 1)
1013 desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
1014 else
1015 desktop->setPF(PixelFormat(8,8,0,0));
1016 }
1017 char str[256];
1018 desktop->getPF().print(str, 256);
1019 vlog.info("Using pixel format %s",str);
1020 cp.setPF(desktop->getPF());
1021 writer()->writeSetPixelFormat(cp.pf());
1022 }
1023 checkEncodings();
1024 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
1025 !formatChange);
1026 formatChange = false;
1027}