blob: f8b579d7d62d9fbf025a4b0b3a969350e79f6d98 [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);
Pierre Ossman0fe375a2018-11-01 16:05:02 +0100384
385 // Adjust the layout relative to the geometry
386 ScreenSet::iterator iter, iter_next;
387 Point offset(-geometry->offsetLeft(), -geometry->offsetTop());
388 for (iter = layout.begin();iter != layout.end();iter = iter_next) {
389 iter_next = iter; ++iter_next;
390 iter->dimensions = iter->dimensions.translate(offset);
391 if (iter->dimensions.enclosed_by(geometry->getRect()))
392 continue;
393 iter->dimensions = iter->dimensions.intersect(geometry->getRect());
394 if (iter->dimensions.is_empty()) {
395 layout.remove_screen(iter->id);
396 }
397 }
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100398#endif
399
400 return layout;
401}
402
403#ifdef HAVE_XRANDR
404/* Get the biggest mode which is equal or smaller to requested
405 size. If no such mode exists, return the smallest. */
406static void GetSmallerMode(XRRScreenResources *res,
407 XRROutputInfo *output,
408 unsigned int *width, unsigned int *height)
409{
410 XRRModeInfo best = {};
411 XRRModeInfo smallest = {};
412 smallest.width = -1;
413 smallest.height = -1;
414
415 for (int i = 0; i < res->nmode; i++) {
416 for (int j = 0; j < output->nmode; j++) {
417 if (output->modes[j] == res->modes[i].id) {
418 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
419 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
420 best = res->modes[i];
421 }
422 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
423 smallest = res->modes[i];
424 }
425 }
426 }
427 }
428
429 if (best.id == 0 && smallest.id != 0) {
430 best = smallest;
431 }
432
433 *width = best.width;
434 *height = best.height;
435}
436#endif /* HAVE_XRANDR */
437
438unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
439 const rfb::ScreenSet& layout)
440{
441#ifdef HAVE_XRANDR
442 char buffer[2048];
443 vlog.debug("Got request for framebuffer resize to %dx%d",
444 fb_width, fb_height);
445 layout.print(buffer, sizeof(buffer));
446 vlog.debug("%s", buffer);
447
448 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
449 if (!res) {
450 vlog.error("XRRGetScreenResources failed");
451 return rfb::resultProhibited;
452 }
453 vncSetGlueContext(dpy, res);
454
455 /* The client may request a screen layout which is not supported by
456 the Xserver. This happens, for example, when adjusting the size
457 of a non-fullscreen vncviewer window. To handle this and other
458 cases, we first call tryScreenLayout. If this fails, we try to
459 adjust the request to one screen with a smaller mode. */
460 vlog.debug("Testing screen layout");
461 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
462 rfb::ScreenSet adjustedLayout;
463 if (tryresult == rfb::resultSuccess) {
464 adjustedLayout = layout;
465 } else {
466 vlog.debug("Impossible layout - trying to adjust");
467
468 ScreenSet::const_iterator firstscreen = layout.begin();
469 adjustedLayout.add_screen(*firstscreen);
470 ScreenSet::iterator iter = adjustedLayout.begin();
471 RROutput outputId = None;
472
473 for (int i = 0;i < vncRandRGetOutputCount();i++) {
474 unsigned int oi = vncRandRGetOutputId(i);
475
476 /* Known? */
477 if (outputIdMap.count(oi) == 0)
478 continue;
479
480 /* Find the corresponding screen... */
481 if (iter->id == outputIdMap[oi]) {
482 outputId = oi;
483 } else {
484 outputIdMap.erase(oi);
485 }
486 }
487
488 /* New screen */
489 if (outputId == None) {
490 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
491 if (i != -1) {
492 outputId = vncRandRGetOutputId(i);
493 }
494 }
495 if (outputId == None) {
496 vlog.debug("Resize adjust: Could not find corresponding screen");
497 XRRFreeScreenResources(res);
498 return rfb::resultInvalid;
499 }
500 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
501 if (!output) {
502 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
503 XRRFreeScreenResources(res);
504 return rfb::resultInvalid;
505 }
506 if (!output->crtc) {
507 vlog.debug("Resize adjust: Selected output has no CRTC");
508 XRRFreeScreenResources(res);
509 XRRFreeOutputInfo(output);
510 return rfb::resultInvalid;
511 }
512 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
513 if (!crtc) {
514 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
515 XRRFreeScreenResources(res);
516 XRRFreeOutputInfo(output);
517 return rfb::resultInvalid;
518 }
519
520 unsigned int swidth = iter->dimensions.width();
521 unsigned int sheight = iter->dimensions.height();
522
523 switch (crtc->rotation) {
524 case RR_Rotate_90:
525 case RR_Rotate_270:
526 unsigned int swap = swidth;
527 swidth = sheight;
528 sheight = swap;
529 break;
530 }
531
532 GetSmallerMode(res, output, &swidth, &sheight);
533 XRRFreeOutputInfo(output);
534
535 switch (crtc->rotation) {
536 case RR_Rotate_90:
537 case RR_Rotate_270:
538 unsigned int swap = swidth;
539 swidth = sheight;
540 sheight = swap;
541 break;
542 }
543
544 XRRFreeCrtcInfo(crtc);
545
546 if (sheight != 0 && swidth != 0) {
547 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
548 iter->dimensions.setXYWH(0, 0, swidth, sheight);
549 fb_width = swidth;
550 fb_height = sheight;
551 } else {
552 vlog.error("Failed to find smaller or equal screen size");
553 XRRFreeScreenResources(res);
554 return rfb::resultInvalid;
555 }
556 }
557
558 vlog.debug("Changing screen layout");
559 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
560 XRRFreeScreenResources(res);
561
562 /* Send a dummy event to the root window. When this event is seen,
563 earlier change events (ConfigureNotify and/or CrtcChange) have
564 been processed. An Expose event is used for simplicity; does not
565 require any Atoms, and will not affect other applications. */
566 unsigned long serial = XNextRequest(dpy);
567 XExposeEvent ev = {}; /* zero x, y, width, height, count */
568 ev.type = Expose;
569 ev.display = dpy;
570 ev.window = DefaultRootWindow(dpy);
571 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
572 while (randrSyncSerial < serial) {
573 TXWindow::handleXEvents(dpy);
574 }
575 } else {
576 vlog.error("XSendEvent failed");
577 }
578
579 /* The protocol requires that an error is returned if the requested
580 layout could not be set. This is checked by
581 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
582 with reason=0 will be sent in response to the changes seen by the
583 event handler. */
Pierre Ossman44a1d712018-09-11 14:22:04 +0200584 if (adjustedLayout != layout)
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100585 return rfb::resultInvalid;
Pierre Ossman44a1d712018-09-11 14:22:04 +0200586
587 // Explicitly update the server state with the result as there
588 // can be corner cases where we don't get feedback from the X server
589 server->setScreenLayout(computeScreenLayout());
590
591 return ret;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100592
593#else
594 return rfb::resultProhibited;
595#endif /* HAVE_XRANDR */
596}
597
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200598
599bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100600 if (ev->type == xkbEventBase + XkbEventCode) {
601 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200602
Pierre Ossman07580a82018-03-07 15:33:42 +0100603 if (kb->any.xkb_type != XkbIndicatorStateNotify)
604 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200605
Pierre Ossman07580a82018-03-07 15:33:42 +0100606 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200607
Pierre Ossman07580a82018-03-07 15:33:42 +0100608 ledState = 0;
609 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
610 if (kb->indicators.state & ledMasks[i])
611 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200612 }
613
Pierre Ossman07580a82018-03-07 15:33:42 +0100614 if (running)
615 server->setLEDState(ledState);
616
617 return true;
618#ifdef HAVE_XDAMAGE
619 } else if (ev->type == xdamageEventBase) {
620 XDamageNotifyEvent* dev;
621 Rect rect;
622
623 if (!running)
624 return true;
625
626 dev = (XDamageNotifyEvent*)ev;
627 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
628 server->add_changed(rect);
629
630 return true;
631#endif
632#ifdef HAVE_XFIXES
633 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
634 XFixesCursorNotifyEvent* cev;
635
636 if (!running)
637 return true;
638
639 cev = (XFixesCursorNotifyEvent*)ev;
640
641 if (cev->subtype != XFixesDisplayCursorNotify)
642 return false;
643
644 return setCursor();
645#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100646#ifdef HAVE_XRANDR
647 } else if (ev->type == Expose) {
648 XExposeEvent* eev = (XExposeEvent*)ev;
649 randrSyncSerial = eev->serial;
650
651 return false;
652
653 } else if (ev->type == ConfigureNotify) {
654 XConfigureEvent* cev = (XConfigureEvent*)ev;
655
656 if (cev->window != DefaultRootWindow(dpy)) {
657 return false;
658 }
659
660 XRRUpdateConfiguration(ev);
661 geometry->recalc(cev->width, cev->height);
662
663 if (!running) {
664 return false;
665 }
666
667 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
668 // Recreate pixel buffer
669 ImageFactory factory((bool)useShm);
670 delete pb;
671 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
672 server->setPixelBuffer(pb, computeScreenLayout());
673
674 // Mark entire screen as changed
675 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
676 }
677
678 return true;
679
680 } else if (ev->type == xrandrEventBase + RRNotify) {
681 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
682
683 if (rev->window != DefaultRootWindow(dpy)) {
684 return false;
685 }
686
687 if (!running)
688 return false;
689
690 if (rev->subtype == RRNotify_CrtcChange) {
691 server->setScreenLayout(computeScreenLayout());
692 }
693
694 return true;
695#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100696 }
697
698 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200699}
700
701bool XDesktop::setCursor()
702{
Pierre Ossman07580a82018-03-07 15:33:42 +0100703 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200704
Pierre Ossman07580a82018-03-07 15:33:42 +0100705 cim = XFixesGetCursorImage(dpy);
706 if (cim == NULL)
707 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200708
Pierre Ossman07580a82018-03-07 15:33:42 +0100709 // Copied from XserverDesktop::setCursor() in
710 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
711 // handle long -> U32 conversion for 64-bit Xlib
712 rdr::U8* cursorData;
713 rdr::U8 *out;
714 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200715
Pierre Ossman07580a82018-03-07 15:33:42 +0100716 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200717
Pierre Ossman07580a82018-03-07 15:33:42 +0100718 // Un-premultiply alpha
719 pixels = cim->pixels;
720 out = cursorData;
721 for (int y = 0; y < cim->height; y++) {
722 for (int x = 0; x < cim->width; x++) {
723 rdr::U8 alpha;
724 rdr::U32 pixel = *pixels++;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200725
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200726 alpha = (pixel >> 24) & 0xff;
Pierre Ossman07580a82018-03-07 15:33:42 +0100727 if (alpha == 0)
728 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200729
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200730 *out++ = ((pixel >> 16) & 0xff) * 255/alpha;
731 *out++ = ((pixel >> 8) & 0xff) * 255/alpha;
732 *out++ = ((pixel >> 0) & 0xff) * 255/alpha;
733 *out++ = ((pixel >> 24) & 0xff);
Pierre Ossman07580a82018-03-07 15:33:42 +0100734 }
735 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200736
Pierre Ossman07580a82018-03-07 15:33:42 +0100737 try {
738 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
739 cursorData);
740 } catch (rdr::Exception& e) {
741 vlog.error("XserverDesktop::setCursor: %s",e.str());
742 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200743
Pierre Ossman07580a82018-03-07 15:33:42 +0100744 delete [] cursorData;
745 XFree(cim);
746 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200747}