blob: 110b79eb7c2dac81d91a6323f8cb1509fcf48ac0 [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
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +010033#ifdef HAVE_XRANDR
34#include <X11/extensions/Xrandr.h>
35#include <RandrGlue.h>
36extern "C" {
37void vncSetGlueContext(Display *dpy, void *res);
38}
39#endif
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020040#include <x0vncserver/Geometry.h>
41#include <x0vncserver/XPixelBuffer.h>
42
43using namespace rfb;
44
45extern const unsigned short code_map_qnum_to_xorgevdev[];
46extern const unsigned int code_map_qnum_to_xorgevdev_len;
47
48extern const unsigned short code_map_qnum_to_xorgkbd[];
49extern const unsigned int code_map_qnum_to_xorgkbd_len;
50
Pierre Ossmance4722f2017-11-08 16:00:05 +010051BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
52BoolParameter rawKeyboard("RawKeyboard",
53 "Send keyboard events straight through and "
54 "avoid mapping them to the current keyboard "
55 "layout", false);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020056
57static rfb::LogWriter vlog("XDesktop");
58
59// order is important as it must match RFB extension
60static const char * ledNames[XDESKTOP_N_LEDS] = {
61 "Scroll Lock", "Num Lock", "Caps Lock"
62};
63
64XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
Pierre Ossman07580a82018-03-07 15:33:42 +010065 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
66 oldButtonMask(0), haveXtest(false), haveDamage(false),
67 maxButtons(0), running(false), ledMasks(), ledState(0),
68 codeMap(0), codeMapLen(0)
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020069{
Pierre Ossman07580a82018-03-07 15:33:42 +010070 int major, minor;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020071
Pierre Ossman07580a82018-03-07 15:33:42 +010072 int xkbOpcode, xkbErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020073
Pierre Ossman07580a82018-03-07 15:33:42 +010074 major = XkbMajorVersion;
75 minor = XkbMinorVersion;
76 if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
77 &xkbErrorBase, &major, &minor)) {
78 vlog.error("XKEYBOARD extension not present");
79 throw Exception();
80 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020081
Pierre Ossman07580a82018-03-07 15:33:42 +010082 XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
83 XkbIndicatorStateNotifyMask);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020084
Pierre Ossman07580a82018-03-07 15:33:42 +010085 // figure out bit masks for the indicators we are interested in
86 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
87 Atom a;
88 int shift;
89 Bool on;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020090
Pierre Ossman07580a82018-03-07 15:33:42 +010091 a = XInternAtom(dpy, ledNames[i], True);
92 if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
93 continue;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020094
Pierre Ossman07580a82018-03-07 15:33:42 +010095 ledMasks[i] = 1u << shift;
96 vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
97 if (on)
98 ledState |= 1u << i;
99 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200100
Pierre Ossman07580a82018-03-07 15:33:42 +0100101 // X11 unfortunately uses keyboard driver specific keycodes and provides no
102 // direct way to query this, so guess based on the keyboard mapping
103 XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
104 if (desc && desc->names) {
105 char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200106
Pierre Ossman07580a82018-03-07 15:33:42 +0100107 if (keycodes) {
108 if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
109 codeMap = code_map_qnum_to_xorgevdev;
110 codeMapLen = code_map_qnum_to_xorgevdev_len;
111 vlog.info("Using evdev codemap\n");
112 } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
113 codeMap = code_map_qnum_to_xorgkbd;
114 codeMapLen = code_map_qnum_to_xorgkbd_len;
115 vlog.info("Using xorgkbd codemap\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200116 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100117 vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200118 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100119 XFree(keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200120 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100121 vlog.debug("Unable to get keycode map\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200122 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100123
124 XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
125 }
126
127#ifdef HAVE_XTEST
128 int xtestEventBase;
129 int xtestErrorBase;
130
131 if (XTestQueryExtension(dpy, &xtestEventBase,
132 &xtestErrorBase, &major, &minor)) {
133 XTestGrabControl(dpy, True);
134 vlog.info("XTest extension present - version %d.%d",major,minor);
135 haveXtest = true;
136 } else {
137#endif
138 vlog.info("XTest extension not present");
139 vlog.info("Unable to inject events or display while server is grabbed");
140#ifdef HAVE_XTEST
141 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200142#endif
143
144#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100145 int xdamageErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200146
Pierre Ossman07580a82018-03-07 15:33:42 +0100147 if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
148 haveDamage = true;
149 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200150#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100151 vlog.info("DAMAGE extension not present");
152 vlog.info("Will have to poll screen for changes");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200153#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100154 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200155#endif
156
157#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100158 int xfixesErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200159
Pierre Ossman07580a82018-03-07 15:33:42 +0100160 if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
161 XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
162 XFixesDisplayCursorNotifyMask);
163 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200164#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100165 vlog.info("XFIXES extension not present");
166 vlog.info("Will not be able to display cursors");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200167#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100168 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200169#endif
170
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100171#ifdef HAVE_XRANDR
172 int xrandrErrorBase;
173
174 randrSyncSerial = 0;
175 if (XRRQueryExtension(dpy, &xrandrEventBase, &xrandrErrorBase)) {
176 XRRSelectInput(dpy, DefaultRootWindow(dpy),
177 RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
178 /* Override TXWindow::init input mask */
179 XSelectInput(dpy, DefaultRootWindow(dpy),
180 PropertyChangeMask | StructureNotifyMask | ExposureMask);
181 } else {
182#endif
183 vlog.info("RANDR extension not present");
184 vlog.info("Will not be able to handle session resize");
185#ifdef HAVE_XRANDR
186 }
187#endif
188
Pierre Ossman07580a82018-03-07 15:33:42 +0100189 TXWindow::setGlobalEventHandler(this);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200190}
191
192XDesktop::~XDesktop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100193 if (running)
194 stop();
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200195}
196
197
198void XDesktop::poll() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100199 if (pb and not haveDamage)
200 pb->poll(server);
201 if (running) {
202 Window root, child;
203 int x, y, wx, wy;
204 unsigned int mask;
205 XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
206 &x, &y, &wx, &wy, &mask);
207 server->setCursorPos(rfb::Point(x, y));
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200208 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100209}
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200210
211
212void XDesktop::start(VNCServer* vs) {
213
Pierre Ossman07580a82018-03-07 15:33:42 +0100214 // Determine actual number of buttons of the X pointer device.
215 unsigned char btnMap[8];
216 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
217 maxButtons = (numButtons > 8) ? 8 : numButtons;
218 vlog.info("Enabling %d button%s of X pointer device",
219 maxButtons, (maxButtons != 1) ? "s" : "");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200220
Pierre Ossman07580a82018-03-07 15:33:42 +0100221 // Create an ImageFactory instance for producing Image objects.
222 ImageFactory factory((bool)useShm);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200223
Pierre Ossman07580a82018-03-07 15:33:42 +0100224 // Create pixel buffer and provide it to the server object.
225 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
226 vlog.info("Allocated %s", pb->getImage()->classDesc());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200227
Pierre Ossman07580a82018-03-07 15:33:42 +0100228 server = (VNCServerST *)vs;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100229 server->setPixelBuffer(pb, computeScreenLayout());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200230
231#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100232 if (haveDamage) {
233 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
234 XDamageReportRawRectangles);
235 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200236#endif
237
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200238#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100239 setCursor();
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200240#endif
241
Pierre Ossman07580a82018-03-07 15:33:42 +0100242 server->setLEDState(ledState);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200243
Pierre Ossman07580a82018-03-07 15:33:42 +0100244 running = true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200245}
246
247void XDesktop::stop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100248 running = false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200249
250#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100251 if (haveDamage)
252 XDamageDestroy(dpy, damage);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200253#endif
254
Pierre Ossman07580a82018-03-07 15:33:42 +0100255 server->setPixelBuffer(0);
256 server = 0;
Michal Srb18a77072017-09-29 14:47:56 +0200257
Pierre Ossman07580a82018-03-07 15:33:42 +0100258 delete pb;
259 pb = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200260}
261
262bool XDesktop::isRunning() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100263 return running;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200264}
265
266void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
267#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100268 if (!haveXtest) return;
269 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
270 geometry->offsetLeft() + pos.x,
271 geometry->offsetTop() + pos.y,
272 CurrentTime);
273 if (buttonMask != oldButtonMask) {
274 for (int i = 0; i < maxButtons; i++) {
275 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
276 if (buttonMask & (1<<i)) {
277 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
278 } else {
279 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200280 }
281 }
282 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100283 }
284 oldButtonMask = buttonMask;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200285#endif
286}
287
288#ifdef HAVE_XTEST
289KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100290 XkbDescPtr xkb;
291 XkbStateRec state;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200292 unsigned int mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100293 unsigned keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200294
Pierre Ossman07580a82018-03-07 15:33:42 +0100295 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
296 if (!xkb)
297 return 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200298
Pierre Ossman07580a82018-03-07 15:33:42 +0100299 XkbGetState(dpy, XkbUseCoreKbd, &state);
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200300 // XkbStateFieldFromRec() doesn't work properly because
301 // state.lookup_mods isn't properly updated, so we do this manually
302 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200303
Pierre Ossman07580a82018-03-07 15:33:42 +0100304 for (keycode = xkb->min_key_code;
305 keycode <= xkb->max_key_code;
306 keycode++) {
307 KeySym cursym;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200308 unsigned int out_mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100309 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
310 if (cursym == keysym)
311 break;
312 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200313
Pierre Ossman07580a82018-03-07 15:33:42 +0100314 if (keycode > xkb->max_key_code)
315 keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200316
Pierre Ossman07580a82018-03-07 15:33:42 +0100317 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200318
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200319 // Shift+Tab is usually ISO_Left_Tab, but RFB hides this fact. Do
320 // another attempt if we failed the initial lookup
321 if ((keycode == 0) && (keysym == XK_Tab) && (mods & ShiftMask))
322 return XkbKeysymToKeycode(dpy, XK_ISO_Left_Tab);
323
Pierre Ossman07580a82018-03-07 15:33:42 +0100324 return keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200325}
326#endif
327
328void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
329#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100330 int keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200331
Pierre Ossman07580a82018-03-07 15:33:42 +0100332 if (!haveXtest)
333 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200334
Pierre Ossman07580a82018-03-07 15:33:42 +0100335 // Use scan code if provided and mapping exists
336 if (codeMap && rawKeyboard && xtcode < codeMapLen)
337 keycode = codeMap[xtcode];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200338
Pierre Ossman07580a82018-03-07 15:33:42 +0100339 if (!keycode) {
340 if (pressedKeys.find(keysym) != pressedKeys.end())
341 keycode = pressedKeys[keysym];
342 else {
343 // XKeysymToKeycode() doesn't respect state, so we have to use
344 // something slightly more complex
345 keycode = XkbKeysymToKeycode(dpy, keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200346 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100347 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200348
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200349 if (!keycode) {
350 vlog.error("Could not map key event to X11 key code");
Pierre Ossman07580a82018-03-07 15:33:42 +0100351 return;
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200352 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200353
Pierre Ossman07580a82018-03-07 15:33:42 +0100354 if (down)
355 pressedKeys[keysym] = keycode;
356 else
357 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200358
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200359 vlog.debug("%d %s", keycode, down ? "down" : "up");
360
Pierre Ossman07580a82018-03-07 15:33:42 +0100361 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200362#endif
363}
364
365void XDesktop::clientCutText(const char* str, int len) {
366}
367
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100368ScreenSet XDesktop::computeScreenLayout()
369{
370 ScreenSet layout;
371
372#ifdef HAVE_XRANDR
373 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
374 if (!res) {
375 vlog.error("XRRGetScreenResources failed");
376 return layout;
377 }
378 vncSetGlueContext(dpy, res);
379
380 layout = ::computeScreenLayout(&outputIdMap);
381 XRRFreeScreenResources(res);
382#endif
383
384 return layout;
385}
386
387#ifdef HAVE_XRANDR
388/* Get the biggest mode which is equal or smaller to requested
389 size. If no such mode exists, return the smallest. */
390static void GetSmallerMode(XRRScreenResources *res,
391 XRROutputInfo *output,
392 unsigned int *width, unsigned int *height)
393{
394 XRRModeInfo best = {};
395 XRRModeInfo smallest = {};
396 smallest.width = -1;
397 smallest.height = -1;
398
399 for (int i = 0; i < res->nmode; i++) {
400 for (int j = 0; j < output->nmode; j++) {
401 if (output->modes[j] == res->modes[i].id) {
402 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
403 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
404 best = res->modes[i];
405 }
406 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
407 smallest = res->modes[i];
408 }
409 }
410 }
411 }
412
413 if (best.id == 0 && smallest.id != 0) {
414 best = smallest;
415 }
416
417 *width = best.width;
418 *height = best.height;
419}
420#endif /* HAVE_XRANDR */
421
422unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
423 const rfb::ScreenSet& layout)
424{
425#ifdef HAVE_XRANDR
426 char buffer[2048];
427 vlog.debug("Got request for framebuffer resize to %dx%d",
428 fb_width, fb_height);
429 layout.print(buffer, sizeof(buffer));
430 vlog.debug("%s", buffer);
431
432 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
433 if (!res) {
434 vlog.error("XRRGetScreenResources failed");
435 return rfb::resultProhibited;
436 }
437 vncSetGlueContext(dpy, res);
438
439 /* The client may request a screen layout which is not supported by
440 the Xserver. This happens, for example, when adjusting the size
441 of a non-fullscreen vncviewer window. To handle this and other
442 cases, we first call tryScreenLayout. If this fails, we try to
443 adjust the request to one screen with a smaller mode. */
444 vlog.debug("Testing screen layout");
445 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
446 rfb::ScreenSet adjustedLayout;
447 if (tryresult == rfb::resultSuccess) {
448 adjustedLayout = layout;
449 } else {
450 vlog.debug("Impossible layout - trying to adjust");
451
452 ScreenSet::const_iterator firstscreen = layout.begin();
453 adjustedLayout.add_screen(*firstscreen);
454 ScreenSet::iterator iter = adjustedLayout.begin();
455 RROutput outputId = None;
456
457 for (int i = 0;i < vncRandRGetOutputCount();i++) {
458 unsigned int oi = vncRandRGetOutputId(i);
459
460 /* Known? */
461 if (outputIdMap.count(oi) == 0)
462 continue;
463
464 /* Find the corresponding screen... */
465 if (iter->id == outputIdMap[oi]) {
466 outputId = oi;
467 } else {
468 outputIdMap.erase(oi);
469 }
470 }
471
472 /* New screen */
473 if (outputId == None) {
474 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
475 if (i != -1) {
476 outputId = vncRandRGetOutputId(i);
477 }
478 }
479 if (outputId == None) {
480 vlog.debug("Resize adjust: Could not find corresponding screen");
481 XRRFreeScreenResources(res);
482 return rfb::resultInvalid;
483 }
484 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
485 if (!output) {
486 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
487 XRRFreeScreenResources(res);
488 return rfb::resultInvalid;
489 }
490 if (!output->crtc) {
491 vlog.debug("Resize adjust: Selected output has no CRTC");
492 XRRFreeScreenResources(res);
493 XRRFreeOutputInfo(output);
494 return rfb::resultInvalid;
495 }
496 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
497 if (!crtc) {
498 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
499 XRRFreeScreenResources(res);
500 XRRFreeOutputInfo(output);
501 return rfb::resultInvalid;
502 }
503
504 unsigned int swidth = iter->dimensions.width();
505 unsigned int sheight = iter->dimensions.height();
506
507 switch (crtc->rotation) {
508 case RR_Rotate_90:
509 case RR_Rotate_270:
510 unsigned int swap = swidth;
511 swidth = sheight;
512 sheight = swap;
513 break;
514 }
515
516 GetSmallerMode(res, output, &swidth, &sheight);
517 XRRFreeOutputInfo(output);
518
519 switch (crtc->rotation) {
520 case RR_Rotate_90:
521 case RR_Rotate_270:
522 unsigned int swap = swidth;
523 swidth = sheight;
524 sheight = swap;
525 break;
526 }
527
528 XRRFreeCrtcInfo(crtc);
529
530 if (sheight != 0 && swidth != 0) {
531 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
532 iter->dimensions.setXYWH(0, 0, swidth, sheight);
533 fb_width = swidth;
534 fb_height = sheight;
535 } else {
536 vlog.error("Failed to find smaller or equal screen size");
537 XRRFreeScreenResources(res);
538 return rfb::resultInvalid;
539 }
540 }
541
542 vlog.debug("Changing screen layout");
543 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
544 XRRFreeScreenResources(res);
545
546 /* Send a dummy event to the root window. When this event is seen,
547 earlier change events (ConfigureNotify and/or CrtcChange) have
548 been processed. An Expose event is used for simplicity; does not
549 require any Atoms, and will not affect other applications. */
550 unsigned long serial = XNextRequest(dpy);
551 XExposeEvent ev = {}; /* zero x, y, width, height, count */
552 ev.type = Expose;
553 ev.display = dpy;
554 ev.window = DefaultRootWindow(dpy);
555 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
556 while (randrSyncSerial < serial) {
557 TXWindow::handleXEvents(dpy);
558 }
559 } else {
560 vlog.error("XSendEvent failed");
561 }
562
563 /* The protocol requires that an error is returned if the requested
564 layout could not be set. This is checked by
565 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
566 with reason=0 will be sent in response to the changes seen by the
567 event handler. */
568 if (adjustedLayout != layout) {
569 return rfb::resultInvalid;
570 } else {
571 return ret;
572 }
573
574#else
575 return rfb::resultProhibited;
576#endif /* HAVE_XRANDR */
577}
578
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200579
580bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100581 if (ev->type == xkbEventBase + XkbEventCode) {
582 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200583
Pierre Ossman07580a82018-03-07 15:33:42 +0100584 if (kb->any.xkb_type != XkbIndicatorStateNotify)
585 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200586
Pierre Ossman07580a82018-03-07 15:33:42 +0100587 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200588
Pierre Ossman07580a82018-03-07 15:33:42 +0100589 ledState = 0;
590 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
591 if (kb->indicators.state & ledMasks[i])
592 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200593 }
594
Pierre Ossman07580a82018-03-07 15:33:42 +0100595 if (running)
596 server->setLEDState(ledState);
597
598 return true;
599#ifdef HAVE_XDAMAGE
600 } else if (ev->type == xdamageEventBase) {
601 XDamageNotifyEvent* dev;
602 Rect rect;
603
604 if (!running)
605 return true;
606
607 dev = (XDamageNotifyEvent*)ev;
608 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
609 server->add_changed(rect);
610
611 return true;
612#endif
613#ifdef HAVE_XFIXES
614 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
615 XFixesCursorNotifyEvent* cev;
616
617 if (!running)
618 return true;
619
620 cev = (XFixesCursorNotifyEvent*)ev;
621
622 if (cev->subtype != XFixesDisplayCursorNotify)
623 return false;
624
625 return setCursor();
626#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100627#ifdef HAVE_XRANDR
628 } else if (ev->type == Expose) {
629 XExposeEvent* eev = (XExposeEvent*)ev;
630 randrSyncSerial = eev->serial;
631
632 return false;
633
634 } else if (ev->type == ConfigureNotify) {
635 XConfigureEvent* cev = (XConfigureEvent*)ev;
636
637 if (cev->window != DefaultRootWindow(dpy)) {
638 return false;
639 }
640
641 XRRUpdateConfiguration(ev);
642 geometry->recalc(cev->width, cev->height);
643
644 if (!running) {
645 return false;
646 }
647
648 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
649 // Recreate pixel buffer
650 ImageFactory factory((bool)useShm);
651 delete pb;
652 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
653 server->setPixelBuffer(pb, computeScreenLayout());
654
655 // Mark entire screen as changed
656 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
657 }
658
659 return true;
660
661 } else if (ev->type == xrandrEventBase + RRNotify) {
662 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
663
664 if (rev->window != DefaultRootWindow(dpy)) {
665 return false;
666 }
667
668 if (!running)
669 return false;
670
671 if (rev->subtype == RRNotify_CrtcChange) {
672 server->setScreenLayout(computeScreenLayout());
673 }
674
675 return true;
676#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100677 }
678
679 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200680}
681
682bool XDesktop::setCursor()
683{
Pierre Ossman07580a82018-03-07 15:33:42 +0100684 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200685
Pierre Ossman07580a82018-03-07 15:33:42 +0100686 cim = XFixesGetCursorImage(dpy);
687 if (cim == NULL)
688 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200689
Pierre Ossman07580a82018-03-07 15:33:42 +0100690 // Copied from XserverDesktop::setCursor() in
691 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
692 // handle long -> U32 conversion for 64-bit Xlib
693 rdr::U8* cursorData;
694 rdr::U8 *out;
695 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200696
Pierre Ossman07580a82018-03-07 15:33:42 +0100697 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200698
Pierre Ossman07580a82018-03-07 15:33:42 +0100699 // Un-premultiply alpha
700 pixels = cim->pixels;
701 out = cursorData;
702 for (int y = 0; y < cim->height; y++) {
703 for (int x = 0; x < cim->width; x++) {
704 rdr::U8 alpha;
705 rdr::U32 pixel = *pixels++;
706 rdr::U8 *in = (rdr::U8 *) &pixel;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200707
Pierre Ossman07580a82018-03-07 15:33:42 +0100708 alpha = in[3];
709 if (alpha == 0)
710 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200711
Pierre Ossman07580a82018-03-07 15:33:42 +0100712 *out++ = (unsigned)*in++ * 255/alpha;
713 *out++ = (unsigned)*in++ * 255/alpha;
714 *out++ = (unsigned)*in++ * 255/alpha;
715 *out++ = *in++;
716 }
717 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200718
Pierre Ossman07580a82018-03-07 15:33:42 +0100719 try {
720 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
721 cursorData);
722 } catch (rdr::Exception& e) {
723 vlog.error("XserverDesktop::setCursor: %s",e.str());
724 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200725
Pierre Ossman07580a82018-03-07 15:33:42 +0100726 delete [] cursorData;
727 XFree(cim);
728 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200729}