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