blob: f429553a3532041c5a1b20f80321e534d351dce6 [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>
24#include <sys/time.h>
25#include <sys/types.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000026#include <sys/stat.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000027#include <unistd.h>
28#include <errno.h>
29#include <rfb/Logger_stdio.h>
30#include <rfb/LogWriter.h>
31#include <rfb/VNCServerST.h>
32#include <rfb/Configuration.h>
33#include <rfb/SSecurityFactoryStandard.h>
34
35#include <network/TcpSocket.h>
36
37#include "Image.h"
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +000038#include "CPUMonitor.h"
39
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000040#include <signal.h>
41#include <X11/X.h>
42#include <X11/Xlib.h>
43#include <X11/Xutil.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000044#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000045#include <X11/extensions/XTest.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000046#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000047
48#include <rfb/Encoder.h>
49
50using namespace rfb;
51using namespace rdr;
52using namespace network;
53
54LogWriter vlog("main");
55
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +000056IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
57 "cycle; actual interval may be dynamically "
58 "adjusted to satisfy MaxProcessorUsage setting", 50);
59IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
60 "CPU time to be consumed", 35);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000061BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
62BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
63 "IRIX or Solaris", true);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000064StringParameter displayname("display", "The X display", "");
65IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000066StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000067VncAuthPasswdFileParameter vncAuthPasswdFile;
68
69static void CleanupSignalHandler(int sig)
70{
71 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
72 // exit() rather than the default which is to abort.
73 fprintf(stderr,"CleanupSignalHandler called\n");
74 exit(1);
75}
76
77
78class XDesktop : public SDesktop, public rfb::ColourMap
79{
80public:
81 XDesktop(Display* dpy_)
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000082 : dpy(dpy_), pb(0), server(0), oldButtonMask(0), haveXtest(false),
83 maxButtons(0), pollingStep(0)
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000084 {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000085#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000086 int xtestEventBase;
87 int xtestErrorBase;
88 int major, minor;
89
90 if (XTestQueryExtension(dpy, &xtestEventBase,
91 &xtestErrorBase, &major, &minor)) {
92 XTestGrabControl(dpy, True);
93 vlog.info("XTest extension present - version %d.%d",major,minor);
94 haveXtest = true;
95 } else {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000096#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000097 vlog.info("XTest extension not present");
98 vlog.info("unable to inject events or display while server is grabbed");
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000099#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000100 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000101#endif
102
103 // Determine actual number of buttons of the X pointer device.
104 unsigned char btnMap[8];
105 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
106 maxButtons = (numButtons > 8) ? 8 : numButtons;
107 vlog.info("Enabling %d button%s of X pointer device",
108 maxButtons, (maxButtons != 1) ? "s" : "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000109
110 int dpyWidth = DisplayWidth(dpy, DefaultScreen(dpy));
111 int dpyHeight = DisplayHeight(dpy, DefaultScreen(dpy));
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000112
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000113 // FIXME: verify that all three images use the same pixel format.
114 ImageFactory factory((bool)useShm, (bool)useOverlay);
115 image = factory.newImage(dpy, dpyWidth, dpyHeight);
116 rowImage = factory.newImage(dpy, dpyWidth, 1);
117 tileImage = factory.newImage(dpy, 32, 32);
118
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000119 image->get(DefaultRootWindow(dpy));
120
121 pf.bpp = image->xim->bits_per_pixel;
122 pf.depth = image->xim->depth;
123 pf.bigEndian = (image->xim->byte_order == MSBFirst);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000124 pf.trueColour = image->isTrueColor();
125 pf.redShift = ffs(image->xim->red_mask) - 1;
126 pf.greenShift = ffs(image->xim->green_mask) - 1;
127 pf.blueShift = ffs(image->xim->blue_mask) - 1;
128 pf.redMax = image->xim->red_mask >> pf.redShift;
129 pf.greenMax = image->xim->green_mask >> pf.greenShift;
130 pf.blueMax = image->xim->blue_mask >> pf.blueShift;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000131
132 pb = new FullFramePixelBuffer(pf, dpyWidth, dpyHeight,
133 (rdr::U8*)image->xim->data, this);
134 }
135 virtual ~XDesktop() {
136 delete pb;
137 }
138
139 void setVNCServer(VNCServer* s) {
140 server = s;
141 server->setPixelBuffer(pb);
142 }
143
144 // -=- SDesktop interface
145
146 virtual void pointerEvent(const Point& pos, rdr::U8 buttonMask) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000147#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000148 if (!haveXtest) return;
149 XTestFakeMotionEvent(dpy, DefaultScreen(dpy), pos.x, pos.y, CurrentTime);
150 if (buttonMask != oldButtonMask) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000151 for (int i = 0; i < maxButtons; i++) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000152 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
153 if (buttonMask & (1<<i)) {
154 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
155 } else {
156 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
157 }
158 }
159 }
160 }
161 oldButtonMask = buttonMask;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000162#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000163 }
164
165 virtual void keyEvent(rdr::U32 key, bool down) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000166#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000167 if (!haveXtest) return;
168 int keycode = XKeysymToKeycode(dpy, key);
169 if (keycode)
170 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000171#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000172 }
173
174 virtual void clientCutText(const char* str, int len) {
175 }
176
177 virtual Point getFbSize() {
178 return Point(pb->width(), pb->height());
179 }
180
181 // rfb::ColourMap callbacks
182 virtual void lookup(int index, int* r, int* g, int* b) {
183 XColor xc;
184 xc.pixel = index;
185 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
186 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
187 } else {
188 xc.red = xc.green = xc.blue = 0;
189 }
190 *r = xc.red;
191 *g = xc.green;
192 *b = xc.blue;
193 }
194
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000195 //
196 // DEBUG: a version of poll() measuring time spent in the function.
197 //
198
199 virtual void pollDebug()
200 {
201 struct timeval timeSaved, timeNow;
202 struct timezone tz;
203 timeSaved.tv_sec = 0;
204 timeSaved.tv_usec = 0;
205 gettimeofday(&timeSaved, &tz);
206
207 poll();
208
209 gettimeofday(&timeNow, &tz);
210 int diff = (int)((timeNow.tv_usec - timeSaved.tv_usec + 500) / 1000 +
211 (timeNow.tv_sec - timeSaved.tv_sec) * 1000);
212 if (diff != 0)
213 fprintf(stderr, "DEBUG: poll(): %4d ms\n", diff);
214 }
215
216 //
217 // Search for changed rectangles on the screen.
218 //
219
220 virtual void poll()
221 {
222 if (server == NULL)
223 return;
224
225 int nTilesChanged = 0;
226 int scanLine = XDesktop::pollingOrder[pollingStep++ % 32];
227 int bytesPerPixel = image->xim->bits_per_pixel / 8;
228 int bytesPerLine = image->xim->bytes_per_line;
229 int w = image->xim->width, h = image->xim->height;
230 Rect rect;
231
232 for (int y = 0; y * 32 < h; y++) {
233 int tile_h = (h - y * 32 >= 32) ? 32 : h - y * 32;
234 if (scanLine >= tile_h)
235 continue;
236 int scan_y = y * 32 + scanLine;
237 rowImage->get(DefaultRootWindow(dpy), 0, scan_y);
238 char *ptr_old = image->xim->data + scan_y * bytesPerLine;
239 char *ptr_new = rowImage->xim->data;
240 for (int x = 0; x * 32 < w; x++) {
241 int tile_w = (w - x * 32 >= 32) ? 32 : w - x * 32;
242 int nBytes = tile_w * bytesPerPixel;
243 if (memcmp(ptr_old, ptr_new, nBytes)) {
244 if (tile_w == 32 && tile_h == 32) {
245 tileImage->get(DefaultRootWindow(dpy), x * 32, y * 32);
246 } else {
247 tileImage->get(DefaultRootWindow(dpy), x * 32, y * 32,
248 tile_w, tile_h);
249 }
250 image->updateRect(tileImage, x * 32, y * 32);
251 rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
252 server->add_changed(rect);
253 nTilesChanged++;
254 }
255 ptr_old += nBytes;
256 ptr_new += nBytes;
257 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000258 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000259
260 if (nTilesChanged)
261 server->tryUpdate();
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000262 }
263
264protected:
265 Display* dpy;
266 PixelFormat pf;
267 PixelBuffer* pb;
268 VNCServer* server;
269 Image* image;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000270 Image* rowImage;
271 Image* tileImage;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000272 int oldButtonMask;
273 bool haveXtest;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000274 int maxButtons;
275 unsigned int pollingStep;
276 static const int pollingOrder[];
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000277};
278
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000279const int XDesktop::pollingOrder[32] = {
280 0, 16, 8, 24, 4, 20, 12, 28,
281 10, 26, 18, 2, 22, 6, 30, 14,
282 1, 17, 9, 25, 7, 23, 15, 31,
283 19, 3, 27, 11, 29, 13, 5, 21
284};
285
286
287class FileTcpFilter : public TcpFilter
288{
289
290public:
291
292 FileTcpFilter(const char *fname)
293 : TcpFilter("-"), fileName(NULL), lastModTime(0)
294 {
295 if (fname != NULL)
296 fileName = strdup((char *)fname);
297 }
298
299 virtual ~FileTcpFilter()
300 {
301 if (fileName != NULL)
302 free(fileName);
303 }
304
305 virtual bool verifyConnection(Socket* s)
306 {
307 if (!reloadRules()) {
308 vlog.error("Could not read IP filtering rules: rejecting all clients");
309 filter.clear();
310 filter.push_back(parsePattern("-"));
311 return false;
312 }
313
314 return TcpFilter::verifyConnection(s);
315 }
316
317protected:
318
319 bool reloadRules()
320 {
321 if (fileName == NULL)
322 return true;
323
324 struct stat st;
325 if (stat(fileName, &st) != 0)
326 return false;
327
328 if (st.st_mtime != lastModTime) {
329 // Actually reload only if the file was modified
330 FILE *fp = fopen(fileName, "r");
331 if (fp == NULL)
332 return false;
333
334 // Remove all the rules from the parent class
335 filter.clear();
336
337 // Parse the file contents adding rules to the parent class
338 char buf[32];
339 while (readLine(buf, 32, fp)) {
340 if (buf[0] && strchr("+-?", buf[0])) {
341 filter.push_back(parsePattern(buf));
342 }
343 }
344
345 fclose(fp);
346 lastModTime = st.st_mtime;
347 }
348 return true;
349 }
350
351protected:
352
353 char *fileName;
354 time_t lastModTime;
355
356private:
357
358 //
359 // NOTE: we silently truncate long lines in this function.
360 //
361
362 bool readLine(char *buf, int bufSize, FILE *fp)
363 {
364 if (fp == NULL || buf == NULL || bufSize == 0)
365 return false;
366
367 if (fgets(buf, bufSize, fp) == NULL)
368 return false;
369
370 char *ptr = strchr(buf, '\n');
371 if (ptr != NULL) {
372 *ptr = '\0'; // remove newline at the end
373 } else {
374 if (!feof(fp)) {
375 int c;
376 do { // skip the rest of a long line
377 c = getc(fp);
378 } while (c != '\n' && c != EOF);
379 }
380 }
381 return true;
382 }
383
384};
385
386
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000387char* programName;
388
389static void usage()
390{
391 fprintf(stderr, "\nusage: %s [<parameters>]\n", programName);
392 fprintf(stderr,"\n"
393 "Parameters can be turned on with -<param> or off with -<param>=0\n"
394 "Parameters which take a value can be specified as "
395 "-<param> <value>\n"
396 "Other valid forms are <param>=<value> -<param>=<value> "
397 "--<param>=<value>\n"
398 "Parameter names are case-insensitive. The parameters are:\n\n");
399 Configuration::listParams(79, 14);
400 exit(1);
401}
402
403int main(int argc, char** argv)
404{
405 initStdIOLoggers();
406 LogWriter::setLogParams("*:stderr:30");
407
408 programName = argv[0];
409 Display* dpy;
410
411 for (int i = 1; i < argc; i++) {
412 if (Configuration::setParam(argv[i]))
413 continue;
414
415 if (argv[i][0] == '-') {
416 if (i+1 < argc) {
417 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
418 i++;
419 continue;
420 }
421 }
422 usage();
423 }
424
425 usage();
426 }
427
428 CharArray dpyStr(displayname.getData());
429 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
430 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
431 programName, XDisplayName(displayname.getData()));
432 exit(1);
433 }
434
435 signal(SIGHUP, CleanupSignalHandler);
436 signal(SIGINT, CleanupSignalHandler);
437 signal(SIGTERM, CleanupSignalHandler);
438
439 try {
440 XDesktop desktop(dpy);
441 VNCServerST server("x0vncserver", &desktop);
442 desktop.setVNCServer(&server);
443
444 TcpSocket::initTcpSockets();
445 TcpListener listener((int)rfbport);
446 vlog.info("Listening on port %d", (int)rfbport);
447
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000448 FileTcpFilter fileTcpFilter(hostsFile.getData());
449 if (strlen(hostsFile.getData()) != 0)
450 listener.setFilter(&fileTcpFilter);
451
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000452 CPUMonitor cpumon((int)maxProcessorUsage, 1000);
453 int dynPollingCycle = (int)pollingCycle;
454
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000455 struct timeval timeSaved, timeNow;
456 struct timezone tz;
457 gettimeofday(&timeSaved, &tz);
458 timeSaved.tv_sec -= 60;
459
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000460 while (true) {
461 fd_set rfds;
462 struct timeval tv;
463
464 tv.tv_sec = 0;
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000465 tv.tv_usec = dynPollingCycle * 1000;
466 if (tv.tv_usec > 500000) {
467 tv.tv_usec = 500000;
468 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000469
470 FD_ZERO(&rfds);
471 FD_SET(listener.getFd(), &rfds);
472
473 std::list<Socket*> sockets;
474 server.getSockets(&sockets);
475 std::list<Socket*>::iterator i;
476 for (i = sockets.begin(); i != sockets.end(); i++) {
477 FD_SET((*i)->getFd(), &rfds);
478 }
479
480 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
Constantin Kaplinsky659e9002005-09-09 08:32:02 +0000481 if (n < 0) {
482 if (errno == EINTR) {
483 vlog.debug("interrupted select() system call");
484 continue;
485 } else {
486 throw rdr::SystemException("select", errno);
487 }
488 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000489
490 if (FD_ISSET(listener.getFd(), &rfds)) {
491 Socket* sock = listener.accept();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000492 if (sock) {
493 server.addClient(sock);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000494 cpumon.update(); // count time from now
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000495 } else {
496 vlog.status("Client connection rejected");
497 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000498 }
499
500 server.getSockets(&sockets);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000501
502 // Nothing more to do if there are no client connections.
503 if (sockets.empty())
504 continue;
505
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000506 for (i = sockets.begin(); i != sockets.end(); i++) {
507 if (FD_ISSET((*i)->getFd(), &rfds)) {
508 server.processSocketEvent(*i);
509 }
510 }
511
512 server.checkTimeouts();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000513
514 if (gettimeofday(&timeNow, &tz) == 0) {
515 int diff = (int)((timeNow.tv_usec - timeSaved.tv_usec + 500) / 1000 +
516 (timeNow.tv_sec - timeSaved.tv_sec) * 1000);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000517 if (diff >= dynPollingCycle) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000518 timeSaved = timeNow;
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000519 int coeff = cpumon.check();
520 if (coeff < 90 || coeff > 110) {
521 // Adjust polling cycle to satisfy MaxProcessorUsage setting
522 dynPollingCycle = (dynPollingCycle * 100 + coeff/2) / coeff;
523 if (dynPollingCycle < (int)pollingCycle) {
524 dynPollingCycle = (int)pollingCycle;
525 } else if (dynPollingCycle > (int)pollingCycle * 32) {
526 dynPollingCycle = (int)pollingCycle * 32;
527 }
528 // DEBUG:
529 // if (dynPollingCycle != old) {
530 // fprintf(stderr, "[%dms]\t", dynPollingCycle);
531 // }
532 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000533 desktop.poll();
534 }
535 } else {
536 // Something strange has happened -- gettimeofday(2) failed.
537 // Poll after each select(), as in the original VNC4 code.
538 desktop.poll();
539 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000540 }
541
542 } catch (rdr::Exception &e) {
543 vlog.error(e.str());
544 };
545
546 return 0;
547}