blob: 3e67fad0754dd4927f9104e63413712865dca02b [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);
426#endif
427
428 return layout;
429}
430
431#ifdef HAVE_XRANDR
432/* Get the biggest mode which is equal or smaller to requested
433 size. If no such mode exists, return the smallest. */
434static void GetSmallerMode(XRRScreenResources *res,
435 XRROutputInfo *output,
436 unsigned int *width, unsigned int *height)
437{
438 XRRModeInfo best = {};
439 XRRModeInfo smallest = {};
440 smallest.width = -1;
441 smallest.height = -1;
442
443 for (int i = 0; i < res->nmode; i++) {
444 for (int j = 0; j < output->nmode; j++) {
445 if (output->modes[j] == res->modes[i].id) {
446 if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
447 (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
448 best = res->modes[i];
449 }
450 if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
451 smallest = res->modes[i];
452 }
453 }
454 }
455 }
456
457 if (best.id == 0 && smallest.id != 0) {
458 best = smallest;
459 }
460
461 *width = best.width;
462 *height = best.height;
463}
464#endif /* HAVE_XRANDR */
465
466unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
467 const rfb::ScreenSet& layout)
468{
469#ifdef HAVE_XRANDR
470 char buffer[2048];
471 vlog.debug("Got request for framebuffer resize to %dx%d",
472 fb_width, fb_height);
473 layout.print(buffer, sizeof(buffer));
474 vlog.debug("%s", buffer);
475
476 XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
477 if (!res) {
478 vlog.error("XRRGetScreenResources failed");
479 return rfb::resultProhibited;
480 }
481 vncSetGlueContext(dpy, res);
482
483 /* The client may request a screen layout which is not supported by
484 the Xserver. This happens, for example, when adjusting the size
485 of a non-fullscreen vncviewer window. To handle this and other
486 cases, we first call tryScreenLayout. If this fails, we try to
487 adjust the request to one screen with a smaller mode. */
488 vlog.debug("Testing screen layout");
489 unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
490 rfb::ScreenSet adjustedLayout;
491 if (tryresult == rfb::resultSuccess) {
492 adjustedLayout = layout;
493 } else {
494 vlog.debug("Impossible layout - trying to adjust");
495
496 ScreenSet::const_iterator firstscreen = layout.begin();
497 adjustedLayout.add_screen(*firstscreen);
498 ScreenSet::iterator iter = adjustedLayout.begin();
499 RROutput outputId = None;
500
501 for (int i = 0;i < vncRandRGetOutputCount();i++) {
502 unsigned int oi = vncRandRGetOutputId(i);
503
504 /* Known? */
505 if (outputIdMap.count(oi) == 0)
506 continue;
507
508 /* Find the corresponding screen... */
509 if (iter->id == outputIdMap[oi]) {
510 outputId = oi;
511 } else {
512 outputIdMap.erase(oi);
513 }
514 }
515
516 /* New screen */
517 if (outputId == None) {
518 int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
519 if (i != -1) {
520 outputId = vncRandRGetOutputId(i);
521 }
522 }
523 if (outputId == None) {
524 vlog.debug("Resize adjust: Could not find corresponding screen");
525 XRRFreeScreenResources(res);
526 return rfb::resultInvalid;
527 }
528 XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
529 if (!output) {
530 vlog.debug("Resize adjust: XRRGetOutputInfo failed");
531 XRRFreeScreenResources(res);
532 return rfb::resultInvalid;
533 }
534 if (!output->crtc) {
535 vlog.debug("Resize adjust: Selected output has no CRTC");
536 XRRFreeScreenResources(res);
537 XRRFreeOutputInfo(output);
538 return rfb::resultInvalid;
539 }
540 XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
541 if (!crtc) {
542 vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
543 XRRFreeScreenResources(res);
544 XRRFreeOutputInfo(output);
545 return rfb::resultInvalid;
546 }
547
548 unsigned int swidth = iter->dimensions.width();
549 unsigned int sheight = iter->dimensions.height();
550
551 switch (crtc->rotation) {
552 case RR_Rotate_90:
553 case RR_Rotate_270:
554 unsigned int swap = swidth;
555 swidth = sheight;
556 sheight = swap;
557 break;
558 }
559
560 GetSmallerMode(res, output, &swidth, &sheight);
561 XRRFreeOutputInfo(output);
562
563 switch (crtc->rotation) {
564 case RR_Rotate_90:
565 case RR_Rotate_270:
566 unsigned int swap = swidth;
567 swidth = sheight;
568 sheight = swap;
569 break;
570 }
571
572 XRRFreeCrtcInfo(crtc);
573
574 if (sheight != 0 && swidth != 0) {
575 vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
576 iter->dimensions.setXYWH(0, 0, swidth, sheight);
577 fb_width = swidth;
578 fb_height = sheight;
579 } else {
580 vlog.error("Failed to find smaller or equal screen size");
581 XRRFreeScreenResources(res);
582 return rfb::resultInvalid;
583 }
584 }
585
586 vlog.debug("Changing screen layout");
587 unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
588 XRRFreeScreenResources(res);
589
590 /* Send a dummy event to the root window. When this event is seen,
591 earlier change events (ConfigureNotify and/or CrtcChange) have
592 been processed. An Expose event is used for simplicity; does not
593 require any Atoms, and will not affect other applications. */
594 unsigned long serial = XNextRequest(dpy);
595 XExposeEvent ev = {}; /* zero x, y, width, height, count */
596 ev.type = Expose;
597 ev.display = dpy;
598 ev.window = DefaultRootWindow(dpy);
599 if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
600 while (randrSyncSerial < serial) {
601 TXWindow::handleXEvents(dpy);
602 }
603 } else {
604 vlog.error("XSendEvent failed");
605 }
606
607 /* The protocol requires that an error is returned if the requested
608 layout could not be set. This is checked by
609 VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
610 with reason=0 will be sent in response to the changes seen by the
611 event handler. */
Pierre Ossman44a1d712018-09-11 14:22:04 +0200612 if (adjustedLayout != layout)
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100613 return rfb::resultInvalid;
Pierre Ossman44a1d712018-09-11 14:22:04 +0200614
615 // Explicitly update the server state with the result as there
616 // can be corner cases where we don't get feedback from the X server
617 server->setScreenLayout(computeScreenLayout());
618
619 return ret;
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100620
621#else
622 return rfb::resultProhibited;
623#endif /* HAVE_XRANDR */
624}
625
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200626
627bool XDesktop::handleGlobalEvent(XEvent* ev) {
Pierre Ossman07580a82018-03-07 15:33:42 +0100628 if (ev->type == xkbEventBase + XkbEventCode) {
629 XkbEvent *kb = (XkbEvent *)ev;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200630
Pierre Ossman07580a82018-03-07 15:33:42 +0100631 if (kb->any.xkb_type != XkbIndicatorStateNotify)
632 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200633
Pierre Ossman07580a82018-03-07 15:33:42 +0100634 vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200635
Pierre Ossman07580a82018-03-07 15:33:42 +0100636 ledState = 0;
637 for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
638 if (kb->indicators.state & ledMasks[i])
639 ledState |= 1u << i;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200640 }
641
Pierre Ossman07580a82018-03-07 15:33:42 +0100642 if (running)
643 server->setLEDState(ledState);
644
645 return true;
646#ifdef HAVE_XDAMAGE
647 } else if (ev->type == xdamageEventBase) {
648 XDamageNotifyEvent* dev;
649 Rect rect;
650
651 if (!running)
652 return true;
653
654 dev = (XDamageNotifyEvent*)ev;
655 rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
656 server->add_changed(rect);
657
658 return true;
659#endif
660#ifdef HAVE_XFIXES
661 } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
662 XFixesCursorNotifyEvent* cev;
663
664 if (!running)
665 return true;
666
667 cev = (XFixesCursorNotifyEvent*)ev;
668
669 if (cev->subtype != XFixesDisplayCursorNotify)
670 return false;
671
672 return setCursor();
673#endif
Peter Åstrand (astrand)242c5b22018-03-07 13:00:47 +0100674#ifdef HAVE_XRANDR
675 } else if (ev->type == Expose) {
676 XExposeEvent* eev = (XExposeEvent*)ev;
677 randrSyncSerial = eev->serial;
678
679 return false;
680
681 } else if (ev->type == ConfigureNotify) {
682 XConfigureEvent* cev = (XConfigureEvent*)ev;
683
684 if (cev->window != DefaultRootWindow(dpy)) {
685 return false;
686 }
687
688 XRRUpdateConfiguration(ev);
689 geometry->recalc(cev->width, cev->height);
690
691 if (!running) {
692 return false;
693 }
694
695 if ((cev->width != pb->width() || (cev->height != pb->height()))) {
696 // Recreate pixel buffer
697 ImageFactory factory((bool)useShm);
698 delete pb;
699 pb = new XPixelBuffer(dpy, factory, geometry->getRect());
700 server->setPixelBuffer(pb, computeScreenLayout());
701
702 // Mark entire screen as changed
703 server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
704 }
705
706 return true;
707
708 } else if (ev->type == xrandrEventBase + RRNotify) {
709 XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
710
711 if (rev->window != DefaultRootWindow(dpy)) {
712 return false;
713 }
714
715 if (!running)
716 return false;
717
718 if (rev->subtype == RRNotify_CrtcChange) {
719 server->setScreenLayout(computeScreenLayout());
720 }
721
722 return true;
723#endif
Pierre Ossman07580a82018-03-07 15:33:42 +0100724 }
725
726 return false;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200727}
728
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200729void XDesktop::queryApproved()
730{
731 assert(isRunning());
732 server->approveConnection(queryConnectSock, true, 0);
733 queryConnectSock = 0;
734}
735
736void XDesktop::queryRejected()
737{
738 assert(isRunning());
739 server->approveConnection(queryConnectSock, false,
740 "Connection rejected by local user");
741 queryConnectSock = 0;
742}
743
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200744bool XDesktop::setCursor()
745{
Pierre Ossman07580a82018-03-07 15:33:42 +0100746 XFixesCursorImage *cim;
Peter Åstrand (astrand)3abc7d42017-10-11 15:12:10 +0200747
Pierre Ossman07580a82018-03-07 15:33:42 +0100748 cim = XFixesGetCursorImage(dpy);
749 if (cim == NULL)
750 return false;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200751
Pierre Ossman07580a82018-03-07 15:33:42 +0100752 // Copied from XserverDesktop::setCursor() in
753 // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
754 // handle long -> U32 conversion for 64-bit Xlib
755 rdr::U8* cursorData;
756 rdr::U8 *out;
757 const unsigned long *pixels;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200758
Pierre Ossman07580a82018-03-07 15:33:42 +0100759 cursorData = new rdr::U8[cim->width * cim->height * 4];
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200760
Pierre Ossman07580a82018-03-07 15:33:42 +0100761 // Un-premultiply alpha
762 pixels = cim->pixels;
763 out = cursorData;
764 for (int y = 0; y < cim->height; y++) {
765 for (int x = 0; x < cim->width; x++) {
766 rdr::U8 alpha;
767 rdr::U32 pixel = *pixels++;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200768
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200769 alpha = (pixel >> 24) & 0xff;
Pierre Ossman07580a82018-03-07 15:33:42 +0100770 if (alpha == 0)
771 alpha = 1; // Avoid division by zero
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200772
Pierre Ossman6d0a5a22018-09-18 15:41:25 +0200773 *out++ = ((pixel >> 16) & 0xff) * 255/alpha;
774 *out++ = ((pixel >> 8) & 0xff) * 255/alpha;
775 *out++ = ((pixel >> 0) & 0xff) * 255/alpha;
776 *out++ = ((pixel >> 24) & 0xff);
Pierre Ossman07580a82018-03-07 15:33:42 +0100777 }
778 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200779
Pierre Ossman07580a82018-03-07 15:33:42 +0100780 try {
781 server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
782 cursorData);
783 } catch (rdr::Exception& e) {
784 vlog.error("XserverDesktop::setCursor: %s",e.str());
785 }
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200786
Pierre Ossman07580a82018-03-07 15:33:42 +0100787 delete [] cursorData;
788 XFree(cim);
789 return true;
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +0200790}