/* * This file is called main.c, because it contains most of the new functions * for use with LibVNCServer. * * LibVNCServer (C) 2001 Johannes E. Schindelin * Original OSXvnc (C) 2001 Dan McGuirk . * Original Xvnc (C) 1999 AT&T Laboratories Cambridge. * All Rights Reserved. * * see GPL (latest version) for full details */ #include #include #include #include #ifndef false #define false 0 #define true -1 #endif #include #include #include #ifdef HAVE_PTHREADS #include #endif #include #include #include #include "rfb.h" #include "region.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; //=(BoxRec*)malloc(sizeof(BoxRec)); RegionRec* region=(RegionRec*)malloc(sizeof(RegionRec)); 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], "-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) { if(x!=cl->screen->cursorX || y!=cl->screen->cursorY) { Bool cursorWasDrawn=cl->screen->cursorIsDrawn; if(cursorWasDrawn) rfbUndrawCursor(cl); cl->screen->cursorX = x; cl->screen->cursorY = y; if(cursorWasDrawn) rfbDrawCursor(cl); } } void defaultSetXCutText(char* text, int len, rfbClientPtr cl) { } /* TODO: add a nice VNC or RFB cursor */ 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 rfbGetScreen(int argc,char** argv, int width,int height,int bitsPerSample,int samplesPerPixel, int bytesPerPixel) { rfbScreenInfoPtr rfbScreen=malloc(sizeof(rfbScreenInfo)); rfbPixelFormat* format=&rfbScreen->rfbServerFormat; if(width&3) fprintf(stderr,"WARNING: Width (%d) is not a multiple of 4. VncViewer has problems with that.\n",width); rfbScreen->rfbPort=5900; rfbScreen->socketInitDone=FALSE; rfbScreen->inetdInitDone = FALSE; rfbScreen->inetdSock=-1; rfbScreen->udpSock=-1; rfbScreen->udpSockConnected=FALSE; rfbScreen->udpPort=0; rfbScreen->maxFd=0; rfbScreen->rfbListenSock=-1; rfbScreen->httpInitDone=FALSE; rfbScreen->httpPort=0; rfbScreen->httpDir=NULL; rfbScreen->httpListenSock=-1; rfbScreen->httpSock=-1; rfbScreen->httpFP=NULL; 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; /* format */ format->bitsPerPixel = rfbScreen->bitsPerPixel; format->depth = rfbScreen->depth; format->bigEndian = rfbEndianTest?FALSE:TRUE; format->trueColour = TRUE; if(bytesPerPixel == 8) { format->redMax = 7; format->greenMax = 7; format->blueMax = 3; format->redShift = 0; format->greenShift = 3; format->blueShift = 6; } else { format->redMax = (1 << bitsPerSample) - 1; format->greenMax = (1 << bitsPerSample) - 1; format->blueMax = (1 << bitsPerSample) - 1; if(rfbEndianTest) { format->redShift = 0; format->greenShift = bitsPerSample; format->blueShift = bitsPerSample * 2; } else { format->redShift = bitsPerSample*3; format->greenShift = bitsPerSample*2; format->blueShift = bitsPerSample; } } /* cursor */ rfbScreen->cursorIsDrawn = FALSE; rfbScreen->dontSendFramebufferUpdate = FALSE; rfbScreen->cursorX=rfbScreen->cursorY=rfbScreen->underCursorBufferLen=0; rfbScreen->underCursorBuffer=NULL; /* proc's and hook's */ rfbScreen->kbdAddEvent = defaultKbdAddEvent; rfbScreen->kbdReleaseAllKeys = doNothingWithClient; rfbScreen->ptrAddEvent = defaultPtrAddEvent; rfbScreen->setXCutText = defaultSetXCutText; rfbScreen->getCursorPtr = defaultGetCursorPtr; rfbScreen->cursor = &myCursor; rfbScreen->newClientHook = doNothingWithClient; /* initialize client list and iterator mutex */ rfbClientListInit(rfbScreen); return(rfbScreen); } void rfbInitServer(rfbScreenInfoPtr rfbScreen) { rfbInitSockets(rfbScreen); httpInitSockets(rfbScreen); } void rfbProcessEvents(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 rfbRunEventLoop(rfbScreenInfoPtr rfbScreen, long usec, Bool runInBackground) { rfbInitServer(rfbScreen); if(runInBackground) { #ifdef HAVE_PTHREADS pthread_t listener_thread; //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 } while(1) rfbProcessEvents(rfbScreen,usec); }