diff -ruN vnc_unixsrc/include/rfbproto.h vnc_unixsrc-col/include/rfbproto.h --- vnc_unixsrc/include/rfbproto.h 2002-10-29 06:18:20.000000000 -0800 +++ vnc_unixsrc-col/include/rfbproto.h 2005-02-09 09:26:42.000000000 -0800 @@ -155,12 +155,14 @@ #define rfbProtocolVersionFormat "RFB %03d.%03d\n" #define rfbProtocolMajorVersion 3 -#define rfbProtocolMinorVersion 3 +#define rfbProtocolMinorVersion 4 typedef char rfbProtocolVersionMsg[13]; /* allow extra byte for null */ #define sz_rfbProtocolVersionMsg 12 +#define rfbVersionIsCollaborative(major,minor) (1000*major+minor >= 3004) + /*----------------------------------------------------------------------------- * Authentication @@ -214,9 +216,11 @@ typedef struct { CARD8 shared; + CARD8 nameLength; } rfbClientInitMsg; -#define sz_rfbClientInitMsg 1 +#define sz_rfbClientInitMsg 2 +#define sz_old_rfbClientInitMsg 1 /*----------------------------------------------------------------------------- @@ -232,10 +236,16 @@ CARD16 framebufferHeight; rfbPixelFormat format; /* the server's preferred pixel format */ CARD32 nameLength; + CARD16 memberID; + CARD8 collaborate; + CARD8 cursorColorRed; + CARD8 cursorColorGreen; + CARD8 cursorColorBlue; /* followed by char name[nameLength] */ } rfbServerInitMsg; -#define sz_rfbServerInitMsg (8 + sz_rfbPixelFormat) +#define sz_rfbServerInitMsg (14 + sz_rfbPixelFormat) +#define sz_old_rfbServerInitMsg (8 + sz_rfbPixelFormat) /* @@ -265,7 +275,11 @@ #define rfbSetColourMapEntries 1 #define rfbBell 2 #define rfbServerCutText 3 - +#define rfbReleaseFloorRequest 4 +#define rfbTakeFloor 5 +#define rfbUpdateMemberList 6 +#define rfbShowMemberPointer 7 +#define rfbMoveMemberPointer 8 /* client -> server */ @@ -276,7 +290,8 @@ #define rfbKeyEvent 4 #define rfbPointerEvent 5 #define rfbClientCutText 6 - +#define rfbTakeFloorRequest 7 +#define rfbReleaseFloor 8 @@ -710,6 +725,19 @@ /*----------------------------------------------------------------------------- + * TakeFloor - Tell client to take the floor + */ + +typedef struct { + CARD8 type; /* always rfbTakeFloor */ + CARD8 allow; +} rfbTakeFloorMsg; + +#define sz_rfbTakeFloorMsg 2 + + + +/*----------------------------------------------------------------------------- * ServerCutText - the server has new text in its cut buffer. */ @@ -725,6 +753,67 @@ /*----------------------------------------------------------------------------- + * ReleaseFloorRequest - Ask this client to release the floor. + * If force is nonzero, this is not a request. :] + */ + +typedef struct { + CARD8 type; /* always rfbReleaseFloorRequest */ + CARD8 force; + CARD16 memberID; +} rfbReleaseFloorRequestMsg; + +#define sz_rfbReleaseFloorRequestMsg 4 + + +/*----------------------------------------------------------------------------- + * MoveMemberPointer - Tell the client to move an inactive pointer. + */ + +typedef struct { + CARD8 type; /* always rfbMoveMemberPointer */ + CARD8 pad1; + CARD16 memberID; + CARD16 x; + CARD16 y; +} rfbMoveMemberPointerMsg; + +#define sz_rfbMoveMemberPointerMsg 8 + + +/*----------------------------------------------------------------------------- + * UpdateMemberList - Add or Remove a member + */ + +typedef struct { + CARD8 type; /* always rfbUpdateMemberList */ + CARD8 add; /* 1 for add, 0 for remove */ + CARD8 colorRed; + CARD8 colorGreen; + CARD8 colorBlue; + CARD8 master; /* if 1 then this member is a master */ + CARD16 memberID; + CARD32 length; /* 0 when add=0 */ + /* followed by char text[length] when add=1 */ +} rfbUpdateMemberListMsg; + +#define sz_rfbUpdateMemberListMsg 12 + + +/*----------------------------------------------------------------------------- + * ShowMemberPointer - Tell the client to show or hide a pointer. + */ + +typedef struct { + CARD8 type; /* always rfbShowMemberPointer */ + CARD8 visible; + CARD16 memberID; +} rfbShowMemberPointerMsg; + +#define sz_rfbShowMemberPointerMsg 4 + + +/*----------------------------------------------------------------------------- * Union of all server->client messages. */ @@ -734,10 +823,16 @@ rfbSetColourMapEntriesMsg scme; rfbBellMsg b; rfbServerCutTextMsg sct; + rfbReleaseFloorRequestMsg rfr; + rfbTakeFloorMsg tf; + rfbMoveMemberPointerMsg mmp; + rfbShowMemberPointerMsg smp; + rfbUpdateMemberListMsg uml; } rfbServerToClientMsg; + /***************************************************************************** * * Message definitions (client -> server) @@ -892,6 +987,33 @@ /*----------------------------------------------------------------------------- + * TakeFloorRequest - Ask this server to ask the other clients to release the floor. + */ + +typedef struct { + CARD8 type; /* always rfbTakeFloorRequest */ +} rfbTakeFloorRequestMsg; + +#define sz_rfbTakeFloorRequestMsg 1 + + + +/*----------------------------------------------------------------------------- + * ReleaseFloor - Tell the server that this client released the floor. + * Optionally specify another memberID to cede control to. + */ + +typedef struct { + CARD8 type; /* always rfbReleaseFloor */ + CARD8 allow; + CARD16 memberID; /* if non-0, give floor to this member */ +} rfbReleaseFloorMsg; + +#define sz_rfbReleaseFloorMsg 4 + + + +/*----------------------------------------------------------------------------- * Union of all client->server messages. */ @@ -904,4 +1026,6 @@ rfbKeyEventMsg ke; rfbPointerEventMsg pe; rfbClientCutTextMsg cct; + rfbTakeFloorRequestMsg tfr; + rfbReleaseFloorMsg rf; } rfbClientToServerMsg; diff -ruN vnc_unixsrc/vncserver vnc_unixsrc-col/vncserver --- vnc_unixsrc/vncserver 2003-07-31 07:19:37.000000000 -0700 +++ vnc_unixsrc-col/vncserver 2005-02-09 09:26:42.000000000 -0800 @@ -33,7 +33,7 @@ # Global variables. You may want to configure some of these for your site. # -$geometry = "1024x768"; +$geometry = "600x800"; $depth = 24; $desktopName = "X"; $vncClasses = "/usr/local/vnc/classes"; @@ -81,7 +81,7 @@ # Check command line options &ParseOptions("-geometry",1,"-depth",1,"-pixelformat",1,"-name",1,"-kill",1, - "-help",0,"-h",0,"--help",0); + "-exec",1,"-help",0,"-h",0,"--help",0); &Usage() if ($opt{'-help'} || $opt{'-h'} || $opt{'--help'}); @@ -211,6 +211,26 @@ warn "\nNew '$desktopName' desktop is $host:$displayNumber\n\n"; + +# If the unix domain socket exists then use that (DISPLAY=:n) otherwise use +# TCP (DISPLAY=host:n) + +if (-e "/tmp/.X11-unix/X$displayNumber") { + $ENV{DISPLAY}= ":$displayNumber"; +} else { + $ENV{DISPLAY}= "$host:$displayNumber"; +} +$ENV{VNCDESKTOP}= $desktopName; + + +# Run the -exec argument instead of the xstartup script if -exec was passed +if ($opt{'-exec'}) { + system("$opt{'-exec'} >> " . "edString($desktopLog) . " 2>&1"); + $opt{'-kill'} = ":$displayNumber"; + &Kill(); + exit; +} + # Create the user's xstartup script if necessary. unless (-e "$xstartup") { @@ -226,16 +246,6 @@ warn "Starting applications specified in $xstartup\n"; warn "Log file is $desktopLog\n\n"; -# If the unix domain socket exists then use that (DISPLAY=:n) otherwise use -# TCP (DISPLAY=host:n) - -if (-e "/tmp/.X11-unix/X$displayNumber") { - $ENV{DISPLAY}= ":$displayNumber"; -} else { - $ENV{DISPLAY}= "$host:$displayNumber"; -} -$ENV{VNCDESKTOP}= $desktopName; - system("$xstartup >> " . "edString($desktopLog) . " 2>&1 &"); exit; diff -ruN vnc_unixsrc/vncviewer/argsresources.c vnc_unixsrc-col/vncviewer/argsresources.c --- vnc_unixsrc/vncviewer/argsresources.c 2003-07-31 07:19:37.000000000 -0700 +++ vnc_unixsrc-col/vncviewer/argsresources.c 2005-02-09 09:26:42.000000000 -0800 @@ -47,6 +47,7 @@ "*desktop.baseTranslations:\ F8: ShowPopup()\\n\ + F9: ToggleFloorControl()\\n\ : SendRFBEvent()\\n\ : SendRFBEvent()\\n\ : SendRFBEvent()\\n\ @@ -74,7 +75,7 @@ "*popup.buttonForm.translations: #override\\n\ : SendRFBEvent() HidePopup()", - "*popupButtonCount: 8", + "*popupButtonCount: 10", "*popup*button1.label: Dismiss popup", "*popup*button1.translations: #override\\n\ @@ -115,6 +116,12 @@ "*popup*button8.translations: #override\\n\ ,: SendRFBEvent(key,F8) HidePopup()", + "*popup*button9.label: Send F9", + "*popup*button9.translations: #override\\n\ + ,: SendRFBEvent(key,F9) HidePopup()", + + "*popup*button10.label: ---- Members: ----", + NULL }; @@ -220,6 +227,15 @@ {"grabKeyboard", "GrabKeyboard", XtRBool, sizeof(Bool), XtOffsetOf(AppData, grabKeyboard), XtRImmediate, (XtPointer) False}, + + {"autoReleaseDelay", "AutoReleaseDelay", XtRInt, sizeof(int), + XtOffsetOf(AppData, autoReleaseDelay), XtRImmediate, (XtPointer) 2}, + + {"name", "Name", XtRString, sizeof(String), + XtOffsetOf(AppData, name), XtRImmediate, (XtPointer) 0}, + + {"showPopups", "ShowPopups", XtRBool, sizeof(Bool), + XtOffsetOf(AppData, showPopups), XtRImmediate, (XtPointer) True}, }; @@ -246,7 +262,12 @@ {"-nojpeg", "*enableJPEG", XrmoptionNoArg, "False"}, {"-nocursorshape", "*useRemoteCursor", XrmoptionNoArg, "False"}, {"-x11cursor", "*useX11Cursor", XrmoptionNoArg, "True"}, - + {"-autoreleasedelay","*autoReleaseDelay", XrmoptionSepArg, 0}, + {"-noautorelease", "*autoReleaseDelay", XrmoptionNoArg, "-1"}, + {"-ard", "*autoReleaseDelay", XrmoptionSepArg, 0}, + {"-nar", "*autoReleaseDelay", XrmoptionNoArg, "-1"}, + {"-name", "*name", XrmoptionSepArg, 0}, + {"-nopopups", "*showPopups", XrmoptionNoArg, "False"}, }; int numCmdLineOptions = XtNumber(cmdLineOptions); @@ -262,6 +283,8 @@ {"HidePopup", HidePopup}, {"ToggleFullScreen", ToggleFullScreen}, {"SetFullScreenState", SetFullScreenState}, + {"SetFloorControlState", SetFloorControlState}, + {"ToggleFloorControl", ToggleFloorControl}, {"SelectionFromVNC", SelectionFromVNC}, {"SelectionToVNC", SelectionToVNC}, {"ServerDialogDone", ServerDialogDone}, @@ -311,7 +334,7 @@ " -noraiseonbeep\n" " -passwd \n" " -encodings (e.g. \"tight copyrect\")\n" - " -bgr233\n" + " -bgr233 (or -bgr)\n" " -owncmap\n" " -truecolour\n" " -depth \n" @@ -320,6 +343,10 @@ " -nojpeg\n" " -nocursorshape\n" " -x11cursor\n" + " -autoreleasedelay (or -ard)\n" + " -noautorelease (or -nar)\n" + " -name (your username by default)\n" + " -nopopups\n" "\n" "Option names may be abbreviated, e.g. -bgr instead of -bgr233.\n" "See the manual page for more information." @@ -406,4 +433,5 @@ } vncServerPort = atoi(colonPos + 1) + portOffset; } + } diff -ruN vnc_unixsrc/vncviewer/collaborate.c vnc_unixsrc-col/vncviewer/collaborate.c --- vnc_unixsrc/vncviewer/collaborate.c 1969-12-31 16:00:00.000000000 -0800 +++ vnc_unixsrc-col/vncviewer/collaborate.c 2005-02-09 09:26:42.000000000 -0800 @@ -0,0 +1,262 @@ +/* + * 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. + */ + +/* + * collaborate.c - code to handle collaborative + */ + +#include +#include + +MemberPtr floorOwner = NULL; +char *windowTitle; +time_t lastEventTime; +short held_x, held_y, held_buttonMask; + +MemberPtr memberHead = NULL; +time_t lastOwnerNameTime = -1; + + +void AddMember(short memberID, Bool master, int colorRed, int colorGreen, int colorBlue, char *name) +{ + MemberPtr newMember; + MemberPtr m; + + newMember = GetMemberFromID(memberID); + + if(newMember) { + if (newMember->name) + free(newMember->name); + } else { + newMember = malloc(sizeof(Member)); + newMember->memberID = memberID; + newMember->x = -1; + newMember->y = -1; + + if (!memberHead) + memberHead = newMember; + else { + if (memberHead->memberID > newMember->memberID) { + newMember->next = memberHead; + memberHead = newMember; + } else { + for (m = memberHead; m; m = m->next) { + if (m->next && m->next->memberID > newMember->memberID) { + newMember->next = m->next; + m->next = newMember; + break; + } + if (m->next == NULL) { + m->next = newMember; + newMember->next = NULL; + } + } + } + } + } + + newMember->master = master; + newMember->name = name; + newMember->colorRed = colorRed; + newMember->colorGreen = colorGreen; + newMember->colorBlue = colorBlue; +} + + +void RemoveMember(short memberID) +{ + MemberPtr m; + MemberPtr old; + + if (!memberHead) return; + + if (memberHead->memberID == memberID) { + old = memberHead; + memberHead = memberHead->next; + free(old->name); + free(old); + return; + } + + for (m = memberHead; m; m = m->next) { + if (m->next && m->next->memberID == memberID) { + old = m->next; + m->next = m->next->next; + free(old->name); + free(old); + return; + } + } +} + + +MemberPtr GetMemberFromID(short memberID) +{ + MemberPtr m; + + if (!memberHead) + return NULL; + + for (m = memberHead; m; m = m->next) { + if (m->memberID == memberID) + return m; + } + + return NULL; +} + + +void ShowMemberPointer(short memberID, Bool visible) +{ + int i; + int oldx, oldy; + MemberPtr m = GetMemberFromID(memberID); + + if (!m) return; + + if (visible) { + DrawMemberPointer(m, -1, -1); + } else { + oldx = m->x; + oldy = m->y; + m->x = -1; + m->y = -1; + DrawMemberPointer(m, oldx, oldy); + } +} + + +void MoveMemberPointer(short memberID, int x, int y) +{ + int oldx, oldy; + MemberPtr m = GetMemberFromID(memberID); + + if (!m) return; + + oldx = m->x; + oldy = m->y; + m->x = x; + m->y = y; + + DrawMemberPointer(m, oldx, oldy); +} + + +Bool TakeFloorRequest() +{ + if (appData.collaborate) + return SendTakeFloorRequestEvent(0); + + return FALSE; +} + + + +Bool ReleaseFloor(Bool allow, MemberPtr m) +{ + if (floorOwner == appData.me && allow) + floorOwner = m; + + if (appData.collaborate) + return SendReleaseFloorEvent(allow, (m) ? m->memberID : 0); + + return TRUE; +} + + +Bool CheckReleaseFloor(void) +{ + return (appData.autoReleaseDelay != -1 && difftime(time(NULL), lastEventTime) >= appData.autoReleaseDelay); +} + + + +void UpdateTitleWithFloorOwner() +{ + char *title; + MemberPtr m; + + if (floorOwner) { + title = XtMalloc(strlen(windowTitle) + strlen(floorOwner->name) + 19 + 1); + sprintf(title, "%s -- Controlled by: %s", windowTitle, floorOwner->name); + XtVaSetValues(toplevel, XtNtitle, title, XtNiconName, title, NULL); + XtFree(title); + + if (appData.showPopups) { + if (lastOwnerNameTime != -1) + ClearFloorOwner(); + + lastOwnerNameTime = time(NULL); + DrawNewFloorOwner(floorOwner->name); + } + } else { + XtVaSetValues(toplevel, XtNtitle, windowTitle, XtNiconName, windowTitle, NULL); + } +} + + +void +GiveFloorToMember(Widget w, XtPointer client_data, XtPointer call_data) +{ + if (!appData.collaborate) + return; + + if(((MemberPtr)client_data) != floorOwner || !floorOwner || floorOwner == appData.me) { + if (((MemberPtr)client_data) == appData.me) { + ToggleFloorControl(w, NULL, NULL, NULL); + } else { + ReleaseFloor(TRUE, ((MemberPtr)client_data)); + UpdateTitleWithFloorOwner(); + } + } + HidePopup(w, NULL, NULL, NULL); +} + + +/* + * SetFloorControlState is an action which sets the "state" resource of a toggle + * widget to reflect whether we're in control of this Desktop. + */ + +void +SetFloorControlState(Widget w, XEvent *ev, String *params, Cardinal *num_params) +{ + if (floorOwner == appData.me) + XtVaSetValues(w, XtNstate, True, NULL); + else + XtVaSetValues(w, XtNstate, False, NULL); +} + + +/* + * ToggleFloorControl is an action which toggles between requesting the floor, and yielding it + */ + +void +ToggleFloorControl(Widget w, XEvent *ev, String *params, Cardinal *num_params) +{ + if (appData.collaborate) { + if (floorOwner == appData.me) { + ReleaseFloor(TRUE, NULL); + UpdateTitleWithFloorOwner(); + } else { + TakeFloorRequest(); + } + } +} diff -ruN vnc_unixsrc/vncviewer/desktop.c vnc_unixsrc-col/vncviewer/desktop.c --- vnc_unixsrc/vncviewer/desktop.c 2003-07-30 22:01:00.000000000 -0700 +++ vnc_unixsrc-col/vncviewer/desktop.c 2005-02-11 09:04:30.000000000 -0800 @@ -28,17 +28,23 @@ #include #endif +#define MIN(a,b) ((ab)?a:b) + GC gc; +XFontStruct *font; +int fontHeight; GC srcGC, dstGC; /* used for debugging copyrect */ Window desktopWin; Cursor dotCursor; Widget form, viewport, desktop; +char *lastFloorName=NULL; static Bool modifierPressed[256]; -static XImage *image = NULL; +XImage *image = NULL; -static Cursor CreateDotCursor(); +static Cursor CreateDotCursor(char red, char green, char blue); static void CopyBGR233ToScreen(CARD8 *buf, int x, int y, int width,int height); static void HandleBasicDesktopEvent(Widget w, XtPointer ptr, XEvent *ev, Boolean *cont); @@ -120,7 +126,11 @@ desktopWin = XtWindow(desktop); - gc = XCreateGC(dpy,desktopWin,0,NULL); + font = XLoadQueryFont(dpy, "fixed"); + fontHeight = font->max_bounds.ascent + font->max_bounds.descent; + gcv.font = font->fid; + + gc = XCreateGC(dpy, desktopWin, GCFont, &gcv); gcv.function = GXxor; gcv.foreground = 0x0f0f0f0f; @@ -136,7 +146,11 @@ valuemask = CWBackingStore; if (!appData.useX11Cursor) { - dotCursor = CreateDotCursor(); + if (appData.collaborate) + dotCursor = CreateDotCursor(appData.me->colorRed, + appData.me->colorGreen, appData.me->colorBlue); + else + dotCursor = CreateDotCursor(0,0,0); attr.cursor = dotCursor; valuemask |= CWCursor; } @@ -260,7 +274,8 @@ x = atoi(params[1]); y = atoi(params[2]); buttonMask = atoi(params[3]); - SendPointerEvent(x, y, buttonMask); + if (!appData.collaborate || floorOwner == appData.me) + SendPointerEvent(x, y, buttonMask); } else if (*num_params == 2) { switch (ev->type) { case ButtonPress: @@ -279,7 +294,8 @@ return; } buttonMask = atoi(params[1]); - SendPointerEvent(x, y, buttonMask); + if (!appData.collaborate || floorOwner == appData.me) + SendPointerEvent(x, y, buttonMask); } else { fprintf(stderr, "Invalid params: SendRFBEvent(ptr,,,)\n" @@ -304,6 +320,14 @@ return; case ButtonPress: + if (appData.collaborate && floorOwner != appData.me) { + held_x = ev->xbutton.x; + held_y = ev->xbutton.y; + held_buttonMask = (((ev->xbutton.state & 0x1f00) >> 8) | + (1 << (ev->xbutton.button - 1))); + TakeFloorRequest(); + } + SendPointerEvent(ev->xbutton.x, ev->xbutton.y, (((ev->xbutton.state & 0x1f00) >> 8) | (1 << (ev->xbutton.button - 1)))); @@ -313,6 +337,10 @@ SendPointerEvent(ev->xbutton.x, ev->xbutton.y, (((ev->xbutton.state & 0x1f00) >> 8) & ~(1 << (ev->xbutton.button - 1)))); + + if (floorOwner == appData.me) + lastEventTime = time(NULL); + return; case KeyPress: @@ -325,6 +353,10 @@ } SendKeyEvent(ks, (ev->type == KeyPress)); + + if (!appData.collaborate || floorOwner == appData.me) + lastEventTime = time(NULL); + return; default: @@ -338,7 +370,7 @@ */ static Cursor -CreateDotCursor() +CreateDotCursor(char red, char green, char blue) { Cursor cursor; Pixmap src, msk; @@ -348,8 +380,14 @@ src = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), srcBits, 5, 5); msk = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), mskBits, 5, 5); - XAllocNamedColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), "black", - &fg, &fg); + + fg.red = red <<8; + fg.green = green <<8; + fg.blue = blue <<8; + + if (!XAllocColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &fg)) + exit(1); + XAllocNamedColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), "white", &bg, &bg); cursor = XCreatePixmapCursor(dpy, src, msk, &fg, &bg, 2, 2); @@ -468,3 +506,118 @@ break; } } + + +void +DrawMemberPointer(MemberPtr m, int oldx, int oldy) +{ + if (oldx != -1) { +#ifdef MITSHM + if (appData.useShm) + XShmPutImage(dpy, desktopWin, gc, image, + MAX(oldx-2, 0), MAX(oldy - 2, 0), + MAX(oldx-2, 0), MAX(oldy - 2, 0), + MIN(5,si.framebufferWidth-oldx +1), + MIN(5,si.framebufferHeight-oldy +1), False); + else +#endif + XPutImage(dpy, desktopWin, gc, image, MAX(oldx-2, 0), MAX(oldy - 2, 0), + MAX(oldx-2, 0), MAX(oldy - 2, 0), + MIN(5,si.framebufferWidth-oldx +1), + MIN(5,si.framebufferHeight-oldy +1)); + ClearName(m->name, oldx+5, oldy-5); + } + /* Re-draw the Floor Owner's name */ + if (floorOwner && lastOwnerNameTime != -1) + DrawNewFloorOwner(floorOwner->name); + + if (m->x != -1) { + XColor color; + + color.red = m->colorRed <<8; + color.green = m->colorGreen <<8; + color.blue = m->colorBlue <<8; + + if (!XAllocColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &color)) + exit(1); + + XSetForeground(dpy, gc, WhitePixel(dpy, DefaultScreen(dpy))); + XFillRectangle(dpy, desktopWin, gc, MAX(m->x-2, 0), MAX(m->y - 2, 0), + MIN(5,si.framebufferWidth - m->x +1), + MIN(5,si.framebufferHeight - m->y +1)); + + XSetForeground(dpy, gc, color.pixel); + XFillRectangle(dpy, desktopWin, gc, MAX(m->x-1, 1), MAX(m->y - 1, 1), + MIN(3,si.framebufferWidth - m->x), + MIN(3,si.framebufferHeight - m->y)); + + DrawName(m->name, m->x+5, m->y-5); + + XSetForeground(dpy, gc, BlackPixel(dpy, DefaultScreen(dpy))); + } +} + + +void +DrawNewFloorOwner(char *name) +{ + DrawName(name, 4,4); + lastFloorName = name; +} + + +void +DrawName(char *name, int x, int y) +{ + XColor color; + char *displayname; + + color.red = 0xe000; + color.green = 0xe000; + color.blue = 0xe000; + + if (!XAllocColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &color)) + exit(1); + + XSetBackground(dpy, gc, color.pixel); + XSetForeground(dpy, gc, BlackPixel(dpy, DefaultScreen(dpy))); + + displayname = malloc(strlen(name)+3); + sprintf(displayname, " %s ", name); + XDrawImageString(dpy, desktopWin, gc, x,MAX(0,y)+font->max_bounds.ascent, displayname, strlen(displayname)); + free(displayname); +} + + +void +ClearFloorOwner(void) +{ + ClearName(lastFloorName, 4,4); + lastOwnerNameTime = -1; +} + + +void +ClearName(char *name, int x, int y) +{ + int width; + char *displayname; + + if (name) { + displayname = malloc(strlen(name)+3); + sprintf(displayname, " %s ", name); + width = XTextWidth(font, displayname, strlen(displayname)); + free(displayname); + } else { + width = 0; + } + + if (width < 1) return; + +#ifdef MITSHM + if (appData.useShm) + XShmPutImage(dpy, desktopWin, gc, image, x,MAX(0,y), x,MAX(0,y), width,fontHeight, False); + else +#endif + XPutImage(dpy, desktopWin, gc, image, x,MAX(0,y), x,MAX(0,y), width,fontHeight); +} diff -ruN vnc_unixsrc/vncviewer/Imakefile vnc_unixsrc-col/vncviewer/Imakefile --- vnc_unixsrc/vncviewer/Imakefile 2002-06-27 22:53:19.000000000 -0700 +++ vnc_unixsrc-col/vncviewer/Imakefile 2005-02-09 09:26:42.000000000 -0800 @@ -40,7 +40,8 @@ shm.c \ sockets.c \ tunnel.c \ - vncviewer.c + vncviewer.c \ + collaborate.c OBJS = $(SRCS:.c=.o) diff -ruN vnc_unixsrc/vncviewer/misc.c vnc_unixsrc-col/vncviewer/misc.c --- vnc_unixsrc/vncviewer/misc.c 2003-01-14 23:58:32.000000000 -0800 +++ vnc_unixsrc-col/vncviewer/misc.c 2005-02-09 09:26:42.000000000 -0800 @@ -33,6 +33,7 @@ Dimension dpyWidth, dpyHeight; Atom wmDeleteWindow, wmState; +char *windowTitle; static Bool xloginIconified = False; static XErrorHandler defaultXErrorHandler; @@ -57,6 +58,9 @@ sprintf(title, titleFormat, desktopName); XtVaSetValues(toplevel, XtNtitle, title, XtNiconName, title, NULL); + windowTitle = XtMalloc( strlen(title) + 1 ); + strcpy(windowTitle, title); + XtVaSetValues(toplevel, XtNmaxWidth, si.framebufferWidth, XtNmaxHeight, si.framebufferHeight, NULL); diff -ruN vnc_unixsrc/vncviewer/popup.c vnc_unixsrc-col/vncviewer/popup.c --- vnc_unixsrc/vncviewer/popup.c 2000-06-11 05:00:53.000000000 -0700 +++ vnc_unixsrc-col/vncviewer/popup.c 2005-02-09 09:26:42.000000000 -0800 @@ -27,11 +27,13 @@ #include #include -Widget popup, fullScreenToggle; +Widget popup = NULL, fullScreenToggle; void ShowPopup(Widget w, XEvent *event, String *params, Cardinal *num_params) { + if (popup) XtDestroyWidget(popup); + CreatePopup(); XtMoveWidget(popup, event->xbutton.x_root, event->xbutton.y_root); XtPopup(popup, XtGrabNone); XSetWMProtocols(dpy, XtWindow(popup), &wmDeleteWindow, 1); @@ -56,9 +58,11 @@ { Widget buttonForm, button, prevButton = NULL; int i; - char buttonName[12]; + char buttonName[32]; String buttonType; + MemberPtr m; + popup = XtVaCreatePopupShell("popup", transientShellWidgetClass, toplevel, NULL); @@ -90,4 +94,37 @@ } prevButton = button; } + + XtSetSensitive(prevButton, False); + + if (appData.collaborate) { + for (m = memberHead; m; m = m->next) { + buttonName[0] = (m == floorOwner) ? '*' : ' '; + buttonName[1] = ' '; + if (strlen(m->name) > 29) { + strncpy(buttonName+2, m->name, 13); + buttonName[15] = buttonName[16] = buttonName[17] = '.'; + strcpy(buttonName+18, m->name+(strlen(m->name)-13)); + } else { + strcpy(buttonName+2, m->name); + } + button = XtVaCreateManagedWidget (buttonName, commandWidgetClass, + buttonForm, NULL); + XtVaSetValues (button, XtNfromVert, prevButton, XtNleft, + XawChainLeft, XtNright, XawChainRight, NULL); + + if (floorOwner == appData.me || appData.me->master) { + if (m == floorOwner && floorOwner != appData.me) + XtSetSensitive(button, False); + else + XtAddCallback(button, XtNcallback, GiveFloorToMember, m); + } else { + if (m == appData.me) + XtAddCallback(button, XtNcallback, GiveFloorToMember, m); + else + XtSetSensitive(button, False); + } + prevButton = button; + } + } } diff -ruN vnc_unixsrc/vncviewer/rfbproto.c vnc_unixsrc-col/vncviewer/rfbproto.c --- vnc_unixsrc/vncviewer/rfbproto.c 2003-01-15 01:46:52.000000000 -0800 +++ vnc_unixsrc-col/vncviewer/rfbproto.c 2005-02-09 09:26:42.000000000 -0800 @@ -152,7 +152,9 @@ CARD8 challenge[CHALLENGESIZE]; char *passwd; int i; + int clientInitMsgSize; rfbClientInitMsg ci; + Bool collaborativeServer; /* if the connection is immediately closed, don't report anything, so that pmw's monitor can make test connections */ @@ -171,6 +173,8 @@ return False; } + collaborativeServer = rfbVersionIsCollaborative(major,minor); + fprintf(stderr,"VNC server supports protocol version %d.%d (viewer %d.%d)\n", major, minor, rfbProtocolMajorVersion, rfbProtocolMinorVersion); @@ -262,11 +266,18 @@ return False; } + if(!appData.name) + appData.name = getpwuid(getuid())->pw_name; + ci.shared = (appData.shareDesktop ? 1 : 0); + ci.nameLength = strlen(appData.name); - if (!WriteExact(rfbsock, (char *)&ci, sz_rfbClientInitMsg)) return False; + clientInitMsgSize = (collaborativeServer) ? sz_rfbClientInitMsg : sz_old_rfbClientInitMsg; + if (!WriteExact(rfbsock, (char *)&ci, clientInitMsgSize)) return False; + if (collaborativeServer && !WriteExact(rfbsock, appData.name, ci.nameLength)) return False; - if (!ReadFromRFBServer((char *)&si, sz_rfbServerInitMsg)) return False; + if (!ReadFromRFBServer((char *)&si, (collaborativeServer) ? + sz_rfbServerInitMsg : sz_old_rfbServerInitMsg)) return False; si.framebufferWidth = Swap16IfLE(si.framebufferWidth); si.framebufferHeight = Swap16IfLE(si.framebufferHeight); @@ -294,6 +305,21 @@ fprintf(stderr,"VNC server default format:\n"); PrintPixelFormat(&si.format); + appData.collaborate = (collaborativeServer) ? si.collaborate : FALSE; + + if (appData.collaborate){ + appData.me = malloc(sizeof(Member)); + appData.me->memberID = si.memberID; + appData.me->name = appData.name; + appData.me->colorRed = si.cursorColorRed; + appData.me->colorGreen = si.cursorColorGreen; + appData.me->colorBlue = si.cursorColorBlue; + appData.me->next = NULL; + + memberHead = appData.me; + + appData.useRemoteCursor = False; + } return True; } @@ -520,6 +546,38 @@ /* + * SendTakeFloorRequestEvent. + */ + +Bool +SendTakeFloorRequestEvent(void) +{ + rfbTakeFloorRequestMsg tfr; + + tfr.type = rfbTakeFloorRequest; + + return WriteExact(rfbsock, (char *)&tfr, sz_rfbTakeFloorRequestMsg); +} + + +/* + * SendReleaseFloorEvent. + */ + +Bool +SendReleaseFloorEvent(Bool allow, short memberID) +{ + rfbReleaseFloorMsg rf; + + rf.type = rfbReleaseFloor; + rf.allow = allow; + rf.memberID = memberID; + + return WriteExact(rfbsock, (char *)&rf, sz_rfbReleaseFloorMsg); +} + + +/* * SendClientCutText. */ @@ -692,9 +750,13 @@ rect.r.w, rect.r.h); } - XCopyArea(dpy, desktopWin, desktopWin, gc, cr.srcX, cr.srcY, - rect.r.w, rect.r.h, rect.r.x, rect.r.y); + XPutImage(dpy, desktopWin, gc, image, cr.srcX, cr.srcY, + rect.r.x, rect.r.y, rect.r.w, rect.r.h); + /* Copy the rect back into the off-scren image to maintain a + full representation of the screen for leter reference */ + XGetSubImage(dpy, desktopWin, rect.r.x, rect.r.y, rect.r.w, rect.r.h, + -1, ZPixmap, image, rect.r.x, rect.r.y); break; } @@ -714,6 +776,8 @@ return False; break; } + XGetSubImage(dpy, desktopWin, rect.r.x, rect.r.y, rect.r.w, rect.r.h, + -1, ZPixmap, image, rect.r.x, rect.r.y); break; } @@ -733,6 +797,8 @@ return False; break; } + XGetSubImage(dpy, desktopWin, rect.r.x, rect.r.y, rect.r.w, rect.r.h, + -1, ZPixmap, image, rect.r.x, rect.r.y); break; } @@ -752,6 +818,8 @@ return False; break; } + XGetSubImage(dpy, desktopWin, rect.r.x, rect.r.y, rect.r.w, rect.r.h, + -1, ZPixmap, image, rect.r.x, rect.r.y); break; } @@ -771,6 +839,8 @@ return False; break; } + XGetSubImage(dpy, desktopWin, rect.r.x, rect.r.y, rect.r.w, rect.r.h, + -1, ZPixmap, image, rect.r.x, rect.r.y); break; } @@ -790,6 +860,8 @@ return False; break; } + XGetSubImage(dpy, desktopWin, rect.r.x, rect.r.y, rect.r.w, rect.r.h, + -1, ZPixmap, image, rect.r.x, rect.r.y); break; } @@ -801,6 +873,10 @@ /* Now we may discard "soft cursor locks". */ SoftCursorUnlockScreen(); + + /* Re-draw the Floor Owner's name */ + if (lastOwnerNameTime != -1 && floorOwner) + DrawNewFloorOwner(floorOwner->name); } #ifdef MITSHM @@ -819,6 +895,32 @@ break; } + case rfbShowMemberPointer: + { + if (!ReadFromRFBServer(((char *)&msg) + 1, + sz_rfbShowMemberPointerMsg - 1)) + return False; + + if (appData.collaborate) { + ShowMemberPointer(msg.smp.memberID, msg.smp.visible); + } + + break; + } + + case rfbMoveMemberPointer: + { + if (!ReadFromRFBServer(((char *)&msg) + 1, + sz_rfbMoveMemberPointerMsg - 1)) + return False; + + if (appData.collaborate) { + MoveMemberPointer(msg.mmp.memberID, msg.mmp.x, msg.mmp.y); + } + + break; + } + case rfbBell: { Window toplevelWin; @@ -833,6 +935,80 @@ break; } + case rfbUpdateMemberList: + { + char *name; + + if (!ReadFromRFBServer(((char *)&msg) + 1, + sz_rfbUpdateMemberListMsg - 1)) + return False; + + msg.uml.length = Swap32IfLE(msg.uml.length); + + name = malloc(msg.uml.length+1); + + if (!ReadFromRFBServer(name, msg.uml.length)) + return False; + name[msg.uml.length] = 0; + + if(msg.uml.add) + AddMember(msg.uml.memberID, msg.uml.master, msg.uml.colorRed, msg.uml.colorGreen, msg.uml.colorBlue, name); + else + RemoveMember(msg.uml.memberID); + + break; + } + + case rfbTakeFloor: + { + char *title; + + if (!ReadFromRFBServer(((char *)&msg) + 1, + sz_rfbTakeFloorMsg - 1)) + return False; + + if (appData.collaborate) { + if (msg.tf.allow) { + floorOwner = appData.me; + if(held_buttonMask != 0){ + SendPointerEvent(held_x, held_y, held_buttonMask); + held_buttonMask = 0; + } + UpdateTitleWithFloorOwner(); + } else { + held_buttonMask = 0; + } + } + + break; + } + + case rfbReleaseFloorRequest: + { + Bool willReleaseFloor; + Bool hadFloor = (floorOwner == appData.me); + + if (!ReadFromRFBServer(((char *)&msg) + 1, + sz_rfbReleaseFloorRequestMsg - 1)) + return False; + + if (appData.collaborate) { + willReleaseFloor = (hadFloor && (msg.rfr.force || CheckReleaseFloor())); + if (hadFloor && !msg.rfr.force) { + if(willReleaseFloor) + ReleaseFloor(True, NULL); + else + ReleaseFloor(False, NULL); + } + + if ((!hadFloor || willReleaseFloor) && (!floorOwner || msg.rfr.memberID != floorOwner->memberID)) { + floorOwner = GetMemberFromID(msg.rfr.memberID); + UpdateTitleWithFloorOwner(); + } + } + break; + } + case rfbServerCutText: { if (!ReadFromRFBServer(((char *)&msg) + 1, @@ -861,6 +1037,10 @@ return False; } + /* Clear the new floor owner's name if the time has come to do so */ + if (lastOwnerNameTime != -1 && difftime(time(NULL), lastOwnerNameTime) >= 2) + ClearFloorOwner(); + return True; } diff -ruN vnc_unixsrc/vncviewer/vncviewer.c vnc_unixsrc-col/vncviewer/vncviewer.c --- vnc_unixsrc/vncviewer/vncviewer.c 2001-09-15 22:58:13.000000000 -0700 +++ vnc_unixsrc-col/vncviewer/vncviewer.c 2005-02-09 09:26:42.000000000 -0800 @@ -89,7 +89,7 @@ /* Create the "popup" widget - this won't actually appear on the screen until some user-defined event causes the "ShowPopup" action to be invoked */ - CreatePopup(); + /* CreatePopup(); */ /* Find the best pixel format and X visual/colormap to use */ diff -ruN vnc_unixsrc/vncviewer/vncviewer.h vnc_unixsrc-col/vncviewer/vncviewer.h --- vnc_unixsrc/vncviewer/vncviewer.h 2003-01-15 01:46:52.000000000 -0800 +++ vnc_unixsrc-col/vncviewer/vncviewer.h 2005-02-09 10:22:46.000000000 -0800 @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,17 @@ #define DEFAULT_VIA_CMD \ (DEFAULT_SSH_CMD " -f -L %L:%H:%R %G sleep 20") +typedef struct member_t { + short memberID; + Bool master; + short x, y; + char colorRed; + char colorGreen; + char colorBlue; + char *name; + int nameWidth; + struct member_t *next; +} Member, *MemberPtr; /* argsresources.c */ @@ -71,6 +83,13 @@ Bool grabKeyboard; Bool raiseOnBeep; + Bool collaborate; + char *name; + int autoReleaseDelay; + Bool showPopups; + + MemberPtr me; + String encodingsString; Bool useBGR233; @@ -148,6 +167,7 @@ extern GC gc; extern GC srcGC, dstGC; extern Dimension dpyWidth, dpyHeight; +extern XImage *image; extern void DesktopInitBeforeRealization(); extern void DesktopInitAfterRealization(); @@ -155,6 +175,13 @@ Cardinal *num_params); extern void CopyDataToScreen(char *buf, int x, int y, int width, int height); extern void SynchroniseScreen(); +extern Bool CheckReleaseFloor(); +extern void UpdateTitleWithFloorOwner(); +extern void DrawMemberPointer(MemberPtr m, int oldx, int oldy); +extern void DrawNewFloorOwner(char *name); +extern void DrawName(char *name, int x, int y); +extern void ClearFloorOwner(void); +extern void ClearName(char *name, int x, int y); /* dialogs.c */ @@ -220,6 +247,8 @@ Bool incremental); extern Bool SendPointerEvent(int x, int y, int buttonMask); extern Bool SendKeyEvent(CARD32 key, Bool down); +extern Bool SendTakeFloorRequestEvent(); +extern Bool SendReleaseFloorEvent(Bool allow, short memberID); extern Bool SendClientCutText(char *str, int len); extern Bool HandleRFBServerMessage(); @@ -265,3 +294,23 @@ extern XtAppContext appContext; extern Display* dpy; extern Widget toplevel; + +/* collaborate.c */ + +extern MemberPtr memberHead; +extern MemberPtr floorOwner; +extern char *windowTitle; +extern time_t lastEventTime; +extern short held_x, held_y, held_buttonMask; +extern time_t lastOwnerNameTime; + +extern Bool TakeFloorRequest(); +extern Bool ReleaseFloor(Bool allow, MemberPtr m); +extern void AddMember(short memberID, Bool master, int colorRed, int colorGreen, int colorBlue, char *name); +extern MemberPtr GetMemberFromID(short memberID); +extern void RemoveMember(short memberID); +extern void ShowMemberPointer(short memberID, Bool visible); +extern void MoveMemberPointer(short memberID, int x, int y); +extern void GiveFloorToMember(Widget w, XtPointer client_data, XtPointer call_data); +extern void ToggleFloorControl(Widget w, XEvent *ev, String *params, Cardinal *num_params); +extern void SetFloorControlState(Widget w, XEvent *ev, String *params, Cardinal *num_params); diff -ruN vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/collaboration.c vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/collaboration.c --- vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/collaboration.c 1969-12-31 16:00:00.000000000 -0800 +++ vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/collaboration.c 2005-02-09 09:26:42.000000000 -0800 @@ -0,0 +1,120 @@ +/* + * collaborate.c + */ + +#include +#include "rfb.h" + +rfbClientPtr floorClient = NULL; +rfbClientPtr floorRequestClient = NULL; +Bool collaborate = FALSE; +Bool mergeKeyboards = FALSE; +Bool master = FALSE; + +void MoveMemberPointer(rfbClientPtr cl, int x, int y) +{ + if (cl) { + cl->cursorX = x; + cl->cursorY = y; + rfbSendMoveMemberPointer(cl); + } +} + + +void ShowMemberPointer(rfbClientPtr cl, Bool visible) +{ + if (cl) { + rfbSendShowMemberPointer(cl, visible); + } +} + + +void GiveFloor(rfbClientPtr cl, Bool allow) +{ + short newID; + rfbClientPtr clPtr; + + if (!collaborate) return; + + if (cl) { + rfbSendTakeFloor(cl, allow); + ShowMemberPointer(cl, FALSE); + } + + if(allow) { + newID = (cl) ? cl->memberID : 0; + for (clPtr = rfbClientHead; clPtr; clPtr = clPtr->next) { + if (clPtr != cl && clPtr != floorClient && clPtr->isCollaborative ) + rfbSendReleaseFloorRequest(clPtr, FALSE, newID); + } + + if (floorClient) + ShowMemberPointer(floorClient, TRUE); + + floorClient = cl; + } + floorRequestClient = NULL; +} + + +void ReleaseFloorRequest(rfbClientPtr cl, Bool force, short memberID) +{ + if (!collaborate) return; + + rfbSendReleaseFloorRequest(cl, force, memberID); +} + + +void UpdateMemberList(Bool add, rfbClientPtr newCl) +{ + rfbClientPtr clPtr; + + if (!collaborate) return; + + for (clPtr = rfbClientHead; clPtr; clPtr = clPtr->next) { + if (clPtr != newCl && clPtr->isCollaborative) + rfbSendUpdateMemberList(clPtr, newCl, add); + } +} + + +void SendMemberList(rfbClientPtr newCl) +{ + rfbClientPtr clPtr; + + if (!collaborate || !newCl->isCollaborative) return; + + for (clPtr = rfbClientHead; clPtr; clPtr = clPtr->next) { + rfbSendUpdateMemberList(newCl, clPtr, TRUE); + } +} + + +rfbClientPtr GetClientByID(short memberID) +{ + rfbClientPtr clPtr; + + for (clPtr = rfbClientHead; clPtr; clPtr = clPtr->next) { + if (clPtr->memberID == memberID) + return clPtr; + } + + return NULL; +} + + +void UniqueName(rfbClientPtr cl, Bool freeOld) +{ + char *unique; + rfbClientPtr clPtr; + + for (clPtr = rfbClientHead; clPtr; clPtr = clPtr->next) { + if (clPtr != cl && strcmp(clPtr->name, cl->name) == 0) { + unique = (char *)xalloc(strlen(cl->name)+6); + sprintf(unique, "%s-%d", cl->name, cl->memberID); + if (freeOld) free(cl->name); + cl->name = unique; + return; + } + } +} diff -ruN vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/Imakefile vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/Imakefile --- vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/Imakefile 2002-04-30 06:07:31.000000000 -0700 +++ vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/Imakefile 2005-02-09 09:26:42.000000000 -0800 @@ -3,11 +3,13 @@ SRCS = init.c sockets.c kbdptr.c cmap.c draw.c cutpaste.c \ dispcur.c sprite.c rfbserver.c translate.c httpd.c auth.c \ - rre.c corre.c stats.c hextile.c zlib.c tight.c cursor.c + rre.c corre.c stats.c hextile.c zlib.c tight.c cursor.c \ + collaboration.c OBJS = init.o sockets.o kbdptr.o cmap.o draw.o cutpaste.o \ dispcur.o sprite.o rfbserver.o translate.o httpd.o auth.o \ - rre.o corre.o stats.o hextile.o zlib.o tight.o cursor.o + rre.o corre.o stats.o hextile.o zlib.o tight.o cursor.o \ + collaboration.o #include INCLUDES = -I. -I$(XBUILDINCDIR) -I$(FONTINCSRC) -I$(XINCLUDESRC) \ diff -ruN vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/init.c vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/init.c --- vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/init.c 2002-10-27 04:36:02.000000000 -0800 +++ vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/init.c 2005-02-09 09:26:42.000000000 -0800 @@ -371,6 +371,22 @@ return 1; } + if (strcmp(argv[i], "-collaborate") == 0) { + collaborate = TRUE; + rfbAlwaysShared = TRUE; + return 1; + } + + if (strcmp(argv[i], "-mergekeyboards") == 0) { + mergeKeyboards = TRUE; + return 1; + } + + if (strcmp(argv[i], "-master") == 0) { + master = TRUE; + return 1; + } + if (strcmp(argv[i], "-version") == 0) { ErrorF("Xvnc version %d.%d.%s\n", rfbProtocolMajorVersion, rfbProtocolMinorVersion, XVNCRELEASE); @@ -927,6 +943,10 @@ ErrorF("-inetd Xvnc is launched by inetd\n"); ErrorF("-compatiblekbd set META key = ALT key as in the original " "VNC\n"); + ErrorF("-collaborate open this Xvnc session in collaborative mode\n"); + ErrorF("-mergekeyboards in collaborative mode, listen to all keyboards\n"); + ErrorF("-master in collaborative mode, the first client to\n" + " connect becomes the master\n"); ErrorF("-version report Xvnc version on stderr\n"); exit(1); } diff -ruN vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/rfb.h vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/rfb.h --- vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/rfb.h 2003-01-15 02:44:05.000000000 -0800 +++ vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/rfb.h 2005-02-09 09:26:42.000000000 -0800 @@ -127,6 +127,7 @@ int sock; char *host; + char *name; /* Possible client states: */ enum { RFB_PROTOCOL_VERSION, /* establishing protocol version */ @@ -145,6 +146,8 @@ int preferredEncoding; int correMaxWidth, correMaxHeight; + Bool isCollaborative; + /* The following member is only used during VNC authentication */ CARD8 authChallenge[CHALLENGESIZE]; @@ -240,7 +243,12 @@ Bool cursorWasChanged; /* cursor shape update should be sent */ Bool cursorWasMoved; /* cursor position update should be sent */ + short memberID; + Bool master; int cursorX, cursorY; /* client's cursor position */ + int pointerColorRed; + int pointerColorGreen; + int pointerColorBlue; struct rfbClientRec *next; @@ -300,6 +308,15 @@ (((l) & 0x0000ff00) << 8) | \ ((l) << 24)) +static const int pointer_colors[8][3] = + { {0xff, 0x00, 0x00}, /* red */ + {0x00, 0xff, 0x00}, /* green */ + {0x00, 0x00, 0xff}, /* blue */ + {0xff, 0xff, 0x00}, /* yellow */ + {0xff, 0x00, 0xff}, /* magenta */ + {0x00, 0xff, 0xff}, /* cyan */ + {0x80, 0x80, 0x80}, /* gray */ + {0x00, 0x00, 0x00} }; /* black */ /* init.c */ @@ -430,8 +447,12 @@ extern Bool rfbSendSetColourMapEntries(rfbClientPtr cl, int firstColour, int nColours); extern void rfbSendBell(); +extern void rfbSendTakeFloor(rfbClientPtr cl, Bool allow); +extern void rfbSendReleaseFloorRequest(rfbClientPtr cl, Bool force, short memberID); extern void rfbSendServerCutText(char *str, int len); - +extern void rfbSendMoveMemberPointer(rfbClientPtr movedCl); +extern void rfbSendShowMemberPointer(rfbClientPtr movedCl, Bool visible); +extern void rfbSendUpdateMemberList(rfbClientPtr cl, rfbClientPtr newCL, Bool add); /* translate.c */ @@ -522,3 +543,21 @@ extern void rfbResetStats(rfbClientPtr cl); extern void rfbPrintStats(rfbClientPtr cl); + + +/* collaborate.c */ + +extern rfbClientPtr floorClient; +extern rfbClientPtr floorRequestClient; +extern Bool collaborate; +extern Bool mergeKeyboards; +extern Bool master; + +extern void GiveFloor(rfbClientPtr cl, Bool allow); +extern void ReleaseFloorRequest(rfbClientPtr cl, Bool force, short memberID); +extern void MoveMemberPointer(rfbClientPtr cl, int x, int y); +extern void ShowMemberPointer(rfbClientPtr cl, Bool visible); +extern void UpdateMemberList(Bool add, rfbClientPtr newCl); +extern void SendMemberList(rfbClientPtr cl); +extern rfbClientPtr GetClientByID(short memberID); +extern void UniqueName(rfbClientPtr cl, Bool freeOld); diff -ruN vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/rfbserver.c vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/rfbserver.c --- vnc_unixsrc/Xvnc/programs/Xserver/hw/vnc/rfbserver.c 2003-01-15 02:44:05.000000000 -0800 +++ vnc_unixsrc-col/Xvnc/programs/Xserver/hw/vnc/rfbserver.c 2005-02-09 09:26:42.000000000 -0800 @@ -54,6 +54,8 @@ Bool rfbDontDisconnect = FALSE; Bool rfbViewOnly = FALSE; /* run server in view only mode - Ehud Karni SW */ +int maxPointerID = 0; + static rfbClientPtr rfbNewClient(int sock); static void rfbProcessClientProtocolVersion(rfbClientPtr cl); static void rfbProcessClientNormalMessage(rfbClientPtr cl); @@ -192,6 +194,12 @@ cl->zlibCompressLevel = 5; + cl->memberID = ++maxPointerID; + cl->cursorX = cl->cursorY = 0; + cl->pointerColorRed = pointer_colors[(maxPointerID-1) % 8][0]; + cl->pointerColorGreen = pointer_colors[(maxPointerID-1) % 8][1]; + cl->pointerColorBlue = pointer_colors[(maxPointerID-1) % 8][2]; + sprintf(pv,rfbProtocolVersionFormat,rfbProtocolMajorVersion, rfbProtocolMinorVersion); @@ -243,6 +251,12 @@ if (pointerClient == cl) pointerClient = NULL; + if (floorClient == cl) + GiveFloor(NULL, TRUE); + + if (floorRequestClient == cl) + floorRequestClient = NULL; + #ifdef CORBA destroyConnection(cl); #endif @@ -260,6 +274,11 @@ if (cl->translateLookupTable) free(cl->translateLookupTable); + if(collaborate) { + ShowMemberPointer(cl, FALSE); + UpdateMemberList(FALSE, cl); + } + xfree(cl); } @@ -356,6 +375,8 @@ rfbLog("Ignoring minor version mismatch\n"); } + cl->isCollaborative = rfbVersionIsCollaborative(major, minor); + rfbAuthNewClient(cl); } @@ -398,10 +419,13 @@ char buf[256]; rfbServerInitMsg *si = (rfbServerInitMsg *)buf; struct passwd *user; - int len, n; + int len, n, clientInitMsgSize, serverInitMsgSize; rfbClientPtr otherCl, nextCl; - if ((n = ReadExact(cl->sock, (char *)&ci,sz_rfbClientInitMsg)) <= 0) { + clientInitMsgSize = (cl->isCollaborative) ? sz_rfbClientInitMsg : sz_old_rfbClientInitMsg; + serverInitMsgSize = (cl->isCollaborative) ? sz_rfbServerInitMsg : sz_old_rfbServerInitMsg; + + if ((n = ReadExact(cl->sock, (char *)&ci,clientInitMsgSize)) <= 0) { if (n == 0) rfbLog("rfbProcessClientInitMessage: client gone\n"); else @@ -410,12 +434,38 @@ return; } + cl->name = NULL; + + if (cl->isCollaborative) { + cl->name = (char *)xalloc(ci.nameLength + 1); + if ((n = ReadExact(cl->sock, cl->name, ci.nameLength)) <= 0) { + if (n == 0) + rfbLog("rfbProcessClientInitMessage: client gone\n"); + else + rfbLogPerror("rfbProcessClientInitMessage: read"); + rfbCloseSock(cl->sock); + return; + } + cl->name[ci.nameLength] = 0; + UniqueName(cl, TRUE); + } + + if(collaborate && !cl->name) { + cl->name = "anonymous"; + UniqueName(cl, FALSE); + } + si->framebufferWidth = Swap16IfLE(rfbScreen.width); si->framebufferHeight = Swap16IfLE(rfbScreen.height); si->format = rfbServerFormat; si->format.redMax = Swap16IfLE(si->format.redMax); si->format.greenMax = Swap16IfLE(si->format.greenMax); si->format.blueMax = Swap16IfLE(si->format.blueMax); + si->collaborate = collaborate; + si->memberID = cl->memberID; + si->cursorColorRed = cl->pointerColorRed; + si->cursorColorGreen = cl->pointerColorGreen; + si->cursorColorBlue = cl->pointerColorBlue; user = getpwuid(getuid()); @@ -423,16 +473,16 @@ desktopName[128] = 0; if (user) { - sprintf(buf + sz_rfbServerInitMsg, "%s's %s desktop (%s:%s)", - user->pw_name, desktopName, rfbThisHost, display); + sprintf(buf + serverInitMsgSize, "%s's %s desktop (%s:%s)", + user->pw_name, desktopName, rfbThisHost, display); } else { - sprintf(buf + sz_rfbServerInitMsg, "%s desktop (%s:%s)", - desktopName, rfbThisHost, display); + sprintf(buf + serverInitMsgSize, "%s desktop (%s:%s)", + desktopName, rfbThisHost, display); } - len = strlen(buf + sz_rfbServerInitMsg); + len = strlen(buf + serverInitMsgSize); si->nameLength = Swap32IfLE(len); - if (WriteExact(cl->sock, buf, sz_rfbServerInitMsg + len) < 0) { + if (WriteExact(cl->sock, buf, serverInitMsgSize + len) < 0) { rfbLogPerror("rfbProcessClientInitMessage: write"); rfbCloseSock(cl->sock); return; @@ -463,8 +513,34 @@ } } } -} + if (!master) cl->master = FALSE; + + if(collaborate) { + + if (master) { + cl->master = TRUE; + for (otherCl = rfbClientHead; otherCl; otherCl = otherCl->next) { + if (otherCl != cl && otherCl->master) { + cl->master = FALSE; + break; + } + } + } + + SendMemberList(cl); + UpdateMemberList(TRUE, cl); + ShowMemberPointer(cl,TRUE); + + if (cl->isCollaborative) { + if (floorClient) { + ReleaseFloorRequest(cl, FALSE, floorClient->memberID); + } else { + GiveFloor(cl, TRUE); + } + } + } +} /* * rfbProcessClientNormalMessage is called when the client has sent a normal @@ -744,9 +820,9 @@ if (!isKeyboardEnabled(cl)) return; #endif - if (!rfbViewOnly && !cl->viewOnly) { + if (!rfbViewOnly && !cl->viewOnly && (!collaborate || cl == floorClient || mergeKeyboards)) KbdAddEvent(msg.ke.down, (KeySym)Swap32IfLE(msg.ke.key), cl); - } + return; @@ -777,14 +853,79 @@ else pointerClient = cl; - if (!rfbViewOnly && !cl->viewOnly) { - cl->cursorX = (int)Swap16IfLE(msg.pe.x); - cl->cursorY = (int)Swap16IfLE(msg.pe.y); + cl->cursorX = (int)Swap16IfLE(msg.pe.x); + cl->cursorY = (int)Swap16IfLE(msg.pe.y); + + if (!rfbViewOnly && !cl->viewOnly) + if (!collaborate || cl == floorClient) PtrAddEvent(msg.pe.buttonMask, cl->cursorX, cl->cursorY, cl); - } + else + MoveMemberPointer(cl, cl->cursorX, cl->cursorY); + return; + case rfbTakeFloorRequest: + { + if (!collaborate) return; + + if ((n = ReadExact(cl->sock, ((char *)&msg) + 1, + sz_rfbTakeFloorRequestMsg - 1)) <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: read"); + rfbCloseSock(cl->sock); + return; + } + + if (!pointerClient && !floorRequestClient) { + floorRequestClient = cl; + + if (floorClient == NULL || cl->master) { + if (floorClient) + ReleaseFloorRequest(floorClient, TRUE, cl->memberID); + GiveFloor(floorRequestClient, TRUE); + } else { + if (floorClient) + ReleaseFloorRequest(floorClient, FALSE, cl->memberID); + } + } else { + if (floorRequestClient != cl) + GiveFloor(cl, FALSE); + } + return; + } + + case rfbReleaseFloor: + { + rfbClientPtr newCl; + + if (!collaborate) return; + + if ((n = ReadExact(cl->sock, ((char *)&msg) + 1, + sz_rfbReleaseFloorMsg - 1)) <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: read"); + rfbCloseSock(cl->sock); + return; + } + + if (floorRequestClient) { + if(msg.rf.memberID > 0 && floorRequestClient->memberID != msg.rf.memberID && (cl == floorClient || cl->master) && (newCl = GetClientByID(msg.rf.memberID)) != NULL) { + GiveFloor(floorRequestClient, FALSE); + GiveFloor(newCl, TRUE); + } else { + GiveFloor(floorRequestClient, msg.rf.allow); + } + } else { + if(msg.rf.memberID > 0 && (cl == floorClient || cl->master)) { + if ((newCl = GetClientByID(msg.rf.memberID)) != NULL) + GiveFloor(newCl, TRUE); + } else + GiveFloor(NULL, TRUE); + } + + return; + } case rfbClientCutText: if ((n = ReadExact(cl->sock, ((char *)&msg) + 1, @@ -1378,6 +1519,147 @@ /* + * rfbSendTakeFloor tells this client to Take the Floor, and tells the other + * clients that this client has control now. + * Accepts a NULL client to take control away from everyone. + */ + +void +rfbSendTakeFloor(rfbClientPtr cl, Bool allow) +{ + rfbTakeFloorMsg tf; + + if (cl && cl->isCollaborative) { + tf.type = rfbTakeFloor; + tf.allow = allow; + if (WriteExact(cl->sock, (char *)&tf, sz_rfbTakeFloorMsg) < 0) { + rfbLogPerror("rfbSendTakeFloor: write"); + rfbCloseSock(cl->sock); + } + } +} + + +/* + * rfbSendReleaseFloorRequest tells this client to release the floor, + * and sends the name of the new floor owner. + */ + +void +rfbSendReleaseFloorRequest(rfbClientPtr cl, Bool force, short memberID) +{ + rfbReleaseFloorRequestMsg rfr; + + if (cl->isCollaborative) { + rfr.type = rfbReleaseFloorRequest; + rfr.force = force; + rfr.memberID = memberID; + if (WriteExact(cl->sock, (char *)&rfr, + sz_rfbReleaseFloorRequestMsg) < 0) { + rfbLogPerror("rfbSendReleaseFloorRequest: write"); + rfbCloseSock(cl->sock); + return; + } + } +} + + +/* + * rfbSendMoveMemberPointer sends the position of a non-active pointer + * to a client. + */ + +void +rfbSendMoveMemberPointer(rfbClientPtr movedCl) +{ + rfbClientPtr cl, nextCl; + rfbMoveMemberPointerMsg mmp; + + mmp.type = rfbMoveMemberPointer; + mmp.memberID = movedCl->memberID; + mmp.x = movedCl->cursorX; + mmp.y = movedCl->cursorY; + + for (cl = rfbClientHead; cl; cl = nextCl) { + nextCl = cl->next; + if (cl != movedCl && cl->isCollaborative) { + if (WriteExact(cl->sock, (char *)&mmp, sz_rfbMoveMemberPointerMsg) < 0) { + rfbLogPerror("rfbMoveMemberPointer: write"); + rfbCloseSock(cl->sock); + } + } + } +} + + +/* + * rfbSendUpdateMemberList sends (to cl) a description of a new client + * (newCl), or the ID of a client that's going away. + */ + +void +rfbSendUpdateMemberList(rfbClientPtr cl, rfbClientPtr newCl, Bool add) +{ + rfbUpdateMemberListMsg uml; + rfbClientPtr clPtr; + + uml.type = rfbUpdateMemberList; + uml.memberID = newCl->memberID; + uml.master = (newCl->master) ? 1 : 0; + uml.add = add; + + if (add) { + uml.colorRed = newCl->pointerColorRed; + uml.colorGreen = newCl->pointerColorGreen; + uml.colorBlue = newCl->pointerColorBlue; + uml.length = Swap32IfLE(strlen(newCl->name)); + } else { + uml.length = 0; + } + + if (cl->isCollaborative) { + if (WriteExact(cl->sock, (char *)¨, sz_rfbUpdateMemberListMsg) < 0) { + rfbLogPerror("rfbUpdateMemberList: write"); + rfbCloseSock(cl->sock); + } + if (add) + if (WriteExact(cl->sock, newCl->name, strlen(newCl->name)) < 0) { + rfbLogPerror("rfbSendUpdateMemberList: write"); + rfbCloseSock(cl->sock); + } + } +} + + + +/* + * rfbSendShowMemberPointer sends the visibility and color of a non-active + * pointer to all clients. + */ + +void +rfbSendShowMemberPointer(rfbClientPtr movedCl, Bool visible) +{ + rfbClientPtr cl, nextCl; + rfbShowMemberPointerMsg smp; + + smp.type = rfbShowMemberPointer; + smp.memberID = movedCl->memberID; + smp.visible = visible; + + for (cl = rfbClientHead; cl; cl = nextCl) { + nextCl = cl->next; + if (cl != movedCl && cl->isCollaborative) { + if (WriteExact(cl->sock, (char *)&smp, sz_rfbShowMemberPointerMsg) < 0) { + rfbLogPerror("rfbShowMemberPointer: write"); + rfbCloseSock(cl->sock); + } + } + } +} + + +/* * rfbSendServerCutText sends a ServerCutText message to all the clients. */ @@ -1477,3 +1759,4 @@ rfbDisconnectUDPSock(); } } +