blob: c7f8ef8d1d8786fd5115a3e8cfc6f79837bee4ba [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
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020023#include <x0vncserver/XDesktop.h>
24
25#include <X11/XKBlib.h>
26#ifdef HAVE_XTEST
27#include <X11/extensions/XTest.h>
28#endif
29#ifdef HAVE_XDAMAGE
30#include <X11/extensions/Xdamage.h>
31#endif
32#ifdef HAVE_XFIXES
33#include <X11/extensions/Xfixes.h>
34#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +010035#ifdef HAVE_XRANDR
36#include <X11/extensions/Xrandr.h>
37#include <RandrGlue.h>
38extern "C" {
39void vncSetGlueContext(Display *dpy, void *res);
40}
41#endif
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020042#include <x0vncserver/Geometry.h>
43#include <x0vncserver/XPixelBuffer.h>
44
45using namespace rfb;
46
47extern const unsigned short code_map_qnum_to_xorgevdev[];
48extern const unsigned int code_map_qnum_to_xorgevdev_len;
49
50extern const unsigned short code_map_qnum_to_xorgkbd[];
51extern const unsigned int code_map_qnum_to_xorgkbd_len;
52
Pierre Ossmance4722f2017-11-08 16:00:05 +010053BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
54BoolParameter rawKeyboard("RawKeyboard",
55 "Send keyboard events straight through and "
56 "avoid mapping them to the current keyboard "
57 "layout", false);
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020058IntParameter queryConnectTimeout("QueryConnectTimeout",
59 "Number of seconds to show the Accept Connection dialog before "
60 "rejecting the connection",
61 10);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020062
63static rfb::LogWriter vlog("XDesktop");
64
65// order is important as it must match RFB extension
66static const char * ledNames[XDESKTOP_N_LEDS] = {
67 "Scroll Lock", "Num Lock", "Caps Lock"
68};
69
70XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
Pierre Ossman07580a82018-03-07 15:33:42 +010071 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020072 queryConnectDialog(0), queryConnectSock(0),
Pierre Ossman07580a82018-03-07 15:33:42 +010073 oldButtonMask(0), haveXtest(false), haveDamage(false),
74 maxButtons(0), running(false), ledMasks(), ledState(0),
75 codeMap(0), codeMapLen(0)
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020076{
Pierre Ossman07580a82018-03-07 15:33:42 +010077 int major, minor;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020078
Pierre Ossman07580a82018-03-07 15:33:42 +010079 int xkbOpcode, xkbErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020080
Pierre Ossman07580a82018-03-07 15:33:42 +010081 major = XkbMajorVersion;
82 minor = XkbMinorVersion;
83 if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
84 &xkbErrorBase, &major, &minor)) {
85 vlog.error("XKEYBOARD extension not present");
86 throw Exception();
87 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020088
Pierre Ossman07580a82018-03-07 15:33:42 +010089 XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
90 XkbIndicatorStateNotifyMask);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020091
Pierre Ossman07580a82018-03-07 15:33:42 +010092 // figure out bit masks for the indicators we are interested in
93 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
94 Atom a;
95 int shift;
96 Bool on;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020097
Pierre Ossman07580a82018-03-07 15:33:42 +010098 a = XInternAtom(dpy, ledNames[i], True);
99 if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
100 continue;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200101
Pierre Ossman07580a82018-03-07 15:33:42 +0100102 ledMasks[i] = 1u << shift;
103 vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
104 if (on)
105 ledState |= 1u << i;
106 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200107
Pierre Ossman07580a82018-03-07 15:33:42 +0100108 // X11 unfortunately uses keyboard driver specific keycodes and provides no
109 // direct way to query this, so guess based on the keyboard mapping
110 XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
111 if (desc && desc->names) {
112 char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200113
Pierre Ossman07580a82018-03-07 15:33:42 +0100114 if (keycodes) {
115 if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
116 codeMap = code_map_qnum_to_xorgevdev;
117 codeMapLen = code_map_qnum_to_xorgevdev_len;
118 vlog.info("Using evdev codemap\n");
119 } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
120 codeMap = code_map_qnum_to_xorgkbd;
121 codeMapLen = code_map_qnum_to_xorgkbd_len;
122 vlog.info("Using xorgkbd codemap\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200123 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100124 vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200125 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100126 XFree(keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200127 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100128 vlog.debug("Unable to get keycode map\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200129 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100130
131 XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
132 }
133
134#ifdef HAVE_XTEST
135 int xtestEventBase;
136 int xtestErrorBase;
137
138 if (XTestQueryExtension(dpy, &xtestEventBase,
139 &xtestErrorBase, &major, &minor)) {
140 XTestGrabControl(dpy, True);
141 vlog.info("XTest extension present - version %d.%d",major,minor);
142 haveXtest = true;
143 } else {
144#endif
145 vlog.info("XTest extension not present");
146 vlog.info("Unable to inject events or display while server is grabbed");
147#ifdef HAVE_XTEST
148 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200149#endif
150
151#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100152 int xdamageErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200153
Pierre Ossman07580a82018-03-07 15:33:42 +0100154 if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
155 haveDamage = true;
156 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200157#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100158 vlog.info("DAMAGE extension not present");
159 vlog.info("Will have to poll screen for changes");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200160#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100161 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200162#endif
163
164#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100165 int xfixesErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200166
Pierre Ossman07580a82018-03-07 15:33:42 +0100167 if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
168 XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
169 XFixesDisplayCursorNotifyMask);
170 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200171#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100172 vlog.info("XFIXES extension not present");
173 vlog.info("Will not be able to display cursors");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200174#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100175 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200176#endif
177
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100178#ifdef HAVE_XRANDR
179 int xrandrErrorBase;
180
181 randrSyncSerial = 0;
182 if (XRRQueryExtension(dpy, &xrandrEventBase, &xrandrErrorBase)) {
183 XRRSelectInput(dpy, DefaultRootWindow(dpy),
184 RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
185 /* Override TXWindow::init input mask */
186 XSelectInput(dpy, DefaultRootWindow(dpy),
187 PropertyChangeMask | StructureNotifyMask | ExposureMask);
188 } else {
189#endif
190 vlog.info("RANDR extension not present");
191 vlog.info("Will not be able to handle session resize");
192#ifdef HAVE_XRANDR
193 }
194#endif
195
Pierre Ossman07580a82018-03-07 15:33:42 +0100196 TXWindow::setGlobalEventHandler(this);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200197}
198
199XDesktop::~XDesktop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100200 if (running)
201 stop();
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200202}
203
204
205void XDesktop::poll() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100206 if (pb and not haveDamage)
207 pb->poll(server);
208 if (running) {
209 Window root, child;
210 int x, y, wx, wy;
211 unsigned int mask;
212 XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
213 &x, &y, &wx, &wy, &mask);
Pierre Ossmanc86ce3e2018-09-10 17:03:17 +0200214 x -= geometry->offsetLeft();
215 y -= geometry->offsetTop();
Pierre Ossman07580a82018-03-07 15:33:42 +0100216 server->setCursorPos(rfb::Point(x, y));
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200217 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100218}
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200219
220
221void XDesktop::start(VNCServer* vs) {
222
Pierre Ossman07580a82018-03-07 15:33:42 +0100223 // Determine actual number of buttons of the X pointer device.
224 unsigned char btnMap[8];
225 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
226 maxButtons = (numButtons > 8) ? 8 : numButtons;
227 vlog.info("Enabling %d button%s of X pointer device",
228 maxButtons, (maxButtons != 1) ? "s" : "");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200229
Pierre Ossman07580a82018-03-07 15:33:42 +0100230 // Create an ImageFactory instance for producing Image objects.
231 ImageFactory factory((bool)useShm);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200232
Pierre Ossman07580a82018-03-07 15:33:42 +0100233 // Create pixel buffer and provide it to the server object.
234 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
235 vlog.info("Allocated %s", pb->getImage()->classDesc());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200236
Pierre Ossman07580a82018-03-07 15:33:42 +0100237 server = (VNCServerST *)vs;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100238 server->setPixelBuffer(pb, computeScreenLayout());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200239
240#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100241 if (haveDamage) {
242 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
243 XDamageReportRawRectangles);
244 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200245#endif
246
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200247#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100248 setCursor();
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200249#endif
250
Pierre Ossman07580a82018-03-07 15:33:42 +0100251 server->setLEDState(ledState);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200252
Pierre Ossman07580a82018-03-07 15:33:42 +0100253 running = true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200254}
255
256void XDesktop::stop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100257 running = false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200258
259#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100260 if (haveDamage)
261 XDamageDestroy(dpy, damage);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200262#endif
263
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200264 delete queryConnectDialog;
265 queryConnectDialog = 0;
266
Pierre Ossman07580a82018-03-07 15:33:42 +0100267 server->setPixelBuffer(0);
268 server = 0;
Michal Srb18a77072017-09-29 14:47:56 +0200269
Pierre Ossman07580a82018-03-07 15:33:42 +0100270 delete pb;
271 pb = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200272}
273
274bool XDesktop::isRunning() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100275 return running;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200276}
277
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200278void XDesktop::queryConnection(network::Socket* sock,
279 const char* userName)
280{
281 assert(isRunning());
282
283 if (queryConnectSock) {
284 server->approveConnection(sock, false, "Another connection is currently being queried.");
285 return;
286 }
287
288 if (!userName)
289 userName = "(anonymous)";
290
291 queryConnectSock = sock;
292
293 CharArray address(sock->getPeerAddress());
294 delete queryConnectDialog;
295 queryConnectDialog = new QueryConnectDialog(dpy, address.buf,
296 userName,
297 queryConnectTimeout,
298 this);
299 queryConnectDialog->map();
300}
301
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200302void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
303#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100304 if (!haveXtest) return;
305 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
306 geometry->offsetLeft() + pos.x,
307 geometry->offsetTop() + pos.y,
308 CurrentTime);
309 if (buttonMask != oldButtonMask) {
310 for (int i = 0; i < maxButtons; i++) {
311 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
312 if (buttonMask & (1<<i)) {
313 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
314 } else {
315 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200316 }
317 }
318 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100319 }
320 oldButtonMask = buttonMask;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200321#endif
322}
323
324#ifdef HAVE_XTEST
325KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100326 XkbDescPtr xkb;
327 XkbStateRec state;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200328 unsigned int mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100329 unsigned keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200330
Pierre Ossman07580a82018-03-07 15:33:42 +0100331 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
332 if (!xkb)
333 return 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200334
Pierre Ossman07580a82018-03-07 15:33:42 +0100335 XkbGetState(dpy, XkbUseCoreKbd, &state);
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200336 // XkbStateFieldFromRec() doesn't work properly because
337 // state.lookup_mods isn't properly updated, so we do this manually
338 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200339
Pierre Ossman07580a82018-03-07 15:33:42 +0100340 for (keycode = xkb->min_key_code;
341 keycode <= xkb->max_key_code;
342 keycode++) {
343 KeySym cursym;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200344 unsigned int out_mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100345 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
346 if (cursym == keysym)
347 break;
348 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200349
Pierre Ossman07580a82018-03-07 15:33:42 +0100350 if (keycode > xkb->max_key_code)
351 keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200352
Pierre Ossman07580a82018-03-07 15:33:42 +0100353 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200354
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200355 // Shift+Tab is usually ISO_Left_Tab, but RFB hides this fact. Do
356 // another attempt if we failed the initial lookup
357 if ((keycode == 0) && (keysym == XK_Tab) && (mods & ShiftMask))
358 return XkbKeysymToKeycode(dpy, XK_ISO_Left_Tab);
359
Pierre Ossman07580a82018-03-07 15:33:42 +0100360 return keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200361}
362#endif
363
364void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
365#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100366 int keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200367
Pierre Ossman07580a82018-03-07 15:33:42 +0100368 if (!haveXtest)
369 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200370
Pierre Ossman07580a82018-03-07 15:33:42 +0100371 // Use scan code if provided and mapping exists
372 if (codeMap && rawKeyboard && xtcode < codeMapLen)
373 keycode = codeMap[xtcode];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200374
Pierre Ossman07580a82018-03-07 15:33:42 +0100375 if (!keycode) {
376 if (pressedKeys.find(keysym) != pressedKeys.end())
377 keycode = pressedKeys[keysym];
378 else {
379 // XKeysymToKeycode() doesn't respect state, so we have to use
380 // something slightly more complex
381 keycode = XkbKeysymToKeycode(dpy, keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200382 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100383 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200384
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200385 if (!keycode) {
386 vlog.error("Could not map key event to X11 key code");
Pierre Ossman07580a82018-03-07 15:33:42 +0100387 return;
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200388 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200389
Pierre Ossman07580a82018-03-07 15:33:42 +0100390 if (down)
391 pressedKeys[keysym] = keycode;
392 else
393 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200394
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200395 vlog.debug("%d %s", keycode, down ? "down" : "up");
396
Pierre Ossman07580a82018-03-07 15:33:42 +0100397 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200398#endif
399}
400
401void XDesktop::clientCutText(const char* str, int len) {
402}
403
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100404ScreenSet XDesktop::computeScreenLayout()
405{
406 ScreenSet layout;
407
408#ifdef HAVE_XRANDR
409 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
410 if (!res) {
411 vlog.error("XRRGetScreenResources failed");
412 return layout;
413 }
414 vncSetGlueContext(dpy, res);
415
416 layout = ::computeScreenLayout(&outputIdMap);
417 XRRFreeScreenResources(res);
418#endif
419
420 return layout;
421}
422
423#ifdef HAVE_XRANDR
424/* Get the biggest mode which is equal or smaller to requested
425 size. If no such mode exists, return the smallest. */
426static void GetSmallerMode(XRRScreenResources *res,
427 XRROutputInfo *output,
428 unsigned int *width, unsigned int *height)
429{
430 XRRModeInfo best = {};
431 XRRModeInfo smallest = {};
432 smallest.width = -1;
433 smallest.height = -1;
434
435 for (int i = 0; i < res->nmode; i++) {
436 for (int j = 0; j < output->nmode; j++) {
437 if (output->modes[j] == res->modes[i].id) {
438 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
439 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
440 best = res->modes[i];
441 }
442 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
443 smallest = res->modes[i];
444 }
445 }
446 }
447 }
448
449 if (best.id == 0 && smallest.id != 0) {
450 best = smallest;
451 }
452
453 *width = best.width;
454 *height = best.height;
455}
456#endif /* HAVE_XRANDR */
457
458unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
459 const rfb::ScreenSet& layout)
460{
461#ifdef HAVE_XRANDR
462 char buffer[2048];
463 vlog.debug("Got request for framebuffer resize to %dx%d",
464 fb_width, fb_height);
465 layout.print(buffer, sizeof(buffer));
466 vlog.debug("%s", buffer);
467
468 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
469 if (!res) {
470 vlog.error("XRRGetScreenResources failed");
471 return rfb::resultProhibited;
472 }
473 vncSetGlueContext(dpy, res);
474
475 /* The client may request a screen layout which is not supported by
476 the Xserver. This happens, for example, when adjusting the size
477 of a non-fullscreen vncviewer window. To handle this and other
478 cases, we first call tryScreenLayout. If this fails, we try to
479 adjust the request to one screen with a smaller mode. */
480 vlog.debug("Testing screen layout");
481 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
482 rfb::ScreenSet adjustedLayout;
483 if (tryresult == rfb::resultSuccess) {
484 adjustedLayout = layout;
485 } else {
486 vlog.debug("Impossible layout - trying to adjust");
487
488 ScreenSet::const_iterator firstscreen = layout.begin();
489 adjustedLayout.add_screen(*firstscreen);
490 ScreenSet::iterator iter = adjustedLayout.begin();
491 RROutput outputId = None;
492
493 for (int i = 0;i < vncRandRGetOutputCount();i++) {
494 unsigned int oi = vncRandRGetOutputId(i);
495
496 /* Known? */
497 if (outputIdMap.count(oi) == 0)
498 continue;
499
500 /* Find the corresponding screen... */
501 if (iter->id == outputIdMap[oi]) {
502 outputId = oi;
503 } else {
504 outputIdMap.erase(oi);
505 }
506 }
507
508 /* New screen */
509 if (outputId == None) {
510 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
511 if (i != -1) {
512 outputId = vncRandRGetOutputId(i);
513 }
514 }
515 if (outputId == None) {
516 vlog.debug("Resize adjust: Could not find corresponding screen");
517 XRRFreeScreenResources(res);
518 return rfb::resultInvalid;
519 }
520 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
521 if (!output) {
522 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
523 XRRFreeScreenResources(res);
524 return rfb::resultInvalid;
525 }
526 if (!output->crtc) {
527 vlog.debug("Resize adjust: Selected output has no CRTC");
528 XRRFreeScreenResources(res);
529 XRRFreeOutputInfo(output);
530 return rfb::resultInvalid;
531 }
532 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
533 if (!crtc) {
534 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
535 XRRFreeScreenResources(res);
536 XRRFreeOutputInfo(output);
537 return rfb::resultInvalid;
538 }
539
540 unsigned int swidth = iter->dimensions.width();
541 unsigned int sheight = iter->dimensions.height();
542
543 switch (crtc->rotation) {
544 case RR_Rotate_90:
545 case RR_Rotate_270:
546 unsigned int swap = swidth;
547 swidth = sheight;
548 sheight = swap;
549 break;
550 }
551
552 GetSmallerMode(res, output, &swidth, &sheight);
553 XRRFreeOutputInfo(output);
554
555 switch (crtc->rotation) {
556 case RR_Rotate_90:
557 case RR_Rotate_270:
558 unsigned int swap = swidth;
559 swidth = sheight;
560 sheight = swap;
561 break;
562 }
563
564 XRRFreeCrtcInfo(crtc);
565
566 if (sheight != 0 && swidth != 0) {
567 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
568 iter->dimensions.setXYWH(0, 0, swidth, sheight);
569 fb_width = swidth;
570 fb_height = sheight;
571 } else {
572 vlog.error("Failed to find smaller or equal screen size");
573 XRRFreeScreenResources(res);
574 return rfb::resultInvalid;
575 }
576 }
577
578 vlog.debug("Changing screen layout");
579 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
580 XRRFreeScreenResources(res);
581
582 /* Send a dummy event to the root window. When this event is seen,
583 earlier change events (ConfigureNotify and/or CrtcChange) have
584 been processed. An Expose event is used for simplicity; does not
585 require any Atoms, and will not affect other applications. */
586 unsigned long serial = XNextRequest(dpy);
587 XExposeEvent ev = {}; /* zero x, y, width, height, count */
588 ev.type = Expose;
589 ev.display = dpy;
590 ev.window = DefaultRootWindow(dpy);
591 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
592 while (randrSyncSerial < serial) {
593 TXWindow::handleXEvents(dpy);
594 }
595 } else {
596 vlog.error("XSendEvent failed");
597 }
598
599 /* The protocol requires that an error is returned if the requested
600 layout could not be set. This is checked by
601 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
602 with reason=0 will be sent in response to the changes seen by the
603 event handler. */
Pierre Ossman44a1d712018-09-11 14:22:04 +0200604 if (adjustedLayout != layout)
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100605 return rfb::resultInvalid;
Pierre Ossman44a1d712018-09-11 14:22:04 +0200606
607 // Explicitly update the server state with the result as there
608 // can be corner cases where we don't get feedback from the X server
609 server->setScreenLayout(computeScreenLayout());
610
611 return ret;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100612
613#else
614 return rfb::resultProhibited;
615#endif /* HAVE_XRANDR */
616}
617
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200618
619bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100620 if (ev->type == xkbEventBase + XkbEventCode) {
621 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200622
Pierre Ossman07580a82018-03-07 15:33:42 +0100623 if (kb->any.xkb_type != XkbIndicatorStateNotify)
624 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200625
Pierre Ossman07580a82018-03-07 15:33:42 +0100626 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200627
Pierre Ossman07580a82018-03-07 15:33:42 +0100628 ledState = 0;
629 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
630 if (kb->indicators.state & ledMasks[i])
631 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200632 }
633
Pierre Ossman07580a82018-03-07 15:33:42 +0100634 if (running)
635 server->setLEDState(ledState);
636
637 return true;
638#ifdef HAVE_XDAMAGE
639 } else if (ev->type == xdamageEventBase) {
640 XDamageNotifyEvent* dev;
641 Rect rect;
642
643 if (!running)
644 return true;
645
646 dev = (XDamageNotifyEvent*)ev;
647 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
648 server->add_changed(rect);
649
650 return true;
651#endif
652#ifdef HAVE_XFIXES
653 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
654 XFixesCursorNotifyEvent* cev;
655
656 if (!running)
657 return true;
658
659 cev = (XFixesCursorNotifyEvent*)ev;
660
661 if (cev->subtype != XFixesDisplayCursorNotify)
662 return false;
663
664 return setCursor();
665#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100666#ifdef HAVE_XRANDR
667 } else if (ev->type == Expose) {
668 XExposeEvent* eev = (XExposeEvent*)ev;
669 randrSyncSerial = eev->serial;
670
671 return false;
672
673 } else if (ev->type == ConfigureNotify) {
674 XConfigureEvent* cev = (XConfigureEvent*)ev;
675
676 if (cev->window != DefaultRootWindow(dpy)) {
677 return false;
678 }
679
680 XRRUpdateConfiguration(ev);
681 geometry->recalc(cev->width, cev->height);
682
683 if (!running) {
684 return false;
685 }
686
687 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
688 // Recreate pixel buffer
689 ImageFactory factory((bool)useShm);
690 delete pb;
691 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
692 server->setPixelBuffer(pb, computeScreenLayout());
693
694 // Mark entire screen as changed
695 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
696 }
697
698 return true;
699
700 } else if (ev->type == xrandrEventBase + RRNotify) {
701 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
702
703 if (rev->window != DefaultRootWindow(dpy)) {
704 return false;
705 }
706
707 if (!running)
708 return false;
709
710 if (rev->subtype == RRNotify_CrtcChange) {
711 server->setScreenLayout(computeScreenLayout());
712 }
713
714 return true;
715#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100716 }
717
718 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200719}
720
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200721void XDesktop::queryApproved()
722{
723 assert(isRunning());
724 server->approveConnection(queryConnectSock, true, 0);
725 queryConnectSock = 0;
726}
727
728void XDesktop::queryRejected()
729{
730 assert(isRunning());
731 server->approveConnection(queryConnectSock, false,
732 "Connection rejected by local user");
733 queryConnectSock = 0;
734}
735
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200736bool XDesktop::setCursor()
737{
Pierre Ossman07580a82018-03-07 15:33:42 +0100738 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200739
Pierre Ossman07580a82018-03-07 15:33:42 +0100740 cim = XFixesGetCursorImage(dpy);
741 if (cim == NULL)
742 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200743
Pierre Ossman07580a82018-03-07 15:33:42 +0100744 // Copied from XserverDesktop::setCursor() in
745 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
746 // handle long -> U32 conversion for 64-bit Xlib
747 rdr::U8* cursorData;
748 rdr::U8 *out;
749 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200750
Pierre Ossman07580a82018-03-07 15:33:42 +0100751 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200752
Pierre Ossman07580a82018-03-07 15:33:42 +0100753 // Un-premultiply alpha
754 pixels = cim->pixels;
755 out = cursorData;
756 for (int y = 0; y < cim->height; y++) {
757 for (int x = 0; x < cim->width; x++) {
758 rdr::U8 alpha;
759 rdr::U32 pixel = *pixels++;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200760
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200761 alpha = (pixel >> 24) & 0xff;
Pierre Ossman07580a82018-03-07 15:33:42 +0100762 if (alpha == 0)
763 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200764
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200765 *out++ = ((pixel >> 16) & 0xff) * 255/alpha;
766 *out++ = ((pixel >> 8) & 0xff) * 255/alpha;
767 *out++ = ((pixel >> 0) & 0xff) * 255/alpha;
768 *out++ = ((pixel >> 24) & 0xff);
Pierre Ossman07580a82018-03-07 15:33:42 +0100769 }
770 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200771
Pierre Ossman07580a82018-03-07 15:33:42 +0100772 try {
773 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
774 cursorData);
775 } catch (rdr::Exception& e) {
776 vlog.error("XserverDesktop::setCursor: %s",e.str());
777 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200778
Pierre Ossman07580a82018-03-07 15:33:42 +0100779 delete [] cursorData;
780 XFree(cim);
781 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200782}