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