diff --git a/include/rfb/rfb.h b/include/rfb/rfb.h index 79a446f1b..42c884705 100644 --- a/include/rfb/rfb.h +++ b/include/rfb/rfb.h @@ -277,7 +277,7 @@ typedef struct _rfbScreenInfo rfbBool neverShared; rfbBool dontDisconnect; struct _rfbClientRec* clientHead; - struct _rfbClientRec* pointerClient; /**< "Mutex" for pointer events */ + struct _rfbClientRec* pointerClient; /**< "Mutex" for pointer drag events */ /* cursor */ @@ -372,6 +372,20 @@ typedef struct _rfbScreenInfo #ifdef LIBVNCSERVER_HAVE_LIBZ rfbSetXCutTextUTF8ProcPtr setXCutTextUTF8; #endif + + /** how many clients have requested to show the cursor in the framebuffer */ + int showCursorRefCount; + /** where the cursor is shown */ + int underCursorBufferX, underCursorBufferY; + RWLOCK(showCursorRWLock); + + /* The client that last moved the pointer + Other clients will automatically receive cursor updates via the traditional + mechanism of drawing the cursor into the framebuffer (AKA "server-side + cursor rendering") so they can track the pointer's movement regardless of + whether cursor shape updates (AKA "client-side cursor rendering") are + enabled. */ + struct _rfbClientRec* pointerOwner; } rfbScreenInfo, *rfbScreenInfoPtr; @@ -716,7 +730,9 @@ typedef struct _rfbClientRec { #define FB_UPDATE_PENDING(cl) \ (((cl)->enableCursorShapeUpdates && (cl)->cursorWasChanged) || \ - (((cl)->enableCursorShapeUpdates == FALSE && \ + (( \ + ((cl)->enableCursorShapeUpdates == FALSE || \ + (cl)->screen->pointerOwner != (cl)) && \ ((cl)->cursorX != (cl)->screen->cursorX || \ (cl)->cursorY != (cl)->screen->cursorY))) || \ ((cl)->useNewFBSize && (cl)->newFBSizePending) || \ diff --git a/include/rfb/threading.h b/include/rfb/threading.h index 2a497814b..ee7fca358 100644 --- a/include/rfb/threading.h +++ b/include/rfb/threading.h @@ -29,6 +29,9 @@ #if 0 /* debugging */ #define LOCK(mutex) (rfbLog("%s:%d LOCK(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_lock(&(mutex))) #define UNLOCK(mutex) (rfbLog("%s:%d UNLOCK(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_unlock(&(mutex))) +#define RDLOCK(rwlock) (rfbLog("%s:%d RDLOCK(%s,0x%x)\n",__FILE__,__LINE__,#rwlock,&(rwlock)), pthread_rwlock_rdlock(&(rwlock))) +#define WRLOCK(rwlock) (rfbLog("%s:%d WRLOCK(%s,0x%x)\n",__FILE__,__LINE__,#rwlock,&(rwlock)), pthread_rwlock_wrlock(&(rwlock))) +#define RWUNLOCK(rwlock) (rfbLog("%s:%d RWUNLOCK(%s,0x%x)\n",__FILE__,__LINE__,#rwlock,&(rwlock)), pthread_rwlock_unlock(&(rwlock))) #define MUTEX(mutex) pthread_mutex_t (mutex) #define INIT_MUTEX(mutex) (rfbLog("%s:%d INIT_MUTEX(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_init(&(mutex),NULL)) #define TINI_MUTEX(mutex) (rfbLog("%s:%d TINI_MUTEX(%s)\n",__FILE__,__LINE__,#mutex), pthread_mutex_destroy(&(mutex))) @@ -37,11 +40,22 @@ #define COND(cond) pthread_cond_t (cond) #define INIT_COND(cond) (rfbLog("%s:%d INIT_COND(%s)\n",__FILE__,__LINE__,#cond), pthread_cond_init(&(cond),NULL)) #define TINI_COND(cond) (rfbLog("%s:%d TINI_COND(%s)\n",__FILE__,__LINE__,#cond), pthread_cond_destroy(&(cond))) +#define RWLOCK(rwlock) pthread_rwlock_t (rwlock) +#define INIT_RWLOCK(rwlock) (rfbLog("%s:%d INIT_RWLOCK(%s)\n",__FILE__,__LINE__,#rwlock), pthread_rwlock_init(&(rwlock),NULL)) +#define TINI_RWLOCK(rwlock) (rfbLog("%s:%d TINI_RWLOCK(%s)\n",__FILE__,__LINE__,#rwlock), pthread_rwlock_destroy(&(rwlock))) #define IF_PTHREADS(x) x +#define THREAD_ROUTINE_RETURN_TYPE void* +#define THREAD_ROUTINE_RETURN_VALUE NULL +#define THREAD_SLEEP_MS(ms) usleep(ms*1000) +#define THREAD_JOIN(thread) pthread_join(thread, NULL) +#define CURRENT_THREAD_ID pthread_self() #else #if !NONETWORK #define LOCK(mutex) pthread_mutex_lock(&(mutex)) #define UNLOCK(mutex) pthread_mutex_unlock(&(mutex)) +#define RDLOCK(rwlock) pthread_rwlock_rdlock(&(rwlock)) +#define WRLOCK(rwlock) pthread_rwlock_wrlock(&(rwlock)) +#define RWUNLOCK(rwlock) pthread_rwlock_unlock(&(rwlock)) #endif #define MUTEX(mutex) pthread_mutex_t (mutex) #define MUTEX_SIZE (sizeof(pthread_mutex_t)) @@ -52,6 +66,9 @@ #define COND(cond) pthread_cond_t (cond) #define INIT_COND(cond) pthread_cond_init(&(cond),NULL) #define TINI_COND(cond) pthread_cond_destroy(&(cond)) +#define RWLOCK(rwlock) pthread_rwlock_t (rwlock) +#define INIT_RWLOCK(rwlock) pthread_rwlock_init(&(rwlock),NULL) +#define TINI_RWLOCK(rwlock) pthread_rwlock_destroy(&(rwlock)) #define IF_PTHREADS(x) x #define THREAD_ROUTINE_RETURN_TYPE void* #define THREAD_ROUTINE_RETURN_VALUE NULL @@ -63,6 +80,9 @@ #include #define LOCK(mutex) EnterCriticalSection(&(mutex)) #define UNLOCK(mutex) LeaveCriticalSection(&(mutex)) +#define RDLOCK(rwlock) LOCK((rwlock)) +#define WRLOCK(rwlock) LOCK((rwlock)) +#define RWUNLOCK(rwlock) UNLOCK((rwlock)) #define MUTEX(mutex) CRITICAL_SECTION (mutex) #define MUTEX_SIZE (sizeof(CRITICAL_SECTION)) #define INIT_MUTEX(mutex) InitializeCriticalSection(&(mutex)) @@ -72,6 +92,9 @@ #define COND(cond) CONDITION_VARIABLE (cond) #define INIT_COND(cond) InitializeConditionVariable(&(cond)); #define TINI_COND(cond) +#define RWLOCK(rwlock) MUTEX((rwlock)) +#define INIT_RWLOCK(rwlock) INIT_MUTEX((rwlock)) +#define TINI_RWLOCK(rwlock) TINI_MUTEX((rwlock)) #define IF_PTHREADS(x) #define THREAD_ROUTINE_RETURN_TYPE void #define THREAD_ROUTINE_RETURN_VALUE @@ -81,6 +104,8 @@ #else #define LOCK(mutex) #define UNLOCK(mutex) +#define WRLOCK(rwlock) +#define RWUNLOCK(rwlock) #define MUTEX(mutex) #define INIT_MUTEX(mutex) #define TINI_MUTEX(mutex) @@ -89,6 +114,9 @@ #define COND(cond) #define INIT_COND(cond) #define TINI_COND(cond) +#define RWLOCK(rwlock) +#define INIT_RWLOCK(rwlock) +#define TINI_RWLOCK(rwlock) #define IF_PTHREADS(x) #endif diff --git a/src/libvncserver/cursor.c b/src/libvncserver/cursor.c index b18c2fd85..fd9e43afd 100644 --- a/src/libvncserver/cursor.c +++ b/src/libvncserver/cursor.c @@ -500,13 +500,16 @@ void rfbMakeRichCursorFromXCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr cursor /* functions to draw/hide cursor directly in the frame buffer */ -void rfbHideCursor(rfbClientPtr cl) +void rfbHideCursor(rfbScreenInfoPtr s) { - rfbScreenInfoPtr s=cl->screen; rfbCursorPtr c; int j,x1,x2,y1,y2,bpp=s->serverFormat.bitsPerPixel/8, rowstride=s->paddedWidthInBytes; LOCK(s->cursorMutex); + if(--s->showCursorRefCount != 0) { + UNLOCK(s->cursorMutex); + return; + } c=s->cursor; if(!c) { UNLOCK(s->cursorMutex); @@ -514,7 +517,7 @@ void rfbHideCursor(rfbClientPtr cl) } /* restore what is under the cursor */ - x1=cl->cursorX-c->xhot; + x1=s->underCursorBufferX-c->xhot; x2=x1+c->width; if(x1<0) x1=0; if(x2>=s->width) x2=s->width-1; @@ -522,7 +525,7 @@ void rfbHideCursor(rfbClientPtr cl) UNLOCK(s->cursorMutex); return; } - y1=cl->cursorY-c->yhot; + y1=s->underCursorBufferY-c->yhot; y2=y1+c->height; if(y1<0) y1=0; if(y2>=s->height) y2=s->height-1; @@ -543,9 +546,8 @@ void rfbHideCursor(rfbClientPtr cl) UNLOCK(s->cursorMutex); } -void rfbShowCursor(rfbClientPtr cl) +void rfbShowCursor(rfbScreenInfoPtr s) { - rfbScreenInfoPtr s=cl->screen; rfbCursorPtr c; int i,j,x1,x2,y1,y2,i1,j1,bpp=s->serverFormat.bitsPerPixel/8, rowstride=s->paddedWidthInBytes, @@ -553,6 +555,10 @@ void rfbShowCursor(rfbClientPtr cl) rfbBool wasChanged=FALSE; LOCK(s->cursorMutex); + if(++s->showCursorRefCount != 1) { + UNLOCK(s->cursorMutex); + return; + } c=s->cursor; if(!c) { UNLOCK(s->cursorMutex); @@ -570,7 +576,7 @@ void rfbShowCursor(rfbClientPtr cl) /* save what is under the cursor */ i1=j1=0; /* offset in cursor */ - x1=cl->cursorX-c->xhot; + x1=s->cursorX-c->xhot; x2=x1+c->width; if(x1<0) { i1=-x1; x1=0; } if(x2>=s->width) x2=s->width-1; @@ -579,7 +585,7 @@ void rfbShowCursor(rfbClientPtr cl) return; /* nothing to do */ } - y1=cl->cursorY-c->yhot; + y1=s->cursorY-c->yhot; y2=y1+c->height; if(y1<0) { j1=-y1; y1=0; } if(y2>=s->height) y2=s->height-1; @@ -704,6 +710,10 @@ void rfbShowCursor(rfbClientPtr cl) /* Copy to all scaled versions */ rfbScaledScreenUpdate(s, x1, y1, x1+x2, y1+y2); + /* Memorize the coords where the cursor was drawn */ + s->underCursorBufferX = s->cursorX; + s->underCursorBufferY = s->cursorY; + UNLOCK(s->cursorMutex); } @@ -764,12 +774,14 @@ void rfbSetCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr c) rfbClientIteratorPtr iterator; rfbClientPtr cl; + /* wait until the cursor is not shown in the framebuffer */ + WRLOCK(rfbScreen->showCursorRWLock); LOCK(rfbScreen->cursorMutex); if(rfbScreen->cursor) { iterator=rfbGetClientIterator(rfbScreen); while((cl=rfbClientIteratorNext(iterator))) - if(!cl->enableCursorShapeUpdates) + if(!cl->enableCursorShapeUpdates || (cl->screen->pointerOwner && cl->screen->pointerOwner != cl)) rfbRedrawAfterHideCursor(cl,NULL); rfbReleaseClientIterator(iterator); @@ -782,11 +794,15 @@ void rfbSetCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr c) iterator=rfbGetClientIterator(rfbScreen); while((cl=rfbClientIteratorNext(iterator))) { cl->cursorWasChanged = TRUE; - if(!cl->enableCursorShapeUpdates) + if(!cl->enableCursorShapeUpdates || (cl->screen->pointerOwner && cl->screen->pointerOwner != cl)) rfbRedrawAfterHideCursor(cl,NULL); + LOCK(cl->updateMutex); + TSIGNAL(cl->updateCond); + UNLOCK(cl->updateMutex); } rfbReleaseClientIterator(iterator); UNLOCK(rfbScreen->cursorMutex); + RWUNLOCK(rfbScreen->showCursorRWLock); } diff --git a/src/libvncserver/main.c b/src/libvncserver/main.c index 32519f14a..132ef1fbc 100644 --- a/src/libvncserver/main.c +++ b/src/libvncserver/main.c @@ -732,11 +732,26 @@ rfbDefaultPtrAddEvent(int buttonMask, int x, int y, rfbClientPtr cl) if (cl->enableCursorPosUpdates) cl->cursorWasMoved = FALSE; + /* Will become the pointer owner, need to hide the server-side cursor. */ + if (!cl->screen->pointerOwner) { + rfbRedrawAfterHideCursor(cl, NULL); + LOCK(cl->updateMutex); + TSIGNAL(cl->updateCond); + UNLOCK(cl->updateMutex); + } + /* But inform all remaining clients about this cursor movement. */ iterator = rfbGetClientIterator(s); while ((other_client = rfbClientIteratorNext(iterator)) != NULL) { - if (other_client != cl && other_client->enableCursorPosUpdates) { - other_client->cursorWasMoved = TRUE; + if (other_client != cl) { + if (other_client->enableCursorPosUpdates) { + other_client->cursorWasMoved = TRUE; + } + if (other_client != cl->screen->pointerOwner) { + LOCK(other_client->updateMutex); + TSIGNAL(other_client->updateCond); + UNLOCK(other_client->updateMutex); + } } } rfbReleaseClientIterator(iterator); @@ -996,6 +1011,13 @@ rfbScreenInfoPtr rfbGetScreen(int* argc,char** argv, screen->permitFileTransfer = FALSE; + screen->showCursorRefCount = 0; + screen->underCursorBufferX = 0; + screen->underCursorBufferY = 0; + INIT_RWLOCK(screen->showCursorRWLock); + + screen->pointerOwner = NULL; + if(!rfbProcessArguments(screen,argc,argv)) { free(screen); return NULL; @@ -1156,6 +1178,8 @@ void rfbScreenCleanup(rfbScreenInfoPtr screen) cl1=cl; } rfbReleaseClientIterator(i); + + TINI_RWLOCK(screen->showCursorRWLock); #define FREE_IF(x) if(screen->x) free(screen->x) FREE_IF(colourMap.data.bytes); diff --git a/src/libvncserver/private.h b/src/libvncserver/private.h index 6f28a2956..78baf2586 100644 --- a/src/libvncserver/private.h +++ b/src/libvncserver/private.h @@ -3,8 +3,8 @@ /* from cursor.c */ -void rfbShowCursor(rfbClientPtr cl); -void rfbHideCursor(rfbClientPtr cl); +void rfbShowCursor(rfbScreenInfoPtr s); +void rfbHideCursor(rfbScreenInfoPtr s); void rfbRedrawAfterHideCursor(rfbClientPtr cl,sraRegionPtr updateRegion); /* from main.c */ diff --git a/src/libvncserver/rfbserver.c b/src/libvncserver/rfbserver.c index 3fed80e43..8849a49f8 100644 --- a/src/libvncserver/rfbserver.c +++ b/src/libvncserver/rfbserver.c @@ -627,6 +627,9 @@ rfbClientConnectionGone(rfbClientPtr cl) if (cl->screen->pointerClient == cl) cl->screen->pointerClient = NULL; + if (cl->screen->pointerOwner == cl) + cl->screen->pointerOwner = NULL; + sraRgnDestroy(cl->modifiedRegion); sraRgnDestroy(cl->requestedRegion); sraRgnDestroy(cl->copyRegion); @@ -2688,6 +2691,14 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) cl->screen->pointerClient = cl; if(!cl->viewOnly) { + /* If the pointer was most recently moved by another client, we set + pointerOwner to NULL here so that the client that is currently + moving the pointer (cl), assuming it understands cursor shape + updates, will receive a cursor shape update with the last known + pointer position. */ + if (cl->screen->pointerOwner != cl) + cl->screen->pointerOwner = NULL; + if (msg.pe.buttonMask != cl->lastPtrButtons || cl->screen->deferPtrUpdateTime == 0) { cl->screen->ptrAddEvent(msg.pe.buttonMask, @@ -2700,6 +2711,8 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) cl->lastPtrY = ScaleY(cl->scaledScreen, cl->screen, Swap16IfLE(msg.pe.y)); cl->lastPtrButtons = msg.pe.buttonMask; } + + cl->screen->pointerOwner = cl; } return; @@ -3158,8 +3171,9 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, } /* - * If this client understands cursor shape updates, cursor should be - * removed from the framebuffer. Otherwise, make sure it's put up. + * If this client understands cursor shape updates and owns the pointer or is + * about to own the pointer, then the cursor should be removed from the + * framebuffer. Otherwise, make sure it's drawn. */ if (cl->enableCursorShapeUpdates) { @@ -3264,7 +3278,7 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, sraRgnOr(updateRegion,cl->copyRegion); if(!sraRgnAnd(updateRegion,cl->requestedRegion) && sraRgnEmpty(updateRegion) && - (cl->enableCursorShapeUpdates || + ((cl->enableCursorShapeUpdates && (!cl->screen->pointerOwner || cl->screen->pointerOwner == cl)) || (cl->cursorX == cl->screen->cursorX && cl->cursorY == cl->screen->cursorY)) && !sendCursorShape && !sendCursorPos && !sendKeyboardLedState && !sendSupportedMessages && !sendSupportedEncodings && !sendServerIdentity) { @@ -3319,7 +3333,7 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, UNLOCK(cl->updateMutex); - if (!cl->enableCursorShapeUpdates) { + if (!cl->enableCursorShapeUpdates || (cl->screen->pointerOwner && cl->screen->pointerOwner != cl)) { if(cl->cursorX != cl->screen->cursorX || cl->cursorY != cl->screen->cursorY) { rfbRedrawAfterHideCursor(cl,updateRegion); LOCK(cl->screen->cursorMutex); @@ -3328,7 +3342,12 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, UNLOCK(cl->screen->cursorMutex); rfbRedrawAfterHideCursor(cl,updateRegion); } - rfbShowCursor(cl); + /* show the cursor if the cursor is not shown in the framebuffer, increment the refcount */ + RDLOCK(cl->screen->showCursorRWLock); + rfbShowCursor(cl->screen); + } else { + /* wait until the cursor is not shown in the framebuffer */ + WRLOCK(cl->screen->showCursorRWLock); } /* @@ -3571,9 +3590,10 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, result = FALSE; } - if (!cl->enableCursorShapeUpdates) { - rfbHideCursor(cl); + if (!cl->enableCursorShapeUpdates || (cl->screen->pointerOwner && cl->screen->pointerOwner != cl)) { + rfbHideCursor(cl->screen); } + RWUNLOCK(cl->screen->showCursorRWLock); if(i) sraRgnReleaseIterator(i);