You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libtdevnc/main.c

473 lines
14 KiB

/*
* OSXvnc Copyright (C) 2001 Dan McGuirk <mcguirk@incompleteness.net>.
* Original Xvnc code Copyright (C) 1999 AT&T Laboratories Cambridge.
* All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#ifndef false
#define false 0
#define true -1
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef HAVE_PTHREADS
#include <pthread.h>
#endif
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include "rfb.h"
#ifdef HAVE_PTHREADS
pthread_mutex_t logMutex;
#endif
/*
* rfbLog prints a time-stamped message to the log file (stderr).
*/
void
rfbLog(char *format, ...)
{
va_list args;
char buf[256];
time_t clock;
IF_PTHREADS(pthread_mutex_lock(&logMutex));
va_start(args, format);
time(&clock);
strftime(buf, 255, "%d/%m/%Y %T ", localtime(&clock));
fprintf(stderr, buf);
vfprintf(stderr, format, args);
fflush(stderr);
va_end(args);
IF_PTHREADS(pthread_mutex_unlock(&logMutex));
}
void rfbLogPerror(char *str)
{
rfbLog("%s: %s\n", str, strerror(errno));
}
void rfbMarkRegionAsModified(rfbScreenInfoPtr rfbScreen,RegionPtr modRegion)
{
rfbClientIteratorPtr iterator;
rfbClientPtr cl;
iterator=rfbGetClientIterator(rfbScreen);
while((cl=rfbClientIteratorNext(iterator))) {
REGION_UNION(cl->screen,&cl->modifiedRegion,&cl->modifiedRegion,modRegion);
}
rfbReleaseClientIterator(iterator);
}
void rfbMarkRectAsModified(rfbScreenInfoPtr rfbScreen,int x1,int y1,int x2,int y2)
{
BoxRec box;
RegionRec region;
int i;
if(x1>x2) { i=x1; x1=x2; x2=i; }
x2++;
if(x1<0) { x1=0; if(x2==x1) x2++; }
if(x2>=rfbScreen->width) { x2=rfbScreen->width-1; if(x1==x2) x1--; }
if(y1>y2) { i=y1; y1=y2; y2=i; }
y2++;
if(y1<0) { y1=0; if(y2==y1) y2++; }
if(y2>=rfbScreen->height) { y2=rfbScreen->height-1; if(y1==y2) y1--; }
box.x1=x1; box.y1=y1; box.x2=x2; box.y2=y2;
REGION_INIT(cl->screen,&region,&box,0);
rfbMarkRegionAsModified(rfbScreen,&region);
}
int rfbDeferUpdateTime = 40; /* ms */
#ifdef HAVE_PTHREADS
static void *
clientOutput(void *data)
{
rfbClientPtr cl = (rfbClientPtr)data;
Bool haveUpdate;
RegionRec updateRegion;
while (1) {
haveUpdate = false;
pthread_mutex_lock(&cl->updateMutex);
while (!haveUpdate) {
if (cl->sock == -1) {
/* Client has disconnected. */
pthread_mutex_unlock(&cl->updateMutex);
return NULL;
}
REGION_INIT(&hackScreen, &updateRegion, NullBox, 0);
REGION_INTERSECT(&hackScreen, &updateRegion,
&cl->modifiedRegion, &cl->requestedRegion);
haveUpdate = REGION_NOTEMPTY(&hackScreen, &updateRegion);
REGION_UNINIT(&hackScreen, &updateRegion);
if (!haveUpdate) {
pthread_cond_wait(&cl->updateCond, &cl->updateMutex);
}
}
/* OK, now, to save bandwidth, wait a little while for more
updates to come along. */
pthread_mutex_unlock(&cl->updateMutex);
usleep(rfbDeferUpdateTime * 1000);
/* Now, get the region we're going to update, and remove
it from cl->modifiedRegion _before_ we send the update.
That way, if anything that overlaps the region we're sending
is updated, we'll be sure to do another update later. */
pthread_mutex_lock(&cl->updateMutex);
REGION_INIT(&hackScreen, &updateRegion, NullBox, 0);
REGION_INTERSECT(&hackScreen, &updateRegion,
&cl->modifiedRegion, &cl->requestedRegion);
REGION_SUBTRACT(&hackScreen, &cl->modifiedRegion,
&cl->modifiedRegion, &updateRegion);
pthread_mutex_unlock(&cl->updateMutex);
/* Now actually send the update. */
rfbSendFramebufferUpdate(cl, updateRegion);
REGION_UNINIT(&hackScreen, &updateRegion);
}
return NULL;
}
static void *
clientInput(void *data)
{
rfbClientPtr cl = (rfbClientPtr)data;
pthread_t output_thread;
pthread_create(&output_thread, NULL, clientOutput, (void *)cl);
while (1) {
rfbProcessClientMessage(cl);
if (cl->sock == -1) {
/* Client has disconnected. */
break;
}
}
/* Get rid of the output thread. */
pthread_mutex_lock(&cl->updateMutex);
pthread_cond_signal(&cl->updateCond);
pthread_mutex_unlock(&cl->updateMutex);
pthread_join(output_thread, NULL);
rfbClientConnectionGone(cl);
return NULL;
}
void*
listenerRun(void *data)
{
rfbScreenInfoPtr rfbScreen=(rfbScreenInfoPtr)data;
int listen_fd, client_fd;
struct sockaddr_in sin, peer;
pthread_t client_thread;
rfbClientPtr cl;
int len, value;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(rfbScreen->rfbPort ? rfbScreen->rfbPort : 5901);
if ((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
return NULL;
}
value = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
&value, sizeof(value)) < 0) {
rfbLog("setsockopt SO_REUSEADDR failed\n");
}
if (bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
rfbLog("failed to bind socket\n");
exit(1);
}
if (listen(listen_fd, 5) < 0) {
rfbLog("listen failed\n");
exit(1);
}
len = sizeof(peer);
while ((client_fd = accept(listen_fd,
(struct sockaddr *)&peer, &len)) >= 0) {
cl = rfbNewClient(rfbScreen,client_fd);
pthread_create(&client_thread, NULL, clientInput, (void *)cl);
len = sizeof(peer);
}
rfbLog("accept failed\n");
exit(1);
}
#endif
static void
usage(void)
{
fprintf(stderr, "-rfbport port TCP port for RFB protocol\n");
fprintf(stderr, "-rfbwait time max time in ms to wait for RFB client\n");
fprintf(stderr, "-rfbauth passwd-file use authentication on RFB protocol\n"
" (use 'storepasswd' to create a password file)\n");
fprintf(stderr, "-deferupdate time time in ms to defer updates "
"(default 40)\n");
fprintf(stderr, "-desktop name VNC desktop name (default \"LibVNCServer\")\n");
fprintf(stderr, "-alwaysshared always treat new clients as shared\n");
fprintf(stderr, "-nevershared never treat new clients as shared\n");
fprintf(stderr, "-dontdisconnect don't disconnect existing clients when a "
"new non-shared\n"
" connection comes in (refuse new connection "
"instead)\n");
exit(1);
}
static void
processArguments(rfbScreenInfoPtr rfbScreen,int argc, char *argv[])
{
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-rfbport") == 0) { /* -rfbport port */
if (i + 1 >= argc) usage();
rfbScreen->rfbPort = atoi(argv[++i]);
} else if (strcmp(argv[i], "-rfbwait") == 0) { /* -rfbwait ms */
if (i + 1 >= argc) usage();
rfbScreen->rfbMaxClientWait = atoi(argv[++i]);
} else if (strcmp(argv[i], "-rfbauth") == 0) { /* -rfbauth passwd-file */
if (i + 1 >= argc) usage();
rfbScreen->rfbAuthPasswdFile = argv[++i];
} else if (strcmp(argv[i], "-deferupdate") == 0) { /* -deferupdate ms */
if (i + 1 >= argc) usage();
rfbScreen->rfbDeferUpdateTime = atoi(argv[++i]);
} else if (strcmp(argv[i], "-desktop") == 0) { /* -desktop desktop-name */
if (i + 1 >= argc) usage();
rfbScreen->desktopName = argv[++i];
} else if (strcmp(argv[i], "-alwaysshared") == 0) {
rfbScreen->rfbAlwaysShared = TRUE;
} else if (strcmp(argv[i], "-nevershared") == 0) {
rfbScreen->rfbNeverShared = TRUE;
} else if (strcmp(argv[i], "-dontdisconnect") == 0) {
rfbScreen->rfbDontDisconnect = TRUE;
} else {
/* usage(); we no longer exit for unknown arguments */
}
}
}
void
defaultKbdAddEvent(Bool down, KeySym keySym, rfbClientPtr cl)
{
}
void
defaultPtrAddEvent(int buttonMask, int x, int y, rfbClientPtr cl)
{
}
void defaultSetXCutText(char* text, int len, rfbClientPtr cl)
{
}
static rfbCursor myCursor =
{
//width: 8, height: 7, xhot: 3, yhot: 3,
width: 8, height: 7, xhot: 0, yhot: 0,
//source: "\000\102\044\030\044\102\000",
//mask: "\347\347\176\074\176\347\347",
source: "\000\074\176\146\176\074\000",
mask: "\176\377\377\377\377\377\176",
foreRed: 0, foreGreen: 0, foreBlue: 0,
backRed: 0xffff, backGreen: 0xffff, backBlue: 0xffff,
#define D "\000\000\000\000"
#define R "\377\000\000\000"
#define G "\000\377\000\000"
#define B "\000\000\377\000"
#define S "\377\377\000\000"
#define H "\000\377\377\000"
#define C "\377\000\377\000"
richSource: 0
/*D D D D D D D D
D D R R R R D D
D S S S S S S D
D G G D D G G D
D H H H H H H D
D D B B B B D D
D D D D D D D D*/
};
rfbCursorPtr defaultGetCursorPtr(rfbClientPtr cl)
{
return(cl->screen->cursor);
}
void doNothingWithClient(rfbClientPtr cl)
{
}
rfbScreenInfoPtr rfbDefaultScreenInit(int argc,char** argv,int width,int height,int bitsPerSample,int samplesPerPixel,int bytesPerPixel)
{
rfbScreenInfoPtr rfbScreen=malloc(sizeof(rfbScreenInfo));
rfbScreen->rfbPort=5900;
rfbScreen->socketInitDone=FALSE;
rfbScreen->inetdSock=-1;
rfbScreen->udpSock=-1;
rfbScreen->udpSockConnected=FALSE;
rfbScreen->maxFd=0;
rfbScreen->rfbListenSock=-1;
rfbScreen->udpPort=0;
rfbScreen->httpPort=0;
rfbScreen->httpDir=NULL;
rfbScreen->httpListenSock=-1;
rfbScreen->httpSock=-1;
rfbScreen->httpFP=NULL;
rfbScreen->inetdInitDone = FALSE;
rfbScreen->desktopName = "LibVNCServer";
rfbScreen->rfbAlwaysShared = FALSE;
rfbScreen->rfbNeverShared = FALSE;
rfbScreen->rfbDontDisconnect = FALSE;
processArguments(rfbScreen,argc,argv);
rfbScreen->width = width;
rfbScreen->height = height;
rfbScreen->bitsPerPixel = rfbScreen->depth = 8*bytesPerPixel;
gethostname(rfbScreen->rfbThisHost, 255);
rfbScreen->paddedWidthInBytes = width*bytesPerPixel;
rfbScreen->rfbServerFormat.bitsPerPixel = rfbScreen->bitsPerPixel;
rfbScreen->rfbServerFormat.depth = rfbScreen->depth;
rfbScreen->rfbServerFormat.bigEndian = !(*(char *)&rfbEndianTest);
rfbScreen->rfbServerFormat.trueColour = TRUE;
/* Why? (TODO)
if (samplesPerPixel != 3) {
rfbLog("screen format not supported. exiting.\n");
exit(1);
}
*/
/* This works for 16 and 32-bit, but not for 8-bit.
What should it be for 8-bit? (Shouldn't 8-bit use a colormap?) */
rfbScreen->rfbServerFormat.redMax = (1 << bitsPerSample) - 1;
rfbScreen->rfbServerFormat.greenMax = (1 << bitsPerSample) - 1;
rfbScreen->rfbServerFormat.blueMax = (1 << bitsPerSample) - 1;
rfbScreen->rfbServerFormat.redShift = bitsPerSample * 2;
rfbScreen->rfbServerFormat.greenShift = bitsPerSample;
rfbScreen->rfbServerFormat.blueShift = 0;
rfbScreen->cursorIsDrawn = FALSE;
rfbScreen->dontSendFramebufferUpdate = FALSE;
rfbScreen->cursorX=rfbScreen->cursorY=rfbScreen->underCursorBufferLen=0;
rfbScreen->underCursorBuffer=NULL;
/* We want to use the X11 REGION_* macros without having an actual
X11 ScreenPtr, so we do this. Pretty ugly, but at least it lets us
avoid hacking up regionstr.h, or changing every call to REGION_*
(which actually I should probably do eventually). */
rfbScreen->screen.RegionCreate = miRegionCreate;
rfbScreen->screen.RegionInit = miRegionInit;
rfbScreen->screen.RegionCopy = miRegionCopy;
rfbScreen->screen.RegionDestroy = miRegionDestroy;
rfbScreen->screen.RegionUninit = miRegionUninit;
rfbScreen->screen.Intersect = miIntersect;
rfbScreen->screen.Union = miUnion;
rfbScreen->screen.Subtract = miSubtract;
rfbScreen->screen.Inverse = miInverse;
rfbScreen->screen.RegionReset = miRegionReset;
rfbScreen->screen.TranslateRegion = miTranslateRegion;
rfbScreen->screen.RectIn = miRectIn;
rfbScreen->screen.PointInRegion = miPointInRegion;
rfbScreen->screen.RegionNotEmpty = miRegionNotEmpty;
rfbScreen->screen.RegionEmpty = miRegionEmpty;
rfbScreen->screen.RegionExtents = miRegionExtents;
rfbScreen->screen.RegionAppend = miRegionAppend;
rfbScreen->screen.RegionValidate = miRegionValidate;
rfbScreen->kbdAddEvent = defaultKbdAddEvent;
rfbScreen->kbdReleaseAllKeys = doNothingWithClient;
rfbScreen->ptrAddEvent = defaultPtrAddEvent;
rfbScreen->setXCutText = defaultSetXCutText;
rfbScreen->getCursorPtr = defaultGetCursorPtr;
rfbScreen->cursor = &myCursor;
rfbScreen->newClientHook = doNothingWithClient;
return(rfbScreen);
}
void
processEvents(rfbScreenInfoPtr rfbScreen,long usec)
{
rfbCheckFds(rfbScreen,usec);
httpCheckFds(rfbScreen);
#ifdef CORBA
corbaCheckFds(rfbScreen);
#endif
{
rfbClientPtr cl,cl_next;
cl=rfbScreen->rfbClientHead;
while(cl) {
cl_next=cl->next;
if(cl->sock>=0 && FB_UPDATE_PENDING(cl)) {
rfbSendFramebufferUpdate(cl,cl->modifiedRegion);
}
cl=cl_next;
}
}
}
void runEventLoop(rfbScreenInfoPtr rfbScreen, long usec, Bool runInBackground)
{
if(runInBackground) {
#ifdef HAVE_PTHREADS
pthread_t listener_thread;
rfbClientListInit(rfbScreen);
//pthread_mutex_init(&logMutex, NULL);
pthread_create(&listener_thread, NULL, listenerRun, rfbScreen);
return;
#else
fprintf(stderr,"Can't run in background, because I don't have PThreads!\n");
#endif
}
rfbInitSockets(rfbScreen);
httpInitSockets(rfbScreen);
while(1)
processEvents(rfbScreen,usec);
}