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