blob: c7961f2a1d6c6305b5e142954460265139225eb6 [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);
Pierre Ossmanc86ce3e2018-09-10 17:03:17 +0200207 x -= geometry->offsetLeft();
208 y -= geometry->offsetTop();
Pierre Ossman07580a82018-03-07 15:33:42 +0100209 server->setCursorPos(rfb::Point(x, y));
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200210 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100211}
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200212
213
214void XDesktop::start(VNCServer* vs) {
215
Pierre Ossman07580a82018-03-07 15:33:42 +0100216 // Determine actual number of buttons of the X pointer device.
217 unsigned char btnMap[8];
218 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
219 maxButtons = (numButtons > 8) ? 8 : numButtons;
220 vlog.info("Enabling %d button%s of X pointer device",
221 maxButtons, (maxButtons != 1) ? "s" : "");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200222
Pierre Ossman07580a82018-03-07 15:33:42 +0100223 // Create an ImageFactory instance for producing Image objects.
224 ImageFactory factory((bool)useShm);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200225
Pierre Ossman07580a82018-03-07 15:33:42 +0100226 // Create pixel buffer and provide it to the server object.
227 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
228 vlog.info("Allocated %s", pb->getImage()->classDesc());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200229
Pierre Ossman07580a82018-03-07 15:33:42 +0100230 server = (VNCServerST *)vs;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100231 server->setPixelBuffer(pb, computeScreenLayout());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200232
233#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100234 if (haveDamage) {
235 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
236 XDamageReportRawRectangles);
237 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200238#endif
239
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200240#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100241 setCursor();
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200242#endif
243
Pierre Ossman07580a82018-03-07 15:33:42 +0100244 server->setLEDState(ledState);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200245
Pierre Ossman07580a82018-03-07 15:33:42 +0100246 running = true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200247}
248
249void XDesktop::stop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100250 running = false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200251
252#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100253 if (haveDamage)
254 XDamageDestroy(dpy, damage);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200255#endif
256
Pierre Ossman07580a82018-03-07 15:33:42 +0100257 server->setPixelBuffer(0);
258 server = 0;
Michal Srb18a77072017-09-29 14:47:56 +0200259
Pierre Ossman07580a82018-03-07 15:33:42 +0100260 delete pb;
261 pb = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200262}
263
264bool XDesktop::isRunning() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100265 return running;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200266}
267
268void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
269#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100270 if (!haveXtest) return;
271 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
272 geometry->offsetLeft() + pos.x,
273 geometry->offsetTop() + pos.y,
274 CurrentTime);
275 if (buttonMask != oldButtonMask) {
276 for (int i = 0; i < maxButtons; i++) {
277 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
278 if (buttonMask & (1<<i)) {
279 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
280 } else {
281 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200282 }
283 }
284 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100285 }
286 oldButtonMask = buttonMask;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200287#endif
288}
289
290#ifdef HAVE_XTEST
291KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100292 XkbDescPtr xkb;
293 XkbStateRec state;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200294 unsigned int mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100295 unsigned keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200296
Pierre Ossman07580a82018-03-07 15:33:42 +0100297 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
298 if (!xkb)
299 return 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200300
Pierre Ossman07580a82018-03-07 15:33:42 +0100301 XkbGetState(dpy, XkbUseCoreKbd, &state);
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200302 // XkbStateFieldFromRec() doesn't work properly because
303 // state.lookup_mods isn't properly updated, so we do this manually
304 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200305
Pierre Ossman07580a82018-03-07 15:33:42 +0100306 for (keycode = xkb->min_key_code;
307 keycode <= xkb->max_key_code;
308 keycode++) {
309 KeySym cursym;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200310 unsigned int out_mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100311 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
312 if (cursym == keysym)
313 break;
314 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200315
Pierre Ossman07580a82018-03-07 15:33:42 +0100316 if (keycode > xkb->max_key_code)
317 keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200318
Pierre Ossman07580a82018-03-07 15:33:42 +0100319 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200320
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200321 // Shift+Tab is usually ISO_Left_Tab, but RFB hides this fact. Do
322 // another attempt if we failed the initial lookup
323 if ((keycode == 0) && (keysym == XK_Tab) && (mods & ShiftMask))
324 return XkbKeysymToKeycode(dpy, XK_ISO_Left_Tab);
325
Pierre Ossman07580a82018-03-07 15:33:42 +0100326 return keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200327}
328#endif
329
330void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
331#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100332 int keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200333
Pierre Ossman07580a82018-03-07 15:33:42 +0100334 if (!haveXtest)
335 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200336
Pierre Ossman07580a82018-03-07 15:33:42 +0100337 // Use scan code if provided and mapping exists
338 if (codeMap && rawKeyboard && xtcode < codeMapLen)
339 keycode = codeMap[xtcode];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200340
Pierre Ossman07580a82018-03-07 15:33:42 +0100341 if (!keycode) {
342 if (pressedKeys.find(keysym) != pressedKeys.end())
343 keycode = pressedKeys[keysym];
344 else {
345 // XKeysymToKeycode() doesn't respect state, so we have to use
346 // something slightly more complex
347 keycode = XkbKeysymToKeycode(dpy, keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200348 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100349 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200350
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200351 if (!keycode) {
352 vlog.error("Could not map key event to X11 key code");
Pierre Ossman07580a82018-03-07 15:33:42 +0100353 return;
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200354 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200355
Pierre Ossman07580a82018-03-07 15:33:42 +0100356 if (down)
357 pressedKeys[keysym] = keycode;
358 else
359 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200360
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200361 vlog.debug("%d %s", keycode, down ? "down" : "up");
362
Pierre Ossman07580a82018-03-07 15:33:42 +0100363 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200364#endif
365}
366
367void XDesktop::clientCutText(const char* str, int len) {
368}
369
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100370ScreenSet XDesktop::computeScreenLayout()
371{
372 ScreenSet layout;
373
374#ifdef HAVE_XRANDR
375 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
376 if (!res) {
377 vlog.error("XRRGetScreenResources failed");
378 return layout;
379 }
380 vncSetGlueContext(dpy, res);
381
382 layout = ::computeScreenLayout(&outputIdMap);
383 XRRFreeScreenResources(res);
384#endif
385
386 return layout;
387}
388
389#ifdef HAVE_XRANDR
390/* Get the biggest mode which is equal or smaller to requested
391 size. If no such mode exists, return the smallest. */
392static void GetSmallerMode(XRRScreenResources *res,
393 XRROutputInfo *output,
394 unsigned int *width, unsigned int *height)
395{
396 XRRModeInfo best = {};
397 XRRModeInfo smallest = {};
398 smallest.width = -1;
399 smallest.height = -1;
400
401 for (int i = 0; i < res->nmode; i++) {
402 for (int j = 0; j < output->nmode; j++) {
403 if (output->modes[j] == res->modes[i].id) {
404 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
405 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
406 best = res->modes[i];
407 }
408 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
409 smallest = res->modes[i];
410 }
411 }
412 }
413 }
414
415 if (best.id == 0 && smallest.id != 0) {
416 best = smallest;
417 }
418
419 *width = best.width;
420 *height = best.height;
421}
422#endif /* HAVE_XRANDR */
423
424unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
425 const rfb::ScreenSet& layout)
426{
427#ifdef HAVE_XRANDR
428 char buffer[2048];
429 vlog.debug("Got request for framebuffer resize to %dx%d",
430 fb_width, fb_height);
431 layout.print(buffer, sizeof(buffer));
432 vlog.debug("%s", buffer);
433
434 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
435 if (!res) {
436 vlog.error("XRRGetScreenResources failed");
437 return rfb::resultProhibited;
438 }
439 vncSetGlueContext(dpy, res);
440
441 /* The client may request a screen layout which is not supported by
442 the Xserver. This happens, for example, when adjusting the size
443 of a non-fullscreen vncviewer window. To handle this and other
444 cases, we first call tryScreenLayout. If this fails, we try to
445 adjust the request to one screen with a smaller mode. */
446 vlog.debug("Testing screen layout");
447 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
448 rfb::ScreenSet adjustedLayout;
449 if (tryresult == rfb::resultSuccess) {
450 adjustedLayout = layout;
451 } else {
452 vlog.debug("Impossible layout - trying to adjust");
453
454 ScreenSet::const_iterator firstscreen = layout.begin();
455 adjustedLayout.add_screen(*firstscreen);
456 ScreenSet::iterator iter = adjustedLayout.begin();
457 RROutput outputId = None;
458
459 for (int i = 0;i < vncRandRGetOutputCount();i++) {
460 unsigned int oi = vncRandRGetOutputId(i);
461
462 /* Known? */
463 if (outputIdMap.count(oi) == 0)
464 continue;
465
466 /* Find the corresponding screen... */
467 if (iter->id == outputIdMap[oi]) {
468 outputId = oi;
469 } else {
470 outputIdMap.erase(oi);
471 }
472 }
473
474 /* New screen */
475 if (outputId == None) {
476 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
477 if (i != -1) {
478 outputId = vncRandRGetOutputId(i);
479 }
480 }
481 if (outputId == None) {
482 vlog.debug("Resize adjust: Could not find corresponding screen");
483 XRRFreeScreenResources(res);
484 return rfb::resultInvalid;
485 }
486 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
487 if (!output) {
488 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
489 XRRFreeScreenResources(res);
490 return rfb::resultInvalid;
491 }
492 if (!output->crtc) {
493 vlog.debug("Resize adjust: Selected output has no CRTC");
494 XRRFreeScreenResources(res);
495 XRRFreeOutputInfo(output);
496 return rfb::resultInvalid;
497 }
498 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
499 if (!crtc) {
500 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
501 XRRFreeScreenResources(res);
502 XRRFreeOutputInfo(output);
503 return rfb::resultInvalid;
504 }
505
506 unsigned int swidth = iter->dimensions.width();
507 unsigned int sheight = iter->dimensions.height();
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 GetSmallerMode(res, output, &swidth, &sheight);
519 XRRFreeOutputInfo(output);
520
521 switch (crtc->rotation) {
522 case RR_Rotate_90:
523 case RR_Rotate_270:
524 unsigned int swap = swidth;
525 swidth = sheight;
526 sheight = swap;
527 break;
528 }
529
530 XRRFreeCrtcInfo(crtc);
531
532 if (sheight != 0 && swidth != 0) {
533 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
534 iter->dimensions.setXYWH(0, 0, swidth, sheight);
535 fb_width = swidth;
536 fb_height = sheight;
537 } else {
538 vlog.error("Failed to find smaller or equal screen size");
539 XRRFreeScreenResources(res);
540 return rfb::resultInvalid;
541 }
542 }
543
544 vlog.debug("Changing screen layout");
545 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
546 XRRFreeScreenResources(res);
547
548 /* Send a dummy event to the root window. When this event is seen,
549 earlier change events (ConfigureNotify and/or CrtcChange) have
550 been processed. An Expose event is used for simplicity; does not
551 require any Atoms, and will not affect other applications. */
552 unsigned long serial = XNextRequest(dpy);
553 XExposeEvent ev = {}; /* zero x, y, width, height, count */
554 ev.type = Expose;
555 ev.display = dpy;
556 ev.window = DefaultRootWindow(dpy);
557 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
558 while (randrSyncSerial < serial) {
559 TXWindow::handleXEvents(dpy);
560 }
561 } else {
562 vlog.error("XSendEvent failed");
563 }
564
565 /* The protocol requires that an error is returned if the requested
566 layout could not be set. This is checked by
567 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
568 with reason=0 will be sent in response to the changes seen by the
569 event handler. */
Pierre Ossman44a1d712018-09-11 14:22:04 +0200570 if (adjustedLayout != layout)
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100571 return rfb::resultInvalid;
Pierre Ossman44a1d712018-09-11 14:22:04 +0200572
573 // Explicitly update the server state with the result as there
574 // can be corner cases where we don't get feedback from the X server
575 server->setScreenLayout(computeScreenLayout());
576
577 return ret;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100578
579#else
580 return rfb::resultProhibited;
581#endif /* HAVE_XRANDR */
582}
583
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200584
585bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100586 if (ev->type == xkbEventBase + XkbEventCode) {
587 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200588
Pierre Ossman07580a82018-03-07 15:33:42 +0100589 if (kb->any.xkb_type != XkbIndicatorStateNotify)
590 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200591
Pierre Ossman07580a82018-03-07 15:33:42 +0100592 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200593
Pierre Ossman07580a82018-03-07 15:33:42 +0100594 ledState = 0;
595 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
596 if (kb->indicators.state & ledMasks[i])
597 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200598 }
599
Pierre Ossman07580a82018-03-07 15:33:42 +0100600 if (running)
601 server->setLEDState(ledState);
602
603 return true;
604#ifdef HAVE_XDAMAGE
605 } else if (ev->type == xdamageEventBase) {
606 XDamageNotifyEvent* dev;
607 Rect rect;
608
609 if (!running)
610 return true;
611
612 dev = (XDamageNotifyEvent*)ev;
613 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
614 server->add_changed(rect);
615
616 return true;
617#endif
618#ifdef HAVE_XFIXES
619 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
620 XFixesCursorNotifyEvent* cev;
621
622 if (!running)
623 return true;
624
625 cev = (XFixesCursorNotifyEvent*)ev;
626
627 if (cev->subtype != XFixesDisplayCursorNotify)
628 return false;
629
630 return setCursor();
631#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100632#ifdef HAVE_XRANDR
633 } else if (ev->type == Expose) {
634 XExposeEvent* eev = (XExposeEvent*)ev;
635 randrSyncSerial = eev->serial;
636
637 return false;
638
639 } else if (ev->type == ConfigureNotify) {
640 XConfigureEvent* cev = (XConfigureEvent*)ev;
641
642 if (cev->window != DefaultRootWindow(dpy)) {
643 return false;
644 }
645
646 XRRUpdateConfiguration(ev);
647 geometry->recalc(cev->width, cev->height);
648
649 if (!running) {
650 return false;
651 }
652
653 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
654 // Recreate pixel buffer
655 ImageFactory factory((bool)useShm);
656 delete pb;
657 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
658 server->setPixelBuffer(pb, computeScreenLayout());
659
660 // Mark entire screen as changed
661 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
662 }
663
664 return true;
665
666 } else if (ev->type == xrandrEventBase + RRNotify) {
667 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
668
669 if (rev->window != DefaultRootWindow(dpy)) {
670 return false;
671 }
672
673 if (!running)
674 return false;
675
676 if (rev->subtype == RRNotify_CrtcChange) {
677 server->setScreenLayout(computeScreenLayout());
678 }
679
680 return true;
681#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100682 }
683
684 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200685}
686
687bool XDesktop::setCursor()
688{
Pierre Ossman07580a82018-03-07 15:33:42 +0100689 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200690
Pierre Ossman07580a82018-03-07 15:33:42 +0100691 cim = XFixesGetCursorImage(dpy);
692 if (cim == NULL)
693 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200694
Pierre Ossman07580a82018-03-07 15:33:42 +0100695 // Copied from XserverDesktop::setCursor() in
696 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
697 // handle long -> U32 conversion for 64-bit Xlib
698 rdr::U8* cursorData;
699 rdr::U8 *out;
700 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200701
Pierre Ossman07580a82018-03-07 15:33:42 +0100702 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200703
Pierre Ossman07580a82018-03-07 15:33:42 +0100704 // Un-premultiply alpha
705 pixels = cim->pixels;
706 out = cursorData;
707 for (int y = 0; y < cim->height; y++) {
708 for (int x = 0; x < cim->width; x++) {
709 rdr::U8 alpha;
710 rdr::U32 pixel = *pixels++;
711 rdr::U8 *in = (rdr::U8 *) &pixel;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200712
Pierre Ossman07580a82018-03-07 15:33:42 +0100713 alpha = in[3];
714 if (alpha == 0)
715 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200716
Pierre Ossman07580a82018-03-07 15:33:42 +0100717 *out++ = (unsigned)*in++ * 255/alpha;
718 *out++ = (unsigned)*in++ * 255/alpha;
719 *out++ = (unsigned)*in++ * 255/alpha;
720 *out++ = *in++;
721 }
722 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200723
Pierre Ossman07580a82018-03-07 15:33:42 +0100724 try {
725 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
726 cursorData);
727 } catch (rdr::Exception& e) {
728 vlog.error("XserverDesktop::setCursor: %s",e.str());
729 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200730
Pierre Ossman07580a82018-03-07 15:33:42 +0100731 delete [] cursorData;
732 XFree(cim);
733 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200734}