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