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