blob: af63ad00e0ab82a460489bccb8c7d6ad861f7ee9 [file] [log] [blame]
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00001/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +00002 * Copyright (C) 2004-2005 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000019
20// FIXME: Check cases when screen width/height is not a multiply of 32.
21// e.g. 800x600.
22
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000023#include <strings.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000024#include <sys/types.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000025#include <sys/stat.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000026#include <unistd.h>
27#include <errno.h>
28#include <rfb/Logger_stdio.h>
29#include <rfb/LogWriter.h>
30#include <rfb/VNCServerST.h>
31#include <rfb/Configuration.h>
32#include <rfb/SSecurityFactoryStandard.h>
33
34#include <network/TcpSocket.h>
35
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000036#include <signal.h>
37#include <X11/X.h>
38#include <X11/Xlib.h>
39#include <X11/Xutil.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000040#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000041#include <X11/extensions/XTest.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000042#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000043
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +000044#include <x0vncserver/Image.h>
45#include <x0vncserver/PollingManager.h>
46#include <x0vncserver/CPUMonitor.h>
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +000047#include <x0vncserver/TimeMillis.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000048
49using namespace rfb;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000050using namespace network;
51
52LogWriter vlog("main");
53
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +000054IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
55 "cycle; actual interval may be dynamically "
Constantin Kaplinskyef7ac9b2006-02-10 12:26:54 +000056 "adjusted to satisfy MaxProcessorUsage setting", 30);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +000057IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
58 "CPU time to be consumed", 35);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000059BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
60BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
61 "IRIX or Solaris", true);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000062StringParameter displayname("display", "The X display", "");
63IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000064StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000065VncAuthPasswdFileParameter vncAuthPasswdFile;
66
67static void CleanupSignalHandler(int sig)
68{
69 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
70 // exit() rather than the default which is to abort.
71 fprintf(stderr,"CleanupSignalHandler called\n");
72 exit(1);
73}
74
75
76class XDesktop : public SDesktop, public rfb::ColourMap
77{
78public:
79 XDesktop(Display* dpy_)
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000080 : dpy(dpy_), pb(0), server(0), oldButtonMask(0), haveXtest(false),
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +000081 maxButtons(0)
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000082 {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000083#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000084 int xtestEventBase;
85 int xtestErrorBase;
86 int major, minor;
87
88 if (XTestQueryExtension(dpy, &xtestEventBase,
89 &xtestErrorBase, &major, &minor)) {
90 XTestGrabControl(dpy, True);
91 vlog.info("XTest extension present - version %d.%d",major,minor);
92 haveXtest = true;
93 } else {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000094#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000095 vlog.info("XTest extension not present");
96 vlog.info("unable to inject events or display while server is grabbed");
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000097#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000098 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000099#endif
100
101 // Determine actual number of buttons of the X pointer device.
102 unsigned char btnMap[8];
103 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
104 maxButtons = (numButtons > 8) ? 8 : numButtons;
105 vlog.info("Enabling %d button%s of X pointer device",
106 maxButtons, (maxButtons != 1) ? "s" : "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000107
108 int dpyWidth = DisplayWidth(dpy, DefaultScreen(dpy));
109 int dpyHeight = DisplayHeight(dpy, DefaultScreen(dpy));
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000110
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000111 ImageFactory factory((bool)useShm, (bool)useOverlay);
112 image = factory.newImage(dpy, dpyWidth, dpyHeight);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000113 image->get(DefaultRootWindow(dpy));
114
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000115 pollmgr = new PollingManager(dpy, image, &factory);
116
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000117 pf.bpp = image->xim->bits_per_pixel;
118 pf.depth = image->xim->depth;
119 pf.bigEndian = (image->xim->byte_order == MSBFirst);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000120 pf.trueColour = image->isTrueColor();
121 pf.redShift = ffs(image->xim->red_mask) - 1;
122 pf.greenShift = ffs(image->xim->green_mask) - 1;
123 pf.blueShift = ffs(image->xim->blue_mask) - 1;
124 pf.redMax = image->xim->red_mask >> pf.redShift;
125 pf.greenMax = image->xim->green_mask >> pf.greenShift;
126 pf.blueMax = image->xim->blue_mask >> pf.blueShift;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000127
128 pb = new FullFramePixelBuffer(pf, dpyWidth, dpyHeight,
129 (rdr::U8*)image->xim->data, this);
130 }
131 virtual ~XDesktop() {
132 delete pb;
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000133 delete pollmgr;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000134 }
135
136 void setVNCServer(VNCServer* s) {
137 server = s;
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000138 pollmgr->setVNCServer(s);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000139 server->setPixelBuffer(pb);
140 }
141
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000142 inline void poll() {
143 pollmgr->poll();
144 }
145
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000146 // -=- SDesktop interface
147
148 virtual void pointerEvent(const Point& pos, rdr::U8 buttonMask) {
Constantin Kaplinskyce676c62006-02-08 13:36:58 +0000149 pollmgr->setPointerPos(pos);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000150#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000151 if (!haveXtest) return;
152 XTestFakeMotionEvent(dpy, DefaultScreen(dpy), pos.x, pos.y, CurrentTime);
153 if (buttonMask != oldButtonMask) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000154 for (int i = 0; i < maxButtons; i++) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000155 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
156 if (buttonMask & (1<<i)) {
157 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
158 } else {
159 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
160 }
161 }
162 }
163 }
164 oldButtonMask = buttonMask;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000165#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000166 }
167
168 virtual void keyEvent(rdr::U32 key, bool down) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000169#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000170 if (!haveXtest) return;
171 int keycode = XKeysymToKeycode(dpy, key);
172 if (keycode)
173 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000174#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000175 }
176
177 virtual void clientCutText(const char* str, int len) {
178 }
179
180 virtual Point getFbSize() {
181 return Point(pb->width(), pb->height());
182 }
183
184 // rfb::ColourMap callbacks
185 virtual void lookup(int index, int* r, int* g, int* b) {
186 XColor xc;
187 xc.pixel = index;
188 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
189 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
190 } else {
191 xc.red = xc.green = xc.blue = 0;
192 }
193 *r = xc.red;
194 *g = xc.green;
195 *b = xc.blue;
196 }
197
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000198protected:
199 Display* dpy;
200 PixelFormat pf;
201 PixelBuffer* pb;
202 VNCServer* server;
203 Image* image;
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000204 PollingManager* pollmgr;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000205 int oldButtonMask;
206 bool haveXtest;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000207 int maxButtons;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000208};
209
210
211class FileTcpFilter : public TcpFilter
212{
213
214public:
215
216 FileTcpFilter(const char *fname)
217 : TcpFilter("-"), fileName(NULL), lastModTime(0)
218 {
219 if (fname != NULL)
220 fileName = strdup((char *)fname);
221 }
222
223 virtual ~FileTcpFilter()
224 {
225 if (fileName != NULL)
226 free(fileName);
227 }
228
229 virtual bool verifyConnection(Socket* s)
230 {
231 if (!reloadRules()) {
232 vlog.error("Could not read IP filtering rules: rejecting all clients");
233 filter.clear();
234 filter.push_back(parsePattern("-"));
235 return false;
236 }
237
238 return TcpFilter::verifyConnection(s);
239 }
240
241protected:
242
243 bool reloadRules()
244 {
245 if (fileName == NULL)
246 return true;
247
248 struct stat st;
249 if (stat(fileName, &st) != 0)
250 return false;
251
252 if (st.st_mtime != lastModTime) {
253 // Actually reload only if the file was modified
254 FILE *fp = fopen(fileName, "r");
255 if (fp == NULL)
256 return false;
257
258 // Remove all the rules from the parent class
259 filter.clear();
260
261 // Parse the file contents adding rules to the parent class
262 char buf[32];
263 while (readLine(buf, 32, fp)) {
264 if (buf[0] && strchr("+-?", buf[0])) {
265 filter.push_back(parsePattern(buf));
266 }
267 }
268
269 fclose(fp);
270 lastModTime = st.st_mtime;
271 }
272 return true;
273 }
274
275protected:
276
277 char *fileName;
278 time_t lastModTime;
279
280private:
281
282 //
283 // NOTE: we silently truncate long lines in this function.
284 //
285
286 bool readLine(char *buf, int bufSize, FILE *fp)
287 {
288 if (fp == NULL || buf == NULL || bufSize == 0)
289 return false;
290
291 if (fgets(buf, bufSize, fp) == NULL)
292 return false;
293
294 char *ptr = strchr(buf, '\n');
295 if (ptr != NULL) {
296 *ptr = '\0'; // remove newline at the end
297 } else {
298 if (!feof(fp)) {
299 int c;
300 do { // skip the rest of a long line
301 c = getc(fp);
302 } while (c != '\n' && c != EOF);
303 }
304 }
305 return true;
306 }
307
308};
309
310
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000311char* programName;
312
313static void usage()
314{
315 fprintf(stderr, "\nusage: %s [<parameters>]\n", programName);
316 fprintf(stderr,"\n"
317 "Parameters can be turned on with -<param> or off with -<param>=0\n"
318 "Parameters which take a value can be specified as "
319 "-<param> <value>\n"
320 "Other valid forms are <param>=<value> -<param>=<value> "
321 "--<param>=<value>\n"
322 "Parameter names are case-insensitive. The parameters are:\n\n");
323 Configuration::listParams(79, 14);
324 exit(1);
325}
326
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000327//
328// Adjust polling cycle to satisfy MaxProcessorUsage setting.
329//
330
331static void adjustPollingCycle(int *cycle, CPUMonitor *mon)
332{
333 int coeff = mon->check();
334 if (coeff < 90 || coeff > 110) {
Constantin Kaplinsky2f125372006-02-16 13:01:22 +0000335 int oldValue = *cycle;
336 int newValue = (oldValue * 100 + coeff/2) / coeff;
337
338 // Correct sudden changes.
339 if (newValue < oldValue / 2) {
340 newValue = oldValue / 2;
341 } else if (newValue > oldValue * 2) {
342 newValue = oldValue * 2;
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000343 }
Constantin Kaplinsky2f125372006-02-16 13:01:22 +0000344
345 // Compute upper limit.
346 int upperLimit = (int)pollingCycle * 32;
347 if (upperLimit < 100) {
348 upperLimit = 100;
349 } else if (upperLimit > 1000) {
350 upperLimit = 1000;
351 }
352
353 // Limit lower and upper bounds.
354 if (newValue < (int)pollingCycle) {
355 newValue = (int)pollingCycle;
356 } else if (newValue > upperLimit) {
357 newValue = upperLimit;
358 }
359
360 if (newValue != oldValue) {
361 *cycle = newValue;
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000362#ifdef DEBUG
Constantin Kaplinsky2f125372006-02-16 13:01:22 +0000363 fprintf(stderr, "\t[new cycle %dms]\n", newValue);
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000364#endif
Constantin Kaplinsky2f125372006-02-16 13:01:22 +0000365 }
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000366 }
367}
368
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000369int main(int argc, char** argv)
370{
371 initStdIOLoggers();
372 LogWriter::setLogParams("*:stderr:30");
373
374 programName = argv[0];
375 Display* dpy;
376
377 for (int i = 1; i < argc; i++) {
378 if (Configuration::setParam(argv[i]))
379 continue;
380
381 if (argv[i][0] == '-') {
382 if (i+1 < argc) {
383 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
384 i++;
385 continue;
386 }
387 }
388 usage();
389 }
390
391 usage();
392 }
393
394 CharArray dpyStr(displayname.getData());
395 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
396 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
397 programName, XDisplayName(displayname.getData()));
398 exit(1);
399 }
400
401 signal(SIGHUP, CleanupSignalHandler);
402 signal(SIGINT, CleanupSignalHandler);
403 signal(SIGTERM, CleanupSignalHandler);
404
405 try {
406 XDesktop desktop(dpy);
407 VNCServerST server("x0vncserver", &desktop);
408 desktop.setVNCServer(&server);
409
410 TcpSocket::initTcpSockets();
411 TcpListener listener((int)rfbport);
412 vlog.info("Listening on port %d", (int)rfbport);
413
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000414 FileTcpFilter fileTcpFilter(hostsFile.getData());
415 if (strlen(hostsFile.getData()) != 0)
416 listener.setFilter(&fileTcpFilter);
417
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000418 CPUMonitor cpumon((int)maxProcessorUsage, 1000);
419 int dynPollingCycle = (int)pollingCycle;
420
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000421 TimeMillis timeSaved, timeNow;
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000422 fd_set rfds;
423 struct timeval tv;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000424
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000425 while (true) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000426
427 FD_ZERO(&rfds);
428 FD_SET(listener.getFd(), &rfds);
429
430 std::list<Socket*> sockets;
431 server.getSockets(&sockets);
432 std::list<Socket*>::iterator i;
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000433 int clients_connected = 0;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000434 for (i = sockets.begin(); i != sockets.end(); i++) {
435 FD_SET((*i)->getFd(), &rfds);
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000436 clients_connected++;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000437 }
438
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000439 if (clients_connected) {
440 int poll_ms = 20;
441 if (timeNow.update()) {
442 poll_ms = timeNow.diffFrom(timeSaved);
443 }
444 int wait_ms = dynPollingCycle - poll_ms;
445 if (wait_ms < 0) {
446 wait_ms = 0;
447 } else if (wait_ms > 500) {
448 wait_ms = 500;
449 }
450 tv.tv_usec = wait_ms * 1000;
451#ifdef DEBUG
452 fprintf(stderr, "[%d]\t", wait_ms);
453#endif
454 } else {
455 tv.tv_usec = 50000;
456 }
457 tv.tv_sec = 0;
458
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000459 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
Constantin Kaplinsky659e9002005-09-09 08:32:02 +0000460 if (n < 0) {
461 if (errno == EINTR) {
462 vlog.debug("interrupted select() system call");
463 continue;
464 } else {
465 throw rdr::SystemException("select", errno);
466 }
467 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000468
469 if (FD_ISSET(listener.getFd(), &rfds)) {
470 Socket* sock = listener.accept();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000471 if (sock) {
472 server.addClient(sock);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000473 cpumon.update(); // count time from now
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000474 } else {
475 vlog.status("Client connection rejected");
476 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000477 }
478
479 server.getSockets(&sockets);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000480
481 // Nothing more to do if there are no client connections.
482 if (sockets.empty())
483 continue;
484
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000485 for (i = sockets.begin(); i != sockets.end(); i++) {
486 if (FD_ISSET((*i)->getFd(), &rfds)) {
487 server.processSocketEvent(*i);
488 }
489 }
490
491 server.checkTimeouts();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000492
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000493 if (timeNow.update()) {
494 int diff = timeNow.diffFrom(timeSaved);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000495 if (diff >= dynPollingCycle) {
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000496 adjustPollingCycle(&dynPollingCycle, &cpumon);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000497 timeSaved = timeNow;
498 desktop.poll();
499 }
500 } else {
Constantin Kaplinskyfbaab7f2006-02-16 04:51:38 +0000501 // Something strange has happened -- TimeMillis::update() failed.
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000502 // Poll after each select(), as in the original VNC4 code.
503 desktop.poll();
504 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000505 }
506
507 } catch (rdr::Exception &e) {
508 vlog.error(e.str());
509 };
510
511 return 0;
512}