blob: 6fd6bc3b7e7b723c40a4bb8421e38d51c73c7391 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Constantin Kaplinsky2c019832008-05-30 11:02:04 +00002 * Copyright (C) 2004-2008 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +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 */
19
20// FIXME: Check cases when screen width/height is not a multiply of 32.
21// e.g. 800x600.
22
23#include <strings.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#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>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000032#include <rfb/Timer.h>
33#include <network/TcpSocket.h>
34#include <tx/TXWindow.h>
35
Constantin Kaplinskya3b60c42006-06-02 04:07:49 +000036#include <vncconfig/QueryConnectDialog.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000037
38#include <signal.h>
39#include <X11/X.h>
40#include <X11/Xlib.h>
41#include <X11/Xutil.h>
Peter Korsgaard5b7ff372017-07-13 00:36:01 +020042#include <X11/XKBlib.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000043#ifdef HAVE_XTEST
44#include <X11/extensions/XTest.h>
45#endif
Pierre Ossmana7b728a2014-06-13 10:56:59 +000046#ifdef HAVE_XDAMAGE
47#include <X11/extensions/Xdamage.h>
48#endif
Alan Coopersmith9a739d42017-08-05 15:16:29 -070049#ifdef HAVE_XFIXES
50#include <X11/extensions/Xfixes.h>
51#endif
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000052
53#include <x0vncserver/Geometry.h>
54#include <x0vncserver/Image.h>
Constantin Kaplinsky614c7b52007-12-26 18:17:09 +000055#include <x0vncserver/XPixelBuffer.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000056#include <x0vncserver/PollingScheduler.h>
57
Peter Korsgaard8fe50902017-07-13 00:35:57 +020058extern const unsigned short code_map_qnum_to_xorgevdev[];
59extern const unsigned int code_map_qnum_to_xorgevdev_len;
60
61extern const unsigned short code_map_qnum_to_xorgkbd[];
62extern const unsigned int code_map_qnum_to_xorgkbd_len;
63
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000064// XXX Lynx/OS 2.3: protos for select(), bzero()
65#ifdef Lynx
66#include <sys/proto.h>
67#endif
68
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000069extern char buildtime[];
70
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000071using namespace rfb;
72using namespace network;
73
Peter Korsgaard5b7ff372017-07-13 00:36:01 +020074// number of XKb indicator leds to handle
75static const int N_LEDS = 3;
76
77// order is important as it must match RFB extension
78static const char * ledNames[N_LEDS] = {
79 "Scroll Lock", "Num Lock", "Caps Lock"
80};
81
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000082static LogWriter vlog("Main");
83
84IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
85 "cycle; actual interval may be dynamically "
86 "adjusted to satisfy MaxProcessorUsage setting", 30);
87IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
88 "CPU time to be consumed", 35);
89BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
90BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
91 "IRIX or Solaris", true);
92StringParameter displayname("display", "The X display", "");
93IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
94IntParameter queryConnectTimeout("QueryConnectTimeout",
95 "Number of seconds to show the Accept Connection dialog before "
96 "rejecting the connection",
97 10);
98StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Peter Korsgaard8fe50902017-07-13 00:35:57 +020099BoolParameter rawKeyboard("RawKeyboard",
100 "Send keyboard events straight through and "
101 "avoid mapping them to the current keyboard "
102 "layout", false);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000103
104//
105// Allow the main loop terminate itself gracefully on receiving a signal.
106//
107
108static bool caughtSignal = false;
109
110static void CleanupSignalHandler(int sig)
111{
112 caughtSignal = true;
113}
114
115
116class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
117 public QueryResultCallback {
118public:
119 QueryConnHandler(Display* dpy, VNCServerST* vs)
120 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
121 ~QueryConnHandler() { delete queryConnectDialog; }
122
123 // -=- VNCServerST::QueryConnectionHandler interface
124 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
125 const char* userName,
126 char** reason) {
127 if (queryConnectSock) {
Adam Tkacd36b6262009-09-04 10:57:20 +0000128 *reason = strDup("Another connection is currently being queried.");
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000129 return VNCServerST::REJECT;
130 }
131 if (!userName) userName = "(anonymous)";
132 queryConnectSock = sock;
133 CharArray address(sock->getPeerAddress());
134 delete queryConnectDialog;
135 queryConnectDialog = new QueryConnectDialog(display, address.buf,
136 userName, queryConnectTimeout,
137 this);
138 queryConnectDialog->map();
139 return VNCServerST::PENDING;
140 }
141
142 // -=- QueryResultCallback interface
143 virtual void queryApproved() {
144 server->approveConnection(queryConnectSock, true, 0);
145 queryConnectSock = 0;
146 }
147 virtual void queryRejected() {
148 server->approveConnection(queryConnectSock, false,
149 "Connection rejected by local user");
150 queryConnectSock = 0;
151 }
152private:
153 Display* display;
154 VNCServerST* server;
155 QueryConnectDialog* queryConnectDialog;
156 network::Socket* queryConnectSock;
157};
158
159
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100160class XDesktop : public SDesktop, public TXGlobalEventHandler
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000161{
162public:
163 XDesktop(Display* dpy_, Geometry *geometry_)
Constantin Kaplinsky303433a2008-06-04 05:57:06 +0000164 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000165 oldButtonMask(0), haveXtest(false), haveDamage(false),
Pierre Ossman61a75ef2017-09-15 14:23:34 +0200166 maxButtons(0), running(false), ledMasks(), ledState(0),
167 codeMap(0), codeMapLen(0)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000168 {
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200169 int major, minor;
170
171 int xkbOpcode, xkbErrorBase;
172
173 major = XkbMajorVersion;
174 minor = XkbMinorVersion;
175 if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
176 &xkbErrorBase, &major, &minor)) {
177 vlog.error("XKEYBOARD extension not present");
178 throw Exception();
179 }
180
181 XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
182 XkbIndicatorStateNotifyMask);
183
184 // figure out bit masks for the indicators we are interested in
185 for (int i = 0; i < N_LEDS; i++) {
186 Atom a;
187 int shift;
188 Bool on;
189
190 a = XInternAtom(dpy, ledNames[i], True);
191 if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
192 continue;
193
194 ledMasks[i] = 1u << shift;
195 vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
196 if (on)
197 ledState |= 1u << i;
198 }
199
Peter Korsgaard8fe50902017-07-13 00:35:57 +0200200 // X11 unfortunately uses keyboard driver specific keycodes and provides no
201 // direct way to query this, so guess based on the keyboard mapping
202 XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
203 if (desc && desc->names) {
204 char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
205
206 if (keycodes) {
207 if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
208 codeMap = code_map_qnum_to_xorgevdev;
209 codeMapLen = code_map_qnum_to_xorgevdev_len;
210 vlog.info("Using evdev codemap\n");
211 } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
212 codeMap = code_map_qnum_to_xorgkbd;
213 codeMapLen = code_map_qnum_to_xorgkbd_len;
214 vlog.info("Using xorgkbd codemap\n");
215 } else {
216 vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
217 }
218 XFree(keycodes);
219 } else {
220 vlog.debug("Unable to get keycode map\n");
221 }
222
223 XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
224 }
225
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000226#ifdef HAVE_XTEST
227 int xtestEventBase;
228 int xtestErrorBase;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000229
230 if (XTestQueryExtension(dpy, &xtestEventBase,
231 &xtestErrorBase, &major, &minor)) {
232 XTestGrabControl(dpy, True);
233 vlog.info("XTest extension present - version %d.%d",major,minor);
234 haveXtest = true;
235 } else {
236#endif
237 vlog.info("XTest extension not present");
238 vlog.info("Unable to inject events or display while server is grabbed");
239#ifdef HAVE_XTEST
240 }
241#endif
242
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000243#ifdef HAVE_XDAMAGE
244 int xdamageErrorBase;
245
246 if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000247 haveDamage = true;
248 } else {
249#endif
250 vlog.info("DAMAGE extension not present");
251 vlog.info("Will have to poll screen for changes");
252#ifdef HAVE_XDAMAGE
253 }
254#endif
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200255
Alan Coopersmith9a739d42017-08-05 15:16:29 -0700256#ifdef HAVE_XFIXES
257 int xfixesErrorBase;
258
259 if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
Pierre Ossman61a75ef2017-09-15 14:23:34 +0200260 XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
261 XFixesDisplayCursorNotifyMask);
Alan Coopersmith9a739d42017-08-05 15:16:29 -0700262 } else {
263#endif
264 vlog.info("XFIXES extension not present");
265 vlog.info("Will not be able to display cursors");
266#ifdef HAVE_XFIXES
267 }
268#endif
Pierre Ossmand092f052017-09-15 13:10:54 +0200269
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200270 TXWindow::setGlobalEventHandler(this);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000271 }
272 virtual ~XDesktop() {
273 stop();
274 }
275
Constantin Kaplinskydc873dc2008-06-04 09:46:57 +0000276 inline void poll() {
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000277 if (pb and not haveDamage)
Constantin Kaplinskydc873dc2008-06-04 09:46:57 +0000278 pb->poll(server);
Pierre Ossman00dec782017-09-15 14:52:21 +0200279 if (running) {
280 Window root, child;
281 int x, y, wx, wy;
282 unsigned int mask;
283 XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
284 &x, &y, &wx, &wy, &mask);
285 server->setCursorPos(rfb::Point(x, y));
286 }
Constantin Kaplinskydc873dc2008-06-04 09:46:57 +0000287 }
288
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000289 // -=- SDesktop interface
290
291 virtual void start(VNCServer* vs) {
292
293 // Determine actual number of buttons of the X pointer device.
294 unsigned char btnMap[8];
295 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
296 maxButtons = (numButtons > 8) ? 8 : numButtons;
297 vlog.info("Enabling %d button%s of X pointer device",
298 maxButtons, (maxButtons != 1) ? "s" : "");
299
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000300 // Create an ImageFactory instance for producing Image objects.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000301 ImageFactory factory((bool)useShm, (bool)useOverlay);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000302
Constantin Kaplinskydc873dc2008-06-04 09:46:57 +0000303 // Create pixel buffer and provide it to the server object.
Pierre Ossmanb6b4dc62014-01-20 15:05:21 +0100304 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000305 vlog.info("Allocated %s", pb->getImage()->classDesc());
306
Constantin Kaplinskyc341ac62008-08-21 03:35:08 +0000307 server = (VNCServerST *)vs;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000308 server->setPixelBuffer(pb);
309
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000310#ifdef HAVE_XDAMAGE
311 if (haveDamage) {
312 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
313 XDamageReportRawRectangles);
314 }
315#endif
316
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200317 server->setLEDState(ledState);
318
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000319 running = true;
320 }
321
322 virtual void stop() {
323 running = false;
324
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000325#ifdef HAVE_XDAMAGE
326 if (haveDamage)
327 XDamageDestroy(dpy, damage);
328#endif
329
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000330 delete pb;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000331 pb = 0;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000332 }
333
334 inline bool isRunning() {
335 return running;
336 }
337
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000338 virtual void pointerEvent(const Point& pos, int buttonMask) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000339#ifdef HAVE_XTEST
340 if (!haveXtest) return;
341 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
342 geometry->offsetLeft() + pos.x,
343 geometry->offsetTop() + pos.y,
344 CurrentTime);
345 if (buttonMask != oldButtonMask) {
346 for (int i = 0; i < maxButtons; i++) {
347 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
348 if (buttonMask & (1<<i)) {
349 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
350 } else {
351 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
352 }
353 }
354 }
355 }
356 oldButtonMask = buttonMask;
357#endif
358 }
359
Pierre Ossmanac730382017-08-16 15:20:20 +0200360#ifdef HAVE_XTEST
361 KeyCode XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
362 XkbDescPtr xkb;
363 XkbStateRec state;
364 unsigned keycode;
365
366 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
367 if (!xkb)
368 return 0;
369
370 XkbGetState(dpy, XkbUseCoreKbd, &state);
371
372 for (keycode = xkb->min_key_code;
373 keycode <= xkb->max_key_code;
374 keycode++) {
375 KeySym cursym;
Pierre Ossman2df2a072017-09-15 15:34:24 +0200376 unsigned int mods, out_mods;
377 // XkbStateFieldFromRec() doesn't work properly because
378 // state.lookup_mods isn't properly updated, so we do this manually
379 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
380 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
Pierre Ossmanac730382017-08-16 15:20:20 +0200381 if (cursym == keysym)
382 break;
383 }
384
385 if (keycode > xkb->max_key_code)
386 keycode = 0;
387
388 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
389
390 return keycode;
391 }
392#endif
393
Pierre Ossman5ae28212017-05-16 14:30:38 +0200394 virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000395#ifdef HAVE_XTEST
Peter Korsgaard8fe50902017-07-13 00:35:57 +0200396 int keycode = 0;
Pierre Ossmanac730382017-08-16 15:20:20 +0200397
398 if (!haveXtest)
399 return;
Peter Korsgaard8fe50902017-07-13 00:35:57 +0200400
401 // Use scan code if provided and mapping exists
402 if (codeMap && rawKeyboard && xtcode < codeMapLen)
403 keycode = codeMap[xtcode];
404
Pierre Ossman63cc07e2017-09-15 11:17:02 +0200405 if (!keycode) {
Pierre Ossman2956bb32017-09-15 15:34:43 +0200406 if (pressedKeys.find(keysym) != pressedKeys.end())
Pierre Ossman63cc07e2017-09-15 11:17:02 +0200407 keycode = pressedKeys[keysym];
Pierre Ossmanac730382017-08-16 15:20:20 +0200408 else {
409 // XKeysymToKeycode() doesn't respect state, so we have to use
410 // something slightly more complex
Pierre Ossman63cc07e2017-09-15 11:17:02 +0200411 keycode = XkbKeysymToKeycode(dpy, keysym);
Pierre Ossmanac730382017-08-16 15:20:20 +0200412 }
Pierre Ossmanac730382017-08-16 15:20:20 +0200413 }
Peter Korsgaard8fe50902017-07-13 00:35:57 +0200414
Pierre Ossman63cc07e2017-09-15 11:17:02 +0200415 if (!keycode)
416 return;
417
418 if (down)
419 pressedKeys[keysym] = keycode;
420 else
421 pressedKeys.erase(keysym);
422
Pierre Ossmanac730382017-08-16 15:20:20 +0200423 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000424#endif
425 }
426
427 virtual void clientCutText(const char* str, int len) {
428 }
429
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000430 // -=- TXGlobalEventHandler interface
431
432 virtual bool handleGlobalEvent(XEvent* ev) {
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200433 if (ev->type == xkbEventBase + XkbEventCode) {
434 XkbEvent *kb = (XkbEvent *)ev;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000435
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200436 if (kb->any.xkb_type != XkbIndicatorStateNotify)
437 return false;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000438
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200439 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
440
441 ledState = 0;
442 for (int i = 0; i < N_LEDS; i++) {
443 if (kb->indicators.state & ledMasks[i])
444 ledState |= 1u << i;
445 }
446
447 if (running)
448 server->setLEDState(ledState);
449
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000450 return true;
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200451#ifdef HAVE_XDAMAGE
452 } else if (ev->type == xdamageEventBase) {
453 XDamageNotifyEvent* dev;
454 Rect rect;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000455
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200456 if (!running)
457 return true;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000458
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200459 dev = (XDamageNotifyEvent*)ev;
460 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
461 server->add_changed(rect);
462
463 return true;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000464#endif
Alan Coopersmith9a739d42017-08-05 15:16:29 -0700465#ifdef HAVE_XFIXES
Pierre Ossmand092f052017-09-15 13:10:54 +0200466 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
Alan Coopersmith9a739d42017-08-05 15:16:29 -0700467 XFixesCursorNotifyEvent* cev;
468 XFixesCursorImage *cim;
469
470 if (!running)
471 return true;
472
473 cev = (XFixesCursorNotifyEvent*)ev;
474
475 if (cev->subtype != XFixesDisplayCursorNotify)
476 return false;
477
478 cim = XFixesGetCursorImage(dpy);
479 if (cim == NULL)
480 return false;
481
482 // Copied from XserverDesktop::setCursor() in
483 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
484 // handle long -> U32 conversion for 64-bit Xlib
485 rdr::U8* cursorData;
486 rdr::U8 *out;
487 const unsigned long *pixels;
488
489 cursorData = new rdr::U8[cim->width * cim->height * 4];
490
491 // Un-premultiply alpha
492 pixels = cim->pixels;
493 out = cursorData;
494 for (int y = 0; y < cim->height; y++) {
495 for (int x = 0; x < cim->width; x++) {
496 rdr::U8 alpha;
497 rdr::U32 pixel = *pixels++;
498 rdr::U8 *in = (rdr::U8 *) &pixel;
499
500 alpha = in[3];
501 if (alpha == 0)
502 alpha = 1; // Avoid division by zero
503
504 *out++ = (unsigned)*in++ * 255/alpha;
505 *out++ = (unsigned)*in++ * 255/alpha;
506 *out++ = (unsigned)*in++ * 255/alpha;
507 *out++ = *in++;
508 }
509 }
510
511 try {
512 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
513 cursorData);
514 } catch (rdr::Exception& e) {
515 vlog.error("XserverDesktop::setCursor: %s",e.str());
516 }
517
518 delete [] cursorData;
519 XFree(cim);
520 return true;
Alan Coopersmith9a739d42017-08-05 15:16:29 -0700521#endif
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200522 }
523
524 return false;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000525 }
526
527protected:
528 Display* dpy;
529 Geometry* geometry;
Constantin Kaplinsky2c019832008-05-30 11:02:04 +0000530 XPixelBuffer* pb;
Constantin Kaplinskyc341ac62008-08-21 03:35:08 +0000531 VNCServerST* server;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000532 int oldButtonMask;
533 bool haveXtest;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000534 bool haveDamage;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000535 int maxButtons;
Pierre Ossmanac730382017-08-16 15:20:20 +0200536 std::map<KeySym, KeyCode> pressedKeys;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000537 bool running;
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000538#ifdef HAVE_XDAMAGE
539 Damage damage;
540 int xdamageEventBase;
541#endif
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200542 int xkbEventBase;
Alan Coopersmith9a739d42017-08-05 15:16:29 -0700543#ifdef HAVE_XFIXES
544 int xfixesEventBase;
545#endif
Peter Korsgaard5b7ff372017-07-13 00:36:01 +0200546 int ledMasks[N_LEDS];
547 unsigned ledState;
Peter Korsgaard8fe50902017-07-13 00:35:57 +0200548 const unsigned short *codeMap;
549 unsigned codeMapLen;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000550};
551
552
553class FileTcpFilter : public TcpFilter
554{
555
556public:
557
558 FileTcpFilter(const char *fname)
559 : TcpFilter("-"), fileName(NULL), lastModTime(0)
560 {
561 if (fname != NULL)
562 fileName = strdup((char *)fname);
563 }
564
565 virtual ~FileTcpFilter()
566 {
567 if (fileName != NULL)
568 free(fileName);
569 }
570
571 virtual bool verifyConnection(Socket* s)
572 {
573 if (!reloadRules()) {
574 vlog.error("Could not read IP filtering rules: rejecting all clients");
575 filter.clear();
576 filter.push_back(parsePattern("-"));
577 return false;
578 }
579
580 return TcpFilter::verifyConnection(s);
581 }
582
583protected:
584
585 bool reloadRules()
586 {
587 if (fileName == NULL)
588 return true;
589
590 struct stat st;
591 if (stat(fileName, &st) != 0)
592 return false;
593
594 if (st.st_mtime != lastModTime) {
595 // Actually reload only if the file was modified
596 FILE *fp = fopen(fileName, "r");
597 if (fp == NULL)
598 return false;
599
600 // Remove all the rules from the parent class
601 filter.clear();
602
603 // Parse the file contents adding rules to the parent class
604 char buf[32];
605 while (readLine(buf, 32, fp)) {
606 if (buf[0] && strchr("+-?", buf[0])) {
607 filter.push_back(parsePattern(buf));
608 }
609 }
610
611 fclose(fp);
612 lastModTime = st.st_mtime;
613 }
614 return true;
615 }
616
617protected:
618
619 char *fileName;
620 time_t lastModTime;
621
622private:
623
624 //
625 // NOTE: we silently truncate long lines in this function.
626 //
627
628 bool readLine(char *buf, int bufSize, FILE *fp)
629 {
630 if (fp == NULL || buf == NULL || bufSize == 0)
631 return false;
632
633 if (fgets(buf, bufSize, fp) == NULL)
634 return false;
635
636 char *ptr = strchr(buf, '\n');
637 if (ptr != NULL) {
638 *ptr = '\0'; // remove newline at the end
639 } else {
640 if (!feof(fp)) {
641 int c;
642 do { // skip the rest of a long line
643 c = getc(fp);
644 } while (c != '\n' && c != EOF);
645 }
646 }
647 return true;
648 }
649
650};
651
652char* programName;
653
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000654static void printVersion(FILE *fp)
655{
Peter Ã…strand4eacc022009-02-27 10:12:14 +0000656 fprintf(fp, "TigerVNC Server version %s, built %s\n",
Constantin Kaplinskyea7b6502008-09-28 05:08:48 +0000657 PACKAGE_VERSION, buildtime);
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000658}
659
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000660static void usage()
661{
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000662 printVersion(stderr);
663 fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
664 fprintf(stderr, " %s --version\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000665 fprintf(stderr,"\n"
666 "Parameters can be turned on with -<param> or off with -<param>=0\n"
667 "Parameters which take a value can be specified as "
668 "-<param> <value>\n"
669 "Other valid forms are <param>=<value> -<param>=<value> "
670 "--<param>=<value>\n"
671 "Parameter names are case-insensitive. The parameters are:\n\n");
672 Configuration::listParams(79, 14);
673 exit(1);
674}
675
676int main(int argc, char** argv)
677{
678 initStdIOLoggers();
679 LogWriter::setLogParams("*:stderr:30");
680
681 programName = argv[0];
682 Display* dpy;
683
Adam Tkacc58b3d12010-04-23 13:55:10 +0000684 Configuration::enableServerParams();
685
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000686 for (int i = 1; i < argc; i++) {
687 if (Configuration::setParam(argv[i]))
688 continue;
689
690 if (argv[i][0] == '-') {
691 if (i+1 < argc) {
692 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
693 i++;
694 continue;
695 }
696 }
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000697 if (strcmp(argv[i], "-v") == 0 ||
698 strcmp(argv[i], "-version") == 0 ||
699 strcmp(argv[i], "--version") == 0) {
700 printVersion(stdout);
701 return 0;
702 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000703 usage();
704 }
705
706 usage();
707 }
708
709 CharArray dpyStr(displayname.getData());
710 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000711 // FIXME: Why not vlog.error(...)?
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000712 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000713 programName, XDisplayName(dpyStr.buf));
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000714 exit(1);
715 }
716
717 signal(SIGHUP, CleanupSignalHandler);
718 signal(SIGINT, CleanupSignalHandler);
719 signal(SIGTERM, CleanupSignalHandler);
720
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200721 std::list<TcpListener*> listeners;
Tim Waugh892d10a2015-03-11 13:12:07 +0000722
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000723 try {
724 TXWindow::init(dpy,"x0vncserver");
725 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
726 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000727 if (geo.getRect().is_empty()) {
728 vlog.error("Exiting with error");
729 return 1;
730 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000731 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000732
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000733 VNCServerST server("x0vncserver", &desktop);
734 QueryConnHandler qcHandler(dpy, &server);
735 server.setQueryConnectionHandler(&qcHandler);
736
Tim Waugh892d10a2015-03-11 13:12:07 +0000737 createTcpListeners(&listeners, 0, (int)rfbport);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000738 vlog.info("Listening on port %d", (int)rfbport);
739
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000740 const char *hostsData = hostsFile.getData();
741 FileTcpFilter fileTcpFilter(hostsData);
742 if (strlen(hostsData) != 0)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200743 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000744 i != listeners.end();
745 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200746 (*i)->setFilter(&fileTcpFilter);
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000747 delete[] hostsData;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000748
749 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
750
751 while (!caughtSignal) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200752 int wait_ms;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000753 struct timeval tv;
Pierre Ossman16419cc2016-04-29 14:29:43 +0200754 fd_set rfds, wfds;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000755 std::list<Socket*> sockets;
756 std::list<Socket*>::iterator i;
757
758 // Process any incoming X events
759 TXWindow::handleXEvents(dpy);
760
761 FD_ZERO(&rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200762 FD_ZERO(&wfds);
763
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000764 FD_SET(ConnectionNumber(dpy), &rfds);
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200765 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000766 i != listeners.end();
767 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200768 FD_SET((*i)->getFd(), &rfds);
Tim Waugh892d10a2015-03-11 13:12:07 +0000769
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000770 server.getSockets(&sockets);
771 int clients_connected = 0;
772 for (i = sockets.begin(); i != sockets.end(); i++) {
773 if ((*i)->isShutdown()) {
774 server.removeSocket(*i);
775 delete (*i);
776 } else {
777 FD_SET((*i)->getFd(), &rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200778 if ((*i)->outStream().bufferUsage() > 0)
779 FD_SET((*i)->getFd(), &wfds);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000780 clients_connected++;
781 }
782 }
783
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000784 if (!clients_connected)
785 sched.reset();
786
Pierre Ossman278e4202016-04-29 14:28:54 +0200787 wait_ms = 0;
788
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000789 if (sched.isRunning()) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200790 wait_ms = sched.millisRemaining();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000791 if (wait_ms > 500) {
792 wait_ms = 500;
793 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000794 }
Pierre Ossman278e4202016-04-29 14:28:54 +0200795
796 soonestTimeout(&wait_ms, server.checkTimeouts());
797
798 tv.tv_sec = wait_ms / 1000;
799 tv.tv_usec = (wait_ms % 1000) * 1000;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000800
801 // Do the wait...
802 sched.sleepStarted();
Pierre Ossman16419cc2016-04-29 14:29:43 +0200803 int n = select(FD_SETSIZE, &rfds, &wfds, 0,
Pierre Ossman278e4202016-04-29 14:28:54 +0200804 wait_ms ? &tv : NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000805 sched.sleepFinished();
806
807 if (n < 0) {
808 if (errno == EINTR) {
809 vlog.debug("Interrupted select() system call");
810 continue;
811 } else {
812 throw rdr::SystemException("select", errno);
813 }
814 }
815
816 // Accept new VNC connections
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200817 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000818 i != listeners.end();
819 i++) {
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200820 if (FD_ISSET((*i)->getFd(), &rfds)) {
821 Socket* sock = (*i)->accept();
Tim Waugh892d10a2015-03-11 13:12:07 +0000822 if (sock) {
Pierre Ossman16419cc2016-04-29 14:29:43 +0200823 sock->outStream().setBlocking(false);
Tim Waugh892d10a2015-03-11 13:12:07 +0000824 server.addSocket(sock);
825 } else {
826 vlog.status("Client connection rejected");
827 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000828 }
829 }
830
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000831 server.checkTimeouts();
832
833 // Client list could have been changed.
834 server.getSockets(&sockets);
835
836 // Nothing more to do if there are no client connections.
837 if (sockets.empty())
838 continue;
839
840 // Process events on existing VNC connections
841 for (i = sockets.begin(); i != sockets.end(); i++) {
842 if (FD_ISSET((*i)->getFd(), &rfds))
Pierre Ossmand408ca52016-04-29 14:26:05 +0200843 server.processSocketReadEvent(*i);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200844 if (FD_ISSET((*i)->getFd(), &wfds))
845 server.processSocketWriteEvent(*i);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000846 }
847
848 if (desktop.isRunning() && sched.goodTimeToPoll()) {
849 sched.newPass();
850 desktop.poll();
851 }
852 }
853
854 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000855 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000856 return 1;
857 }
858
Constantin Kaplinsky0c4306c2008-09-05 07:13:55 +0000859 TXWindow::handleXEvents(dpy);
860
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000861 vlog.info("Terminated");
862 return 0;
863}