blob: 564b2d51b1b1622c128f50d9a7cb0e2ce8d980f2 [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>
Pierre Ossman10688ef2018-09-29 11:24:19 +020022#include <signal.h>
23#include <unistd.h>
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020024
Pierre Ossman6c97fa42018-10-05 17:35:51 +020025#include <rfb/LogWriter.h>
26
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020027#include <x0vncserver/XDesktop.h>
28
29#include <X11/XKBlib.h>
30#ifdef HAVE_XTEST
31#include <X11/extensions/XTest.h>
32#endif
33#ifdef HAVE_XDAMAGE
34#include <X11/extensions/Xdamage.h>
35#endif
36#ifdef HAVE_XFIXES
37#include <X11/extensions/Xfixes.h>
38#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +010039#ifdef HAVE_XRANDR
40#include <X11/extensions/Xrandr.h>
41#include <RandrGlue.h>
42extern "C" {
43void vncSetGlueContext(Display *dpy, void *res);
44}
45#endif
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020046#include <x0vncserver/Geometry.h>
47#include <x0vncserver/XPixelBuffer.h>
48
49using namespace rfb;
50
51extern const unsigned short code_map_qnum_to_xorgevdev[];
52extern const unsigned int code_map_qnum_to_xorgevdev_len;
53
54extern const unsigned short code_map_qnum_to_xorgkbd[];
55extern const unsigned int code_map_qnum_to_xorgkbd_len;
56
Pierre Ossmance4722f2017-11-08 16:00:05 +010057BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
58BoolParameter rawKeyboard("RawKeyboard",
59 "Send keyboard events straight through and "
60 "avoid mapping them to the current keyboard "
61 "layout", false);
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020062IntParameter queryConnectTimeout("QueryConnectTimeout",
63 "Number of seconds to show the Accept Connection dialog before "
64 "rejecting the connection",
65 10);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020066
67static rfb::LogWriter vlog("XDesktop");
68
69// order is important as it must match RFB extension
70static const char * ledNames[XDESKTOP_N_LEDS] = {
71 "Scroll Lock", "Num Lock", "Caps Lock"
72};
73
74XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
Pierre Ossman07580a82018-03-07 15:33:42 +010075 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020076 queryConnectDialog(0), queryConnectSock(0),
Pierre Ossman07580a82018-03-07 15:33:42 +010077 oldButtonMask(0), haveXtest(false), haveDamage(false),
78 maxButtons(0), running(false), ledMasks(), ledState(0),
79 codeMap(0), codeMapLen(0)
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020080{
Pierre Ossman07580a82018-03-07 15:33:42 +010081 int major, minor;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020082
Pierre Ossman07580a82018-03-07 15:33:42 +010083 int xkbOpcode, xkbErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020084
Pierre Ossman07580a82018-03-07 15:33:42 +010085 major = XkbMajorVersion;
86 minor = XkbMinorVersion;
87 if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
88 &xkbErrorBase, &major, &minor)) {
89 vlog.error("XKEYBOARD extension not present");
90 throw Exception();
91 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020092
Pierre Ossman07580a82018-03-07 15:33:42 +010093 XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
94 XkbIndicatorStateNotifyMask);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020095
Pierre Ossman07580a82018-03-07 15:33:42 +010096 // figure out bit masks for the indicators we are interested in
97 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
98 Atom a;
99 int shift;
100 Bool on;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200101
Pierre Ossman07580a82018-03-07 15:33:42 +0100102 a = XInternAtom(dpy, ledNames[i], True);
103 if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
104 continue;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200105
Pierre Ossman07580a82018-03-07 15:33:42 +0100106 ledMasks[i] = 1u << shift;
107 vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
108 if (on)
109 ledState |= 1u << i;
110 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200111
Pierre Ossman07580a82018-03-07 15:33:42 +0100112 // X11 unfortunately uses keyboard driver specific keycodes and provides no
113 // direct way to query this, so guess based on the keyboard mapping
114 XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
115 if (desc && desc->names) {
116 char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200117
Pierre Ossman07580a82018-03-07 15:33:42 +0100118 if (keycodes) {
119 if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
120 codeMap = code_map_qnum_to_xorgevdev;
121 codeMapLen = code_map_qnum_to_xorgevdev_len;
122 vlog.info("Using evdev codemap\n");
123 } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
124 codeMap = code_map_qnum_to_xorgkbd;
125 codeMapLen = code_map_qnum_to_xorgkbd_len;
126 vlog.info("Using xorgkbd codemap\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200127 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100128 vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200129 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100130 XFree(keycodes);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200131 } else {
Pierre Ossman07580a82018-03-07 15:33:42 +0100132 vlog.debug("Unable to get keycode map\n");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200133 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100134
135 XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
136 }
137
138#ifdef HAVE_XTEST
139 int xtestEventBase;
140 int xtestErrorBase;
141
142 if (XTestQueryExtension(dpy, &xtestEventBase,
143 &xtestErrorBase, &major, &minor)) {
144 XTestGrabControl(dpy, True);
145 vlog.info("XTest extension present - version %d.%d",major,minor);
146 haveXtest = true;
147 } else {
148#endif
149 vlog.info("XTest extension not present");
150 vlog.info("Unable to inject events or display while server is grabbed");
151#ifdef HAVE_XTEST
152 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200153#endif
154
155#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100156 int xdamageErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200157
Pierre Ossman07580a82018-03-07 15:33:42 +0100158 if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
159 haveDamage = true;
160 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200161#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100162 vlog.info("DAMAGE extension not present");
163 vlog.info("Will have to poll screen for changes");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200164#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100165 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200166#endif
167
168#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100169 int xfixesErrorBase;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200170
Pierre Ossman07580a82018-03-07 15:33:42 +0100171 if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
172 XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
173 XFixesDisplayCursorNotifyMask);
174 } else {
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200175#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100176 vlog.info("XFIXES extension not present");
177 vlog.info("Will not be able to display cursors");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200178#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100179 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200180#endif
181
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100182#ifdef HAVE_XRANDR
183 int xrandrErrorBase;
184
185 randrSyncSerial = 0;
186 if (XRRQueryExtension(dpy, &xrandrEventBase, &xrandrErrorBase)) {
187 XRRSelectInput(dpy, DefaultRootWindow(dpy),
188 RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
189 /* Override TXWindow::init input mask */
190 XSelectInput(dpy, DefaultRootWindow(dpy),
191 PropertyChangeMask | StructureNotifyMask | ExposureMask);
192 } else {
193#endif
194 vlog.info("RANDR extension not present");
195 vlog.info("Will not be able to handle session resize");
196#ifdef HAVE_XRANDR
197 }
198#endif
199
Pierre Ossman07580a82018-03-07 15:33:42 +0100200 TXWindow::setGlobalEventHandler(this);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200201}
202
203XDesktop::~XDesktop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100204 if (running)
205 stop();
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200206}
207
208
209void XDesktop::poll() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100210 if (pb and not haveDamage)
211 pb->poll(server);
212 if (running) {
213 Window root, child;
214 int x, y, wx, wy;
215 unsigned int mask;
216 XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
217 &x, &y, &wx, &wy, &mask);
Pierre Ossmanc86ce3e2018-09-10 17:03:17 +0200218 x -= geometry->offsetLeft();
219 y -= geometry->offsetTop();
Pierre Ossman07580a82018-03-07 15:33:42 +0100220 server->setCursorPos(rfb::Point(x, y));
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200221 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100222}
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200223
224
225void XDesktop::start(VNCServer* vs) {
226
Pierre Ossman07580a82018-03-07 15:33:42 +0100227 // Determine actual number of buttons of the X pointer device.
228 unsigned char btnMap[8];
229 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
230 maxButtons = (numButtons > 8) ? 8 : numButtons;
231 vlog.info("Enabling %d button%s of X pointer device",
232 maxButtons, (maxButtons != 1) ? "s" : "");
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200233
Pierre Ossman07580a82018-03-07 15:33:42 +0100234 // Create an ImageFactory instance for producing Image objects.
235 ImageFactory factory((bool)useShm);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200236
Pierre Ossman07580a82018-03-07 15:33:42 +0100237 // Create pixel buffer and provide it to the server object.
238 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
239 vlog.info("Allocated %s", pb->getImage()->classDesc());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200240
Pierre Ossmancd7931d2018-10-05 17:48:58 +0200241 server = vs;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100242 server->setPixelBuffer(pb, computeScreenLayout());
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200243
244#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100245 if (haveDamage) {
246 damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
247 XDamageReportRawRectangles);
248 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200249#endif
250
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200251#ifdef HAVE_XFIXES
Pierre Ossman07580a82018-03-07 15:33:42 +0100252 setCursor();
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200253#endif
254
Pierre Ossman07580a82018-03-07 15:33:42 +0100255 server->setLEDState(ledState);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200256
Pierre Ossman07580a82018-03-07 15:33:42 +0100257 running = true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200258}
259
260void XDesktop::stop() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100261 running = false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200262
263#ifdef HAVE_XDAMAGE
Pierre Ossman07580a82018-03-07 15:33:42 +0100264 if (haveDamage)
265 XDamageDestroy(dpy, damage);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200266#endif
267
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200268 delete queryConnectDialog;
269 queryConnectDialog = 0;
270
Pierre Ossman07580a82018-03-07 15:33:42 +0100271 server->setPixelBuffer(0);
272 server = 0;
Michal Srb18a77072017-09-29 14:47:56 +0200273
Pierre Ossman07580a82018-03-07 15:33:42 +0100274 delete pb;
275 pb = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200276}
277
Pierre Ossman10688ef2018-09-29 11:24:19 +0200278void XDesktop::terminate() {
279 kill(getpid(), SIGTERM);
280}
281
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200282bool XDesktop::isRunning() {
Pierre Ossman07580a82018-03-07 15:33:42 +0100283 return running;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200284}
285
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200286void XDesktop::queryConnection(network::Socket* sock,
287 const char* userName)
288{
289 assert(isRunning());
290
291 if (queryConnectSock) {
292 server->approveConnection(sock, false, "Another connection is currently being queried.");
293 return;
294 }
295
296 if (!userName)
297 userName = "(anonymous)";
298
299 queryConnectSock = sock;
300
301 CharArray address(sock->getPeerAddress());
302 delete queryConnectDialog;
303 queryConnectDialog = new QueryConnectDialog(dpy, address.buf,
304 userName,
305 queryConnectTimeout,
306 this);
307 queryConnectDialog->map();
308}
309
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200310void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
311#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100312 if (!haveXtest) return;
313 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
314 geometry->offsetLeft() + pos.x,
315 geometry->offsetTop() + pos.y,
316 CurrentTime);
317 if (buttonMask != oldButtonMask) {
318 for (int i = 0; i < maxButtons; i++) {
319 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
320 if (buttonMask & (1<<i)) {
321 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
322 } else {
323 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200324 }
325 }
326 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100327 }
328 oldButtonMask = buttonMask;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200329#endif
330}
331
332#ifdef HAVE_XTEST
333KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100334 XkbDescPtr xkb;
335 XkbStateRec state;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200336 unsigned int mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100337 unsigned keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200338
Pierre Ossman07580a82018-03-07 15:33:42 +0100339 xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
340 if (!xkb)
341 return 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200342
Pierre Ossman07580a82018-03-07 15:33:42 +0100343 XkbGetState(dpy, XkbUseCoreKbd, &state);
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200344 // XkbStateFieldFromRec() doesn't work properly because
345 // state.lookup_mods isn't properly updated, so we do this manually
346 mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200347
Pierre Ossman07580a82018-03-07 15:33:42 +0100348 for (keycode = xkb->min_key_code;
349 keycode <= xkb->max_key_code;
350 keycode++) {
351 KeySym cursym;
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200352 unsigned int out_mods;
Pierre Ossman07580a82018-03-07 15:33:42 +0100353 XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
354 if (cursym == keysym)
355 break;
356 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200357
Pierre Ossman07580a82018-03-07 15:33:42 +0100358 if (keycode > xkb->max_key_code)
359 keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200360
Pierre Ossman07580a82018-03-07 15:33:42 +0100361 XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200362
Pierre Ossman9ee59ec2018-07-25 20:02:02 +0200363 // Shift+Tab is usually ISO_Left_Tab, but RFB hides this fact. Do
364 // another attempt if we failed the initial lookup
365 if ((keycode == 0) && (keysym == XK_Tab) && (mods & ShiftMask))
366 return XkbKeysymToKeycode(dpy, XK_ISO_Left_Tab);
367
Pierre Ossman07580a82018-03-07 15:33:42 +0100368 return keycode;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200369}
370#endif
371
372void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
373#ifdef HAVE_XTEST
Pierre Ossman07580a82018-03-07 15:33:42 +0100374 int keycode = 0;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200375
Pierre Ossman07580a82018-03-07 15:33:42 +0100376 if (!haveXtest)
377 return;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200378
Pierre Ossman07580a82018-03-07 15:33:42 +0100379 // Use scan code if provided and mapping exists
380 if (codeMap && rawKeyboard && xtcode < codeMapLen)
381 keycode = codeMap[xtcode];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200382
Pierre Ossman07580a82018-03-07 15:33:42 +0100383 if (!keycode) {
384 if (pressedKeys.find(keysym) != pressedKeys.end())
385 keycode = pressedKeys[keysym];
386 else {
387 // XKeysymToKeycode() doesn't respect state, so we have to use
388 // something slightly more complex
389 keycode = XkbKeysymToKeycode(dpy, keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200390 }
Pierre Ossman07580a82018-03-07 15:33:42 +0100391 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200392
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200393 if (!keycode) {
394 vlog.error("Could not map key event to X11 key code");
Pierre Ossman07580a82018-03-07 15:33:42 +0100395 return;
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200396 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200397
Pierre Ossman07580a82018-03-07 15:33:42 +0100398 if (down)
399 pressedKeys[keysym] = keycode;
400 else
401 pressedKeys.erase(keysym);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200402
Pierre Ossmana6072cc2018-07-25 20:02:20 +0200403 vlog.debug("%d %s", keycode, down ? "down" : "up");
404
Pierre Ossman07580a82018-03-07 15:33:42 +0100405 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200406#endif
407}
408
409void XDesktop::clientCutText(const char* str, int len) {
410}
411
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100412ScreenSet XDesktop::computeScreenLayout()
413{
414 ScreenSet layout;
415
416#ifdef HAVE_XRANDR
417 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
418 if (!res) {
419 vlog.error("XRRGetScreenResources failed");
420 return layout;
421 }
422 vncSetGlueContext(dpy, res);
423
424 layout = ::computeScreenLayout(&outputIdMap);
425 XRRFreeScreenResources(res);
Pierre Ossman0fe375a2018-11-01 16:05:02 +0100426
427 // Adjust the layout relative to the geometry
428 ScreenSet::iterator iter, iter_next;
429 Point offset(-geometry->offsetLeft(), -geometry->offsetTop());
430 for (iter = layout.begin();iter != layout.end();iter = iter_next) {
431 iter_next = iter; ++iter_next;
432 iter->dimensions = iter->dimensions.translate(offset);
433 if (iter->dimensions.enclosed_by(geometry->getRect()))
434 continue;
435 iter->dimensions = iter->dimensions.intersect(geometry->getRect());
436 if (iter->dimensions.is_empty()) {
437 layout.remove_screen(iter->id);
438 }
439 }
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100440#endif
441
Pierre Ossmanc3826bb2018-11-01 16:10:19 +0100442 // Make sure that we have at least one screen
443 if (layout.num_screens() == 0)
444 layout.add_screen(rfb::Screen(0, 0, 0, geometry->width(),
445 geometry->height(), 0));
446
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100447 return layout;
448}
449
450#ifdef HAVE_XRANDR
451/* Get the biggest mode which is equal or smaller to requested
452 size. If no such mode exists, return the smallest. */
453static void GetSmallerMode(XRRScreenResources *res,
454 XRROutputInfo *output,
455 unsigned int *width, unsigned int *height)
456{
457 XRRModeInfo best = {};
458 XRRModeInfo smallest = {};
459 smallest.width = -1;
460 smallest.height = -1;
461
462 for (int i = 0; i < res->nmode; i++) {
463 for (int j = 0; j < output->nmode; j++) {
464 if (output->modes[j] == res->modes[i].id) {
465 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
466 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
467 best = res->modes[i];
468 }
469 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
470 smallest = res->modes[i];
471 }
472 }
473 }
474 }
475
476 if (best.id == 0 && smallest.id != 0) {
477 best = smallest;
478 }
479
480 *width = best.width;
481 *height = best.height;
482}
483#endif /* HAVE_XRANDR */
484
485unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
486 const rfb::ScreenSet& layout)
487{
488#ifdef HAVE_XRANDR
489 char buffer[2048];
490 vlog.debug("Got request for framebuffer resize to %dx%d",
491 fb_width, fb_height);
492 layout.print(buffer, sizeof(buffer));
493 vlog.debug("%s", buffer);
494
495 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
496 if (!res) {
497 vlog.error("XRRGetScreenResources failed");
498 return rfb::resultProhibited;
499 }
500 vncSetGlueContext(dpy, res);
501
502 /* The client may request a screen layout which is not supported by
503 the Xserver. This happens, for example, when adjusting the size
504 of a non-fullscreen vncviewer window. To handle this and other
505 cases, we first call tryScreenLayout. If this fails, we try to
506 adjust the request to one screen with a smaller mode. */
507 vlog.debug("Testing screen layout");
508 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
509 rfb::ScreenSet adjustedLayout;
510 if (tryresult == rfb::resultSuccess) {
511 adjustedLayout = layout;
512 } else {
513 vlog.debug("Impossible layout - trying to adjust");
514
515 ScreenSet::const_iterator firstscreen = layout.begin();
516 adjustedLayout.add_screen(*firstscreen);
517 ScreenSet::iterator iter = adjustedLayout.begin();
518 RROutput outputId = None;
519
520 for (int i = 0;i < vncRandRGetOutputCount();i++) {
521 unsigned int oi = vncRandRGetOutputId(i);
522
523 /* Known? */
524 if (outputIdMap.count(oi) == 0)
525 continue;
526
527 /* Find the corresponding screen... */
528 if (iter->id == outputIdMap[oi]) {
529 outputId = oi;
530 } else {
531 outputIdMap.erase(oi);
532 }
533 }
534
535 /* New screen */
536 if (outputId == None) {
537 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
538 if (i != -1) {
539 outputId = vncRandRGetOutputId(i);
540 }
541 }
542 if (outputId == None) {
543 vlog.debug("Resize adjust: Could not find corresponding screen");
544 XRRFreeScreenResources(res);
545 return rfb::resultInvalid;
546 }
547 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
548 if (!output) {
549 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
550 XRRFreeScreenResources(res);
551 return rfb::resultInvalid;
552 }
553 if (!output->crtc) {
554 vlog.debug("Resize adjust: Selected output has no CRTC");
555 XRRFreeScreenResources(res);
556 XRRFreeOutputInfo(output);
557 return rfb::resultInvalid;
558 }
559 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
560 if (!crtc) {
561 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
562 XRRFreeScreenResources(res);
563 XRRFreeOutputInfo(output);
564 return rfb::resultInvalid;
565 }
566
567 unsigned int swidth = iter->dimensions.width();
568 unsigned int sheight = iter->dimensions.height();
569
570 switch (crtc->rotation) {
571 case RR_Rotate_90:
572 case RR_Rotate_270:
573 unsigned int swap = swidth;
574 swidth = sheight;
575 sheight = swap;
576 break;
577 }
578
579 GetSmallerMode(res, output, &swidth, &sheight);
580 XRRFreeOutputInfo(output);
581
582 switch (crtc->rotation) {
583 case RR_Rotate_90:
584 case RR_Rotate_270:
585 unsigned int swap = swidth;
586 swidth = sheight;
587 sheight = swap;
588 break;
589 }
590
591 XRRFreeCrtcInfo(crtc);
592
593 if (sheight != 0 && swidth != 0) {
594 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
595 iter->dimensions.setXYWH(0, 0, swidth, sheight);
596 fb_width = swidth;
597 fb_height = sheight;
598 } else {
599 vlog.error("Failed to find smaller or equal screen size");
600 XRRFreeScreenResources(res);
601 return rfb::resultInvalid;
602 }
603 }
604
605 vlog.debug("Changing screen layout");
606 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
607 XRRFreeScreenResources(res);
608
609 /* Send a dummy event to the root window. When this event is seen,
610 earlier change events (ConfigureNotify and/or CrtcChange) have
611 been processed. An Expose event is used for simplicity; does not
612 require any Atoms, and will not affect other applications. */
613 unsigned long serial = XNextRequest(dpy);
614 XExposeEvent ev = {}; /* zero x, y, width, height, count */
615 ev.type = Expose;
616 ev.display = dpy;
617 ev.window = DefaultRootWindow(dpy);
618 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
619 while (randrSyncSerial < serial) {
620 TXWindow::handleXEvents(dpy);
621 }
622 } else {
623 vlog.error("XSendEvent failed");
624 }
625
626 /* The protocol requires that an error is returned if the requested
627 layout could not be set. This is checked by
628 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
629 with reason=0 will be sent in response to the changes seen by the
630 event handler. */
Pierre Ossman44a1d712018-09-11 14:22:04 +0200631 if (adjustedLayout != layout)
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100632 return rfb::resultInvalid;
Pierre Ossman44a1d712018-09-11 14:22:04 +0200633
634 // Explicitly update the server state with the result as there
635 // can be corner cases where we don't get feedback from the X server
636 server->setScreenLayout(computeScreenLayout());
637
638 return ret;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100639
640#else
641 return rfb::resultProhibited;
642#endif /* HAVE_XRANDR */
643}
644
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200645
646bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100647 if (ev->type == xkbEventBase + XkbEventCode) {
648 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200649
Pierre Ossman07580a82018-03-07 15:33:42 +0100650 if (kb->any.xkb_type != XkbIndicatorStateNotify)
651 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200652
Pierre Ossman07580a82018-03-07 15:33:42 +0100653 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200654
Pierre Ossman07580a82018-03-07 15:33:42 +0100655 ledState = 0;
656 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
657 if (kb->indicators.state & ledMasks[i])
658 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200659 }
660
Pierre Ossman07580a82018-03-07 15:33:42 +0100661 if (running)
662 server->setLEDState(ledState);
663
664 return true;
665#ifdef HAVE_XDAMAGE
666 } else if (ev->type == xdamageEventBase) {
667 XDamageNotifyEvent* dev;
668 Rect rect;
669
670 if (!running)
671 return true;
672
673 dev = (XDamageNotifyEvent*)ev;
674 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
Pierre Ossman785cb9d2018-11-05 16:49:47 +0100675 rect = rect.translate(Point(-geometry->offsetLeft(),
676 -geometry->offsetTop()));
Pierre Ossman07580a82018-03-07 15:33:42 +0100677 server->add_changed(rect);
678
679 return true;
680#endif
681#ifdef HAVE_XFIXES
682 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
683 XFixesCursorNotifyEvent* cev;
684
685 if (!running)
686 return true;
687
688 cev = (XFixesCursorNotifyEvent*)ev;
689
690 if (cev->subtype != XFixesDisplayCursorNotify)
691 return false;
692
693 return setCursor();
694#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100695#ifdef HAVE_XRANDR
696 } else if (ev->type == Expose) {
697 XExposeEvent* eev = (XExposeEvent*)ev;
698 randrSyncSerial = eev->serial;
699
700 return false;
701
702 } else if (ev->type == ConfigureNotify) {
703 XConfigureEvent* cev = (XConfigureEvent*)ev;
704
705 if (cev->window != DefaultRootWindow(dpy)) {
706 return false;
707 }
708
709 XRRUpdateConfiguration(ev);
710 geometry->recalc(cev->width, cev->height);
711
712 if (!running) {
713 return false;
714 }
715
716 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
717 // Recreate pixel buffer
718 ImageFactory factory((bool)useShm);
719 delete pb;
720 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
721 server->setPixelBuffer(pb, computeScreenLayout());
722
723 // Mark entire screen as changed
724 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
725 }
726
727 return true;
728
729 } else if (ev->type == xrandrEventBase + RRNotify) {
730 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
731
732 if (rev->window != DefaultRootWindow(dpy)) {
733 return false;
734 }
735
736 if (!running)
737 return false;
738
739 if (rev->subtype == RRNotify_CrtcChange) {
740 server->setScreenLayout(computeScreenLayout());
741 }
742
743 return true;
744#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100745 }
746
747 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200748}
749
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200750void XDesktop::queryApproved()
751{
752 assert(isRunning());
753 server->approveConnection(queryConnectSock, true, 0);
754 queryConnectSock = 0;
755}
756
757void XDesktop::queryRejected()
758{
759 assert(isRunning());
760 server->approveConnection(queryConnectSock, false,
761 "Connection rejected by local user");
762 queryConnectSock = 0;
763}
764
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200765bool XDesktop::setCursor()
766{
Pierre Ossman07580a82018-03-07 15:33:42 +0100767 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200768
Pierre Ossman07580a82018-03-07 15:33:42 +0100769 cim = XFixesGetCursorImage(dpy);
770 if (cim == NULL)
771 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200772
Pierre Ossman07580a82018-03-07 15:33:42 +0100773 // Copied from XserverDesktop::setCursor() in
774 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
775 // handle long -> U32 conversion for 64-bit Xlib
776 rdr::U8* cursorData;
777 rdr::U8 *out;
778 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200779
Pierre Ossman07580a82018-03-07 15:33:42 +0100780 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200781
Pierre Ossman07580a82018-03-07 15:33:42 +0100782 // Un-premultiply alpha
783 pixels = cim->pixels;
784 out = cursorData;
785 for (int y = 0; y < cim->height; y++) {
786 for (int x = 0; x < cim->width; x++) {
787 rdr::U8 alpha;
788 rdr::U32 pixel = *pixels++;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200789
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200790 alpha = (pixel >> 24) & 0xff;
Pierre Ossman07580a82018-03-07 15:33:42 +0100791 if (alpha == 0)
792 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200793
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200794 *out++ = ((pixel >> 16) & 0xff) * 255/alpha;
795 *out++ = ((pixel >> 8) & 0xff) * 255/alpha;
796 *out++ = ((pixel >> 0) & 0xff) * 255/alpha;
797 *out++ = ((pixel >> 24) & 0xff);
Pierre Ossman07580a82018-03-07 15:33:42 +0100798 }
799 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200800
Pierre Ossman07580a82018-03-07 15:33:42 +0100801 try {
802 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
803 cursorData);
804 } catch (rdr::Exception& e) {
805 vlog.error("XserverDesktop::setCursor: %s",e.str());
806 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200807
Pierre Ossman07580a82018-03-07 15:33:42 +0100808 delete [] cursorData;
809 XFree(cim);
810 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200811}