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