blob: 9a047c8ef4f117a005218f9f975a3712a67afa73 [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
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020021#include <assert.h>
22
Pierre Ossman6c97fa42018-10-05 17:35:51 +020023#include <rfb/LogWriter.h>
24
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020025#include <x0vncserver/XDesktop.h>
26
27#include <X11/XKBlib.h>
28#ifdef HAVE_XTEST
29#include <X11/extensions/XTest.h>
30#endif
31#ifdef HAVE_XDAMAGE
32#include <X11/extensions/Xdamage.h>
33#endif
34#ifdef HAVE_XFIXES
35#include <X11/extensions/Xfixes.h>
36#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +010037#ifdef HAVE_XRANDR
38#include <X11/extensions/Xrandr.h>
39#include <RandrGlue.h>
40extern "C" {
41void vncSetGlueContext(Display *dpy, void *res);
42}
43#endif
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020044#include <x0vncserver/Geometry.h>
45#include <x0vncserver/XPixelBuffer.h>
46
47using namespace rfb;
48
49extern const unsigned short code_map_qnum_to_xorgevdev[];
50extern const unsigned int code_map_qnum_to_xorgevdev_len;
51
52extern const unsigned short code_map_qnum_to_xorgkbd[];
53extern const unsigned int code_map_qnum_to_xorgkbd_len;
54
Pierre Ossmance4722f2017-11-08 16:00:05 +010055BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
56BoolParameter rawKeyboard("RawKeyboard",
57 "Send keyboard events straight through and "
58 "avoid mapping them to the current keyboard "
59 "layout", false);
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020060IntParameter queryConnectTimeout("QueryConnectTimeout",
61 "Number of seconds to show the Accept Connection dialog before "
62 "rejecting the connection",
63 10);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020064
65static rfb::LogWriter vlog("XDesktop");
66
67// order is important as it must match RFB extension
68static const char * ledNames[XDESKTOP_N_LEDS] = {
69 "Scroll Lock", "Num Lock", "Caps Lock"
70};
71
72XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
Pierre Ossman07580a82018-03-07 15:33:42 +010073 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020074 queryConnectDialog(0), queryConnectSock(0),
Pierre Ossman07580a82018-03-07 15:33:42 +010075 oldButtonMask(0), haveXtest(false), haveDamage(false),
76 maxButtons(0), running(false), ledMasks(), ledState(0),
77 codeMap(0), codeMapLen(0)
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020078{
Pierre Ossman07580a82018-03-07 15:33:42 +010079 int major, minor;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020080
Pierre Ossman07580a82018-03-07 15:33:42 +010081 int xkbOpcode, xkbErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020082
Pierre Ossman07580a82018-03-07 15:33:42 +010083 major = XkbMajorVersion;
84 minor = XkbMinorVersion;
85 if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
86 &xkbErrorBase, &major, &minor)) {
87 vlog.error("XKEYBOARD extension not present");
88 throw Exception();
89 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020090
Pierre Ossman07580a82018-03-07 15:33:42 +010091 XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
92 XkbIndicatorStateNotifyMask);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020093
Pierre Ossman07580a82018-03-07 15:33:42 +010094 // figure out bit masks for the indicators we are interested in
95 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
96 Atom a;
97 int shift;
98 Bool on;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020099
Pierre Ossman07580a82018-03-07 15:33:42 +0100100 a = XInternAtom(dpy, ledNames[i], True);
101 if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
102 continue;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200103
Pierre Ossman07580a82018-03-07 15:33:42 +0100104 ledMasks[i] = 1u << shift;
105 vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
106 if (on)
107 ledState |= 1u << i;
108 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200109
Pierre Ossman07580a82018-03-07 15:33:42 +0100110 // X11 unfortunately uses keyboard driver specific keycodes and provides no
111 // direct way to query this, so guess based on the keyboard mapping
112 XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
113 if (desc && desc->names) {
114 char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200115
Pierre Ossman07580a82018-03-07 15:33:42 +0100116 if (keycodes) {
117 if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
118 codeMap = code_map_qnum_to_xorgevdev;
119 codeMapLen = code_map_qnum_to_xorgevdev_len;
120 vlog.info("Using evdev codemap\n");
121 } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
122 codeMap = code_map_qnum_to_xorgkbd;
123 codeMapLen = code_map_qnum_to_xorgkbd_len;
124 vlog.info("Using xorgkbd codemap\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200125 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100126 vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200127 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100128 XFree(keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200129 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100130 vlog.debug("Unable to get keycode map\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200131 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100132
133 XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
134 }
135
136#ifdef HAVE_XTEST
137 int xtestEventBase;
138 int xtestErrorBase;
139
140 if (XTestQueryExtension(dpy, &xtestEventBase,
141 &xtestErrorBase, &major, &minor)) {
142 XTestGrabControl(dpy, True);
143 vlog.info("XTest extension present - version %d.%d",major,minor);
144 haveXtest = true;
145 } else {
146#endif
147 vlog.info("XTest extension not present");
148 vlog.info("Unable to inject events or display while server is grabbed");
149#ifdef HAVE_XTEST
150 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200151#endif
152
153#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100154 int xdamageErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200155
Pierre Ossman07580a82018-03-07 15:33:42 +0100156 if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
157 haveDamage = true;
158 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200159#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100160 vlog.info("DAMAGE extension not present");
161 vlog.info("Will have to poll screen for changes");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200162#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100163 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200164#endif
165
166#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100167 int xfixesErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200168
Pierre Ossman07580a82018-03-07 15:33:42 +0100169 if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
170 XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
171 XFixesDisplayCursorNotifyMask);
172 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200173#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100174 vlog.info("XFIXES extension not present");
175 vlog.info("Will not be able to display cursors");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200176#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100177 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200178#endif
179
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100180#ifdef HAVE_XRANDR
181 int xrandrErrorBase;
182
183 randrSyncSerial = 0;
184 if (XRRQueryExtension(dpy, &xrandrEventBase, &xrandrErrorBase)) {
185 XRRSelectInput(dpy, DefaultRootWindow(dpy),
186 RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
187 /* Override TXWindow::init input mask */
188 XSelectInput(dpy, DefaultRootWindow(dpy),
189 PropertyChangeMask | StructureNotifyMask | ExposureMask);
190 } else {
191#endif
192 vlog.info("RANDR extension not present");
193 vlog.info("Will not be able to handle session resize");
194#ifdef HAVE_XRANDR
195 }
196#endif
197
Pierre Ossman07580a82018-03-07 15:33:42 +0100198 TXWindow::setGlobalEventHandler(this);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200199}
200
201XDesktop::~XDesktop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100202 if (running)
203 stop();
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200204}
205
206
207void XDesktop::poll() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100208 if (pb and not haveDamage)
209 pb->poll(server);
210 if (running) {
211 Window root, child;
212 int x, y, wx, wy;
213 unsigned int mask;
214 XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
215 &x, &y, &wx, &wy, &mask);
Pierre Ossmanc86ce3e2018-09-10 17:03:17 +0200216 x -= geometry->offsetLeft();
217 y -= geometry->offsetTop();
Pierre Ossman07580a82018-03-07 15:33:42 +0100218 server->setCursorPos(rfb::Point(x, y));
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200219 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100220}
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200221
222
223void XDesktop::start(VNCServer* vs) {
224
Pierre Ossman07580a82018-03-07 15:33:42 +0100225 // Determine actual number of buttons of the X pointer device.
226 unsigned char btnMap[8];
227 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
228 maxButtons = (numButtons > 8) ? 8 : numButtons;
229 vlog.info("Enabling %d button%s of X pointer device",
230 maxButtons, (maxButtons != 1) ? "s" : "");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200231
Pierre Ossman07580a82018-03-07 15:33:42 +0100232 // Create an ImageFactory instance for producing Image objects.
233 ImageFactory factory((bool)useShm);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200234
Pierre Ossman07580a82018-03-07 15:33:42 +0100235 // Create pixel buffer and provide it to the server object.
236 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
237 vlog.info("Allocated %s", pb->getImage()->classDesc());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200238
Pierre Ossmancd7931d2018-10-05 17:48:58 +0200239 server = vs;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100240 server->setPixelBuffer(pb, computeScreenLayout());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200241
242#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100243 if (haveDamage) {
244 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
245 XDamageReportRawRectangles);
246 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200247#endif
248
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200249#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100250 setCursor();
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200251#endif
252
Pierre Ossman07580a82018-03-07 15:33:42 +0100253 server->setLEDState(ledState);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200254
Pierre Ossman07580a82018-03-07 15:33:42 +0100255 running = true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200256}
257
258void XDesktop::stop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100259 running = false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200260
261#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100262 if (haveDamage)
263 XDamageDestroy(dpy, damage);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200264#endif
265
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200266 delete queryConnectDialog;
267 queryConnectDialog = 0;
268
Pierre Ossman07580a82018-03-07 15:33:42 +0100269 server->setPixelBuffer(0);
270 server = 0;
Michal Srb18a77072017-09-29 14:47:56 +0200271
Pierre Ossman07580a82018-03-07 15:33:42 +0100272 delete pb;
273 pb = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200274}
275
276bool XDesktop::isRunning() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100277 return running;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200278}
279
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200280void XDesktop::queryConnection(network::Socket* sock,
281 const char* userName)
282{
283 assert(isRunning());
284
285 if (queryConnectSock) {
286 server->approveConnection(sock, false, "Another connection is currently being queried.");
287 return;
288 }
289
290 if (!userName)
291 userName = "(anonymous)";
292
293 queryConnectSock = sock;
294
295 CharArray address(sock->getPeerAddress());
296 delete queryConnectDialog;
297 queryConnectDialog = new QueryConnectDialog(dpy, address.buf,
298 userName,
299 queryConnectTimeout,
300 this);
301 queryConnectDialog->map();
302}
303
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200304void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
305#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100306 if (!haveXtest) return;
307 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
308 geometry->offsetLeft() + pos.x,
309 geometry->offsetTop() + pos.y,
310 CurrentTime);
311 if (buttonMask != oldButtonMask) {
312 for (int i = 0; i < maxButtons; i++) {
313 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
314 if (buttonMask & (1<<i)) {
315 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
316 } else {
317 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200318 }
319 }
320 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100321 }
322 oldButtonMask = buttonMask;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200323#endif
324}
325
326#ifdef HAVE_XTEST
327KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100328 XkbDescPtr xkb;
329 XkbStateRec state;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200330 unsigned int mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100331 unsigned keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200332
Pierre Ossman07580a82018-03-07 15:33:42 +0100333 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
334 if (!xkb)
335 return 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200336
Pierre Ossman07580a82018-03-07 15:33:42 +0100337 XkbGetState(dpy, XkbUseCoreKbd, &state);
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200338 // XkbStateFieldFromRec() doesn't work properly because
339 // state.lookup_mods isn't properly updated, so we do this manually
340 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200341
Pierre Ossman07580a82018-03-07 15:33:42 +0100342 for (keycode = xkb->min_key_code;
343 keycode <= xkb->max_key_code;
344 keycode++) {
345 KeySym cursym;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200346 unsigned int out_mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100347 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
348 if (cursym == keysym)
349 break;
350 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200351
Pierre Ossman07580a82018-03-07 15:33:42 +0100352 if (keycode > xkb->max_key_code)
353 keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200354
Pierre Ossman07580a82018-03-07 15:33:42 +0100355 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200356
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200357 // Shift+Tab is usually ISO_Left_Tab, but RFB hides this fact. Do
358 // another attempt if we failed the initial lookup
359 if ((keycode == 0) && (keysym == XK_Tab) && (mods & ShiftMask))
360 return XkbKeysymToKeycode(dpy, XK_ISO_Left_Tab);
361
Pierre Ossman07580a82018-03-07 15:33:42 +0100362 return keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200363}
364#endif
365
366void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
367#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100368 int keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200369
Pierre Ossman07580a82018-03-07 15:33:42 +0100370 if (!haveXtest)
371 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200372
Pierre Ossman07580a82018-03-07 15:33:42 +0100373 // Use scan code if provided and mapping exists
374 if (codeMap && rawKeyboard && xtcode < codeMapLen)
375 keycode = codeMap[xtcode];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200376
Pierre Ossman07580a82018-03-07 15:33:42 +0100377 if (!keycode) {
378 if (pressedKeys.find(keysym) != pressedKeys.end())
379 keycode = pressedKeys[keysym];
380 else {
381 // XKeysymToKeycode() doesn't respect state, so we have to use
382 // something slightly more complex
383 keycode = XkbKeysymToKeycode(dpy, keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200384 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100385 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200386
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200387 if (!keycode) {
388 vlog.error("Could not map key event to X11 key code");
Pierre Ossman07580a82018-03-07 15:33:42 +0100389 return;
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200390 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200391
Pierre Ossman07580a82018-03-07 15:33:42 +0100392 if (down)
393 pressedKeys[keysym] = keycode;
394 else
395 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200396
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200397 vlog.debug("%d %s", keycode, down ? "down" : "up");
398
Pierre Ossman07580a82018-03-07 15:33:42 +0100399 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200400#endif
401}
402
403void XDesktop::clientCutText(const char* str, int len) {
404}
405
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100406ScreenSet XDesktop::computeScreenLayout()
407{
408 ScreenSet layout;
409
410#ifdef HAVE_XRANDR
411 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
412 if (!res) {
413 vlog.error("XRRGetScreenResources failed");
414 return layout;
415 }
416 vncSetGlueContext(dpy, res);
417
418 layout = ::computeScreenLayout(&outputIdMap);
419 XRRFreeScreenResources(res);
420#endif
421
422 return layout;
423}
424
425#ifdef HAVE_XRANDR
426/* Get the biggest mode which is equal or smaller to requested
427 size. If no such mode exists, return the smallest. */
428static void GetSmallerMode(XRRScreenResources *res,
429 XRROutputInfo *output,
430 unsigned int *width, unsigned int *height)
431{
432 XRRModeInfo best = {};
433 XRRModeInfo smallest = {};
434 smallest.width = -1;
435 smallest.height = -1;
436
437 for (int i = 0; i < res->nmode; i++) {
438 for (int j = 0; j < output->nmode; j++) {
439 if (output->modes[j] == res->modes[i].id) {
440 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
441 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
442 best = res->modes[i];
443 }
444 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
445 smallest = res->modes[i];
446 }
447 }
448 }
449 }
450
451 if (best.id == 0 && smallest.id != 0) {
452 best = smallest;
453 }
454
455 *width = best.width;
456 *height = best.height;
457}
458#endif /* HAVE_XRANDR */
459
460unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
461 const rfb::ScreenSet& layout)
462{
463#ifdef HAVE_XRANDR
464 char buffer[2048];
465 vlog.debug("Got request for framebuffer resize to %dx%d",
466 fb_width, fb_height);
467 layout.print(buffer, sizeof(buffer));
468 vlog.debug("%s", buffer);
469
470 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
471 if (!res) {
472 vlog.error("XRRGetScreenResources failed");
473 return rfb::resultProhibited;
474 }
475 vncSetGlueContext(dpy, res);
476
477 /* The client may request a screen layout which is not supported by
478 the Xserver. This happens, for example, when adjusting the size
479 of a non-fullscreen vncviewer window. To handle this and other
480 cases, we first call tryScreenLayout. If this fails, we try to
481 adjust the request to one screen with a smaller mode. */
482 vlog.debug("Testing screen layout");
483 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
484 rfb::ScreenSet adjustedLayout;
485 if (tryresult == rfb::resultSuccess) {
486 adjustedLayout = layout;
487 } else {
488 vlog.debug("Impossible layout - trying to adjust");
489
490 ScreenSet::const_iterator firstscreen = layout.begin();
491 adjustedLayout.add_screen(*firstscreen);
492 ScreenSet::iterator iter = adjustedLayout.begin();
493 RROutput outputId = None;
494
495 for (int i = 0;i < vncRandRGetOutputCount();i++) {
496 unsigned int oi = vncRandRGetOutputId(i);
497
498 /* Known? */
499 if (outputIdMap.count(oi) == 0)
500 continue;
501
502 /* Find the corresponding screen... */
503 if (iter->id == outputIdMap[oi]) {
504 outputId = oi;
505 } else {
506 outputIdMap.erase(oi);
507 }
508 }
509
510 /* New screen */
511 if (outputId == None) {
512 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
513 if (i != -1) {
514 outputId = vncRandRGetOutputId(i);
515 }
516 }
517 if (outputId == None) {
518 vlog.debug("Resize adjust: Could not find corresponding screen");
519 XRRFreeScreenResources(res);
520 return rfb::resultInvalid;
521 }
522 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
523 if (!output) {
524 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
525 XRRFreeScreenResources(res);
526 return rfb::resultInvalid;
527 }
528 if (!output->crtc) {
529 vlog.debug("Resize adjust: Selected output has no CRTC");
530 XRRFreeScreenResources(res);
531 XRRFreeOutputInfo(output);
532 return rfb::resultInvalid;
533 }
534 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
535 if (!crtc) {
536 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
537 XRRFreeScreenResources(res);
538 XRRFreeOutputInfo(output);
539 return rfb::resultInvalid;
540 }
541
542 unsigned int swidth = iter->dimensions.width();
543 unsigned int sheight = iter->dimensions.height();
544
545 switch (crtc->rotation) {
546 case RR_Rotate_90:
547 case RR_Rotate_270:
548 unsigned int swap = swidth;
549 swidth = sheight;
550 sheight = swap;
551 break;
552 }
553
554 GetSmallerMode(res, output, &swidth, &sheight);
555 XRRFreeOutputInfo(output);
556
557 switch (crtc->rotation) {
558 case RR_Rotate_90:
559 case RR_Rotate_270:
560 unsigned int swap = swidth;
561 swidth = sheight;
562 sheight = swap;
563 break;
564 }
565
566 XRRFreeCrtcInfo(crtc);
567
568 if (sheight != 0 && swidth != 0) {
569 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
570 iter->dimensions.setXYWH(0, 0, swidth, sheight);
571 fb_width = swidth;
572 fb_height = sheight;
573 } else {
574 vlog.error("Failed to find smaller or equal screen size");
575 XRRFreeScreenResources(res);
576 return rfb::resultInvalid;
577 }
578 }
579
580 vlog.debug("Changing screen layout");
581 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
582 XRRFreeScreenResources(res);
583
584 /* Send a dummy event to the root window. When this event is seen,
585 earlier change events (ConfigureNotify and/or CrtcChange) have
586 been processed. An Expose event is used for simplicity; does not
587 require any Atoms, and will not affect other applications. */
588 unsigned long serial = XNextRequest(dpy);
589 XExposeEvent ev = {}; /* zero x, y, width, height, count */
590 ev.type = Expose;
591 ev.display = dpy;
592 ev.window = DefaultRootWindow(dpy);
593 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
594 while (randrSyncSerial < serial) {
595 TXWindow::handleXEvents(dpy);
596 }
597 } else {
598 vlog.error("XSendEvent failed");
599 }
600
601 /* The protocol requires that an error is returned if the requested
602 layout could not be set. This is checked by
603 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
604 with reason=0 will be sent in response to the changes seen by the
605 event handler. */
Pierre Ossman44a1d712018-09-11 14:22:04 +0200606 if (adjustedLayout != layout)
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100607 return rfb::resultInvalid;
Pierre Ossman44a1d712018-09-11 14:22:04 +0200608
609 // Explicitly update the server state with the result as there
610 // can be corner cases where we don't get feedback from the X server
611 server->setScreenLayout(computeScreenLayout());
612
613 return ret;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100614
615#else
616 return rfb::resultProhibited;
617#endif /* HAVE_XRANDR */
618}
619
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200620
621bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100622 if (ev->type == xkbEventBase + XkbEventCode) {
623 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200624
Pierre Ossman07580a82018-03-07 15:33:42 +0100625 if (kb->any.xkb_type != XkbIndicatorStateNotify)
626 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200627
Pierre Ossman07580a82018-03-07 15:33:42 +0100628 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200629
Pierre Ossman07580a82018-03-07 15:33:42 +0100630 ledState = 0;
631 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
632 if (kb->indicators.state & ledMasks[i])
633 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200634 }
635
Pierre Ossman07580a82018-03-07 15:33:42 +0100636 if (running)
637 server->setLEDState(ledState);
638
639 return true;
640#ifdef HAVE_XDAMAGE
641 } else if (ev->type == xdamageEventBase) {
642 XDamageNotifyEvent* dev;
643 Rect rect;
644
645 if (!running)
646 return true;
647
648 dev = (XDamageNotifyEvent*)ev;
649 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
650 server->add_changed(rect);
651
652 return true;
653#endif
654#ifdef HAVE_XFIXES
655 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
656 XFixesCursorNotifyEvent* cev;
657
658 if (!running)
659 return true;
660
661 cev = (XFixesCursorNotifyEvent*)ev;
662
663 if (cev->subtype != XFixesDisplayCursorNotify)
664 return false;
665
666 return setCursor();
667#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100668#ifdef HAVE_XRANDR
669 } else if (ev->type == Expose) {
670 XExposeEvent* eev = (XExposeEvent*)ev;
671 randrSyncSerial = eev->serial;
672
673 return false;
674
675 } else if (ev->type == ConfigureNotify) {
676 XConfigureEvent* cev = (XConfigureEvent*)ev;
677
678 if (cev->window != DefaultRootWindow(dpy)) {
679 return false;
680 }
681
682 XRRUpdateConfiguration(ev);
683 geometry->recalc(cev->width, cev->height);
684
685 if (!running) {
686 return false;
687 }
688
689 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
690 // Recreate pixel buffer
691 ImageFactory factory((bool)useShm);
692 delete pb;
693 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
694 server->setPixelBuffer(pb, computeScreenLayout());
695
696 // Mark entire screen as changed
697 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
698 }
699
700 return true;
701
702 } else if (ev->type == xrandrEventBase + RRNotify) {
703 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
704
705 if (rev->window != DefaultRootWindow(dpy)) {
706 return false;
707 }
708
709 if (!running)
710 return false;
711
712 if (rev->subtype == RRNotify_CrtcChange) {
713 server->setScreenLayout(computeScreenLayout());
714 }
715
716 return true;
717#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100718 }
719
720 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200721}
722
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200723void XDesktop::queryApproved()
724{
725 assert(isRunning());
726 server->approveConnection(queryConnectSock, true, 0);
727 queryConnectSock = 0;
728}
729
730void XDesktop::queryRejected()
731{
732 assert(isRunning());
733 server->approveConnection(queryConnectSock, false,
734 "Connection rejected by local user");
735 queryConnectSock = 0;
736}
737
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200738bool XDesktop::setCursor()
739{
Pierre Ossman07580a82018-03-07 15:33:42 +0100740 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200741
Pierre Ossman07580a82018-03-07 15:33:42 +0100742 cim = XFixesGetCursorImage(dpy);
743 if (cim == NULL)
744 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200745
Pierre Ossman07580a82018-03-07 15:33:42 +0100746 // Copied from XserverDesktop::setCursor() in
747 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
748 // handle long -> U32 conversion for 64-bit Xlib
749 rdr::U8* cursorData;
750 rdr::U8 *out;
751 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200752
Pierre Ossman07580a82018-03-07 15:33:42 +0100753 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200754
Pierre Ossman07580a82018-03-07 15:33:42 +0100755 // Un-premultiply alpha
756 pixels = cim->pixels;
757 out = cursorData;
758 for (int y = 0; y < cim->height; y++) {
759 for (int x = 0; x < cim->width; x++) {
760 rdr::U8 alpha;
761 rdr::U32 pixel = *pixels++;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200762
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200763 alpha = (pixel >> 24) & 0xff;
Pierre Ossman07580a82018-03-07 15:33:42 +0100764 if (alpha == 0)
765 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200766
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200767 *out++ = ((pixel >> 16) & 0xff) * 255/alpha;
768 *out++ = ((pixel >> 8) & 0xff) * 255/alpha;
769 *out++ = ((pixel >> 0) & 0xff) * 255/alpha;
770 *out++ = ((pixel >> 24) & 0xff);
Pierre Ossman07580a82018-03-07 15:33:42 +0100771 }
772 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200773
Pierre Ossman07580a82018-03-07 15:33:42 +0100774 try {
775 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
776 cursorData);
777 } catch (rdr::Exception& e) {
778 vlog.error("XserverDesktop::setCursor: %s",e.str());
779 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200780
Pierre Ossman07580a82018-03-07 15:33:42 +0100781 delete [] cursorData;
782 XFree(cim);
783 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200784}