blob: 0273a6df72f2f55f0f28ca28bbb03b872f52e6e4 [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 Ossman07580a82018-03-07 15:33:42 +0100349 if (!keycode)
350 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200351
Pierre Ossman07580a82018-03-07 15:33:42 +0100352 if (down)
353 pressedKeys[keysym] = keycode;
354 else
355 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200356
Pierre Ossman07580a82018-03-07 15:33:42 +0100357 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200358#endif
359}
360
361void XDesktop::clientCutText(const char* str, int len) {
362}
363
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100364ScreenSet XDesktop::computeScreenLayout()
365{
366 ScreenSet layout;
367
368#ifdef HAVE_XRANDR
369 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
370 if (!res) {
371 vlog.error("XRRGetScreenResources failed");
372 return layout;
373 }
374 vncSetGlueContext(dpy, res);
375
376 layout = ::computeScreenLayout(&outputIdMap);
377 XRRFreeScreenResources(res);
378#endif
379
380 return layout;
381}
382
383#ifdef HAVE_XRANDR
384/* Get the biggest mode which is equal or smaller to requested
385 size. If no such mode exists, return the smallest. */
386static void GetSmallerMode(XRRScreenResources *res,
387 XRROutputInfo *output,
388 unsigned int *width, unsigned int *height)
389{
390 XRRModeInfo best = {};
391 XRRModeInfo smallest = {};
392 smallest.width = -1;
393 smallest.height = -1;
394
395 for (int i = 0; i < res->nmode; i++) {
396 for (int j = 0; j < output->nmode; j++) {
397 if (output->modes[j] == res->modes[i].id) {
398 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
399 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
400 best = res->modes[i];
401 }
402 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
403 smallest = res->modes[i];
404 }
405 }
406 }
407 }
408
409 if (best.id == 0 && smallest.id != 0) {
410 best = smallest;
411 }
412
413 *width = best.width;
414 *height = best.height;
415}
416#endif /* HAVE_XRANDR */
417
418unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
419 const rfb::ScreenSet& layout)
420{
421#ifdef HAVE_XRANDR
422 char buffer[2048];
423 vlog.debug("Got request for framebuffer resize to %dx%d",
424 fb_width, fb_height);
425 layout.print(buffer, sizeof(buffer));
426 vlog.debug("%s", buffer);
427
428 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
429 if (!res) {
430 vlog.error("XRRGetScreenResources failed");
431 return rfb::resultProhibited;
432 }
433 vncSetGlueContext(dpy, res);
434
435 /* The client may request a screen layout which is not supported by
436 the Xserver. This happens, for example, when adjusting the size
437 of a non-fullscreen vncviewer window. To handle this and other
438 cases, we first call tryScreenLayout. If this fails, we try to
439 adjust the request to one screen with a smaller mode. */
440 vlog.debug("Testing screen layout");
441 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
442 rfb::ScreenSet adjustedLayout;
443 if (tryresult == rfb::resultSuccess) {
444 adjustedLayout = layout;
445 } else {
446 vlog.debug("Impossible layout - trying to adjust");
447
448 ScreenSet::const_iterator firstscreen = layout.begin();
449 adjustedLayout.add_screen(*firstscreen);
450 ScreenSet::iterator iter = adjustedLayout.begin();
451 RROutput outputId = None;
452
453 for (int i = 0;i < vncRandRGetOutputCount();i++) {
454 unsigned int oi = vncRandRGetOutputId(i);
455
456 /* Known? */
457 if (outputIdMap.count(oi) == 0)
458 continue;
459
460 /* Find the corresponding screen... */
461 if (iter->id == outputIdMap[oi]) {
462 outputId = oi;
463 } else {
464 outputIdMap.erase(oi);
465 }
466 }
467
468 /* New screen */
469 if (outputId == None) {
470 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
471 if (i != -1) {
472 outputId = vncRandRGetOutputId(i);
473 }
474 }
475 if (outputId == None) {
476 vlog.debug("Resize adjust: Could not find corresponding screen");
477 XRRFreeScreenResources(res);
478 return rfb::resultInvalid;
479 }
480 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
481 if (!output) {
482 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
483 XRRFreeScreenResources(res);
484 return rfb::resultInvalid;
485 }
486 if (!output->crtc) {
487 vlog.debug("Resize adjust: Selected output has no CRTC");
488 XRRFreeScreenResources(res);
489 XRRFreeOutputInfo(output);
490 return rfb::resultInvalid;
491 }
492 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
493 if (!crtc) {
494 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
495 XRRFreeScreenResources(res);
496 XRRFreeOutputInfo(output);
497 return rfb::resultInvalid;
498 }
499
500 unsigned int swidth = iter->dimensions.width();
501 unsigned int sheight = iter->dimensions.height();
502
503 switch (crtc->rotation) {
504 case RR_Rotate_90:
505 case RR_Rotate_270:
506 unsigned int swap = swidth;
507 swidth = sheight;
508 sheight = swap;
509 break;
510 }
511
512 GetSmallerMode(res, output, &swidth, &sheight);
513 XRRFreeOutputInfo(output);
514
515 switch (crtc->rotation) {
516 case RR_Rotate_90:
517 case RR_Rotate_270:
518 unsigned int swap = swidth;
519 swidth = sheight;
520 sheight = swap;
521 break;
522 }
523
524 XRRFreeCrtcInfo(crtc);
525
526 if (sheight != 0 && swidth != 0) {
527 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
528 iter->dimensions.setXYWH(0, 0, swidth, sheight);
529 fb_width = swidth;
530 fb_height = sheight;
531 } else {
532 vlog.error("Failed to find smaller or equal screen size");
533 XRRFreeScreenResources(res);
534 return rfb::resultInvalid;
535 }
536 }
537
538 vlog.debug("Changing screen layout");
539 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
540 XRRFreeScreenResources(res);
541
542 /* Send a dummy event to the root window. When this event is seen,
543 earlier change events (ConfigureNotify and/or CrtcChange) have
544 been processed. An Expose event is used for simplicity; does not
545 require any Atoms, and will not affect other applications. */
546 unsigned long serial = XNextRequest(dpy);
547 XExposeEvent ev = {}; /* zero x, y, width, height, count */
548 ev.type = Expose;
549 ev.display = dpy;
550 ev.window = DefaultRootWindow(dpy);
551 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
552 while (randrSyncSerial < serial) {
553 TXWindow::handleXEvents(dpy);
554 }
555 } else {
556 vlog.error("XSendEvent failed");
557 }
558
559 /* The protocol requires that an error is returned if the requested
560 layout could not be set. This is checked by
561 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
562 with reason=0 will be sent in response to the changes seen by the
563 event handler. */
564 if (adjustedLayout != layout) {
565 return rfb::resultInvalid;
566 } else {
567 return ret;
568 }
569
570#else
571 return rfb::resultProhibited;
572#endif /* HAVE_XRANDR */
573}
574
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200575
576bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100577 if (ev->type == xkbEventBase + XkbEventCode) {
578 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200579
Pierre Ossman07580a82018-03-07 15:33:42 +0100580 if (kb->any.xkb_type != XkbIndicatorStateNotify)
581 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200582
Pierre Ossman07580a82018-03-07 15:33:42 +0100583 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200584
Pierre Ossman07580a82018-03-07 15:33:42 +0100585 ledState = 0;
586 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
587 if (kb->indicators.state & ledMasks[i])
588 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200589 }
590
Pierre Ossman07580a82018-03-07 15:33:42 +0100591 if (running)
592 server->setLEDState(ledState);
593
594 return true;
595#ifdef HAVE_XDAMAGE
596 } else if (ev->type == xdamageEventBase) {
597 XDamageNotifyEvent* dev;
598 Rect rect;
599
600 if (!running)
601 return true;
602
603 dev = (XDamageNotifyEvent*)ev;
604 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
605 server->add_changed(rect);
606
607 return true;
608#endif
609#ifdef HAVE_XFIXES
610 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
611 XFixesCursorNotifyEvent* cev;
612
613 if (!running)
614 return true;
615
616 cev = (XFixesCursorNotifyEvent*)ev;
617
618 if (cev->subtype != XFixesDisplayCursorNotify)
619 return false;
620
621 return setCursor();
622#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100623#ifdef HAVE_XRANDR
624 } else if (ev->type == Expose) {
625 XExposeEvent* eev = (XExposeEvent*)ev;
626 randrSyncSerial = eev->serial;
627
628 return false;
629
630 } else if (ev->type == ConfigureNotify) {
631 XConfigureEvent* cev = (XConfigureEvent*)ev;
632
633 if (cev->window != DefaultRootWindow(dpy)) {
634 return false;
635 }
636
637 XRRUpdateConfiguration(ev);
638 geometry->recalc(cev->width, cev->height);
639
640 if (!running) {
641 return false;
642 }
643
644 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
645 // Recreate pixel buffer
646 ImageFactory factory((bool)useShm);
647 delete pb;
648 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
649 server->setPixelBuffer(pb, computeScreenLayout());
650
651 // Mark entire screen as changed
652 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
653 }
654
655 return true;
656
657 } else if (ev->type == xrandrEventBase + RRNotify) {
658 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
659
660 if (rev->window != DefaultRootWindow(dpy)) {
661 return false;
662 }
663
664 if (!running)
665 return false;
666
667 if (rev->subtype == RRNotify_CrtcChange) {
668 server->setScreenLayout(computeScreenLayout());
669 }
670
671 return true;
672#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100673 }
674
675 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200676}
677
678bool XDesktop::setCursor()
679{
Pierre Ossman07580a82018-03-07 15:33:42 +0100680 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200681
Pierre Ossman07580a82018-03-07 15:33:42 +0100682 cim = XFixesGetCursorImage(dpy);
683 if (cim == NULL)
684 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200685
Pierre Ossman07580a82018-03-07 15:33:42 +0100686 // Copied from XserverDesktop::setCursor() in
687 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
688 // handle long -> U32 conversion for 64-bit Xlib
689 rdr::U8* cursorData;
690 rdr::U8 *out;
691 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200692
Pierre Ossman07580a82018-03-07 15:33:42 +0100693 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200694
Pierre Ossman07580a82018-03-07 15:33:42 +0100695 // Un-premultiply alpha
696 pixels = cim->pixels;
697 out = cursorData;
698 for (int y = 0; y < cim->height; y++) {
699 for (int x = 0; x < cim->width; x++) {
700 rdr::U8 alpha;
701 rdr::U32 pixel = *pixels++;
702 rdr::U8 *in = (rdr::U8 *) &pixel;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200703
Pierre Ossman07580a82018-03-07 15:33:42 +0100704 alpha = in[3];
705 if (alpha == 0)
706 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200707
Pierre Ossman07580a82018-03-07 15:33:42 +0100708 *out++ = (unsigned)*in++ * 255/alpha;
709 *out++ = (unsigned)*in++ * 255/alpha;
710 *out++ = (unsigned)*in++ * 255/alpha;
711 *out++ = *in++;
712 }
713 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200714
Pierre Ossman07580a82018-03-07 15:33:42 +0100715 try {
716 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
717 cursorData);
718 } catch (rdr::Exception& e) {
719 vlog.error("XserverDesktop::setCursor: %s",e.str());
720 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200721
Pierre Ossman07580a82018-03-07 15:33:42 +0100722 delete [] cursorData;
723 XFree(cim);
724 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200725}