1 /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright 2009-2019 Pierre Ossman for Cendio AB
3 * Copyright 2014 Brian P. Hinz
4 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20 //
21 // XserverDesktop.cxx
22 //
23
24 #include <assert.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <strings.h>
29 #include <unistd.h>
30 #include <pwd.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <sys/utsname.h>
35
36 #include <network/Socket.h>
37 #include <rfb/Exception.h>
38 #include <rfb/VNCServerST.h>
39 #include <rfb/LogWriter.h>
40 #include <rfb/Configuration.h>
41 #include <rfb/ServerCore.h>
42
43 #include "XserverDesktop.h"
44 #include "vncBlockHandler.h"
45 #include "vncExtInit.h"
46 #include "vncHooks.h"
47 #include "vncSelection.h"
48 #include "XorgGlue.h"
49 #include "vncInput.h"
50
51 extern "C" {
52 void vncSetGlueContext(int screenIndex);
53 }
54
55 using namespace rfb;
56 using namespace network;
57
58 static LogWriter vlog("XserverDesktop");
59
60 BoolParameter rawKeyboard("RawKeyboard",
61 "Send keyboard events straight through and "
62 "avoid mapping them to the current keyboard "
63 "layout", false);
64 IntParameter queryConnectTimeout("QueryConnectTimeout",
65 "Number of seconds to show the "
66 "Accept Connection dialog before "
67 "rejecting the connection",
68 10);
69
70
XserverDesktop(int screenIndex_,std::list<network::SocketListener * > listeners_,const char * name,const rfb::PixelFormat & pf,int width,int height,void * fbptr,int stride_)71 XserverDesktop::XserverDesktop(int screenIndex_,
72 std::list<network::SocketListener*> listeners_,
73 const char* name, const rfb::PixelFormat &pf,
74 int width, int height,
75 void* fbptr, int stride_)
76 : screenIndex(screenIndex_),
77 server(0), listeners(listeners_),
78 shadowFramebuffer(NULL),
79 queryConnectId(0), queryConnectTimer(this)
80 {
81 format = pf;
82
83 server = new VNCServerST(name, this);
84 setFramebuffer(width, height, fbptr, stride_);
85
86 for (std::list<SocketListener*>::iterator i = listeners.begin();
87 i != listeners.end();
88 i++) {
89 vncSetNotifyFd((*i)->getFd(), screenIndex, true, false);
90 }
91 }
92
~XserverDesktop()93 XserverDesktop::~XserverDesktop()
94 {
95 while (!listeners.empty()) {
96 vncRemoveNotifyFd(listeners.back()->getFd());
97 delete listeners.back();
98 listeners.pop_back();
99 }
100 if (shadowFramebuffer)
101 delete [] shadowFramebuffer;
102 delete server;
103 }
104
blockUpdates()105 void XserverDesktop::blockUpdates()
106 {
107 server->blockUpdates();
108 }
109
unblockUpdates()110 void XserverDesktop::unblockUpdates()
111 {
112 server->unblockUpdates();
113 }
114
setFramebuffer(int w,int h,void * fbptr,int stride_)115 void XserverDesktop::setFramebuffer(int w, int h, void* fbptr, int stride_)
116 {
117 ScreenSet layout;
118
119 if (shadowFramebuffer) {
120 delete [] shadowFramebuffer;
121 shadowFramebuffer = NULL;
122 }
123
124 if (!fbptr) {
125 shadowFramebuffer = new rdr::U8[w * h * (format.bpp/8)];
126 fbptr = shadowFramebuffer;
127 stride_ = w;
128 }
129
130 setBuffer(w, h, (rdr::U8*)fbptr, stride_);
131
132 vncSetGlueContext(screenIndex);
133 layout = ::computeScreenLayout(&outputIdMap);
134
135 server->setPixelBuffer(this, layout);
136 }
137
refreshScreenLayout()138 void XserverDesktop::refreshScreenLayout()
139 {
140 vncSetGlueContext(screenIndex);
141 server->setScreenLayout(::computeScreenLayout(&outputIdMap));
142 }
143
start(rfb::VNCServer * vs)144 void XserverDesktop::start(rfb::VNCServer* vs)
145 {
146 // We already own the server object, and we always keep it in a
147 // ready state
148 assert(vs == server);
149 }
150
stop()151 void XserverDesktop::stop()
152 {
153 }
154
queryConnection(network::Socket * sock,const char * userName)155 void XserverDesktop::queryConnection(network::Socket* sock,
156 const char* userName)
157 {
158 int count;
159
160 if (queryConnectTimer.isStarted()) {
161 server->approveConnection(sock, false, "Another connection is currently being queried.");
162 return;
163 }
164
165 count = vncNotifyQueryConnect();
166 if (count == 0) {
167 server->approveConnection(sock, false, "Unable to query the local user to accept the connection.");
168 return;
169 }
170
171 queryConnectAddress.replaceBuf(sock->getPeerAddress());
172 if (!userName)
173 userName = "(anonymous)";
174 queryConnectUsername.replaceBuf(strDup(userName));
175 queryConnectId = (uint32_t)(intptr_t)sock;
176 queryConnectSocket = sock;
177
178 queryConnectTimer.start(queryConnectTimeout * 1000);
179 }
180
requestClipboard()181 void XserverDesktop::requestClipboard()
182 {
183 try {
184 server->requestClipboard();
185 } catch (rdr::Exception& e) {
186 vlog.error("XserverDesktop::requestClipboard: %s",e.str());
187 }
188 }
189
announceClipboard(bool available)190 void XserverDesktop::announceClipboard(bool available)
191 {
192 try {
193 server->announceClipboard(available);
194 } catch (rdr::Exception& e) {
195 vlog.error("XserverDesktop::announceClipboard: %s",e.str());
196 }
197 }
198
sendClipboardData(const char * data_)199 void XserverDesktop::sendClipboardData(const char* data_)
200 {
201 try {
202 server->sendClipboardData(data_);
203 } catch (rdr::Exception& e) {
204 vlog.error("XserverDesktop::sendClipboardData: %s",e.str());
205 }
206 }
207
bell()208 void XserverDesktop::bell()
209 {
210 server->bell();
211 }
212
setLEDState(unsigned int state)213 void XserverDesktop::setLEDState(unsigned int state)
214 {
215 server->setLEDState(state);
216 }
217
setDesktopName(const char * name)218 void XserverDesktop::setDesktopName(const char* name)
219 {
220 try {
221 server->setName(name);
222 } catch (rdr::Exception& e) {
223 vlog.error("XserverDesktop::setDesktopName: %s",e.str());
224 }
225 }
226
setCursor(int width,int height,int hotX,int hotY,const unsigned char * rgbaData)227 void XserverDesktop::setCursor(int width, int height, int hotX, int hotY,
228 const unsigned char *rgbaData)
229 {
230 rdr::U8* cursorData;
231
232 rdr::U8 *out;
233 const unsigned char *in;
234
235 cursorData = new rdr::U8[width * height * 4];
236
237 // Un-premultiply alpha
238 in = rgbaData;
239 out = cursorData;
240 for (int y = 0; y < height; y++) {
241 for (int x = 0; x < width; x++) {
242 rdr::U8 alpha;
243
244 alpha = in[3];
245 if (alpha == 0)
246 alpha = 1; // Avoid division by zero
247
248 *out++ = (unsigned)*in++ * 255/alpha;
249 *out++ = (unsigned)*in++ * 255/alpha;
250 *out++ = (unsigned)*in++ * 255/alpha;
251 *out++ = *in++;
252 }
253 }
254
255 try {
256 server->setCursor(width, height, Point(hotX, hotY), cursorData);
257 } catch (rdr::Exception& e) {
258 vlog.error("XserverDesktop::setCursor: %s",e.str());
259 }
260
261 delete [] cursorData;
262 }
263
setCursorPos(int x,int y,bool warped)264 void XserverDesktop::setCursorPos(int x, int y, bool warped)
265 {
266 try {
267 server->setCursorPos(Point(x, y), warped);
268 } catch (rdr::Exception& e) {
269 vlog.error("XserverDesktop::setCursorPos: %s",e.str());
270 }
271 }
272
add_changed(const rfb::Region & region)273 void XserverDesktop::add_changed(const rfb::Region ®ion)
274 {
275 try {
276 server->add_changed(region);
277 } catch (rdr::Exception& e) {
278 vlog.error("XserverDesktop::add_changed: %s",e.str());
279 }
280 }
281
add_copied(const rfb::Region & dest,const rfb::Point & delta)282 void XserverDesktop::add_copied(const rfb::Region &dest, const rfb::Point &delta)
283 {
284 try {
285 server->add_copied(dest, delta);
286 } catch (rdr::Exception& e) {
287 vlog.error("XserverDesktop::add_copied: %s",e.str());
288 }
289 }
290
handleSocketEvent(int fd,bool read,bool write)291 void XserverDesktop::handleSocketEvent(int fd, bool read, bool write)
292 {
293 try {
294 if (read) {
295 if (handleListenerEvent(fd, &listeners, server))
296 return;
297 }
298
299 if (handleSocketEvent(fd, server, read, write))
300 return;
301
302 vlog.error("Cannot find file descriptor for socket event");
303 } catch (rdr::Exception& e) {
304 vlog.error("XserverDesktop::handleSocketEvent: %s",e.str());
305 }
306 }
307
handleListenerEvent(int fd,std::list<SocketListener * > * sockets,SocketServer * sockserv)308 bool XserverDesktop::handleListenerEvent(int fd,
309 std::list<SocketListener*>* sockets,
310 SocketServer* sockserv)
311 {
312 std::list<SocketListener*>::iterator i;
313
314 for (i = sockets->begin(); i != sockets->end(); i++) {
315 if ((*i)->getFd() == fd)
316 break;
317 }
318
319 if (i == sockets->end())
320 return false;
321
322 Socket* sock = (*i)->accept();
323 vlog.debug("new client, sock %d", sock->getFd());
324 sockserv->addSocket(sock);
325 vncSetNotifyFd(sock->getFd(), screenIndex, true, false);
326
327 return true;
328 }
329
handleSocketEvent(int fd,SocketServer * sockserv,bool read,bool write)330 bool XserverDesktop::handleSocketEvent(int fd,
331 SocketServer* sockserv,
332 bool read, bool write)
333 {
334 std::list<Socket*> sockets;
335 std::list<Socket*>::iterator i;
336
337 sockserv->getSockets(&sockets);
338 for (i = sockets.begin(); i != sockets.end(); i++) {
339 if ((*i)->getFd() == fd)
340 break;
341 }
342
343 if (i == sockets.end())
344 return false;
345
346 if (read)
347 sockserv->processSocketReadEvent(*i);
348
349 if (write)
350 sockserv->processSocketWriteEvent(*i);
351
352 return true;
353 }
354
blockHandler(int * timeout)355 void XserverDesktop::blockHandler(int* timeout)
356 {
357 // We don't have a good callback for when we can init input devices[1],
358 // so we abuse the fact that this routine will be called first thing
359 // once the dix is done initialising.
360 // [1] Technically Xvnc has InitInput(), but libvnc.so has nothing.
361 vncInitInputDevice();
362
363 try {
364 std::list<Socket*> sockets;
365 std::list<Socket*>::iterator i;
366 server->getSockets(&sockets);
367 for (i = sockets.begin(); i != sockets.end(); i++) {
368 int fd = (*i)->getFd();
369 if ((*i)->isShutdown()) {
370 vlog.debug("client gone, sock %d",fd);
371 vncRemoveNotifyFd(fd);
372 server->removeSocket(*i);
373 vncClientGone(fd);
374 delete (*i);
375 } else {
376 /* Update existing NotifyFD to listen for write (or not) */
377 vncSetNotifyFd(fd, screenIndex, true, (*i)->outStream().hasBufferedData());
378 }
379 }
380
381 // We are responsible for propagating mouse movement between clients
382 int cursorX, cursorY;
383 vncGetPointerPos(&cursorX, &cursorY);
384 cursorX -= vncGetScreenX(screenIndex);
385 cursorY -= vncGetScreenY(screenIndex);
386 if (oldCursorPos.x != cursorX || oldCursorPos.y != cursorY) {
387 oldCursorPos.x = cursorX;
388 oldCursorPos.y = cursorY;
389 server->setCursorPos(oldCursorPos, false);
390 }
391
392 // Trigger timers and check when the next will expire
393 int nextTimeout = Timer::checkTimeouts();
394 if (nextTimeout > 0 && (*timeout == -1 || nextTimeout < *timeout))
395 *timeout = nextTimeout;
396 } catch (rdr::Exception& e) {
397 vlog.error("XserverDesktop::blockHandler: %s",e.str());
398 }
399 }
400
addClient(Socket * sock,bool reverse)401 void XserverDesktop::addClient(Socket* sock, bool reverse)
402 {
403 vlog.debug("new client, sock %d reverse %d",sock->getFd(),reverse);
404 server->addSocket(sock, reverse);
405 vncSetNotifyFd(sock->getFd(), screenIndex, true, false);
406 }
407
disconnectClients()408 void XserverDesktop::disconnectClients()
409 {
410 vlog.debug("disconnecting all clients");
411 return server->closeClients("Disconnection from server end");
412 }
413
414
getQueryConnect(uint32_t * opaqueId,const char ** address,const char ** username,int * timeout)415 void XserverDesktop::getQueryConnect(uint32_t* opaqueId,
416 const char** address,
417 const char** username,
418 int *timeout)
419 {
420 *opaqueId = queryConnectId;
421
422 if (!queryConnectTimer.isStarted()) {
423 *address = "";
424 *username = "";
425 *timeout = 0;
426 } else {
427 *address = queryConnectAddress.buf;
428 *username = queryConnectUsername.buf;
429 *timeout = queryConnectTimeout;
430 }
431 }
432
approveConnection(uint32_t opaqueId,bool accept,const char * rejectMsg)433 void XserverDesktop::approveConnection(uint32_t opaqueId, bool accept,
434 const char* rejectMsg)
435 {
436 if (queryConnectId == opaqueId) {
437 server->approveConnection(queryConnectSocket, accept, rejectMsg);
438 queryConnectId = 0;
439 queryConnectTimer.stop();
440 }
441 }
442
443 ///////////////////////////////////////////////////////////////////////////
444 //
445 // SDesktop callbacks
446
447
terminate()448 void XserverDesktop::terminate()
449 {
450 kill(getpid(), SIGTERM);
451 }
452
pointerEvent(const Point & pos,int buttonMask)453 void XserverDesktop::pointerEvent(const Point& pos, int buttonMask)
454 {
455 vncPointerMove(pos.x + vncGetScreenX(screenIndex),
456 pos.y + vncGetScreenY(screenIndex));
457 vncPointerButtonAction(buttonMask);
458 }
459
setScreenLayout(int fb_width,int fb_height,const rfb::ScreenSet & layout)460 unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height,
461 const rfb::ScreenSet& layout)
462 {
463 unsigned int result;
464
465 char buffer[2048];
466 vlog.debug("Got request for framebuffer resize to %dx%d",
467 fb_width, fb_height);
468 layout.print(buffer, sizeof(buffer));
469 vlog.debug("%s", buffer);
470
471 vncSetGlueContext(screenIndex);
472 result = ::setScreenLayout(fb_width, fb_height, layout, &outputIdMap);
473
474 // Explicitly update the server state with the result as there
475 // can be corner cases where we don't get feedback from the X core
476 refreshScreenLayout();
477
478 return result;
479 }
480
handleClipboardRequest()481 void XserverDesktop::handleClipboardRequest()
482 {
483 vncHandleClipboardRequest();
484 }
485
handleClipboardAnnounce(bool available)486 void XserverDesktop::handleClipboardAnnounce(bool available)
487 {
488 vncHandleClipboardAnnounce(available);
489 }
490
handleClipboardData(const char * data_)491 void XserverDesktop::handleClipboardData(const char* data_)
492 {
493 vncHandleClipboardData(data_);
494 }
495
grabRegion(const rfb::Region & region)496 void XserverDesktop::grabRegion(const rfb::Region& region)
497 {
498 if (shadowFramebuffer == NULL)
499 return;
500
501 std::vector<rfb::Rect> rects;
502 std::vector<rfb::Rect>::iterator i;
503 region.get_rects(&rects);
504 for (i = rects.begin(); i != rects.end(); i++) {
505 rdr::U8 *buffer;
506 int bufStride;
507
508 buffer = getBufferRW(*i, &bufStride);
509 vncGetScreenImage(screenIndex, i->tl.x, i->tl.y, i->width(), i->height(),
510 (char*)buffer, bufStride * format.bpp/8);
511 commitBufferRW(*i);
512 }
513 }
514
keyEvent(rdr::U32 keysym,rdr::U32 keycode,bool down)515 void XserverDesktop::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
516 {
517 if (!rawKeyboard)
518 keycode = 0;
519
520 vncKeyboardEvent(keysym, keycode, down);
521 }
522
handleTimeout(Timer * t)523 bool XserverDesktop::handleTimeout(Timer* t)
524 {
525 if (t == &queryConnectTimer) {
526 server->approveConnection(queryConnectSocket, false,
527 "The attempt to prompt the user to "
528 "accept the connection failed");
529 }
530
531 return false;
532 }
533