blob: 4a989d3fec8308c00573712d0be4acf4c90e897a [file] [log] [blame]
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +02001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright (C) 2004-2008 Constantin Kaplinsky. All Rights Reserved.
Peter Åstrand (astrand)3a1db162017-10-16 11:11:45 +02003 * Copyright 2017 Peter Astrand <astrand@cendio.se> for Cendio AB
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +02004 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20
21#include <x0vncserver/XDesktop.h>
22
23#include <X11/XKBlib.h>
24#ifdef HAVE_XTEST
25#include <X11/extensions/XTest.h>
26#endif
27#ifdef HAVE_XDAMAGE
28#include <X11/extensions/Xdamage.h>
29#endif
30#ifdef HAVE_XFIXES
31#include <X11/extensions/Xfixes.h>
32#endif
33
34#include <x0vncserver/Geometry.h>
35#include <x0vncserver/XPixelBuffer.h>
36
37using namespace rfb;
38
39extern const unsigned short code_map_qnum_to_xorgevdev[];
40extern const unsigned int code_map_qnum_to_xorgevdev_len;
41
42extern const unsigned short code_map_qnum_to_xorgkbd[];
43extern const unsigned int code_map_qnum_to_xorgkbd_len;
44
Pierre Ossmance4722f2017-11-08 16:00:05 +010045BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
46BoolParameter rawKeyboard("RawKeyboard",
47 "Send keyboard events straight through and "
48 "avoid mapping them to the current keyboard "
49 "layout", false);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020050
51static rfb::LogWriter vlog("XDesktop");
52
53// order is important as it must match RFB extension
54static const char * ledNames[XDESKTOP_N_LEDS] = {
55 "Scroll Lock", "Num Lock", "Caps Lock"
56};
57
58XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
59 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
60 oldButtonMask(0), haveXtest(false), haveDamage(false),
61 maxButtons(0), running(false), ledMasks(), ledState(0),
62 codeMap(0), codeMapLen(0)
63{
64 int major, minor;
65
66 int xkbOpcode, xkbErrorBase;
67
68 major = XkbMajorVersion;
69 minor = XkbMinorVersion;
70 if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
71 &xkbErrorBase, &major, &minor)) {
72 vlog.error("XKEYBOARD extension not present");
73 throw Exception();
74 }
75
76 XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
77 XkbIndicatorStateNotifyMask);
78
79 // figure out bit masks for the indicators we are interested in
80 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
81 Atom a;
82 int shift;
83 Bool on;
84
85 a = XInternAtom(dpy, ledNames[i], True);
86 if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
87 continue;
88
89 ledMasks[i] = 1u << shift;
90 vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
91 if (on)
92 ledState |= 1u << i;
93 }
94
95 // X11 unfortunately uses keyboard driver specific keycodes and provides no
96 // direct way to query this, so guess based on the keyboard mapping
97 XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
98 if (desc && desc->names) {
99 char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
100
101 if (keycodes) {
102 if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
103 codeMap = code_map_qnum_to_xorgevdev;
104 codeMapLen = code_map_qnum_to_xorgevdev_len;
105 vlog.info("Using evdev codemap\n");
106 } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
107 codeMap = code_map_qnum_to_xorgkbd;
108 codeMapLen = code_map_qnum_to_xorgkbd_len;
109 vlog.info("Using xorgkbd codemap\n");
110 } else {
111 vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
112 }
113 XFree(keycodes);
114 } else {
115 vlog.debug("Unable to get keycode map\n");
116 }
117
118 XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
119 }
120
121#ifdef HAVE_XTEST
122 int xtestEventBase;
123 int xtestErrorBase;
124
125 if (XTestQueryExtension(dpy, &xtestEventBase,
126 &xtestErrorBase, &major, &minor)) {
127 XTestGrabControl(dpy, True);
128 vlog.info("XTest extension present - version %d.%d",major,minor);
129 haveXtest = true;
130 } else {
131#endif
132 vlog.info("XTest extension not present");
133 vlog.info("Unable to inject events or display while server is grabbed");
134#ifdef HAVE_XTEST
135 }
136#endif
137
138#ifdef HAVE_XDAMAGE
139 int xdamageErrorBase;
140
141 if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
142 haveDamage = true;
143 } else {
144#endif
145 vlog.info("DAMAGE extension not present");
146 vlog.info("Will have to poll screen for changes");
147#ifdef HAVE_XDAMAGE
148 }
149#endif
150
151#ifdef HAVE_XFIXES
152 int xfixesErrorBase;
153
154 if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
155 XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
156 XFixesDisplayCursorNotifyMask);
157 } else {
158#endif
159 vlog.info("XFIXES extension not present");
160 vlog.info("Will not be able to display cursors");
161#ifdef HAVE_XFIXES
162 }
163#endif
164
165 TXWindow::setGlobalEventHandler(this);
166}
167
168XDesktop::~XDesktop() {
169 stop();
170}
171
172
173void XDesktop::poll() {
174 if (pb and not haveDamage)
175 pb->poll(server);
176 if (running) {
177 Window root, child;
178 int x, y, wx, wy;
179 unsigned int mask;
180 XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
181 &x, &y, &wx, &wy, &mask);
182 server->setCursorPos(rfb::Point(x, y));
183 }
184 }
185
186
187void XDesktop::start(VNCServer* vs) {
188
189 // Determine actual number of buttons of the X pointer device.
190 unsigned char btnMap[8];
191 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
192 maxButtons = (numButtons > 8) ? 8 : numButtons;
193 vlog.info("Enabling %d button%s of X pointer device",
194 maxButtons, (maxButtons != 1) ? "s" : "");
195
196 // Create an ImageFactory instance for producing Image objects.
Peter Åstrand (astrand)dcd0b132017-10-16 15:18:00 +0200197 ImageFactory factory((bool)useShm);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200198
199 // Create pixel buffer and provide it to the server object.
200 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
201 vlog.info("Allocated %s", pb->getImage()->classDesc());
202
203 server = (VNCServerST *)vs;
204 server->setPixelBuffer(pb);
205
206#ifdef HAVE_XDAMAGE
207 if (haveDamage) {
208 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
209 XDamageReportRawRectangles);
210 }
211#endif
212
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200213#ifdef HAVE_XFIXES
214 setCursor();
215#endif
216
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200217 server->setLEDState(ledState);
218
219 running = true;
220}
221
222void XDesktop::stop() {
223 running = false;
224
225#ifdef HAVE_XDAMAGE
226 if (haveDamage)
227 XDamageDestroy(dpy, damage);
228#endif
229
230 delete pb;
231 pb = 0;
232}
233
234bool XDesktop::isRunning() {
235 return running;
236}
237
238void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
239#ifdef HAVE_XTEST
240 if (!haveXtest) return;
241 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
242 geometry->offsetLeft() + pos.x,
243 geometry->offsetTop() + pos.y,
244 CurrentTime);
245 if (buttonMask != oldButtonMask) {
246 for (int i = 0; i < maxButtons; i++) {
247 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
248 if (buttonMask & (1<<i)) {
249 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
250 } else {
251 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
252 }
253 }
254 }
255 }
256 oldButtonMask = buttonMask;
257#endif
258}
259
260#ifdef HAVE_XTEST
261KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
262 XkbDescPtr xkb;
263 XkbStateRec state;
264 unsigned keycode;
265
266 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
267 if (!xkb)
268 return 0;
269
270 XkbGetState(dpy, XkbUseCoreKbd, &state);
271
272 for (keycode = xkb->min_key_code;
273 keycode <= xkb->max_key_code;
274 keycode++) {
275 KeySym cursym;
276 unsigned int mods, out_mods;
277 // XkbStateFieldFromRec() doesn't work properly because
278 // state.lookup_mods isn't properly updated, so we do this manually
279 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
280 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
281 if (cursym == keysym)
282 break;
283 }
284
285 if (keycode > xkb->max_key_code)
286 keycode = 0;
287
288 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
289
290 return keycode;
291}
292#endif
293
294void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
295#ifdef HAVE_XTEST
296 int keycode = 0;
297
298 if (!haveXtest)
299 return;
300
301 // Use scan code if provided and mapping exists
302 if (codeMap && rawKeyboard && xtcode < codeMapLen)
303 keycode = codeMap[xtcode];
304
305 if (!keycode) {
306 if (pressedKeys.find(keysym) != pressedKeys.end())
307 keycode = pressedKeys[keysym];
308 else {
309 // XKeysymToKeycode() doesn't respect state, so we have to use
310 // something slightly more complex
311 keycode = XkbKeysymToKeycode(dpy, keysym);
312 }
313 }
314
315 if (!keycode)
316 return;
317
318 if (down)
319 pressedKeys[keysym] = keycode;
320 else
321 pressedKeys.erase(keysym);
322
323 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
324#endif
325}
326
327void XDesktop::clientCutText(const char* str, int len) {
328}
329
330
331bool XDesktop::handleGlobalEvent(XEvent* ev) {
332 if (ev->type == xkbEventBase + XkbEventCode) {
333 XkbEvent *kb = (XkbEvent *)ev;
334
335 if (kb->any.xkb_type != XkbIndicatorStateNotify)
336 return false;
337
338 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
339
340 ledState = 0;
341 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
342 if (kb->indicators.state & ledMasks[i])
343 ledState |= 1u << i;
344 }
345
346 if (running)
347 server->setLEDState(ledState);
348
349 return true;
350#ifdef HAVE_XDAMAGE
351 } else if (ev->type == xdamageEventBase) {
352 XDamageNotifyEvent* dev;
353 Rect rect;
354
355 if (!running)
356 return true;
357
358 dev = (XDamageNotifyEvent*)ev;
359 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
360 server->add_changed(rect);
361
362 return true;
363#endif
364#ifdef HAVE_XFIXES
365 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
366 XFixesCursorNotifyEvent* cev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200367
368 if (!running)
369 return true;
370
371 cev = (XFixesCursorNotifyEvent*)ev;
372
373 if (cev->subtype != XFixesDisplayCursorNotify)
374 return false;
375
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200376 return setCursor();
377#endif
378 }
379
380 return false;
381}
382
383bool XDesktop::setCursor()
384{
385 XFixesCursorImage *cim;
386
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200387 cim = XFixesGetCursorImage(dpy);
388 if (cim == NULL)
389 return false;
390
391 // Copied from XserverDesktop::setCursor() in
392 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
393 // handle long -> U32 conversion for 64-bit Xlib
394 rdr::U8* cursorData;
395 rdr::U8 *out;
396 const unsigned long *pixels;
397
398 cursorData = new rdr::U8[cim->width * cim->height * 4];
399
400 // Un-premultiply alpha
401 pixels = cim->pixels;
402 out = cursorData;
403 for (int y = 0; y < cim->height; y++) {
404 for (int x = 0; x < cim->width; x++) {
405 rdr::U8 alpha;
406 rdr::U32 pixel = *pixels++;
407 rdr::U8 *in = (rdr::U8 *) &pixel;
408
409 alpha = in[3];
410 if (alpha == 0)
411 alpha = 1; // Avoid division by zero
412
413 *out++ = (unsigned)*in++ * 255/alpha;
414 *out++ = (unsigned)*in++ * 255/alpha;
415 *out++ = (unsigned)*in++ * 255/alpha;
416 *out++ = *in++;
417 }
418 }
419
420 try {
421 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
422 cursorData);
423 } catch (rdr::Exception& e) {
424 vlog.error("XserverDesktop::setCursor: %s",e.str());
425 }
426
427 delete [] cursorData;
428 XFree(cim);
429 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200430}