blob: f93a3d60da1294771395b350c6d39a8da5db298e [file] [log] [blame]
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2009 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
//
// CConn.cxx
//
#include <unistd.h>
#include "CConn.h"
#include <rfb/CMsgWriter.h>
#include <rfb/encodings.h>
#include <rfb/Security.h>
#include <rfb/CSecurityNone.h>
#include <rfb/CSecurityVncAuth.h>
#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif
#include <rfb/Hostname.h>
#include <rfb/LogWriter.h>
#include <rfb/util.h>
#include <rfb/Password.h>
#include <rfb/screenTypes.h>
#include <network/TcpSocket.h>
#include <cassert>
#include <list>
#include "TXViewport.h"
#include "DesktopWindow.h"
#include "ServerDialog.h"
#include "PasswdDialog.h"
#include "parameters.h"
using namespace rdr;
using namespace rfb;
using namespace std;
static rfb::LogWriter vlog("CConn");
IntParameter debugDelay("DebugDelay","Milliseconds to display inverted "
"pixel data - a debugging feature", 0);
StringParameter menuKey("MenuKey", "The key which brings up the popup menu",
"F8");
StringParameter windowName("name", "The X window name", "");
CConn::CConn(Display* dpy_, int argc_, char** argv_, network::Socket* sock_,
char* vncServerName, bool reverse)
: dpy(dpy_), argc(argc_),
argv(argv_), serverHost(0), serverPort(0), sock(sock_), viewport(0),
desktop(0), desktopEventHandler(0),
currentEncoding(encodingTight), lastServerEncoding((unsigned int)-1),
fullColour(::fullColour),
autoSelect(::autoSelect), shared(::shared), formatChange(false),
encodingChange(false), sameMachine(false), fullScreen(::fullScreen),
ctrlDown(false), altDown(false),
menuKeysym(0), menu(dpy, this), options(dpy, this), about(dpy), info(dpy),
reverseConnection(reverse), firstUpdate(true), pendingUpdate(false)
{
CharArray menuKeyStr(menuKey.getData());
menuKeysym = XStringToKeysym(menuKeyStr.buf);
setShared(shared);
CSecurity::upg = this; /* Security instance is created in CConnection constructor. */
#ifdef HAVE_GNUTLS
CSecurityTLS::msg = this;
#endif
CharArray encStr(preferredEncoding.getData());
int encNum = encodingNum(encStr.buf);
if (encNum != -1) {
currentEncoding = encNum;
}
cp.supportsDesktopResize = true;
cp.supportsExtendedDesktopSize = true;
cp.supportsDesktopRename = true;
cp.supportsLocalCursor = useLocalCursor;
cp.customCompressLevel = customCompressLevel;
cp.compressLevel = compressLevel;
cp.noJpeg = noJpeg;
cp.qualityLevel = qualityLevel;
initMenu();
if (sock) {
char* name = sock->getPeerEndpoint();
vlog.info("Accepted connection from %s", name);
if (name) free(name);
} else {
if (vncServerName) {
getHostAndPort(vncServerName, &serverHost, &serverPort);
} else {
ServerDialog dlg(dpy, &options, &about);
if (!dlg.show() || dlg.entry.getText()[0] == 0) {
exit(1);
}
getHostAndPort(dlg.entry.getText(), &serverHost, &serverPort);
}
sock = new network::TcpSocket(serverHost, serverPort);
vlog.info("connected to host %s port %d", serverHost, serverPort);
}
sameMachine = sock->sameMachine();
sock->inStream().setBlockCallback(this);
setServerName(serverHost);
setStreams(&sock->inStream(), &sock->outStream());
initialiseProtocol();
}
CConn::~CConn() {
free(serverHost);
delete desktop;
delete viewport;
delete sock;
}
bool CConn::showMsgBox(int flags, const char* title, const char* text)
{
CharArray titleText(12 + strlen(title) + 1);
sprintf(titleText.buf, "VNC Viewer: %s", title);
TXMsgBox msgBox(dpy,text,flags,titleText.buf);
return msgBox.show();
}
// deleteWindow() is called when the user closes the desktop or menu windows.
void CConn::deleteWindow(TXWindow* w) {
if (w == &menu) {
menu.unmap();
} else if (w == viewport) {
exit(1);
}
}
// handleEvent() filters all events on the desktop and menu. Most are passed
// straight through. The exception is the F8 key. When pressed on the
// desktop, it is used to bring up the menu. An F8 press or release on the
// menu is passed through as if it were on the desktop.
void CConn::handleEvent(TXWindow* w, XEvent* ev)
{
KeySym ks;
char str[256];
switch (ev->type) {
case KeyPress:
case KeyRelease:
XLookupString(&ev->xkey, str, 256, &ks, NULL);
if (ks == menuKeysym && (ev->xkey.state & (ShiftMask|ControlMask)) == 0) {
if (w == desktop && ev->type == KeyPress) {
showMenu(ev->xkey.x_root, ev->xkey.y_root);
break;
} else if (w == &menu) {
if (ev->type == KeyPress) menu.unmap();
desktopEventHandler->handleEvent(w, ev);
break;
}
}
// drop through
default:
if (w == desktop) desktopEventHandler->handleEvent(w, ev);
else if (w == &menu) menuEventHandler->handleEvent(w, ev);
}
}
// blockCallback() is called when reading from the socket would block. We
// process X events until the socket is ready for reading again.
void CConn::blockCallback() {
fd_set rfds;
do {
struct timeval tv;
struct timeval* tvp = 0;
// Process any incoming X events
TXWindow::handleXEvents(dpy);
// Process expired timers and get the time until the next one
int timeoutMs = Timer::checkTimeouts();
if (timeoutMs) {
tv.tv_sec = timeoutMs / 1000;
tv.tv_usec = (timeoutMs % 1000) * 1000;
tvp = &tv;
}
// If there are X requests pending then poll, don't wait!
if (XPending(dpy)) {
tv.tv_usec = tv.tv_sec = 0;
tvp = &tv;
}
// Wait for X events, VNC traffic, or the next timer expiry
FD_ZERO(&rfds);
FD_SET(ConnectionNumber(dpy), &rfds);
FD_SET(sock->getFd(), &rfds);
int n = select(FD_SETSIZE, &rfds, 0, 0, tvp);
if (n < 0) throw rdr::SystemException("select",errno);
} while (!(FD_ISSET(sock->getFd(), &rfds)));
}
// getPasswd() is called by the CSecurity object when it needs us to read a
// password from the user.
void CConn::getUserPasswd(char** user, char** password)
{
CharArray passwordFileStr(passwordFile.getData());
if (!user && passwordFileStr.buf[0]) {
FILE* fp = fopen(passwordFileStr.buf, "r");
if (!fp) throw rfb::Exception("Opening password file failed");
ObfuscatedPasswd obfPwd(256);
obfPwd.length = fread(obfPwd.buf, 1, obfPwd.length, fp);
fclose(fp);
PlainPasswd passwd(obfPwd);
*password = passwd.takeBuf();
return;
}
const char* secType = secTypeName(csecurity->getType());
const char* titlePrefix = _("VNC authentication");
unsigned int titleLen = strlen(titlePrefix) + strlen(secType) + 4;
CharArray title(titleLen);
snprintf(title.buf, titleLen, "%s [%s]", titlePrefix, secType);
PasswdDialog dlg(dpy, title.buf, !user);
if (!dlg.show()) throw rfb::Exception("Authentication cancelled");
if (user)
*user = strDup(dlg.userEntry.getText());
*password = strDup(dlg.passwdEntry.getText());
}
// CConnection callback methods
// serverInit() is called when the serverInit message has been received. At
// this point we create the desktop window and display it. We also tell the
// server the pixel format and encodings to use and request the first update.
void CConn::serverInit() {
CConnection::serverInit();
// If using AutoSelect with old servers, start in FullColor
// mode. See comment in autoSelectFormatAndEncoding.
if (cp.beforeVersion(3, 8) && autoSelect) {
fullColour = true;
}
serverPF = cp.pf();
desktop = new DesktopWindow(dpy, cp.width, cp.height, serverPF, this);
desktopEventHandler = desktop->setEventHandler(this);
desktop->addEventMask(KeyPressMask | KeyReleaseMask);
fullColourPF = desktop->getPF();
if (!serverPF.trueColour)
fullColour = true;
recreateViewport();
formatChange = encodingChange = true;
requestNewUpdate();
}
// setDesktopSize() is called when the desktop size changes (including when
// it is set initially).
void CConn::setDesktopSize(int w, int h) {
CConnection::setDesktopSize(w,h);
resizeFramebuffer();
}
// setExtendedDesktopSize() is a more advanced version of setDesktopSize()
void CConn::setExtendedDesktopSize(int reason, int result, int w, int h,
const rfb::ScreenSet& layout) {
CConnection::setExtendedDesktopSize(reason, result, w, h, layout);
if ((reason == reasonClient) && (result != resultSuccess)) {
vlog.error("SetDesktopSize failed: %d", result);
return;
}
resizeFramebuffer();
}
// setName() is called when the desktop name changes
void CConn::setName(const char* name) {
CConnection::setName(name);
CharArray windowNameStr(windowName.getData());
if (!windowNameStr.buf[0]) {
windowNameStr.replaceBuf(new char[256]);
snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
}
if (viewport) {
viewport->setName(windowNameStr.buf);
}
}
// framebufferUpdateStart() is called at the beginning of an update.
// Here we try to send out a new framebuffer update request so that the
// next update can be sent out in parallel with us decoding the current
// one. We cannot do this if we're in the middle of a format change
// though.
void CConn::framebufferUpdateStart() {
if (!formatChange) {
pendingUpdate = true;
requestNewUpdate();
} else
pendingUpdate = false;
}
// framebufferUpdateEnd() is called at the end of an update.
// For each rectangle, the FdInStream will have timed the speed
// of the connection, allowing us to select format and encoding
// appropriately, and then request another incremental update.
void CConn::framebufferUpdateEnd() {
if (debugDelay != 0) {
XSync(dpy, False);
struct timeval tv;
tv.tv_sec = debugDelay / 1000;
tv.tv_usec = (debugDelay % 1000) * 1000;
select(0, 0, 0, 0, &tv);
std::list<rfb::Rect>::iterator i;
for (i = debugRects.begin(); i != debugRects.end(); i++) {
desktop->invertRect(*i);
}
debugRects.clear();
}
desktop->framebufferUpdateEnd();
if (firstUpdate) {
int width, height;
if (cp.supportsSetDesktopSize &&
sscanf(desktopSize.getValueStr(), "%dx%d", &width, &height) == 2) {
ScreenSet layout;
layout = cp.screenLayout;
if (layout.num_screens() == 0)
layout.add_screen(rfb::Screen());
else if (layout.num_screens() != 1) {
ScreenSet::iterator iter;
while (true) {
iter = layout.begin();
++iter;
if (iter == layout.end())
break;
layout.remove_screen(iter->id);
}
}
layout.begin()->dimensions.tl.x = 0;
layout.begin()->dimensions.tl.y = 0;
layout.begin()->dimensions.br.x = width;
layout.begin()->dimensions.br.y = height;
writer()->writeSetDesktopSize(width, height, layout);
}
firstUpdate = false;
}
// A format change prevented us from sending this before the update,
// so make sure to send it now.
if (formatChange && !pendingUpdate)
requestNewUpdate();
// Compute new settings based on updated bandwidth values
if (autoSelect)
autoSelectFormatAndEncoding();
// Make sure that the X11 handling and the timers gets some CPU time
// in case of back to back framebuffer updates.
TXWindow::handleXEvents(dpy);
Timer::checkTimeouts();
}
// The rest of the callbacks are fairly self-explanatory...
void CConn::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs)
{
desktop->setColourMapEntries(firstColour, nColours, rgbs);
}
void CConn::bell() { XBell(dpy, 0); }
void CConn::serverCutText(const char* str, rdr::U32 len) {
desktop->serverCutText(str,len);
}
// We start timing on beginRect and stop timing on endRect, to
// avoid skewing the bandwidth estimation as a result of the server
// being slow or the network having high latency
void CConn::beginRect(const Rect& r, int encoding)
{
sock->inStream().startTiming();
if (encoding != encodingCopyRect) {
lastServerEncoding = encoding;
}
}
void CConn::endRect(const Rect& r, int encoding)
{
sock->inStream().stopTiming();
if (debugDelay != 0) {
desktop->invertRect(r);
debugRects.push_back(r);
}
}
void CConn::fillRect(const rfb::Rect& r, rfb::Pixel p) {
desktop->fillRect(r,p);
}
void CConn::imageRect(const rfb::Rect& r, void* p) {
desktop->imageRect(r,p);
}
void CConn::copyRect(const rfb::Rect& r, int sx, int sy) {
desktop->copyRect(r,sx,sy);
}
void CConn::setCursor(int width, int height, const Point& hotspot,
void* data, void* mask) {
desktop->setCursor(width, height, hotspot, data, mask);
}
// Menu stuff - menuSelect() is called when the user selects a menu option.
enum { ID_OPTIONS, ID_INFO, ID_FULLSCREEN, ID_REFRESH, ID_F8, ID_CTRLALTDEL,
ID_ABOUT, ID_DISMISS, ID_EXIT, ID_NEWCONN, ID_CTRL, ID_ALT };
void CConn::initMenu() {
menuEventHandler = menu.setEventHandler(this);
menu.addEventMask(KeyPressMask | KeyReleaseMask);
menu.addEntry(_("Exit viewer"), ID_EXIT);
menu.addEntry(0, 0);
menu.addEntry(_("Full screen"), ID_FULLSCREEN);
menu.check(ID_FULLSCREEN, fullScreen);
menu.addEntry(0, 0);
menu.addEntry(_("Ctrl"), ID_CTRL);
menu.addEntry(_("Alt"), ID_ALT);
CharArray menuKeyStr(menuKey.getData());
CharArray sendMenuKey(64);
snprintf(sendMenuKey.buf, 64, _("Send %s"), menuKeyStr.buf);
menu.addEntry(sendMenuKey.buf, ID_F8);
menu.addEntry(_("Send Ctrl-Alt-Del"), ID_CTRLALTDEL);
menu.addEntry(0, 0);
menu.addEntry(_("Refresh screen"), ID_REFRESH);
menu.addEntry(0, 0);
menu.addEntry(_("New connection..."), ID_NEWCONN);
menu.addEntry(_("Options..."), ID_OPTIONS);
menu.addEntry(_("Connection info..."), ID_INFO);
menu.addEntry(_("About TigerVNC viewer..."), ID_ABOUT);
menu.addEntry(0, 0);
menu.addEntry(_("Dismiss menu"), ID_DISMISS);
menu.toplevel(_("VNC Menu"), this);
menu.setBorderWidth(1);
}
void CConn::showMenu(int x, int y) {
menu.check(ID_FULLSCREEN, fullScreen);
if (x + menu.width() > viewport->width())
x = viewport->width() - menu.width();
if (y + menu.height() > viewport->height())
y = viewport->height() - menu.height();
menu.move(x, y);
menu.raise();
menu.map();
}
void CConn::menuSelect(long id, TXMenu* m) {
switch (id) {
case ID_NEWCONN:
{
menu.unmap();
if (fullScreen) {
fullScreen = false;
if (viewport) recreateViewport();
}
int pid = fork();
if (pid < 0) { perror("fork"); exit(1); }
if (pid == 0) {
delete sock;
close(ConnectionNumber(dpy));
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200*1000;
select(0, 0, 0, 0, &tv);
execlp(programName, programName, NULL);
perror("execlp"); exit(1);
}
break;
}
case ID_OPTIONS:
menu.unmap();
options.show();
break;
case ID_INFO:
{
menu.unmap();
char pfStr[100];
char spfStr[100];
cp.pf().print(pfStr, 100);
serverPF.print(spfStr, 100);
int secType = csecurity->getType();
char infoText[1024];
snprintf(infoText, sizeof(infoText),
_("Desktop name: %.80s\n"
"Host: %.80s port: %d\n"
"Size: %d x %d\n"
"Pixel format: %s\n"
"(server default %s)\n"
"Requested encoding: %s\n"
"Last used encoding: %s\n"
"Line speed estimate: %d kbit/s\n"
"Protocol version: %d.%d\n"
"Security method: %s\n"),
cp.name(), serverHost, serverPort, cp.width, cp.height,
pfStr, spfStr, encodingName(currentEncoding),
encodingName(lastServerEncoding),
sock->inStream().kbitsPerSecond(),
cp.majorVersion, cp.minorVersion,
secTypeName(secType));
info.setText(infoText);
info.show();
break;
}
case ID_FULLSCREEN:
menu.unmap();
fullScreen = !fullScreen;
if (viewport) recreateViewport();
break;
case ID_REFRESH:
menu.unmap();
if (!formatChange) {
writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
false);
pendingUpdate = true;
}
break;
case ID_F8:
menu.unmap();
if (!viewOnly) {
writer()->keyEvent(menuKeysym, true);
writer()->keyEvent(menuKeysym, false);
}
break;
case ID_CTRLALTDEL:
menu.unmap();
if (!viewOnly) {
writer()->keyEvent(XK_Control_L, true);
writer()->keyEvent(XK_Alt_L, true);
writer()->keyEvent(XK_Delete, true);
writer()->keyEvent(XK_Delete, false);
writer()->keyEvent(XK_Alt_L, false);
writer()->keyEvent(XK_Control_L, false);
}
break;
case ID_CTRL:
menu.unmap();
if (!viewOnly) {
ctrlDown = !ctrlDown;
writer()->keyEvent(XK_Control_L, ctrlDown);
menu.check(ID_CTRL, ctrlDown);
}
break;
case ID_ALT:
menu.unmap();
if (!viewOnly) {
altDown = !altDown;
writer()->keyEvent(XK_Alt_L, altDown);
menu.check(ID_ALT, altDown);
}
break;
case ID_ABOUT:
menu.unmap();
about.show();
break;
case ID_DISMISS:
menu.unmap();
break;
case ID_EXIT:
exit(1);
break;
}
}
// OptionsDialogCallback. setOptions() sets the options dialog's checkboxes
// etc to reflect our flags. getOptions() sets our flags according to the
// options dialog's checkboxes.
void CConn::setOptions() {
char digit[2] = "0";
options.autoSelect.checked(autoSelect);
options.fullColour.checked(fullColour);
options.veryLowColour.checked(!fullColour && lowColourLevel == 0);
options.lowColour.checked(!fullColour && lowColourLevel == 1);
options.mediumColour.checked(!fullColour && lowColourLevel == 2);
options.tight.checked(currentEncoding == encodingTight);
options.zrle.checked(currentEncoding == encodingZRLE);
options.hextile.checked(currentEncoding == encodingHextile);
options.raw.checked(currentEncoding == encodingRaw);
options.customCompressLevel.checked(customCompressLevel);
digit[0] = '0' + compressLevel;
options.compressLevel.setText(digit);
options.noJpeg.checked(!noJpeg);
digit[0] = '0' + qualityLevel;
options.qualityLevel.setText(digit);
options.viewOnly.checked(viewOnly);
options.acceptClipboard.checked(acceptClipboard);
options.sendClipboard.checked(sendClipboard);
options.sendPrimary.checked(sendPrimary);
if (state() == RFBSTATE_NORMAL) {
options.shared.disabled(true);
#ifdef HAVE_GNUTLS
options.secVeNCrypt.disabled(true);
options.encNone.disabled(true);
options.encTLS.disabled(true);
options.encX509.disabled(true);
options.ca.disabled(true);
options.crl.disabled(true);
options.secNone.disabled(true);
options.secVnc.disabled(true);
options.secPlain.disabled(true);
#endif
} else {
options.shared.checked(shared);
#ifdef HAVE_GNUTLS
/* Process non-VeNCrypt sectypes */
list<U8> secTypes = security->GetEnabledSecTypes();
list<U8>::iterator i;
for (i = secTypes.begin(); i != secTypes.end(); i++) {
switch (*i) {
case secTypeVeNCrypt:
options.secVeNCrypt.checked(true);
break;
case secTypeNone:
options.encNone.checked(true);
options.secNone.checked(true);
break;
case secTypeVncAuth:
options.encNone.checked(true);
options.secVnc.checked(true);
break;
}
}
/* Process VeNCrypt subtypes */
if (options.secVeNCrypt.checked()) {
list<U32> secTypesExt = security->GetEnabledExtSecTypes();
list<U32>::iterator iext;
for (iext = secTypesExt.begin(); iext != secTypesExt.end(); iext++) {
switch (*iext) {
case secTypePlain:
options.encNone.checked(true);
options.secPlain.checked(true);
break;
case secTypeTLSNone:
options.encTLS.checked(true);
options.secNone.checked(true);
break;
case secTypeTLSVnc:
options.encTLS.checked(true);
options.secVnc.checked(true);
break;
case secTypeTLSPlain:
options.encTLS.checked(true);
options.secPlain.checked(true);
break;
case secTypeX509None:
options.encX509.checked(true);
options.secNone.checked(true);
break;
case secTypeX509Vnc:
options.encX509.checked(true);
options.secVnc.checked(true);
break;
case secTypeX509Plain:
options.encX509.checked(true);
options.secPlain.checked(true);
break;
}
}
}
#endif
}
options.fullScreen.checked(fullScreen);
options.useLocalCursor.checked(useLocalCursor);
options.dotWhenNoCursor.checked(dotWhenNoCursor);
}
void CConn::getOptions() {
autoSelect = options.autoSelect.checked();
if (fullColour != options.fullColour.checked())
formatChange = true;
fullColour = options.fullColour.checked();
if (!fullColour) {
int newLowColourLevel = (options.veryLowColour.checked() ? 0 :
options.lowColour.checked() ? 1 : 2);
if (newLowColourLevel != lowColourLevel) {
lowColourLevel.setParam(newLowColourLevel);
formatChange = true;
}
}
int newEncoding = (options.tight.checked() ? encodingTight :
options.zrle.checked() ? encodingZRLE :
options.hextile.checked() ? encodingHextile :
encodingRaw);
if (newEncoding != currentEncoding) {
currentEncoding = newEncoding;
encodingChange = true;
}
customCompressLevel.setParam(options.customCompressLevel.checked());
if (cp.customCompressLevel != customCompressLevel) {
cp.customCompressLevel = customCompressLevel;
encodingChange = true;
}
compressLevel.setParam(options.compressLevel.getText());
if (cp.compressLevel != compressLevel) {
cp.compressLevel = compressLevel;
encodingChange = true;
}
noJpeg.setParam(!options.noJpeg.checked());
if (cp.noJpeg != noJpeg) {
cp.noJpeg = noJpeg;
encodingChange = true;
}
qualityLevel.setParam(options.qualityLevel.getText());
if (cp.qualityLevel != qualityLevel) {
cp.qualityLevel = qualityLevel;
encodingChange = true;
}
viewOnly.setParam(options.viewOnly.checked());
acceptClipboard.setParam(options.acceptClipboard.checked());
sendClipboard.setParam(options.sendClipboard.checked());
sendPrimary.setParam(options.sendPrimary.checked());
shared = options.shared.checked();
setShared(shared);
if (fullScreen != options.fullScreen.checked()) {
fullScreen = options.fullScreen.checked();
if (viewport) recreateViewport();
}
useLocalCursor.setParam(options.useLocalCursor.checked());
if (cp.supportsLocalCursor != useLocalCursor) {
cp.supportsLocalCursor = useLocalCursor;
encodingChange = true;
if (desktop)
desktop->resetLocalCursor();
}
dotWhenNoCursor.setParam(options.dotWhenNoCursor.checked());
if (desktop)
desktop->setNoCursor();
checkEncodings();
#ifdef HAVE_GNUTLS
/* Process security types which don't use encryption */
if (options.encNone.checked()) {
if (options.secNone.checked())
security->EnableSecType(secTypeNone);
if (options.secVnc.checked())
security->EnableSecType(secTypeVncAuth);
if (options.secPlain.checked())
security->EnableSecType(secTypePlain);
} else {
security->DisableSecType(secTypeNone);
security->DisableSecType(secTypeVncAuth);
security->DisableSecType(secTypePlain);
}
/* Process security types which use TLS encryption */
if (options.encTLS.checked()) {
if (options.secNone.checked())
security->EnableSecType(secTypeTLSNone);
if (options.secVnc.checked())
security->EnableSecType(secTypeTLSVnc);
if (options.secPlain.checked())
security->EnableSecType(secTypeTLSPlain);
} else {
security->DisableSecType(secTypeTLSNone);
security->DisableSecType(secTypeTLSVnc);
security->DisableSecType(secTypeTLSPlain);
}
/* Process security types which use X509 encryption */
if (options.encX509.checked()) {
if (options.secNone.checked())
security->EnableSecType(secTypeX509None);
if (options.secVnc.checked())
security->EnableSecType(secTypeX509Vnc);
if (options.secPlain.checked())
security->EnableSecType(secTypeX509Plain);
} else {
security->DisableSecType(secTypeX509None);
security->DisableSecType(secTypeX509Vnc);
security->DisableSecType(secTypeX509Plain);
}
/* Process *None security types */
if (options.secNone.checked()) {
if (options.encNone.checked())
security->EnableSecType(secTypeNone);
if (options.encTLS.checked())
security->EnableSecType(secTypeTLSNone);
if (options.encX509.checked())
security->EnableSecType(secTypeX509None);
} else {
security->DisableSecType(secTypeNone);
security->DisableSecType(secTypeTLSNone);
security->DisableSecType(secTypeX509None);
}
/* Process *Vnc security types */
if (options.secVnc.checked()) {
if (options.encNone.checked())
security->EnableSecType(secTypeVncAuth);
if (options.encTLS.checked())
security->EnableSecType(secTypeTLSVnc);
if (options.encX509.checked())
security->EnableSecType(secTypeX509Vnc);
} else {
security->DisableSecType(secTypeVncAuth);
security->DisableSecType(secTypeTLSVnc);
security->DisableSecType(secTypeX509Vnc);
}
/* Process *Plain security types */
if (options.secPlain.checked()) {
if (options.encNone.checked())
security->EnableSecType(secTypePlain);
if (options.encTLS.checked())
security->EnableSecType(secTypeTLSPlain);
if (options.encX509.checked())
security->EnableSecType(secTypeX509Plain);
} else {
security->DisableSecType(secTypePlain);
security->DisableSecType(secTypeTLSPlain);
security->DisableSecType(secTypeX509Plain);
}
CSecurityTLS::x509ca.setParam(options.ca.getText());
CSecurityTLS::x509crl.setParam(options.crl.getText());
#endif
}
void CConn::resizeFramebuffer()
{
if (!desktop)
return;
if ((desktop->width() == cp.width) && (desktop->height() == cp.height))
return;
desktop->resize(cp.width, cp.height);
recreateViewport();
}
void CConn::recreateViewport()
{
TXViewport* oldViewport = viewport;
viewport = new TXViewport(dpy, cp.width, cp.height);
desktop->setViewport(viewport);
CharArray windowNameStr(windowName.getData());
if (!windowNameStr.buf[0]) {
windowNameStr.replaceBuf(new char[256]);
snprintf(windowNameStr.buf, 256, _("TigerVNC: %.240s"), cp.name());
}
viewport->toplevel(windowNameStr.buf, this, argc, argv);
viewport->setBumpScroll(fullScreen);
XSetWindowAttributes attr;
attr.override_redirect = fullScreen;
XChangeWindowAttributes(dpy, viewport->win(), CWOverrideRedirect, &attr);
XChangeWindowAttributes(dpy, menu.win(), CWOverrideRedirect, &attr);
XChangeWindowAttributes(dpy, options.win(), CWOverrideRedirect, &attr);
XChangeWindowAttributes(dpy, about.win(), CWOverrideRedirect, &attr);
XChangeWindowAttributes(dpy, info.win(), CWOverrideRedirect, &attr);
reconfigureViewport();
menu.setTransientFor(viewport->win());
viewport->map();
if (fullScreen) {
XGrabKeyboard(dpy, desktop->win(), True, GrabModeAsync, GrabModeAsync,
CurrentTime);
} else {
XUngrabKeyboard(dpy, CurrentTime);
}
if (oldViewport) delete oldViewport;
}
void CConn::reconfigureViewport()
{
viewport->setMaxSize(cp.width, cp.height);
if (fullScreen) {
viewport->resize(DisplayWidth(dpy,DefaultScreen(dpy)),
DisplayHeight(dpy,DefaultScreen(dpy)));
} else {
int w = cp.width;
int h = cp.height;
if (w + wmDecorationWidth >= DisplayWidth(dpy,DefaultScreen(dpy)))
w = DisplayWidth(dpy,DefaultScreen(dpy)) - wmDecorationWidth;
if (h + wmDecorationHeight >= DisplayHeight(dpy,DefaultScreen(dpy)))
h = DisplayHeight(dpy,DefaultScreen(dpy)) - wmDecorationHeight;
int x = (DisplayWidth(dpy,DefaultScreen(dpy)) - w - wmDecorationWidth) / 2;
int y = (DisplayHeight(dpy,DefaultScreen(dpy)) - h - wmDecorationHeight)/2;
CharArray geometryStr(geometry.getData());
viewport->setGeometry(geometryStr.buf, x, y, w, h);
}
}
// Note: The method below is duplicated in win/vncviewer/CConn.cxx!
// autoSelectFormatAndEncoding() chooses the format and encoding appropriate
// to the connection speed:
//
// First we wait for at least one second of bandwidth measurement.
//
// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
// which should be perceptually lossless.
//
// If the bandwidth is below that, we choose a more lossy JPEG quality.
//
// If the bandwidth drops below 256 Kbps, we switch to palette mode.
//
// Note: The system here is fairly arbitrary and should be replaced
// with something more intelligent at the server end.
//
void CConn::autoSelectFormatAndEncoding()
{
int kbitsPerSecond = sock->inStream().kbitsPerSecond();
unsigned int timeWaited = sock->inStream().timeWaited();
bool newFullColour = fullColour;
int newQualityLevel = qualityLevel;
// Always use Tight
if (currentEncoding != encodingTight) {
currentEncoding = encodingTight;
encodingChange = true;
}
// Check that we have a decent bandwidth measurement
if ((kbitsPerSecond == 0) || (timeWaited < 10000))
return;
// Select appropriate quality level
if (!noJpeg) {
if (kbitsPerSecond > 16000)
newQualityLevel = 8;
else
newQualityLevel = 6;
if (newQualityLevel != qualityLevel) {
vlog.info("Throughput %d kbit/s - changing to quality %d ",
kbitsPerSecond, newQualityLevel);
cp.qualityLevel = newQualityLevel;
qualityLevel.setParam(newQualityLevel);
encodingChange = true;
}
}
if (cp.beforeVersion(3, 8)) {
// Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
// cursors "asynchronously". If this happens in the middle of a
// pixel format change, the server will encode the cursor with
// the old format, but the client will try to decode it
// according to the new format. This will lead to a
// crash. Therefore, we do not allow automatic format change for
// old servers.
return;
}
// Select best color level
newFullColour = (kbitsPerSecond > 256);
if (newFullColour != fullColour) {
vlog.info("Throughput %d kbit/s - full color is now %s",
kbitsPerSecond,
newFullColour ? "enabled" : "disabled");
fullColour = newFullColour;
formatChange = true;
}
}
// checkEncodings() sends a setEncodings message if one is needed.
void CConn::checkEncodings()
{
if (encodingChange && writer()) {
vlog.info("Using %s encoding",encodingName(currentEncoding));
writer()->writeSetEncodings(currentEncoding, true);
encodingChange = false;
}
}
// requestNewUpdate() requests an update from the server, having set the
// format and encoding appropriately.
void CConn::requestNewUpdate()
{
if (formatChange) {
/* Catch incorrect requestNewUpdate calls */
assert(pendingUpdate == false);
if (fullColour) {
desktop->setPF(fullColourPF);
} else {
if (lowColourLevel == 0)
desktop->setPF(PixelFormat(8,3,0,1,1,1,1,2,1,0));
else if (lowColourLevel == 1)
desktop->setPF(PixelFormat(8,6,0,1,3,3,3,4,2,0));
else
desktop->setPF(PixelFormat(8,8,0,0));
}
char str[256];
desktop->getPF().print(str, 256);
vlog.info("Using pixel format %s",str);
cp.setPF(desktop->getPF());
writer()->writeSetPixelFormat(cp.pf());
}
checkEncodings();
writer()->writeFramebufferUpdateRequest(Rect(0, 0, cp.width, cp.height),
!formatChange);
formatChange = false;
}