blob: 47ccbb27afa991f72567dee9036543687eb70fd5 [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>
27#include <rfb/secTypes.h>
28#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>
36
37#include "TXViewport.h"
38#include "DesktopWindow.h"
39#include "ServerDialog.h"
40#include "PasswdDialog.h"
41#include "parameters.h"
42
43using namespace rfb;
44
45static rfb::LogWriter vlog("CConn");
46
47IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
48 "pixel data - a debugging feature", 0);
49
50StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
51 "F8");
52StringParameter windowName("name", "The X window name", "");
53
54CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
55 char* vncServerName, bool reverse)
56 : dpy(dpy_), argc(argc_),
57 argv(argv_), serverHost(0), serverPort(0), sock(sock_), viewport(0),
58 desktop(0), desktopEventHandler(0),
Pierre Ossman7dfa22e2009-03-12 13:03:22 +000059 currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000060 fullColour(::fullColour),
61 autoSelect(::autoSelect), shared(::shared), formatChange(false),
62 encodingChange(false), sameMachine(false), fullScreen(::fullScreen),
63 ctrlDown(false), altDown(false),
64 menuKeysym(0), menu(dpy, this), options(dpy, this), about(dpy), info(dpy),
65 reverseConnection(reverse)
66{
67 CharArray menuKeyStr(menuKey.getData());
68 menuKeysym = XStringToKeysym(menuKeyStr.buf);
69
70 setShared(shared);
71 addSecType(secTypeNone);
72 addSecType(secTypeVncAuth);
73 CharArray encStr(preferredEncoding.getData());
74 int encNum = encodingNum(encStr.buf);
75 if (encNum != -1) {
76 currentEncoding = encNum;
77 }
78 cp.supportsDesktopResize = true;
Pierre Ossman49f88222009-03-20 13:02:50 +000079 cp.supportsExtendedDesktopSize = true;
Peter Åstrandc39e0782009-01-15 12:21:42 +000080 cp.supportsDesktopRename = true;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000081 cp.supportsLocalCursor = useLocalCursor;
82 cp.customCompressLevel = customCompressLevel;
83 cp.compressLevel = compressLevel;
84 cp.noJpeg = noJpeg;
85 cp.qualityLevel = qualityLevel;
86 initMenu();
87
88 if (sock) {
89 char* name = sock->getPeerEndpoint();
90 vlog.info("Accepted connection from %s", name);
91 if (name) free(name);
92 } else {
93 if (vncServerName) {
94 getHostAndPort(vncServerName, &serverHost, &serverPort);
95 } else {
96 ServerDialog dlg(dpy, &options, &about);
97 if (!dlg.show() || dlg.entry.getText()[0] == 0) {
98 exit(1);
99 }
100 getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
101 }
102
103 sock = new network::TcpSocket(serverHost, serverPort);
104 vlog.info("connected to host %s port %d", serverHost, serverPort);
105 }
106
107 sameMachine = sock->sameMachine();
108 sock->inStream().setBlockCallback(this);
109 setServerName(sock->getPeerEndpoint());
110 setStreams(&sock->inStream(), &sock->outStream());
111 initialiseProtocol();
112}
113
114CConn::~CConn() {
115 free(serverHost);
116 delete desktop;
117 delete viewport;
118 delete sock;
119}
120
121// deleteWindow() is called when the user closes the desktop or menu windows.
122
123void CConn::deleteWindow(TXWindow* w) {
124 if (w == &menu) {
125 menu.unmap();
126 } else if (w == viewport) {
127 exit(1);
128 }
129}
130
131// handleEvent() filters all events on the desktop and menu. Most are passed
132// straight through. The exception is the F8 key. When pressed on the
133// desktop, it is used to bring up the menu. An F8 press or release on the
134// menu is passed through as if it were on the desktop.
135
136void CConn::handleEvent(TXWindow* w, XEvent* ev)
137{
138 KeySym ks;
139 char str[256];
140
141 switch (ev->type) {
142 case KeyPress:
143 case KeyRelease:
144 XLookupString(&ev->xkey, str, 256, &ks, NULL);
145 if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
146 if (w == desktop && ev->type == KeyPress) {
147 showMenu(ev->xkey.x_root, ev->xkey.y_root);
148 break;
149 } else if (w == &menu) {
150 if (ev->type == KeyPress) menu.unmap();
151 desktopEventHandler->handleEvent(w, ev);
152 break;
153 }
154 }
155 // drop through
156
157 default:
158 if (w == desktop) desktopEventHandler->handleEvent(w, ev);
159 else if (w == &menu) menuEventHandler->handleEvent(w, ev);
160 }
161}
162
163// blockCallback() is called when reading from the socket would block. We
164// process X events until the socket is ready for reading again.
165
166void CConn::blockCallback() {
167 fd_set rfds;
168 do {
169 struct timeval tv;
170 struct timeval* tvp = 0;
171
172 // Process any incoming X events
173 TXWindow::handleXEvents(dpy);
174
175 // Process expired timers and get the time until the next one
176 int timeoutMs = Timer::checkTimeouts();
177 if (timeoutMs) {
178 tv.tv_sec = timeoutMs / 1000;
179 tv.tv_usec = (timeoutMs % 1000) * 1000;
180 tvp = &tv;
181 }
182
183 // If there are X requests pending then poll, don't wait!
184 if (XPending(dpy)) {
185 tv.tv_usec = tv.tv_sec = 0;
186 tvp = &tv;
187 }
188
189 // Wait for X events, VNC traffic, or the next timer expiry
190 FD_ZERO(&rfds);
191 FD_SET(ConnectionNumber(dpy), &rfds);
192 FD_SET(sock->getFd(), &rfds);
193 int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
194 if (n < 0) throw rdr::SystemException("select",errno);
195 } while (!(FD_ISSET(sock->getFd(), &rfds)));
196}
197
198
199// getPasswd() is called by the CSecurity object when it needs us to read a
200// password from the user.
201
202void CConn::getUserPasswd(char** user, char** password)
203{
204 CharArray passwordFileStr(passwordFile.getData());
205 if (!user && passwordFileStr.buf[0]) {
206 FILE* fp = fopen(passwordFileStr.buf, "r");
207 if (!fp) throw rfb::Exception("Opening password file failed");
208 ObfuscatedPasswd obfPwd(256);
209 obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
210 fclose(fp);
211 PlainPasswd passwd(obfPwd);
212 *password = passwd.takeBuf();
213 return;
214 }
215
216 const char* secType = secTypeName(getCurrentCSecurity()->getType());
217 const char* titlePrefix = _("VNC authentication");
218 unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
219 CharArray title(titleLen);
220 snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
221 PasswdDialog dlg(dpy, title.buf, !user);
222 if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
223 if (user)
224 *user = strDup(dlg.userEntry.getText());
225 *password = strDup(dlg.passwdEntry.getText());
226}
227
228
229// CConnection callback methods
230
231// getCSecurity() gets the appropriate CSecurity object for the security
232// types which we support.
233CSecurity* CConn::getCSecurity(int secType) {
234 switch (secType) {
235 case secTypeNone:
236 return new CSecurityNone();
237 case secTypeVncAuth:
238 return new CSecurityVncAuth(this);
239 default:
240 throw rfb::Exception("Unsupported secType?");
241 }
242}
243
244// serverInit() is called when the serverInit message has been received. At
245// this point we create the desktop window and display it. We also tell the
246// server the pixel format and encodings to use and request the first update.
247void CConn::serverInit() {
248 CConnection::serverInit();
249
250 // If using AutoSelect with old servers, start in FullColor
251 // mode. See comment in autoSelectFormatAndEncoding.
252 if (cp.beforeVersion(3, 8) && autoSelect) {
253 fullColour = true;
254 }
255
256 serverPF = cp.pf();
257 desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
258 desktopEventHandler = desktop->setEventHandler(this);
259 desktop->addEventMask(KeyPressMask | KeyReleaseMask);
260 fullColourPF = desktop->getPF();
261 if (!serverPF.trueColour)
262 fullColour = true;
263 recreateViewport();
264 formatChange = encodingChange = true;
265 requestNewUpdate();
266}
267
268// setDesktopSize() is called when the desktop size changes (including when
269// it is set initially).
270void CConn::setDesktopSize(int w, int h) {
271 CConnection::setDesktopSize(w,h);
Pierre Ossman49f88222009-03-20 13:02:50 +0000272 resizeFramebuffer();
273}
274
275// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
Pierre Ossmancbd1b2c2009-03-20 16:05:04 +0000276void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
277 const rfb::ScreenSet& layout) {
278 CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
Pierre Ossman49f88222009-03-20 13:02:50 +0000279
280 if ((reason == reasonClient) && (result != resultSuccess))
281 return;
282
283 resizeFramebuffer();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000284}
285
Peter Åstrandc39e0782009-01-15 12:21:42 +0000286// setName() is called when the desktop name changes
287void CConn::setName(const char* name) {
288 CConnection::setName(name);
Peter Åstrand051a83a2009-01-15 13:36:03 +0000289
290 CharArray windowNameStr(windowName.getData());
291 if (!windowNameStr.buf[0]) {
292 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000293 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Peter Åstrand051a83a2009-01-15 13:36:03 +0000294 }
295
Peter Åstrandc39e0782009-01-15 12:21:42 +0000296 if (viewport) {
Peter Åstrand051a83a2009-01-15 13:36:03 +0000297 viewport->setName(windowNameStr.buf);
Peter Åstrandc39e0782009-01-15 12:21:42 +0000298 }
299}
300
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000301// framebufferUpdateEnd() is called at the end of an update.
302// For each rectangle, the FdInStream will have timed the speed
303// of the connection, allowing us to select format and encoding
304// appropriately, and then request another incremental update.
305void CConn::framebufferUpdateEnd() {
306 if (debugDelay != 0) {
307 XSync(dpy, False);
308 struct timeval tv;
309 tv.tv_sec = debugDelay / 1000;
310 tv.tv_usec = (debugDelay % 1000) * 1000;
311 select(0, 0, 0, 0, &tv);
312 std::list<rfb::Rect>::iterator i;
313 for (i = debugRects.begin(); i != debugRects.end(); i++) {
314 desktop->invertRect(*i);
315 }
316 debugRects.clear();
317 }
318 desktop->framebufferUpdateEnd();
319 if (autoSelect)
320 autoSelectFormatAndEncoding();
321 requestNewUpdate();
322}
323
324// The rest of the callbacks are fairly self-explanatory...
325
326void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
327{
328 desktop->setColourMapEntries(firstColour, nColours, rgbs);
329}
330
331void CConn::bell() { XBell(dpy, 0); }
332
333void CConn::serverCutText(const char* str, int len) {
334 desktop->serverCutText(str,len);
335}
336
337// We start timing on beginRect and stop timing on endRect, to
338// avoid skewing the bandwidth estimation as a result of the server
339// being slow or the network having high latency
340void CConn::beginRect(const Rect& r, unsigned int encoding)
341{
342 sock->inStream().startTiming();
343 if (encoding != encodingCopyRect) {
344 lastServerEncoding = encoding;
345 }
346}
347
348void CConn::endRect(const Rect& r, unsigned int encoding)
349{
350 sock->inStream().stopTiming();
351 if (debugDelay != 0) {
352 desktop->invertRect(r);
353 debugRects.push_back(r);
354 }
355}
356
357void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
358 desktop->fillRect(r,p);
359}
360void CConn::imageRect(const rfb::Rect& r, void* p) {
361 desktop->imageRect(r,p);
362}
363void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
364 desktop->copyRect(r,sx,sy);
365}
366void CConn::setCursor(int width, int height, const Point& hotspot,
367 void* data, void* mask) {
368 desktop->setCursor(width, height, hotspot, data, mask);
369}
370
371
372// Menu stuff - menuSelect() is called when the user selects a menu option.
373
374enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
375 ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
376
377void CConn::initMenu() {
378 menuEventHandler = menu.setEventHandler(this);
379 menu.addEventMask(KeyPressMask | KeyReleaseMask);
380 menu.addEntry(_("Exit viewer"), ID_EXIT);
381 menu.addEntry(0, 0);
382 menu.addEntry(_("Full screen"), ID_FULLSCREEN);
383 menu.check(ID_FULLSCREEN, fullScreen);
384 menu.addEntry(0, 0);
385 menu.addEntry(_("Ctrl"), ID_CTRL);
386 menu.addEntry(_("Alt"), ID_ALT);
387 CharArray menuKeyStr(menuKey.getData());
388 CharArray sendMenuKey(64);
389 snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
390 menu.addEntry(sendMenuKey.buf, ID_F8);
391 menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
392 menu.addEntry(0, 0);
393 menu.addEntry(_("Refresh screen"), ID_REFRESH);
394 menu.addEntry(0, 0);
395 menu.addEntry(_("New connection..."), ID_NEWCONN);
396 menu.addEntry(_("Options..."), ID_OPTIONS);
397 menu.addEntry(_("Connection info..."), ID_INFO);
398 menu.addEntry(_("About VNCviewer..."), ID_ABOUT);
399 menu.addEntry(0, 0);
400 menu.addEntry(_("Dismiss menu"), ID_DISMISS);
401 menu.toplevel(_("VNC Menu"), this);
402 menu.setBorderWidth(1);
403}
404
405void CConn::showMenu(int x, int y) {
406 menu.check(ID_FULLSCREEN, fullScreen);
407 if (x + menu.width() > viewport->width())
408 x = viewport->width() - menu.width();
409 if (y + menu.height() > viewport->height())
410 y = viewport->height() - menu.height();
411 menu.move(x, y);
412 menu.raise();
413 menu.map();
414}
415
416void CConn::menuSelect(long id, TXMenu* m) {
417 switch (id) {
418 case ID_NEWCONN:
419 {
420 menu.unmap();
421 if (fullScreen) {
422 fullScreen = false;
423 if (viewport) recreateViewport();
424 }
425 int pid = fork();
426 if (pid < 0) { perror("fork"); exit(1); }
427 if (pid == 0) {
428 delete sock;
429 close(ConnectionNumber(dpy));
430 struct timeval tv;
431 tv.tv_sec = 0;
432 tv.tv_usec = 200*1000;
433 select(0, 0, 0, 0, &tv);
Adam Tkacfee32e32008-10-10 15:48:22 +0000434 execlp(programName, programName, NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000435 perror("execlp"); exit(1);
436 }
437 break;
438 }
439 case ID_OPTIONS:
440 menu.unmap();
441 options.show();
442 break;
443 case ID_INFO:
444 {
445 menu.unmap();
446 char pfStr[100];
447 char spfStr[100];
448 cp.pf().print(pfStr, 100);
449 serverPF.print(spfStr, 100);
450 int secType = getCurrentCSecurity()->getType();
451 char infoText[1024];
452 snprintf(infoText, sizeof(infoText),
453 _("Desktop name: %.80s\n"
454 "Host: %.80s port: %d\n"
455 "Size: %d x %d\n"
456 "Pixel format: %s\n"
457 "(server default %s)\n"
458 "Requested encoding: %s\n"
459 "Last used encoding: %s\n"
460 "Line speed estimate: %d kbit/s\n"
461 "Protocol version: %d.%d\n"
462 "Security method: %s\n"),
463 cp.name(), serverHost, serverPort, cp.width, cp.height,
464 pfStr, spfStr, encodingName(currentEncoding),
465 encodingName(lastServerEncoding),
466 sock->inStream().kbitsPerSecond(),
467 cp.majorVersion, cp.minorVersion,
468 secTypeName(secType));
469 info.setText(infoText);
470 info.show();
471 break;
472 }
473 case ID_FULLSCREEN:
474 menu.unmap();
475 fullScreen = !fullScreen;
476 if (viewport) recreateViewport();
477 break;
478 case ID_REFRESH:
479 menu.unmap();
480 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
481 false);
482 break;
483 case ID_F8:
484 menu.unmap();
485 if (!viewOnly) {
486 writer()->keyEvent(menuKeysym, true);
487 writer()->keyEvent(menuKeysym, false);
488 }
489 break;
490 case ID_CTRLALTDEL:
491 menu.unmap();
492 if (!viewOnly) {
493 writer()->keyEvent(XK_Control_L, true);
494 writer()->keyEvent(XK_Alt_L, true);
495 writer()->keyEvent(XK_Delete, true);
496 writer()->keyEvent(XK_Delete, false);
497 writer()->keyEvent(XK_Alt_L, false);
498 writer()->keyEvent(XK_Control_L, false);
499 }
500 break;
501 case ID_CTRL:
502 menu.unmap();
503 if (!viewOnly) {
504 ctrlDown = !ctrlDown;
505 writer()->keyEvent(XK_Control_L, ctrlDown);
506 menu.check(ID_CTRL, ctrlDown);
507 }
508 break;
509 case ID_ALT:
510 menu.unmap();
511 if (!viewOnly) {
512 altDown = !altDown;
513 writer()->keyEvent(XK_Alt_L, altDown);
514 menu.check(ID_ALT, altDown);
515 }
516 break;
517 case ID_ABOUT:
518 menu.unmap();
519 about.show();
520 break;
521 case ID_DISMISS:
522 menu.unmap();
523 break;
524 case ID_EXIT:
525 exit(1);
526 break;
527 }
528}
529
530
531// OptionsDialogCallback. setOptions() sets the options dialog's checkboxes
532// etc to reflect our flags. getOptions() sets our flags according to the
533// options dialog's checkboxes.
534
535void CConn::setOptions() {
536 char digit[2] = "0";
537 options.autoSelect.checked(autoSelect);
538 options.fullColour.checked(fullColour);
539 options.veryLowColour.checked(!fullColour && lowColourLevel == 0);
540 options.lowColour.checked(!fullColour && lowColourLevel == 1);
541 options.mediumColour.checked(!fullColour && lowColourLevel == 2);
542 options.tight.checked(currentEncoding == encodingTight);
543 options.zrle.checked(currentEncoding == encodingZRLE);
544 options.hextile.checked(currentEncoding == encodingHextile);
545 options.raw.checked(currentEncoding == encodingRaw);
546
547 options.customCompressLevel.checked(customCompressLevel);
548 digit[0] = '0' + compressLevel;
549 options.compressLevel.setText(digit);
550 options.noJpeg.checked(!noJpeg);
551 digit[0] = '0' + qualityLevel;
552 options.qualityLevel.setText(digit);
553
554 options.viewOnly.checked(viewOnly);
555 options.acceptClipboard.checked(acceptClipboard);
556 options.sendClipboard.checked(sendClipboard);
557 options.sendPrimary.checked(sendPrimary);
558 if (state() == RFBSTATE_NORMAL)
559 options.shared.disabled(true);
560 else
561 options.shared.checked(shared);
562 options.fullScreen.checked(fullScreen);
563 options.useLocalCursor.checked(useLocalCursor);
564 options.dotWhenNoCursor.checked(dotWhenNoCursor);
565}
566
567void CConn::getOptions() {
568 autoSelect = options.autoSelect.checked();
569 if (fullColour != options.fullColour.checked())
570 formatChange = true;
571 fullColour = options.fullColour.checked();
572 if (!fullColour) {
573 int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
574 options.lowColour.checked() ? 1 : 2);
575 if (newLowColourLevel != lowColourLevel) {
576 lowColourLevel.setParam(newLowColourLevel);
577 formatChange = true;
578 }
579 }
580 unsigned int newEncoding = (options.tight.checked() ? encodingTight :
581 options.zrle.checked() ? encodingZRLE :
582 options.hextile.checked() ? encodingHextile :
583 encodingRaw);
584 if (newEncoding != currentEncoding) {
585 currentEncoding = newEncoding;
586 encodingChange = true;
587 }
588
589 customCompressLevel.setParam(options.customCompressLevel.checked());
590 if (cp.customCompressLevel != customCompressLevel) {
591 cp.customCompressLevel = customCompressLevel;
592 encodingChange = true;
593 }
594 compressLevel.setParam(options.compressLevel.getText());
595 if (cp.compressLevel != compressLevel) {
596 cp.compressLevel = compressLevel;
597 encodingChange = true;
598 }
599 noJpeg.setParam(!options.noJpeg.checked());
600 if (cp.noJpeg != noJpeg) {
601 cp.noJpeg = noJpeg;
602 encodingChange = true;
603 }
604 qualityLevel.setParam(options.qualityLevel.getText());
605 if (cp.qualityLevel != qualityLevel) {
606 cp.qualityLevel = qualityLevel;
607 encodingChange = true;
608 }
609
610 viewOnly.setParam(options.viewOnly.checked());
611 acceptClipboard.setParam(options.acceptClipboard.checked());
612 sendClipboard.setParam(options.sendClipboard.checked());
613 sendPrimary.setParam(options.sendPrimary.checked());
614 shared = options.shared.checked();
615 setShared(shared);
616 if (fullScreen != options.fullScreen.checked()) {
617 fullScreen = options.fullScreen.checked();
618 if (viewport) recreateViewport();
619 }
620 useLocalCursor.setParam(options.useLocalCursor.checked());
621 if (cp.supportsLocalCursor != useLocalCursor) {
622 cp.supportsLocalCursor = useLocalCursor;
623 encodingChange = true;
624 if (desktop)
625 desktop->resetLocalCursor();
626 }
627 dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
628 checkEncodings();
629}
630
Pierre Ossman49f88222009-03-20 13:02:50 +0000631void CConn::resizeFramebuffer()
632{
633 if (!desktop)
634 return;
635 if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
636 return;
637
638 desktop->resize(cp.width, cp.height);
639 recreateViewport();
640}
641
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000642void CConn::recreateViewport()
643{
644 TXViewport* oldViewport = viewport;
645 viewport = new TXViewport(dpy, cp.width, cp.height);
646 desktop->setViewport(viewport);
647 CharArray windowNameStr(windowName.getData());
648 if (!windowNameStr.buf[0]) {
649 windowNameStr.replaceBuf(new char[256]);
Peter Åstrand4eacc022009-02-27 10:12:14 +0000650 snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000651 }
652 viewport->toplevel(windowNameStr.buf, this, argc, argv);
653 viewport->setBumpScroll(fullScreen);
654 XSetWindowAttributes attr;
655 attr.override_redirect = fullScreen;
656 XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
657 XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
658 XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
659 XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
660 XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
661 reconfigureViewport();
662 menu.setTransientFor(viewport->win());
663 viewport->map();
664 if (fullScreen) {
665 XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
666 CurrentTime);
667 } else {
668 XUngrabKeyboard(dpy, CurrentTime);
669 }
670 if (oldViewport) delete oldViewport;
671}
672
673void CConn::reconfigureViewport()
674{
675 viewport->setMaxSize(cp.width, cp.height);
676 if (fullScreen) {
677 viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
678 DisplayHeight(dpy,DefaultScreen(dpy)));
679 } else {
680 int w = cp.width;
681 int h = cp.height;
682 if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
683 w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
684 if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
685 h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
686
687 int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
688 int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
689
690 CharArray geometryStr(geometry.getData());
691 viewport->setGeometry(geometryStr.buf, x, y, w, h);
692 }
693}
694
Pierre Ossman78b23592009-03-12 12:25:11 +0000695// Note: The method below is duplicated in win/vncviewer/CConn.cxx!
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000696
697// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
698// to the connection speed:
699//
Pierre Ossman78b23592009-03-12 12:25:11 +0000700// First we wait for at least one second of bandwidth measurement.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000701//
Pierre Ossman78b23592009-03-12 12:25:11 +0000702// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
703// which should be perceptually lossless.
704//
705// If the bandwidth is below that, we choose a more lossy JPEG quality.
706//
707// If the bandwidth drops below 256 Kbps, we switch to palette mode.
708//
709// Note: The system here is fairly arbitrary and should be replaced
710// with something more intelligent at the server end.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000711//
712void CConn::autoSelectFormatAndEncoding()
713{
714 int kbitsPerSecond = sock->inStream().kbitsPerSecond();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000715 unsigned int timeWaited = sock->inStream().timeWaited();
Pierre Ossman78b23592009-03-12 12:25:11 +0000716 bool newFullColour = fullColour;
717 int newQualityLevel = qualityLevel;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000718
Pierre Ossman78b23592009-03-12 12:25:11 +0000719 // Always use Tight
720 currentEncoding = encodingTight;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000721
Pierre Ossman78b23592009-03-12 12:25:11 +0000722 // Check that we have a decent bandwidth measurement
723 if ((kbitsPerSecond == 0) || (timeWaited < 10000))
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000724 return;
Pierre Ossman78b23592009-03-12 12:25:11 +0000725
726 // Select appropriate quality level
727 if (!noJpeg) {
728 if (kbitsPerSecond > 16000)
729 newQualityLevel = 8;
730 else
731 newQualityLevel = 6;
732
733 if (newQualityLevel != qualityLevel) {
734 vlog.info("Throughput %d kbit/s - changing to quality %d ",
735 kbitsPerSecond, newQualityLevel);
736 cp.qualityLevel = newQualityLevel;
737 qualityLevel.setParam(newQualityLevel);
738 encodingChange = true;
739 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000740 }
741
742 if (cp.beforeVersion(3, 8)) {
743 // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
744 // cursors "asynchronously". If this happens in the middle of a
745 // pixel format change, the server will encode the cursor with
746 // the old format, but the client will try to decode it
747 // according to the new format. This will lead to a
748 // crash. Therefore, we do not allow automatic format change for
749 // old servers.
750 return;
751 }
752
753 // Select best color level
754 newFullColour = (kbitsPerSecond > 256);
755 if (newFullColour != fullColour) {
756 vlog.info("Throughput %d kbit/s - full color is now %s",
757 kbitsPerSecond,
758 newFullColour ? "enabled" : "disabled");
759 fullColour = newFullColour;
760 formatChange = true;
761 }
762}
763
764// checkEncodings() sends a setEncodings message if one is needed.
765void CConn::checkEncodings()
766{
767 if (encodingChange && writer()) {
768 vlog.info("Using %s encoding",encodingName(currentEncoding));
769 writer()->writeSetEncodings(currentEncoding, true);
770 encodingChange = false;
771 }
772}
773
774// requestNewUpdate() requests an update from the server, having set the
775// format and encoding appropriately.
776void CConn::requestNewUpdate()
777{
778 if (formatChange) {
779 if (fullColour) {
780 desktop->setPF(fullColourPF);
781 } else {
782 if (lowColourLevel == 0)
783 desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
784 else if (lowColourLevel == 1)
785 desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
786 else
787 desktop->setPF(PixelFormat(8,8,0,0));
788 }
789 char str[256];
790 desktop->getPF().print(str, 256);
791 vlog.info("Using pixel format %s",str);
792 cp.setPF(desktop->getPF());
793 writer()->writeSetPixelFormat(cp.pf());
794 }
795 checkEncodings();
796 writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
797 !formatChange);
798 formatChange = false;
799}