blob: ca96fd7c75fefc9053b2b05574e55a3c8c05f707 [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"
38#include <signal.h>
39#include <X11/X.h>
40#include <X11/Xlib.h>
41#include <X11/Xutil.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000042#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000043#include <X11/extensions/XTest.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000044#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000045
46#include <rfb/Encoder.h>
47
48using namespace rfb;
49using namespace rdr;
50using namespace network;
51
52LogWriter vlog("main");
53
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000054IntParameter pollingCycle("PollingCycle", "Milliseconds per one "
55 "polling cycle", 50);
56BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
57BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
58 "IRIX or Solaris", true);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000059StringParameter displayname("display", "The X display", "");
60IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000061StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000062VncAuthPasswdFileParameter vncAuthPasswdFile;
63
64static void CleanupSignalHandler(int sig)
65{
66 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
67 // exit() rather than the default which is to abort.
68 fprintf(stderr,"CleanupSignalHandler called\n");
69 exit(1);
70}
71
72
73class XDesktop : public SDesktop, public rfb::ColourMap
74{
75public:
76 XDesktop(Display* dpy_)
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000077 : dpy(dpy_), pb(0), server(0), oldButtonMask(0), haveXtest(false),
78 maxButtons(0), pollingStep(0)
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000079 {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000080#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000081 int xtestEventBase;
82 int xtestErrorBase;
83 int major, minor;
84
85 if (XTestQueryExtension(dpy, &xtestEventBase,
86 &xtestErrorBase, &major, &minor)) {
87 XTestGrabControl(dpy, True);
88 vlog.info("XTest extension present - version %d.%d",major,minor);
89 haveXtest = true;
90 } else {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000091#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000092 vlog.info("XTest extension not present");
93 vlog.info("unable to inject events or display while server is grabbed");
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000094#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000095 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000096#endif
97
98 // Determine actual number of buttons of the X pointer device.
99 unsigned char btnMap[8];
100 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
101 maxButtons = (numButtons > 8) ? 8 : numButtons;
102 vlog.info("Enabling %d button%s of X pointer device",
103 maxButtons, (maxButtons != 1) ? "s" : "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000104
105 int dpyWidth = DisplayWidth(dpy, DefaultScreen(dpy));
106 int dpyHeight = DisplayHeight(dpy, DefaultScreen(dpy));
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000107
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000108 // FIXME: verify that all three images use the same pixel format.
109 ImageFactory factory((bool)useShm, (bool)useOverlay);
110 image = factory.newImage(dpy, dpyWidth, dpyHeight);
111 rowImage = factory.newImage(dpy, dpyWidth, 1);
112 tileImage = factory.newImage(dpy, 32, 32);
113
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000114 image->get(DefaultRootWindow(dpy));
115
116 pf.bpp = image->xim->bits_per_pixel;
117 pf.depth = image->xim->depth;
118 pf.bigEndian = (image->xim->byte_order == MSBFirst);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000119 pf.trueColour = image->isTrueColor();
120 pf.redShift = ffs(image->xim->red_mask) - 1;
121 pf.greenShift = ffs(image->xim->green_mask) - 1;
122 pf.blueShift = ffs(image->xim->blue_mask) - 1;
123 pf.redMax = image->xim->red_mask >> pf.redShift;
124 pf.greenMax = image->xim->green_mask >> pf.greenShift;
125 pf.blueMax = image->xim->blue_mask >> pf.blueShift;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000126
127 pb = new FullFramePixelBuffer(pf, dpyWidth, dpyHeight,
128 (rdr::U8*)image->xim->data, this);
129 }
130 virtual ~XDesktop() {
131 delete pb;
132 }
133
134 void setVNCServer(VNCServer* s) {
135 server = s;
136 server->setPixelBuffer(pb);
137 }
138
139 // -=- SDesktop interface
140
141 virtual void pointerEvent(const Point& pos, rdr::U8 buttonMask) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000142#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000143 if (!haveXtest) return;
144 XTestFakeMotionEvent(dpy, DefaultScreen(dpy), pos.x, pos.y, CurrentTime);
145 if (buttonMask != oldButtonMask) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000146 for (int i = 0; i < maxButtons; i++) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000147 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
148 if (buttonMask & (1<<i)) {
149 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
150 } else {
151 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
152 }
153 }
154 }
155 }
156 oldButtonMask = buttonMask;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000157#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000158 }
159
160 virtual void keyEvent(rdr::U32 key, bool down) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000161#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000162 if (!haveXtest) return;
163 int keycode = XKeysymToKeycode(dpy, key);
164 if (keycode)
165 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000166#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000167 }
168
169 virtual void clientCutText(const char* str, int len) {
170 }
171
172 virtual Point getFbSize() {
173 return Point(pb->width(), pb->height());
174 }
175
176 // rfb::ColourMap callbacks
177 virtual void lookup(int index, int* r, int* g, int* b) {
178 XColor xc;
179 xc.pixel = index;
180 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
181 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
182 } else {
183 xc.red = xc.green = xc.blue = 0;
184 }
185 *r = xc.red;
186 *g = xc.green;
187 *b = xc.blue;
188 }
189
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000190 //
191 // DEBUG: a version of poll() measuring time spent in the function.
192 //
193
194 virtual void pollDebug()
195 {
196 struct timeval timeSaved, timeNow;
197 struct timezone tz;
198 timeSaved.tv_sec = 0;
199 timeSaved.tv_usec = 0;
200 gettimeofday(&timeSaved, &tz);
201
202 poll();
203
204 gettimeofday(&timeNow, &tz);
205 int diff = (int)((timeNow.tv_usec - timeSaved.tv_usec + 500) / 1000 +
206 (timeNow.tv_sec - timeSaved.tv_sec) * 1000);
207 if (diff != 0)
208 fprintf(stderr, "DEBUG: poll(): %4d ms\n", diff);
209 }
210
211 //
212 // Search for changed rectangles on the screen.
213 //
214
215 virtual void poll()
216 {
217 if (server == NULL)
218 return;
219
220 int nTilesChanged = 0;
221 int scanLine = XDesktop::pollingOrder[pollingStep++ % 32];
222 int bytesPerPixel = image->xim->bits_per_pixel / 8;
223 int bytesPerLine = image->xim->bytes_per_line;
224 int w = image->xim->width, h = image->xim->height;
225 Rect rect;
226
227 for (int y = 0; y * 32 < h; y++) {
228 int tile_h = (h - y * 32 >= 32) ? 32 : h - y * 32;
229 if (scanLine >= tile_h)
230 continue;
231 int scan_y = y * 32 + scanLine;
232 rowImage->get(DefaultRootWindow(dpy), 0, scan_y);
233 char *ptr_old = image->xim->data + scan_y * bytesPerLine;
234 char *ptr_new = rowImage->xim->data;
235 for (int x = 0; x * 32 < w; x++) {
236 int tile_w = (w - x * 32 >= 32) ? 32 : w - x * 32;
237 int nBytes = tile_w * bytesPerPixel;
238 if (memcmp(ptr_old, ptr_new, nBytes)) {
239 if (tile_w == 32 && tile_h == 32) {
240 tileImage->get(DefaultRootWindow(dpy), x * 32, y * 32);
241 } else {
242 tileImage->get(DefaultRootWindow(dpy), x * 32, y * 32,
243 tile_w, tile_h);
244 }
245 image->updateRect(tileImage, x * 32, y * 32);
246 rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
247 server->add_changed(rect);
248 nTilesChanged++;
249 }
250 ptr_old += nBytes;
251 ptr_new += nBytes;
252 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000253 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000254
255 if (nTilesChanged)
256 server->tryUpdate();
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000257 }
258
259protected:
260 Display* dpy;
261 PixelFormat pf;
262 PixelBuffer* pb;
263 VNCServer* server;
264 Image* image;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000265 Image* rowImage;
266 Image* tileImage;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000267 int oldButtonMask;
268 bool haveXtest;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000269 int maxButtons;
270 unsigned int pollingStep;
271 static const int pollingOrder[];
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000272};
273
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000274const int XDesktop::pollingOrder[32] = {
275 0, 16, 8, 24, 4, 20, 12, 28,
276 10, 26, 18, 2, 22, 6, 30, 14,
277 1, 17, 9, 25, 7, 23, 15, 31,
278 19, 3, 27, 11, 29, 13, 5, 21
279};
280
281
282class FileTcpFilter : public TcpFilter
283{
284
285public:
286
287 FileTcpFilter(const char *fname)
288 : TcpFilter("-"), fileName(NULL), lastModTime(0)
289 {
290 if (fname != NULL)
291 fileName = strdup((char *)fname);
292 }
293
294 virtual ~FileTcpFilter()
295 {
296 if (fileName != NULL)
297 free(fileName);
298 }
299
300 virtual bool verifyConnection(Socket* s)
301 {
302 if (!reloadRules()) {
303 vlog.error("Could not read IP filtering rules: rejecting all clients");
304 filter.clear();
305 filter.push_back(parsePattern("-"));
306 return false;
307 }
308
309 return TcpFilter::verifyConnection(s);
310 }
311
312protected:
313
314 bool reloadRules()
315 {
316 if (fileName == NULL)
317 return true;
318
319 struct stat st;
320 if (stat(fileName, &st) != 0)
321 return false;
322
323 if (st.st_mtime != lastModTime) {
324 // Actually reload only if the file was modified
325 FILE *fp = fopen(fileName, "r");
326 if (fp == NULL)
327 return false;
328
329 // Remove all the rules from the parent class
330 filter.clear();
331
332 // Parse the file contents adding rules to the parent class
333 char buf[32];
334 while (readLine(buf, 32, fp)) {
335 if (buf[0] && strchr("+-?", buf[0])) {
336 filter.push_back(parsePattern(buf));
337 }
338 }
339
340 fclose(fp);
341 lastModTime = st.st_mtime;
342 }
343 return true;
344 }
345
346protected:
347
348 char *fileName;
349 time_t lastModTime;
350
351private:
352
353 //
354 // NOTE: we silently truncate long lines in this function.
355 //
356
357 bool readLine(char *buf, int bufSize, FILE *fp)
358 {
359 if (fp == NULL || buf == NULL || bufSize == 0)
360 return false;
361
362 if (fgets(buf, bufSize, fp) == NULL)
363 return false;
364
365 char *ptr = strchr(buf, '\n');
366 if (ptr != NULL) {
367 *ptr = '\0'; // remove newline at the end
368 } else {
369 if (!feof(fp)) {
370 int c;
371 do { // skip the rest of a long line
372 c = getc(fp);
373 } while (c != '\n' && c != EOF);
374 }
375 }
376 return true;
377 }
378
379};
380
381
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000382char* programName;
383
384static void usage()
385{
386 fprintf(stderr, "\nusage: %s [<parameters>]\n", programName);
387 fprintf(stderr,"\n"
388 "Parameters can be turned on with -<param> or off with -<param>=0\n"
389 "Parameters which take a value can be specified as "
390 "-<param> <value>\n"
391 "Other valid forms are <param>=<value> -<param>=<value> "
392 "--<param>=<value>\n"
393 "Parameter names are case-insensitive. The parameters are:\n\n");
394 Configuration::listParams(79, 14);
395 exit(1);
396}
397
398int main(int argc, char** argv)
399{
400 initStdIOLoggers();
401 LogWriter::setLogParams("*:stderr:30");
402
403 programName = argv[0];
404 Display* dpy;
405
406 for (int i = 1; i < argc; i++) {
407 if (Configuration::setParam(argv[i]))
408 continue;
409
410 if (argv[i][0] == '-') {
411 if (i+1 < argc) {
412 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
413 i++;
414 continue;
415 }
416 }
417 usage();
418 }
419
420 usage();
421 }
422
423 CharArray dpyStr(displayname.getData());
424 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
425 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
426 programName, XDisplayName(displayname.getData()));
427 exit(1);
428 }
429
430 signal(SIGHUP, CleanupSignalHandler);
431 signal(SIGINT, CleanupSignalHandler);
432 signal(SIGTERM, CleanupSignalHandler);
433
434 try {
435 XDesktop desktop(dpy);
436 VNCServerST server("x0vncserver", &desktop);
437 desktop.setVNCServer(&server);
438
439 TcpSocket::initTcpSockets();
440 TcpListener listener((int)rfbport);
441 vlog.info("Listening on port %d", (int)rfbport);
442
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000443 FileTcpFilter fileTcpFilter(hostsFile.getData());
444 if (strlen(hostsFile.getData()) != 0)
445 listener.setFilter(&fileTcpFilter);
446
447 struct timeval timeSaved, timeNow;
448 struct timezone tz;
449 gettimeofday(&timeSaved, &tz);
450 timeSaved.tv_sec -= 60;
451
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000452 while (true) {
453 fd_set rfds;
454 struct timeval tv;
455
456 tv.tv_sec = 0;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000457 tv.tv_usec = (int)pollingCycle * 1000;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000458
459 FD_ZERO(&rfds);
460 FD_SET(listener.getFd(), &rfds);
461
462 std::list<Socket*> sockets;
463 server.getSockets(&sockets);
464 std::list<Socket*>::iterator i;
465 for (i = sockets.begin(); i != sockets.end(); i++) {
466 FD_SET((*i)->getFd(), &rfds);
467 }
468
469 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
Constantin Kaplinsky659e9002005-09-09 08:32:02 +0000470 if (n < 0) {
471 if (errno == EINTR) {
472 vlog.debug("interrupted select() system call");
473 continue;
474 } else {
475 throw rdr::SystemException("select", errno);
476 }
477 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000478
479 if (FD_ISSET(listener.getFd(), &rfds)) {
480 Socket* sock = listener.accept();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000481 if (sock) {
482 server.addClient(sock);
483 } else {
484 vlog.status("Client connection rejected");
485 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000486 }
487
488 server.getSockets(&sockets);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000489
490 // Nothing more to do if there are no client connections.
491 if (sockets.empty())
492 continue;
493
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000494 for (i = sockets.begin(); i != sockets.end(); i++) {
495 if (FD_ISSET((*i)->getFd(), &rfds)) {
496 server.processSocketEvent(*i);
497 }
498 }
499
500 server.checkTimeouts();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000501
502 if (gettimeofday(&timeNow, &tz) == 0) {
503 int diff = (int)((timeNow.tv_usec - timeSaved.tv_usec + 500) / 1000 +
504 (timeNow.tv_sec - timeSaved.tv_sec) * 1000);
505 if (diff >= (int)pollingCycle) {
506 timeSaved = timeNow;
507 desktop.poll();
508 }
509 } else {
510 // Something strange has happened -- gettimeofday(2) failed.
511 // Poll after each select(), as in the original VNC4 code.
512 desktop.poll();
513 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000514 }
515
516 } catch (rdr::Exception &e) {
517 vlog.error(e.str());
518 };
519
520 return 0;
521}