From 0f7130553d0e38d3e0c2add470246a30fb0723a1 Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Thu, 20 Apr 2023 12:01:08 +0200 Subject: [PATCH] libvncserver: Always show ptr. movement to collaborators When multiple viewers are sharing a single TurboVNC session, ordinarily it would be necessary for a viewer to disable remote cursor shape updates in order to see the pointer movements initiated by other viewers. This commit automates that by using server-side cursor rendering (drawing the cursor into the framebuffer, not using remote cursor shape updates) to send cursor updates to all viewers that aren't moving the pointer ("collaborators.") The viewer that is moving the pointer (the "pointer owner") receives remote cursor shape updates while it is moving the pointer, if it has elected to receive them. This mimics the behavior of RealVNC. --- include/rfb/rfb.h | 14 ++++++++++++-- src/libvncserver/cursor.c | 7 +++++-- src/libvncserver/main.c | 21 +++++++++++++++++++-- src/libvncserver/rfbserver.c | 26 ++++++++++++++++++++------ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/include/rfb/rfb.h b/include/rfb/rfb.h index 6cfac6d4..42c88470 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 */ @@ -378,6 +378,14 @@ typedef struct _rfbScreenInfo /** 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; @@ -722,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/src/libvncserver/cursor.c b/src/libvncserver/cursor.c index 51008bde..fd9e43af 100644 --- a/src/libvncserver/cursor.c +++ b/src/libvncserver/cursor.c @@ -781,7 +781,7 @@ void rfbSetCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr c) 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); @@ -794,8 +794,11 @@ 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); diff --git a/src/libvncserver/main.c b/src/libvncserver/main.c index a8e9a581..132ef1fb 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); @@ -1001,6 +1016,8 @@ rfbScreenInfoPtr rfbGetScreen(int* argc,char** argv, screen->underCursorBufferY = 0; INIT_RWLOCK(screen->showCursorRWLock); + screen->pointerOwner = NULL; + if(!rfbProcessArguments(screen,argc,argv)) { free(screen); return NULL; diff --git a/src/libvncserver/rfbserver.c b/src/libvncserver/rfbserver.c index cd3952b6..8849a49f 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); @@ -3576,8 +3590,8 @@ 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);