diff --git a/CHANGES b/CHANGES index 48f7998..2ce6420 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,5 @@ + got patch from Const Kaplisnky with CursorPosUpdate encoding and some Docs + sync'ed with newest RealVNC (ZRLE encoding) a HTTP request for tunnelling was added (to fool strict web proxies) sync'ed with TightVNC 1.2.5 0.4 diff --git a/README b/README index 3ed194b..f6a1586 100644 --- a/README +++ b/README @@ -94,8 +94,6 @@ all the details. Just set the cursor and don't bother any more. To set the mouse coordinates (or emulate mouse clicks), call defaultPtrAddEvent(buttonMask,x,y,cl); -However, this works only if your client doesn't do local cursor drawing. There -is no way (to my knowledge) to set the pointer of a client via RFB protocol. IMPORTANT: do this at the end of your function, because this actually draws the cursor if no cursor encoding is active. diff --git a/cursor.c b/cursor.c index fa28a82..7408a8a 100644 --- a/cursor.c +++ b/cursor.c @@ -24,6 +24,8 @@ #include "rfb.h" +static unsigned char rfbReverseByte[0x100]; + /* * Send cursor shape either in X-style format or in client pixel format. */ @@ -73,8 +75,8 @@ rfbSendCursorShape(cl) sz_rfbFramebufferUpdateRectHeader); cl->ublen += sz_rfbFramebufferUpdateRectHeader; - cl->rfbCursorBytesSent += sz_rfbFramebufferUpdateRectHeader; - cl->rfbCursorUpdatesSent++; + cl->rfbCursorShapeBytesSent += sz_rfbFramebufferUpdateRectHeader; + cl->rfbCursorShapeUpdatesSent++; if (!rfbSendUpdateBuf(cl)) return FALSE; @@ -163,8 +165,8 @@ rfbSendCursorShape(cl) /* Send everything we have prepared in the cl->updateBuf[]. */ - cl->rfbCursorBytesSent += (cl->ublen - saved_ublen); - cl->rfbCursorUpdatesSent++; + cl->rfbCursorShapeBytesSent += (cl->ublen - saved_ublen); + cl->rfbCursorShapeUpdatesSent++; if (!rfbSendUpdateBuf(cl)) return FALSE; @@ -172,8 +174,41 @@ rfbSendCursorShape(cl) return TRUE; } +/* + * Send cursor position (PointerPos pseudo-encoding). + */ + +Bool +rfbSendCursorPos(rfbClientPtr cl) +{ + rfbFramebufferUpdateRectHeader rect; + + if (cl->ublen + sz_rfbFramebufferUpdateRectHeader > UPDATE_BUF_SIZE) { + if (!rfbSendUpdateBuf(cl)) + return FALSE; + } + + rect.encoding = Swap32IfLE(rfbEncodingPointerPos); + rect.r.x = Swap16IfLE(cl->screen->cursorX); + rect.r.y = Swap16IfLE(cl->screen->cursorY); + rect.r.w = 0; + rect.r.h = 0; + + memcpy(&cl->updateBuf[cl->ublen], (char *)&rect, + sz_rfbFramebufferUpdateRectHeader); + cl->ublen += sz_rfbFramebufferUpdateRectHeader; + + cl->rfbCursorPosBytesSent += sz_rfbFramebufferUpdateRectHeader; + cl->rfbCursorPosUpdatesSent++; + + if (!rfbSendUpdateBuf(cl)) + return FALSE; + + return TRUE; +} + /* conversion routine for predefined cursors in LSB order */ -unsigned char rfbReverseByte[0x100] = { +static unsigned char rfbReverseByte[0x100] = { /* copied from Xvnc/lib/font/util/utilbitmap.c */ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, @@ -452,7 +487,7 @@ void rfbPrintXCursor(rfbCursorPtr cursor) } } -extern void rfbSetCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr c,Bool freeOld) +void rfbSetCursor(rfbScreenInfoPtr rfbScreen,rfbCursorPtr c,Bool freeOld) { LOCK(rfbScreen->cursorMutex); while(rfbScreen->cursorIsDrawn) { diff --git a/main.c b/main.c index 044e3c6..0f4bf7d 100644 --- a/main.c +++ b/main.c @@ -364,16 +364,32 @@ 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) { - if(cl->screen->cursorIsDrawn) - rfbUndrawCursor(cl->screen); - LOCK(cl->screen->cursorMutex); - if(!cl->screen->cursorIsDrawn) { - cl->screen->cursorX = x; - cl->screen->cursorY = y; + rfbClientIteratorPtr iterator; + rfbClientPtr other_client; + + if (x != cl->screen->cursorX || y != cl->screen->cursorY) { + if (cl->screen->cursorIsDrawn) + rfbUndrawCursor(cl->screen); + LOCK(cl->screen->cursorMutex); + if (!cl->screen->cursorIsDrawn) { + cl->screen->cursorX = x; + cl->screen->cursorY = y; + } + UNLOCK(cl->screen->cursorMutex); + + /* The cursor was moved by this client, so don't send CursorPos. */ + if (cl->enableCursorPosUpdates) + cl->cursorWasMoved = FALSE; + + /* But inform all remaining clients about this cursor movement. */ + iterator = rfbGetClientIterator(cl->screen); + while ((other_client = rfbClientIteratorNext(iterator)) != NULL) { + if (other_client != cl && other_client->enableCursorPosUpdates) { + other_client->cursorWasMoved = TRUE; } - UNLOCK(cl->screen->cursorMutex); - } + } + rfbReleaseClientIterator(iterator); + } } void defaultSetXCutText(char* text, int len, rfbClientPtr cl) diff --git a/rfb.h b/rfb.h index 7e096f0..856dd9b 100644 --- a/rfb.h +++ b/rfb.h @@ -483,8 +483,10 @@ typedef struct _rfbClientRec { int rfbRectanglesSent[MAX_ENCODINGS]; int rfbLastRectMarkersSent; int rfbLastRectBytesSent; - int rfbCursorBytesSent; - int rfbCursorUpdatesSent; + int rfbCursorShapeBytesSent; + int rfbCursorShapeUpdatesSent; + int rfbCursorPosBytesSent; + int rfbCursorPosUpdatesSent; int rfbFramebufferUpdateMessagesSent; int rfbRawBytesEquivalent; int rfbKeyEventsRcvd; @@ -506,8 +508,10 @@ typedef struct _rfbClientRec { Bool enableLastRectEncoding; /* client supports LastRect encoding */ Bool enableCursorShapeUpdates; /* client supports cursor shape updates */ + Bool enableCursorPosUpdates; /* client supports cursor position updates */ Bool useRichCursorEncoding; /* rfbEncodingRichCursor is preferred */ Bool cursorWasChanged; /* cursor shape update should be sent */ + Bool cursorWasMoved; /* cursor position update should be sent */ Bool useNewFBSize; /* client supports NewFBSize encoding */ Bool newFBSizePending; /* framebuffer size was changed */ @@ -545,6 +549,7 @@ typedef struct _rfbClientRec { ((!(cl)->enableCursorShapeUpdates && !(cl)->screen->cursorIsDrawn) || \ ((cl)->enableCursorShapeUpdates && (cl)->cursorWasChanged) || \ ((cl)->useNewFBSize && (cl)->newFBSizePending) || \ + ((cl)->enableCursorPosUpdates && (cl)->cursorWasMoved) || \ !sraRgnEmpty((cl)->copyRegion) || !sraRgnEmpty((cl)->modifiedRegion)) /* @@ -705,7 +710,7 @@ typedef struct rfbCursor { } rfbCursor, *rfbCursorPtr; extern Bool rfbSendCursorShape(rfbClientPtr cl/*, rfbScreenInfoPtr pScreen*/); -extern unsigned char rfbReverseByte[0x100]; +extern Bool rfbSendCursorPos(rfbClientPtr cl); extern void rfbConvertLSBCursorBitmapOrMask(int width,int height,unsigned char* bitmap); extern rfbCursorPtr rfbMakeXCursor(int width,int height,char* cursorString,char* maskString); extern char* rfbMakeMaskForXCursor(int width,int height,char* cursorString); diff --git a/rfbproto.h b/rfbproto.h index e84f6a0..0fb19b3 100644 --- a/rfbproto.h +++ b/rfbproto.h @@ -2,7 +2,7 @@ #define RFBPROTO_H /* - * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved. + * Copyright (C) 2000-2002 Constantin Kaplinsky. All Rights Reserved. * Copyright (C) 2000 Tridia Corporation. All Rights Reserved. * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. * @@ -327,6 +327,7 @@ typedef struct { #define rfbEncodingXCursor 0xFFFFFF10 #define rfbEncodingRichCursor 0xFFFFFF11 +#define rfbEncodingPointerPos 0xFFFFFF18 #define rfbEncodingLastRect 0xFFFFFF20 #define rfbEncodingNewFBSize 0xFFFFFF21 @@ -502,7 +503,128 @@ typedef struct { /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * Tight Encoding. FIXME: Add more documentation. + * Tight Encoding. + * + *-- The first byte of each Tight-encoded rectangle is a "compression control + * byte". Its format is as follows (bit 0 is the least significant one): + * + * bit 0: if 1, then compression stream 0 should be reset; + * bit 1: if 1, then compression stream 1 should be reset; + * bit 2: if 1, then compression stream 2 should be reset; + * bit 3: if 1, then compression stream 3 should be reset; + * bits 7-4: if 1000 (0x08), then the compression type is "fill", + * if 1001 (0x09), then the compression type is "jpeg", + * if 0xxx, then the compression type is "basic", + * values greater than 1001 are not valid. + * + * If the compression type is "basic", then bits 6..4 of the + * compression control byte (those xxx in 0xxx) specify the following: + * + * bits 5-4: decimal representation is the index of a particular zlib + * stream which should be used for decompressing the data; + * bit 6: if 1, then a "filter id" byte is following this byte. + * + *-- The data that follows after the compression control byte described + * above depends on the compression type ("fill", "jpeg" or "basic"). + * + *-- If the compression type is "fill", then the only pixel value follows, in + * client pixel format (see NOTE 1). This value applies to all pixels of the + * rectangle. + * + *-- If the compression type is "jpeg", the following data stream looks like + * this: + * + * 1..3 bytes: data size (N) in compact representation; + * N bytes: JPEG image. + * + * Data size is compactly represented in one, two or three bytes, according + * to the following scheme: + * + * 0xxxxxxx (for values 0..127) + * 1xxxxxxx 0yyyyyyy (for values 128..16383) + * 1xxxxxxx 1yyyyyyy zzzzzzzz (for values 16384..4194303) + * + * Here each character denotes one bit, xxxxxxx are the least significant 7 + * bits of the value (bits 0-6), yyyyyyy are bits 7-13, and zzzzzzzz are the + * most significant 8 bits (bits 14-21). For example, decimal value 10000 + * should be represented as two bytes: binary 10010000 01001110, or + * hexadecimal 90 4E. + * + *-- If the compression type is "basic" and bit 6 of the compression control + * byte was set to 1, then the next (second) byte specifies "filter id" which + * tells the decoder what filter type was used by the encoder to pre-process + * pixel data before the compression. The "filter id" byte can be one of the + * following: + * + * 0: no filter ("copy" filter); + * 1: "palette" filter; + * 2: "gradient" filter. + * + *-- If bit 6 of the compression control byte is set to 0 (no "filter id" + * byte), or if the filter id is 0, then raw pixel values in the client + * format (see NOTE 1) will be compressed. See below details on the + * compression. + * + *-- The "gradient" filter pre-processes pixel data with a simple algorithm + * which converts each color component to a difference between a "predicted" + * intensity and the actual intensity. Such a technique does not affect + * uncompressed data size, but helps to compress photo-like images better. + * Pseudo-code for converting intensities to differences is the following: + * + * P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1]; + * if (P[i,j] < 0) then P[i,j] := 0; + * if (P[i,j] > MAX) then P[i,j] := MAX; + * D[i,j] := V[i,j] - P[i,j]; + * + * Here V[i,j] is the intensity of a color component for a pixel at + * coordinates (i,j). MAX is the maximum value of intensity for a color + * component. + * + *-- The "palette" filter converts true-color pixel data to indexed colors + * and a palette which can consist of 2..256 colors. If the number of colors + * is 2, then each pixel is encoded in 1 bit, otherwise 8 bits is used to + * encode one pixel. 1-bit encoding is performed such way that the most + * significant bits correspond to the leftmost pixels, and each raw of pixels + * is aligned to the byte boundary. When "palette" filter is used, the + * palette is sent before the pixel data. The palette begins with an unsigned + * byte which value is the number of colors in the palette minus 1 (i.e. 1 + * means 2 colors, 255 means 256 colors in the palette). Then follows the + * palette itself which consist of pixel values in client pixel format (see + * NOTE 1). + * + *-- The pixel data is compressed using the zlib library. But if the data + * size after applying the filter but before the compression is less then 12, + * then the data is sent as is, uncompressed. Four separate zlib streams + * (0..3) can be used and the decoder should read the actual stream id from + * the compression control byte (see NOTE 2). + * + * If the compression is not used, then the pixel data is sent as is, + * otherwise the data stream looks like this: + * + * 1..3 bytes: data size (N) in compact representation; + * N bytes: zlib-compressed data. + * + * Data size is compactly represented in one, two or three bytes, just like + * in the "jpeg" compression method (see above). + * + *-- NOTE 1. If the color depth is 24, and all three color components are + * 8-bit wide, then one pixel in Tight encoding is always represented by + * three bytes, where the first byte is red component, the second byte is + * green component, and the third byte is blue component of the pixel color + * value. This applies to colors in palettes as well. + * + *-- NOTE 2. The decoder must reset compression streams' states before + * decoding the rectangle, if some of bits 0,1,2,3 in the compression control + * byte are set to 1. Note that the decoder must reset zlib streams even if + * the compression type is "fill" or "jpeg". + * + *-- NOTE 3. The "gradient" filter and "jpeg" compression may be used only + * when bits-per-pixel value is either 16 or 32, not 8. + * + *-- NOTE 4. The width of any Tight-encoded rectangle cannot exceed 2048 + * pixels. If a rectangle is wider, it must be split into several rectangles + * and each one should be encoded separately. + * */ #define rfbTightExplicitFilter 0x04 diff --git a/rfbserver.c b/rfbserver.c index 51769be..9cdda1e 100644 --- a/rfbserver.c +++ b/rfbserver.c @@ -293,6 +293,7 @@ rfbNewTCPOrUDPClient(rfbScreen,sock,isUDP) cl->zsActive[i] = FALSE; cl->enableCursorShapeUpdates = FALSE; + cl->enableCursorPosUpdates = FALSE; cl->useRichCursorEncoding = FALSE; cl->enableLastRectEncoding = FALSE; cl->useNewFBSize = FALSE; @@ -696,6 +697,7 @@ rfbProcessClientNormalMessage(cl) cl->preferredEncoding = -1; cl->useCopyRect = FALSE; cl->enableCursorShapeUpdates = FALSE; + cl->enableCursorPosUpdates = FALSE; cl->enableLastRectEncoding = FALSE; cl->useNewFBSize = FALSE; @@ -764,12 +766,20 @@ rfbProcessClientNormalMessage(cl) } break; case rfbEncodingRichCursor: - rfbLog("Enabling full-color cursor updates for client " - "%s\n", cl->host); + rfbLog("Enabling full-color cursor updates for client %s\n", + cl->host); cl->enableCursorShapeUpdates = TRUE; cl->useRichCursorEncoding = TRUE; cl->cursorWasChanged = TRUE; break; + case rfbEncodingPointerPos: + if (!cl->enableCursorPosUpdates) { + rfbLog("Enabling cursor position updates for client %s\n", + cl->host); + cl->enableCursorPosUpdates = TRUE; + cl->cursorWasMoved = TRUE; + } + break; case rfbEncodingLastRect: if (!cl->enableLastRectEncoding) { rfbLog("Enabling LastRect protocol extension for client " @@ -824,6 +834,12 @@ rfbProcessClientNormalMessage(cl) cl->preferredEncoding = rfbEncodingRaw; } + if (cl->enableCursorPosUpdates && !cl->enableCursorShapeUpdates) { + rfbLog("Disabling cursor position updates for client %s\n", + cl->host); + cl->enableCursorPosUpdates = FALSE; + } + return; } @@ -972,6 +988,7 @@ rfbSendFramebufferUpdate(cl, givenUpdateRegion) sraRegionPtr updateRegion,updateCopyRegion,tmpRegion; int dx, dy; Bool sendCursorShape = FALSE; + Bool sendCursorPos = FALSE; if(cl->screen->displayHook) cl->screen->displayHook(cl); @@ -1013,6 +1030,13 @@ rfbSendFramebufferUpdate(cl, givenUpdateRegion) } } + /* + * Do we plan to send cursor position update? + */ + + if (cl->enableCursorPosUpdates && cl->cursorWasMoved) + sendCursorPos = TRUE; + LOCK(cl->updateMutex); /* @@ -1032,7 +1056,8 @@ rfbSendFramebufferUpdate(cl, givenUpdateRegion) updateRegion = sraRgnCreateRgn(givenUpdateRegion); sraRgnOr(updateRegion,cl->copyRegion); - if(!sraRgnAnd(updateRegion,cl->requestedRegion) && !sendCursorShape) { + if(!sraRgnAnd(updateRegion,cl->requestedRegion) && + !sendCursorShape && !sendCursorPos) { sraRgnDestroy(updateRegion); UNLOCK(cl->updateMutex); return TRUE; @@ -1131,8 +1156,9 @@ rfbSendFramebufferUpdate(cl, givenUpdateRegion) fu->type = rfbFramebufferUpdate; if (nUpdateRegionRects != 0xFFFF) { - fu->nRects = Swap16IfLE((CARD16)(sraRgnCountRects(updateCopyRegion) - + nUpdateRegionRects + !!sendCursorShape)); + fu->nRects = Swap16IfLE((CARD16)(sraRgnCountRects(updateCopyRegion) + + nUpdateRegionRects + + !!sendCursorShape + !!sendCursorPos)); } else { fu->nRects = 0xFFFF; } @@ -1146,6 +1172,14 @@ rfbSendFramebufferUpdate(cl, givenUpdateRegion) } } + if (sendCursorPos) { + cl->cursorWasMoved = FALSE; + if (!rfbSendCursorPos(cl)) { + sraRgnDestroy(updateRegion); + return FALSE; + } + } + if (!sraRgnEmpty(updateCopyRegion)) { if (!rfbSendCopyRegion(cl,updateCopyRegion,dx,dy)) { sraRgnDestroy(updateRegion); diff --git a/stats.c b/stats.c index a941741..7d8b6b6 100644 --- a/stats.c +++ b/stats.c @@ -51,8 +51,10 @@ rfbResetStats(rfbClientPtr cl) } cl->rfbLastRectMarkersSent = 0; cl->rfbLastRectBytesSent = 0; - cl->rfbCursorBytesSent = 0; - cl->rfbCursorUpdatesSent = 0; + cl->rfbCursorShapeBytesSent = 0; + cl->rfbCursorShapeUpdatesSent = 0; + cl->rfbCursorPosBytesSent = 0; + cl->rfbCursorPosUpdatesSent = 0; cl->rfbFramebufferUpdateMessagesSent = 0; cl->rfbRawBytesEquivalent = 0; cl->rfbKeyEventsRcvd = 0; @@ -77,9 +79,12 @@ rfbPrintStats(rfbClientPtr cl) totalBytesSent += cl->rfbBytesSent[i]; } - totalRectanglesSent += (cl->rfbCursorUpdatesSent + + totalRectanglesSent += (cl->rfbCursorShapeUpdatesSent + + cl->rfbCursorPosUpdatesSent + cl->rfbLastRectMarkersSent); - totalBytesSent += (cl->rfbCursorBytesSent + cl->rfbLastRectBytesSent); + totalBytesSent += (cl->rfbCursorShapeBytesSent + + cl->rfbCursorPosBytesSent + + cl->rfbLastRectBytesSent); rfbLog(" framebuffer updates %d, rectangles %d, bytes %d\n", cl->rfbFramebufferUpdateMessagesSent, totalRectanglesSent, @@ -89,9 +94,13 @@ rfbPrintStats(rfbClientPtr cl) rfbLog(" LastRect and NewFBSize markers %d, bytes %d\n", cl->rfbLastRectMarkersSent, cl->rfbLastRectBytesSent); - if (cl->rfbCursorUpdatesSent != 0) + if (cl->rfbCursorShapeUpdatesSent != 0) rfbLog(" cursor shape updates %d, bytes %d\n", - cl->rfbCursorUpdatesSent, cl->rfbCursorBytesSent); + cl->rfbCursorShapeUpdatesSent, cl->rfbCursorShapeBytesSent); + + if (cl->rfbCursorPosUpdatesSent != 0) + rfbLog(" cursor position updates %d, bytes %d\n", + cl->rfbCursorPosUpdatesSent, cl->rfbCursorPosBytesSent); for (i = 0; i < MAX_ENCODINGS; i++) { if (cl->rfbRectanglesSent[i] != 0) @@ -105,7 +114,8 @@ rfbPrintStats(rfbClientPtr cl) (double)cl->rfbRawBytesEquivalent / (double)(totalBytesSent - cl->rfbBytesSent[rfbEncodingCopyRect]- - cl->rfbCursorBytesSent - + cl->rfbCursorShapeBytesSent - + cl->rfbCursorPosBytesSent - cl->rfbLastRectBytesSent)); } }