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