| /* 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; |
| } |