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