blob: 59e2532372358dd4fcc6c519fc3f063312d296a8 [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;
292 unsigned keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200293
Pierre Ossman07580a82018-03-07 15:33:42 +0100294 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
295 if (!xkb)
296 return 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200297
Pierre Ossman07580a82018-03-07 15:33:42 +0100298 XkbGetState(dpy, XkbUseCoreKbd, &state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200299
Pierre Ossman07580a82018-03-07 15:33:42 +0100300 for (keycode = xkb->min_key_code;
301 keycode <= xkb->max_key_code;
302 keycode++) {
303 KeySym cursym;
304 unsigned int mods, out_mods;
305 // XkbStateFieldFromRec() doesn't work properly because
306 // state.lookup_mods isn't properly updated, so we do this manually
307 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
308 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
309 if (cursym == keysym)
310 break;
311 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200312
Pierre Ossman07580a82018-03-07 15:33:42 +0100313 if (keycode > xkb->max_key_code)
314 keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200315
Pierre Ossman07580a82018-03-07 15:33:42 +0100316 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200317
Pierre Ossman07580a82018-03-07 15:33:42 +0100318 return keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200319}
320#endif
321
322void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
323#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100324 int keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200325
Pierre Ossman07580a82018-03-07 15:33:42 +0100326 if (!haveXtest)
327 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200328
Pierre Ossman07580a82018-03-07 15:33:42 +0100329 // Use scan code if provided and mapping exists
330 if (codeMap && rawKeyboard && xtcode < codeMapLen)
331 keycode = codeMap[xtcode];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200332
Pierre Ossman07580a82018-03-07 15:33:42 +0100333 if (!keycode) {
334 if (pressedKeys.find(keysym) != pressedKeys.end())
335 keycode = pressedKeys[keysym];
336 else {
337 // XKeysymToKeycode() doesn't respect state, so we have to use
338 // something slightly more complex
339 keycode = XkbKeysymToKeycode(dpy, keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200340 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100341 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200342
Pierre Ossman07580a82018-03-07 15:33:42 +0100343 if (!keycode)
344 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200345
Pierre Ossman07580a82018-03-07 15:33:42 +0100346 if (down)
347 pressedKeys[keysym] = keycode;
348 else
349 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200350
Pierre Ossman07580a82018-03-07 15:33:42 +0100351 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200352#endif
353}
354
355void XDesktop::clientCutText(const char* str, int len) {
356}
357
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100358ScreenSet XDesktop::computeScreenLayout()
359{
360 ScreenSet layout;
361
362#ifdef HAVE_XRANDR
363 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
364 if (!res) {
365 vlog.error("XRRGetScreenResources failed");
366 return layout;
367 }
368 vncSetGlueContext(dpy, res);
369
370 layout = ::computeScreenLayout(&outputIdMap);
371 XRRFreeScreenResources(res);
372#endif
373
374 return layout;
375}
376
377#ifdef HAVE_XRANDR
378/* Get the biggest mode which is equal or smaller to requested
379 size. If no such mode exists, return the smallest. */
380static void GetSmallerMode(XRRScreenResources *res,
381 XRROutputInfo *output,
382 unsigned int *width, unsigned int *height)
383{
384 XRRModeInfo best = {};
385 XRRModeInfo smallest = {};
386 smallest.width = -1;
387 smallest.height = -1;
388
389 for (int i = 0; i < res->nmode; i++) {
390 for (int j = 0; j < output->nmode; j++) {
391 if (output->modes[j] == res->modes[i].id) {
392 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
393 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
394 best = res->modes[i];
395 }
396 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
397 smallest = res->modes[i];
398 }
399 }
400 }
401 }
402
403 if (best.id == 0 && smallest.id != 0) {
404 best = smallest;
405 }
406
407 *width = best.width;
408 *height = best.height;
409}
410#endif /* HAVE_XRANDR */
411
412unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
413 const rfb::ScreenSet& layout)
414{
415#ifdef HAVE_XRANDR
416 char buffer[2048];
417 vlog.debug("Got request for framebuffer resize to %dx%d",
418 fb_width, fb_height);
419 layout.print(buffer, sizeof(buffer));
420 vlog.debug("%s", buffer);
421
422 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
423 if (!res) {
424 vlog.error("XRRGetScreenResources failed");
425 return rfb::resultProhibited;
426 }
427 vncSetGlueContext(dpy, res);
428
429 /* The client may request a screen layout which is not supported by
430 the Xserver. This happens, for example, when adjusting the size
431 of a non-fullscreen vncviewer window. To handle this and other
432 cases, we first call tryScreenLayout. If this fails, we try to
433 adjust the request to one screen with a smaller mode. */
434 vlog.debug("Testing screen layout");
435 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
436 rfb::ScreenSet adjustedLayout;
437 if (tryresult == rfb::resultSuccess) {
438 adjustedLayout = layout;
439 } else {
440 vlog.debug("Impossible layout - trying to adjust");
441
442 ScreenSet::const_iterator firstscreen = layout.begin();
443 adjustedLayout.add_screen(*firstscreen);
444 ScreenSet::iterator iter = adjustedLayout.begin();
445 RROutput outputId = None;
446
447 for (int i = 0;i < vncRandRGetOutputCount();i++) {
448 unsigned int oi = vncRandRGetOutputId(i);
449
450 /* Known? */
451 if (outputIdMap.count(oi) == 0)
452 continue;
453
454 /* Find the corresponding screen... */
455 if (iter->id == outputIdMap[oi]) {
456 outputId = oi;
457 } else {
458 outputIdMap.erase(oi);
459 }
460 }
461
462 /* New screen */
463 if (outputId == None) {
464 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
465 if (i != -1) {
466 outputId = vncRandRGetOutputId(i);
467 }
468 }
469 if (outputId == None) {
470 vlog.debug("Resize adjust: Could not find corresponding screen");
471 XRRFreeScreenResources(res);
472 return rfb::resultInvalid;
473 }
474 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
475 if (!output) {
476 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
477 XRRFreeScreenResources(res);
478 return rfb::resultInvalid;
479 }
480 if (!output->crtc) {
481 vlog.debug("Resize adjust: Selected output has no CRTC");
482 XRRFreeScreenResources(res);
483 XRRFreeOutputInfo(output);
484 return rfb::resultInvalid;
485 }
486 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
487 if (!crtc) {
488 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
489 XRRFreeScreenResources(res);
490 XRRFreeOutputInfo(output);
491 return rfb::resultInvalid;
492 }
493
494 unsigned int swidth = iter->dimensions.width();
495 unsigned int sheight = iter->dimensions.height();
496
497 switch (crtc->rotation) {
498 case RR_Rotate_90:
499 case RR_Rotate_270:
500 unsigned int swap = swidth;
501 swidth = sheight;
502 sheight = swap;
503 break;
504 }
505
506 GetSmallerMode(res, output, &swidth, &sheight);
507 XRRFreeOutputInfo(output);
508
509 switch (crtc->rotation) {
510 case RR_Rotate_90:
511 case RR_Rotate_270:
512 unsigned int swap = swidth;
513 swidth = sheight;
514 sheight = swap;
515 break;
516 }
517
518 XRRFreeCrtcInfo(crtc);
519
520 if (sheight != 0 && swidth != 0) {
521 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
522 iter->dimensions.setXYWH(0, 0, swidth, sheight);
523 fb_width = swidth;
524 fb_height = sheight;
525 } else {
526 vlog.error("Failed to find smaller or equal screen size");
527 XRRFreeScreenResources(res);
528 return rfb::resultInvalid;
529 }
530 }
531
532 vlog.debug("Changing screen layout");
533 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
534 XRRFreeScreenResources(res);
535
536 /* Send a dummy event to the root window. When this event is seen,
537 earlier change events (ConfigureNotify and/or CrtcChange) have
538 been processed. An Expose event is used for simplicity; does not
539 require any Atoms, and will not affect other applications. */
540 unsigned long serial = XNextRequest(dpy);
541 XExposeEvent ev = {}; /* zero x, y, width, height, count */
542 ev.type = Expose;
543 ev.display = dpy;
544 ev.window = DefaultRootWindow(dpy);
545 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
546 while (randrSyncSerial < serial) {
547 TXWindow::handleXEvents(dpy);
548 }
549 } else {
550 vlog.error("XSendEvent failed");
551 }
552
553 /* The protocol requires that an error is returned if the requested
554 layout could not be set. This is checked by
555 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
556 with reason=0 will be sent in response to the changes seen by the
557 event handler. */
558 if (adjustedLayout != layout) {
559 return rfb::resultInvalid;
560 } else {
561 return ret;
562 }
563
564#else
565 return rfb::resultProhibited;
566#endif /* HAVE_XRANDR */
567}
568
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200569
570bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100571 if (ev->type == xkbEventBase + XkbEventCode) {
572 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200573
Pierre Ossman07580a82018-03-07 15:33:42 +0100574 if (kb->any.xkb_type != XkbIndicatorStateNotify)
575 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200576
Pierre Ossman07580a82018-03-07 15:33:42 +0100577 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200578
Pierre Ossman07580a82018-03-07 15:33:42 +0100579 ledState = 0;
580 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
581 if (kb->indicators.state & ledMasks[i])
582 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200583 }
584
Pierre Ossman07580a82018-03-07 15:33:42 +0100585 if (running)
586 server->setLEDState(ledState);
587
588 return true;
589#ifdef HAVE_XDAMAGE
590 } else if (ev->type == xdamageEventBase) {
591 XDamageNotifyEvent* dev;
592 Rect rect;
593
594 if (!running)
595 return true;
596
597 dev = (XDamageNotifyEvent*)ev;
598 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
599 server->add_changed(rect);
600
601 return true;
602#endif
603#ifdef HAVE_XFIXES
604 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
605 XFixesCursorNotifyEvent* cev;
606
607 if (!running)
608 return true;
609
610 cev = (XFixesCursorNotifyEvent*)ev;
611
612 if (cev->subtype != XFixesDisplayCursorNotify)
613 return false;
614
615 return setCursor();
616#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100617#ifdef HAVE_XRANDR
618 } else if (ev->type == Expose) {
619 XExposeEvent* eev = (XExposeEvent*)ev;
620 randrSyncSerial = eev->serial;
621
622 return false;
623
624 } else if (ev->type == ConfigureNotify) {
625 XConfigureEvent* cev = (XConfigureEvent*)ev;
626
627 if (cev->window != DefaultRootWindow(dpy)) {
628 return false;
629 }
630
631 XRRUpdateConfiguration(ev);
632 geometry->recalc(cev->width, cev->height);
633
634 if (!running) {
635 return false;
636 }
637
638 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
639 // Recreate pixel buffer
640 ImageFactory factory((bool)useShm);
641 delete pb;
642 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
643 server->setPixelBuffer(pb, computeScreenLayout());
644
645 // Mark entire screen as changed
646 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
647 }
648
649 return true;
650
651 } else if (ev->type == xrandrEventBase + RRNotify) {
652 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
653
654 if (rev->window != DefaultRootWindow(dpy)) {
655 return false;
656 }
657
658 if (!running)
659 return false;
660
661 if (rev->subtype == RRNotify_CrtcChange) {
662 server->setScreenLayout(computeScreenLayout());
663 }
664
665 return true;
666#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100667 }
668
669 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200670}
671
672bool XDesktop::setCursor()
673{
Pierre Ossman07580a82018-03-07 15:33:42 +0100674 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200675
Pierre Ossman07580a82018-03-07 15:33:42 +0100676 cim = XFixesGetCursorImage(dpy);
677 if (cim == NULL)
678 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200679
Pierre Ossman07580a82018-03-07 15:33:42 +0100680 // Copied from XserverDesktop::setCursor() in
681 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
682 // handle long -> U32 conversion for 64-bit Xlib
683 rdr::U8* cursorData;
684 rdr::U8 *out;
685 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200686
Pierre Ossman07580a82018-03-07 15:33:42 +0100687 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200688
Pierre Ossman07580a82018-03-07 15:33:42 +0100689 // Un-premultiply alpha
690 pixels = cim->pixels;
691 out = cursorData;
692 for (int y = 0; y < cim->height; y++) {
693 for (int x = 0; x < cim->width; x++) {
694 rdr::U8 alpha;
695 rdr::U32 pixel = *pixels++;
696 rdr::U8 *in = (rdr::U8 *) &pixel;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200697
Pierre Ossman07580a82018-03-07 15:33:42 +0100698 alpha = in[3];
699 if (alpha == 0)
700 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200701
Pierre Ossman07580a82018-03-07 15:33:42 +0100702 *out++ = (unsigned)*in++ * 255/alpha;
703 *out++ = (unsigned)*in++ * 255/alpha;
704 *out++ = (unsigned)*in++ * 255/alpha;
705 *out++ = *in++;
706 }
707 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200708
Pierre Ossman07580a82018-03-07 15:33:42 +0100709 try {
710 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
711 cursorData);
712 } catch (rdr::Exception& e) {
713 vlog.error("XserverDesktop::setCursor: %s",e.str());
714 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200715
Pierre Ossman07580a82018-03-07 15:33:42 +0100716 delete [] cursorData;
717 XFree(cim);
718 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200719}