/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2004-2008 Constantin Kaplinsky. All Rights Reserved. * * 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. */ // FIXME: Check cases when screen width/height is not a multiply of 32. // e.g. 800x600. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_XTEST #include #endif #ifdef HAVE_XDAMAGE #include #endif #ifdef HAVE_XFIXES #include #endif #include #include #include #include extern const unsigned short code_map_qnum_to_xorgevdev[]; extern const unsigned int code_map_qnum_to_xorgevdev_len; extern const unsigned short code_map_qnum_to_xorgkbd[]; extern const unsigned int code_map_qnum_to_xorgkbd_len; // XXX Lynx/OS 2.3: protos for select(), bzero() #ifdef Lynx #include #endif extern char buildtime[]; using namespace rfb; using namespace network; // number of XKb indicator leds to handle static const int N_LEDS = 3; // order is important as it must match RFB extension static const char * ledNames[N_LEDS] = { "Scroll Lock", "Num Lock", "Caps Lock" }; static LogWriter vlog("Main"); IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling " "cycle; actual interval may be dynamically " "adjusted to satisfy MaxProcessorUsage setting", 30); IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of " "CPU time to be consumed", 35); BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true); BoolParameter useOverlay("OverlayMode", "Use overlay mode under " "IRIX or Solaris", true); StringParameter displayname("display", "The X display", ""); IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900); IntParameter queryConnectTimeout("QueryConnectTimeout", "Number of seconds to show the Accept Connection dialog before " "rejecting the connection", 10); StringParameter hostsFile("HostsFile", "File with IP access control rules", ""); BoolParameter rawKeyboard("RawKeyboard", "Send keyboard events straight through and " "avoid mapping them to the current keyboard " "layout", false); // // Allow the main loop terminate itself gracefully on receiving a signal. // static bool caughtSignal = false; static void CleanupSignalHandler(int sig) { caughtSignal = true; } class QueryConnHandler : public VNCServerST::QueryConnectionHandler, public QueryResultCallback { public: QueryConnHandler(Display* dpy, VNCServerST* vs) : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {} ~QueryConnHandler() { delete queryConnectDialog; } // -=- VNCServerST::QueryConnectionHandler interface virtual VNCServerST::queryResult queryConnection(network::Socket* sock, const char* userName, char** reason) { if (queryConnectSock) { *reason = strDup("Another connection is currently being queried."); return VNCServerST::REJECT; } if (!userName) userName = "(anonymous)"; queryConnectSock = sock; CharArray address(sock->getPeerAddress()); delete queryConnectDialog; queryConnectDialog = new QueryConnectDialog(display, address.buf, userName, queryConnectTimeout, this); queryConnectDialog->map(); return VNCServerST::PENDING; } // -=- QueryResultCallback interface virtual void queryApproved() { server->approveConnection(queryConnectSock, true, 0); queryConnectSock = 0; } virtual void queryRejected() { server->approveConnection(queryConnectSock, false, "Connection rejected by local user"); queryConnectSock = 0; } private: Display* display; VNCServerST* server; QueryConnectDialog* queryConnectDialog; network::Socket* queryConnectSock; }; class XDesktop : public SDesktop, public TXGlobalEventHandler { public: XDesktop(Display* dpy_, Geometry *geometry_) : dpy(dpy_), geometry(geometry_), pb(0), server(0), oldButtonMask(0), haveXtest(false), haveDamage(false), maxButtons(0), running(false), ledMasks(), ledState(0), codeMap(0), codeMapLen(0) { int major, minor; int xkbOpcode, xkbErrorBase; major = XkbMajorVersion; minor = XkbMinorVersion; if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase, &xkbErrorBase, &major, &minor)) { vlog.error("XKEYBOARD extension not present"); throw Exception(); } XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask, XkbIndicatorStateNotifyMask); // figure out bit masks for the indicators we are interested in for (int i = 0; i < N_LEDS; i++) { Atom a; int shift; Bool on; a = XInternAtom(dpy, ledNames[i], True); if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL)) continue; ledMasks[i] = 1u << shift; vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]); if (on) ledState |= 1u << i; } // X11 unfortunately uses keyboard driver specific keycodes and provides no // direct way to query this, so guess based on the keyboard mapping XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd); if (desc && desc->names) { char *keycodes = XGetAtomName(dpy, desc->names->keycodes); if (keycodes) { if (strncmp("evdev", keycodes, strlen("evdev")) == 0) { codeMap = code_map_qnum_to_xorgevdev; codeMapLen = code_map_qnum_to_xorgevdev_len; vlog.info("Using evdev codemap\n"); } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) { codeMap = code_map_qnum_to_xorgkbd; codeMapLen = code_map_qnum_to_xorgkbd_len; vlog.info("Using xorgkbd codemap\n"); } else { vlog.info("Unknown keycode '%s', no codemap\n", keycodes); } XFree(keycodes); } else { vlog.debug("Unable to get keycode map\n"); } XkbFreeKeyboard(desc, XkbAllComponentsMask, True); } #ifdef HAVE_XTEST int xtestEventBase; int xtestErrorBase; if (XTestQueryExtension(dpy, &xtestEventBase, &xtestErrorBase, &major, &minor)) { XTestGrabControl(dpy, True); vlog.info("XTest extension present - version %d.%d",major,minor); haveXtest = true; } else { #endif vlog.info("XTest extension not present"); vlog.info("Unable to inject events or display while server is grabbed"); #ifdef HAVE_XTEST } #endif #ifdef HAVE_XDAMAGE int xdamageErrorBase; if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) { haveDamage = true; } else { #endif vlog.info("DAMAGE extension not present"); vlog.info("Will have to poll screen for changes"); #ifdef HAVE_XDAMAGE } #endif #ifdef HAVE_XFIXES int xfixesErrorBase; if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) { XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy), XFixesDisplayCursorNotifyMask); } else { #endif vlog.info("XFIXES extension not present"); vlog.info("Will not be able to display cursors"); #ifdef HAVE_XFIXES } #endif TXWindow::setGlobalEventHandler(this); } virtual ~XDesktop() { stop(); } inline void poll() { if (pb and not haveDamage) pb->poll(server); if (running) { Window root, child; int x, y, wx, wy; unsigned int mask; XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child, &x, &y, &wx, &wy, &mask); server->setCursorPos(rfb::Point(x, y)); } } // -=- SDesktop interface virtual void start(VNCServer* vs) { // Determine actual number of buttons of the X pointer device. unsigned char btnMap[8]; int numButtons = XGetPointerMapping(dpy, btnMap, 8); maxButtons = (numButtons > 8) ? 8 : numButtons; vlog.info("Enabling %d button%s of X pointer device", maxButtons, (maxButtons != 1) ? "s" : ""); // Create an ImageFactory instance for producing Image objects. ImageFactory factory((bool)useShm, (bool)useOverlay); // Create pixel buffer and provide it to the server object. pb = new XPixelBuffer(dpy, factory, geometry->getRect()); vlog.info("Allocated %s", pb->getImage()->classDesc()); server = (VNCServerST *)vs; server->setPixelBuffer(pb); #ifdef HAVE_XDAMAGE if (haveDamage) { damage = XDamageCreate(dpy, DefaultRootWindow(dpy), XDamageReportRawRectangles); } #endif server->setLEDState(ledState); running = true; } virtual void stop() { running = false; #ifdef HAVE_XDAMAGE if (haveDamage) XDamageDestroy(dpy, damage); #endif delete pb; pb = 0; } inline bool isRunning() { return running; } virtual void pointerEvent(const Point& pos, int buttonMask) { #ifdef HAVE_XTEST if (!haveXtest) return; XTestFakeMotionEvent(dpy, DefaultScreen(dpy), geometry->offsetLeft() + pos.x, geometry->offsetTop() + pos.y, CurrentTime); if (buttonMask != oldButtonMask) { for (int i = 0; i < maxButtons; i++) { if ((buttonMask ^ oldButtonMask) & (1<min_key_code; keycode <= xkb->max_key_code; keycode++) { KeySym cursym; unsigned int mods, out_mods; // XkbStateFieldFromRec() doesn't work properly because // state.lookup_mods isn't properly updated, so we do this manually mods = XkbBuildCoreState(XkbStateMods(&state), state.group); XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym); if (cursym == keysym) break; } if (keycode > xkb->max_key_code) keycode = 0; XkbFreeKeyboard(xkb, XkbAllComponentsMask, True); return keycode; } #endif virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) { #ifdef HAVE_XTEST int keycode = 0; if (!haveXtest) return; // Use scan code if provided and mapping exists if (codeMap && rawKeyboard && xtcode < codeMapLen) keycode = codeMap[xtcode]; if (!keycode) { if (pressedKeys.find(keysym) != pressedKeys.end()) keycode = pressedKeys[keysym]; else { // XKeysymToKeycode() doesn't respect state, so we have to use // something slightly more complex keycode = XkbKeysymToKeycode(dpy, keysym); } } if (!keycode) return; if (down) pressedKeys[keysym] = keycode; else pressedKeys.erase(keysym); XTestFakeKeyEvent(dpy, keycode, down, CurrentTime); #endif } virtual void clientCutText(const char* str, int len) { } virtual Point getFbSize() { return Point(pb->width(), pb->height()); } // -=- TXGlobalEventHandler interface virtual bool handleGlobalEvent(XEvent* ev) { if (ev->type == xkbEventBase + XkbEventCode) { XkbEvent *kb = (XkbEvent *)ev; if (kb->any.xkb_type != XkbIndicatorStateNotify) return false; vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state); ledState = 0; for (int i = 0; i < N_LEDS; i++) { if (kb->indicators.state & ledMasks[i]) ledState |= 1u << i; } if (running) server->setLEDState(ledState); return true; #ifdef HAVE_XDAMAGE } else if (ev->type == xdamageEventBase) { XDamageNotifyEvent* dev; Rect rect; if (!running) return true; dev = (XDamageNotifyEvent*)ev; rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height); server->add_changed(rect); return true; #endif #ifdef HAVE_XFIXES } else if (ev->type == xfixesEventBase + XFixesCursorNotify) { XFixesCursorNotifyEvent* cev; XFixesCursorImage *cim; if (!running) return true; cev = (XFixesCursorNotifyEvent*)ev; if (cev->subtype != XFixesDisplayCursorNotify) return false; cim = XFixesGetCursorImage(dpy); if (cim == NULL) return false; // Copied from XserverDesktop::setCursor() in // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to // handle long -> U32 conversion for 64-bit Xlib rdr::U8* cursorData; rdr::U8 *out; const unsigned long *pixels; cursorData = new rdr::U8[cim->width * cim->height * 4]; // Un-premultiply alpha pixels = cim->pixels; out = cursorData; for (int y = 0; y < cim->height; y++) { for (int x = 0; x < cim->width; x++) { rdr::U8 alpha; rdr::U32 pixel = *pixels++; rdr::U8 *in = (rdr::U8 *) &pixel; alpha = in[3]; if (alpha == 0) alpha = 1; // Avoid division by zero *out++ = (unsigned)*in++ * 255/alpha; *out++ = (unsigned)*in++ * 255/alpha; *out++ = (unsigned)*in++ * 255/alpha; *out++ = *in++; } } try { server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot), cursorData); } catch (rdr::Exception& e) { vlog.error("XserverDesktop::setCursor: %s",e.str()); } delete [] cursorData; XFree(cim); return true; #endif } return false; } protected: Display* dpy; Geometry* geometry; XPixelBuffer* pb; VNCServerST* server; int oldButtonMask; bool haveXtest; bool haveDamage; int maxButtons; std::map pressedKeys; bool running; #ifdef HAVE_XDAMAGE Damage damage; int xdamageEventBase; #endif int xkbEventBase; #ifdef HAVE_XFIXES int xfixesEventBase; #endif int ledMasks[N_LEDS]; unsigned ledState; const unsigned short *codeMap; unsigned codeMapLen; }; class FileTcpFilter : public TcpFilter { public: FileTcpFilter(const char *fname) : TcpFilter("-"), fileName(NULL), lastModTime(0) { if (fname != NULL) fileName = strdup((char *)fname); } virtual ~FileTcpFilter() { if (fileName != NULL) free(fileName); } virtual bool verifyConnection(Socket* s) { if (!reloadRules()) { vlog.error("Could not read IP filtering rules: rejecting all clients"); filter.clear(); filter.push_back(parsePattern("-")); return false; } return TcpFilter::verifyConnection(s); } protected: bool reloadRules() { if (fileName == NULL) return true; struct stat st; if (stat(fileName, &st) != 0) return false; if (st.st_mtime != lastModTime) { // Actually reload only if the file was modified FILE *fp = fopen(fileName, "r"); if (fp == NULL) return false; // Remove all the rules from the parent class filter.clear(); // Parse the file contents adding rules to the parent class char buf[32]; while (readLine(buf, 32, fp)) { if (buf[0] && strchr("+-?", buf[0])) { filter.push_back(parsePattern(buf)); } } fclose(fp); lastModTime = st.st_mtime; } return true; } protected: char *fileName; time_t lastModTime; private: // // NOTE: we silently truncate long lines in this function. // bool readLine(char *buf, int bufSize, FILE *fp) { if (fp == NULL || buf == NULL || bufSize == 0) return false; if (fgets(buf, bufSize, fp) == NULL) return false; char *ptr = strchr(buf, '\n'); if (ptr != NULL) { *ptr = '\0'; // remove newline at the end } else { if (!feof(fp)) { int c; do { // skip the rest of a long line c = getc(fp); } while (c != '\n' && c != EOF); } } return true; } }; char* programName; static void printVersion(FILE *fp) { fprintf(fp, "TigerVNC Server version %s, built %s\n", PACKAGE_VERSION, buildtime); } static void usage() { printVersion(stderr); fprintf(stderr, "\nUsage: %s []\n", programName); fprintf(stderr, " %s --version\n", programName); fprintf(stderr,"\n" "Parameters can be turned on with - or off with -=0\n" "Parameters which take a value can be specified as " "- \n" "Other valid forms are = -= " "--=\n" "Parameter names are case-insensitive. The parameters are:\n\n"); Configuration::listParams(79, 14); exit(1); } int main(int argc, char** argv) { initStdIOLoggers(); LogWriter::setLogParams("*:stderr:30"); programName = argv[0]; Display* dpy; Configuration::enableServerParams(); for (int i = 1; i < argc; i++) { if (Configuration::setParam(argv[i])) continue; if (argv[i][0] == '-') { if (i+1 < argc) { if (Configuration::setParam(&argv[i][1], argv[i+1])) { i++; continue; } } if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) { printVersion(stdout); return 0; } usage(); } usage(); } CharArray dpyStr(displayname.getData()); if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) { // FIXME: Why not vlog.error(...)? fprintf(stderr,"%s: unable to open display \"%s\"\r\n", programName, XDisplayName(dpyStr.buf)); exit(1); } signal(SIGHUP, CleanupSignalHandler); signal(SIGINT, CleanupSignalHandler); signal(SIGTERM, CleanupSignalHandler); std::list listeners; try { TXWindow::init(dpy,"x0vncserver"); Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)), DisplayHeight(dpy, DefaultScreen(dpy))); if (geo.getRect().is_empty()) { vlog.error("Exiting with error"); return 1; } XDesktop desktop(dpy, &geo); VNCServerST server("x0vncserver", &desktop); QueryConnHandler qcHandler(dpy, &server); server.setQueryConnectionHandler(&qcHandler); createTcpListeners(&listeners, 0, (int)rfbport); vlog.info("Listening on port %d", (int)rfbport); const char *hostsData = hostsFile.getData(); FileTcpFilter fileTcpFilter(hostsData); if (strlen(hostsData) != 0) for (std::list::iterator i = listeners.begin(); i != listeners.end(); i++) (*i)->setFilter(&fileTcpFilter); delete[] hostsData; PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage); while (!caughtSignal) { int wait_ms; struct timeval tv; fd_set rfds, wfds; std::list sockets; std::list::iterator i; // Process any incoming X events TXWindow::handleXEvents(dpy); FD_ZERO(&rfds); FD_ZERO(&wfds); FD_SET(ConnectionNumber(dpy), &rfds); for (std::list::iterator i = listeners.begin(); i != listeners.end(); i++) FD_SET((*i)->getFd(), &rfds); server.getSockets(&sockets); int clients_connected = 0; for (i = sockets.begin(); i != sockets.end(); i++) { if ((*i)->isShutdown()) { server.removeSocket(*i); delete (*i); } else { FD_SET((*i)->getFd(), &rfds); if ((*i)->outStream().bufferUsage() > 0) FD_SET((*i)->getFd(), &wfds); clients_connected++; } } if (!clients_connected) sched.reset(); wait_ms = 0; if (sched.isRunning()) { wait_ms = sched.millisRemaining(); if (wait_ms > 500) { wait_ms = 500; } } soonestTimeout(&wait_ms, server.checkTimeouts()); tv.tv_sec = wait_ms / 1000; tv.tv_usec = (wait_ms % 1000) * 1000; // Do the wait... sched.sleepStarted(); int n = select(FD_SETSIZE, &rfds, &wfds, 0, wait_ms ? &tv : NULL); sched.sleepFinished(); if (n < 0) { if (errno == EINTR) { vlog.debug("Interrupted select() system call"); continue; } else { throw rdr::SystemException("select", errno); } } // Accept new VNC connections for (std::list::iterator i = listeners.begin(); i != listeners.end(); i++) { if (FD_ISSET((*i)->getFd(), &rfds)) { Socket* sock = (*i)->accept(); if (sock) { sock->outStream().setBlocking(false); server.addSocket(sock); } else { vlog.status("Client connection rejected"); } } } server.checkTimeouts(); // Client list could have been changed. server.getSockets(&sockets); // Nothing more to do if there are no client connections. if (sockets.empty()) continue; // Process events on existing VNC connections for (i = sockets.begin(); i != sockets.end(); i++) { if (FD_ISSET((*i)->getFd(), &rfds)) server.processSocketReadEvent(*i); if (FD_ISSET((*i)->getFd(), &wfds)) server.processSocketWriteEvent(*i); } if (desktop.isRunning() && sched.goodTimeToPoll()) { sched.newPass(); desktop.poll(); } } } catch (rdr::Exception &e) { vlog.error("%s", e.str()); return 1; } TXWindow::handleXEvents(dpy); vlog.info("Terminated"); return 0; }