]> git.sesse.net Git - rdpsrv/blobdiff - Xserver/programs/Xserver/dix/dixutils.c
Import X server from vnc-3.3.7.
[rdpsrv] / Xserver / programs / Xserver / dix / dixutils.c
diff --git a/Xserver/programs/Xserver/dix/dixutils.c b/Xserver/programs/Xserver/dix/dixutils.c
new file mode 100644 (file)
index 0000000..edabfd7
--- /dev/null
@@ -0,0 +1,1011 @@
+/***********************************************************
+
+Copyright (c) 1987  X Consortium
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of the X Consortium shall not be
+used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization from the X Consortium.
+
+
+Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
+
+                        All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its 
+documentation for any purpose and without fee is hereby granted, 
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in 
+supporting documentation, and that the name of Digital not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.  
+
+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+******************************************************************/
+
+/*
+
+(c)Copyright 1988,1991 Adobe Systems Incorporated. All rights reserved.
+
+Permission to use, copy, modify, distribute, and sublicense this software and its
+documentation for any purpose and without fee is hereby granted, provided that
+the above copyright notices appear in all copies and that both those copyright
+notices and this permission notice appear in supporting documentation and that
+the name of Adobe Systems Incorporated not be used in advertising or publicity
+pertaining to distribution of the software without specific, written prior
+permission.  No trademark license to use the Adobe trademarks is hereby
+granted.  If the Adobe trademark "Display PostScript"(tm) is used to describe
+this software, its functionality or for any other purpose, such use shall be
+limited to a statement that this software works in conjunction with the Display
+PostScript system.  Proper trademark attribution to reflect Adobe's ownership
+of the trademark shall be given whenever any such reference to the Display
+PostScript system is made.
+
+ADOBE MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THE SOFTWARE FOR ANY
+PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.  ADOBE
+DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
+INFRINGEMENT OF THIRD PARTY RIGHTS.  IN NO EVENT SHALL ADOBE BE LIABLE TO YOU
+OR ANY OTHER PARTY FOR ANY SPECIAL, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+DAMAGES WHATSOEVER WHETHER IN AN ACTION OF CONTRACT,NEGLIGENCE, STRICT
+LIABILITY OR ANY OTHER ACTION ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.  ADOBE WILL NOT PROVIDE ANY TRAINING OR OTHER
+SUPPORT FOR THE SOFTWARE.
+
+Adobe, PostScript, and Display PostScript are trademarks of Adobe Systems
+Incorporated which may be registered in certain jurisdictions.
+
+Author:  Adobe Systems Incorporated
+
+*/
+
+/* $TOG: dixutils.c /main/33 1997/05/22 10:02:20 kaleb $ */
+
+
+
+
+/* $XFree86: xc/programs/Xserver/dix/dixutils.c,v 3.1.2.1 1997/05/23 12:19:35 dawes Exp $ */
+
+#include "X.h"
+#include "Xmd.h"
+#include "misc.h"
+#include "windowstr.h"
+#include "dixstruct.h"
+#include "pixmapstr.h"
+#include "scrnintstr.h"
+#define  XK_LATIN1
+#include "keysymdef.h"
+#ifdef XCSECURITY
+#define _SECURITY_SERVER
+#include "extensions/security.h"
+#endif
+
+/*
+ * CompareTimeStamps returns -1, 0, or +1 depending on if the first
+ * argument is less than, equal to or greater than the second argument.
+ */
+
+int
+CompareTimeStamps(a, b)
+    TimeStamp a, b;
+{
+    if (a.months < b.months)
+       return EARLIER;
+    if (a.months > b.months)
+       return LATER;
+    if (a.milliseconds < b.milliseconds)
+       return EARLIER;
+    if (a.milliseconds > b.milliseconds)
+       return LATER;
+    return SAMETIME;
+}
+
+/*
+ * convert client times to server TimeStamps
+ */
+
+#define HALFMONTH ((unsigned long) 1<<31)
+TimeStamp
+ClientTimeToServerTime(c)
+     CARD32 c;
+{
+    TimeStamp ts;
+    if (c == CurrentTime)
+       return currentTime;
+    ts.months = currentTime.months;
+    ts.milliseconds = c;
+    if (c > currentTime.milliseconds)
+    {
+       if (((unsigned long) c - currentTime.milliseconds) > HALFMONTH)
+           ts.months -= 1;
+    }
+    else if (c < currentTime.milliseconds)
+    {
+       if (((unsigned long)currentTime.milliseconds - c) > HALFMONTH)
+           ts.months += 1;
+    }
+    return ts;
+}
+
+/*
+ * ISO Latin-1 case conversion routine
+ *
+ * this routine always null-terminates the result, so
+ * beware of too-small buffers
+ */
+
+void
+CopyISOLatin1Lowered(dest, source, length)
+    register unsigned char *dest, *source;
+    int length;
+{
+    register int i;
+
+    for (i = 0; i < length; i++, source++, dest++)
+    {
+       if ((*source >= XK_A) && (*source <= XK_Z))
+           *dest = *source + (XK_a - XK_A);
+       else if ((*source >= XK_Agrave) && (*source <= XK_Odiaeresis))
+           *dest = *source + (XK_agrave - XK_Agrave);
+       else if ((*source >= XK_Ooblique) && (*source <= XK_Thorn))
+           *dest = *source + (XK_oslash - XK_Ooblique);
+       else
+           *dest = *source;
+    }
+    *dest = '\0';
+}
+
+#ifdef XCSECURITY
+
+/* SecurityLookupWindow and SecurityLookupDrawable:
+ * Look up the window/drawable taking into account the client doing
+ * the lookup and the type of access desired.  Return the window/drawable
+ * if it exists and the client is allowed access, else return NULL.
+ * Most Proc* functions should be calling these instead of
+ * LookupWindow and LookupDrawable, which do no access checks.
+ */
+
+WindowPtr
+SecurityLookupWindow(rid, client, access_mode)
+    XID rid;
+    ClientPtr client;
+    Mask access_mode;
+{
+    WindowPtr  pWin;
+
+    client->errorValue = rid;
+    if(rid == INVALID)
+       return NULL;
+    if (client->trustLevel != XSecurityClientTrusted)
+       return (WindowPtr)SecurityLookupIDByType(client, rid, RT_WINDOW, access_mode);
+    if (client->lastDrawableID == rid)
+    {
+        if (client->lastDrawable->type == DRAWABLE_WINDOW)
+            return ((WindowPtr) client->lastDrawable);
+        return (WindowPtr) NULL;
+    }
+    pWin = (WindowPtr)SecurityLookupIDByType(client, rid, RT_WINDOW, access_mode);
+    if (pWin && pWin->drawable.type == DRAWABLE_WINDOW) {
+       client->lastDrawable = (DrawablePtr) pWin;
+       client->lastDrawableID = rid;
+       client->lastGCID = INVALID;
+       client->lastGC = (GCPtr)NULL;
+    }
+    return pWin;
+}
+
+
+pointer
+SecurityLookupDrawable(rid, client, access_mode)
+    XID rid;
+    ClientPtr client;
+    Mask access_mode;
+{
+    register DrawablePtr pDraw;
+
+    if(rid == INVALID)
+       return (pointer) NULL;
+    if (client->trustLevel != XSecurityClientTrusted)
+       return (DrawablePtr)SecurityLookupIDByClass(client, rid, RC_DRAWABLE,
+                                                   access_mode);
+    if (client->lastDrawableID == rid)
+       return ((pointer) client->lastDrawable);
+    pDraw = (DrawablePtr)SecurityLookupIDByClass(client, rid, RC_DRAWABLE,
+                                                access_mode);
+    if (pDraw && (pDraw->type != UNDRAWABLE_WINDOW))
+        return (pointer)pDraw;         
+    return (pointer)NULL;
+}
+
+/* We can't replace the LookupWindow and LookupDrawable functions with
+ * macros because of compatibility with loadable servers.
+ */
+
+WindowPtr
+LookupWindow(rid, client)
+    XID rid;
+    ClientPtr client;
+{
+    return SecurityLookupWindow(rid, client, SecurityUnknownAccess);
+}
+
+pointer
+LookupDrawable(rid, client)
+    XID rid;
+    ClientPtr client;
+{
+    return SecurityLookupDrawable(rid, client, SecurityUnknownAccess);
+}
+
+#else /* not XCSECURITY */
+
+WindowPtr
+LookupWindow(rid, client)
+    XID rid;
+    ClientPtr client;
+{
+    WindowPtr  pWin;
+
+    client->errorValue = rid;
+    if(rid == INVALID)
+       return NULL;
+    if (client->lastDrawableID == rid)
+    {
+        if (client->lastDrawable->type == DRAWABLE_WINDOW)
+            return ((WindowPtr) client->lastDrawable);
+        return (WindowPtr) NULL;
+    }
+    pWin = (WindowPtr)LookupIDByType(rid, RT_WINDOW);
+    if (pWin && pWin->drawable.type == DRAWABLE_WINDOW) {
+       client->lastDrawable = (DrawablePtr) pWin;
+       client->lastDrawableID = rid;
+       client->lastGCID = INVALID;
+       client->lastGC = (GCPtr)NULL;
+    }
+    return pWin;
+}
+
+
+pointer
+LookupDrawable(rid, client)
+    XID rid;
+    ClientPtr client;
+{
+    register DrawablePtr pDraw;
+
+    if(rid == INVALID)
+       return (pointer) NULL;
+    if (client->lastDrawableID == rid)
+       return ((pointer) client->lastDrawable);
+    pDraw = (DrawablePtr)LookupIDByClass(rid, RC_DRAWABLE);
+    if (pDraw && (pDraw->type != UNDRAWABLE_WINDOW))
+        return (pointer)pDraw;         
+    return (pointer)NULL;
+}
+
+#endif /* XCSECURITY */
+
+ClientPtr
+LookupClient(rid, client)
+    XID rid;
+    ClientPtr client;
+{
+    pointer pRes = (pointer)SecurityLookupIDByClass(client, rid, RC_ANY,
+                                                   SecurityReadAccess);
+    int clientIndex = CLIENT_ID(rid);
+
+    if (clientIndex && pRes && clients[clientIndex] && !(rid & SERVER_BIT))
+    {
+       return clients[clientIndex];
+    }
+    return (ClientPtr)NULL;
+}
+
+
+int
+AlterSaveSetForClient(client, pWin, mode)
+    ClientPtr client;
+    WindowPtr pWin;
+    unsigned mode;
+{
+    int numnow;
+    pointer *pTmp;
+    int j;
+
+    numnow = client->numSaved;
+    j = 0;
+    if (numnow)
+    {
+       pTmp = client->saveSet;
+       while ((j < numnow) && (pTmp[j] != (pointer)pWin))
+           j++;
+    }
+    if (mode == SetModeInsert)
+    {
+       if (j < numnow)         /* duplicate */
+          return(Success);
+       numnow++;
+       pTmp = (pointer *)xrealloc(client->saveSet, sizeof(pointer) * numnow);
+       if (!pTmp)
+           return(BadAlloc);
+       client->saveSet = pTmp;
+               client->numSaved = numnow;
+       client->saveSet[numnow - 1] = (pointer)pWin;
+       return(Success);
+    }
+    else if ((mode == SetModeDelete) && (j < numnow))
+    {
+       while (j < numnow-1)
+       {
+           pTmp[j] = pTmp[j+1];
+          j++;
+       }
+       numnow--;
+        if (numnow)
+       {
+           pTmp = (pointer *)xrealloc(client->saveSet,
+                                      sizeof(pointer) * numnow);
+           if (pTmp)
+               client->saveSet = pTmp;
+       }
+        else
+        {
+            xfree(client->saveSet);
+           client->saveSet = (pointer *)NULL;
+       }
+       client->numSaved = numnow;
+       return(Success);
+    }
+    return(Success);
+}
+
+void
+DeleteWindowFromAnySaveSet(pWin)
+    WindowPtr pWin;
+{
+    register int i;
+    register ClientPtr client;
+    
+    for (i = 0; i< currentMaxClients; i++)
+    {    
+       client = clients[i];
+       if (client && client->numSaved)
+           (void)AlterSaveSetForClient(client, pWin, SetModeDelete);
+    }
+}
+
+/* No-op Don't Do Anything : sometimes we need to be able to call a procedure
+ * that doesn't do anything.  For example, on screen with only static
+ * colormaps, if someone calls install colormap, it's easier to have a dummy
+ * procedure to call than to check if there's a procedure 
+ */
+void
+NoopDDA(
+#if NeedVarargsPrototypes
+    void* f, ...
+#endif
+)
+{
+}
+
+typedef struct _BlockHandler {
+    BlockHandlerProcPtr BlockHandler;
+    WakeupHandlerProcPtr WakeupHandler;
+    pointer blockData;
+    Bool    deleted;
+} BlockHandlerRec, *BlockHandlerPtr;
+
+static BlockHandlerPtr handlers;
+static int             numHandlers;
+static int             sizeHandlers;
+static Bool            inHandler;
+static Bool            handlerDeleted;
+
+/* called from the OS layer */
+void
+BlockHandler(pTimeout, pReadmask)
+pointer        pTimeout;       /* DIX doesn't want to know how OS represents time */
+pointer pReadmask;     /* nor how it represents the set of descriptors */
+{
+    register int i, j;
+    
+    ++inHandler;
+    for (i = 0; i < screenInfo.numScreens; i++)
+       (* screenInfo.screens[i]->BlockHandler)(i, 
+                               screenInfo.screens[i]->blockData,
+                               pTimeout, pReadmask);
+    for (i = 0; i < numHandlers; i++)
+       (*handlers[i].BlockHandler) (handlers[i].blockData,
+                                    pTimeout, pReadmask);
+    if (handlerDeleted)
+    {
+       for (i = 0; i < numHandlers;)
+           if (handlers[i].deleted)
+           {
+               for (j = i; j < numHandlers - 1; j++)
+                   handlers[j] = handlers[j+1];
+               numHandlers--;
+           }
+           else
+               i++;
+       handlerDeleted = FALSE;
+    }
+    --inHandler;
+}
+
+void
+WakeupHandler(result, pReadmask)
+int    result; /* 32 bits of undefined result from the wait */
+pointer pReadmask;     /* the resulting descriptor mask */
+{
+    register int i, j;
+
+    ++inHandler;
+    for (i = numHandlers - 1; i >= 0; i--)
+       (*handlers[i].WakeupHandler) (handlers[i].blockData,
+                                     result, pReadmask);
+    for (i = 0; i < screenInfo.numScreens; i++)
+       (* screenInfo.screens[i]->WakeupHandler)(i, 
+                               screenInfo.screens[i]->wakeupData,
+                               result, pReadmask);
+    if (handlerDeleted)
+    {
+       for (i = 0; i < numHandlers;)
+           if (handlers[i].deleted)
+           {
+               for (j = i; j < numHandlers - 1; j++)
+                   handlers[j] = handlers[j+1];
+               numHandlers--;
+           }
+           else
+               i++;
+       handlerDeleted = FALSE;
+    }
+    --inHandler;
+}
+
+/* Reentrant with BlockHandler and WakeupHandler, except wakeup won't
+ * get called until next time
+ */
+
+Bool
+RegisterBlockAndWakeupHandlers (blockHandler, wakeupHandler, blockData)
+    BlockHandlerProcPtr blockHandler;
+    WakeupHandlerProcPtr wakeupHandler;
+    pointer blockData;
+{
+    BlockHandlerPtr new;
+
+    if (numHandlers >= sizeHandlers)
+    {
+       new = (BlockHandlerPtr) xrealloc (handlers, (numHandlers + 1) *
+                                         sizeof (BlockHandlerRec));
+       if (!new)
+           return FALSE;
+       handlers = new;
+       sizeHandlers = numHandlers + 1;
+    }
+    handlers[numHandlers].BlockHandler = blockHandler;
+    handlers[numHandlers].WakeupHandler = wakeupHandler;
+    handlers[numHandlers].blockData = blockData;
+    handlers[numHandlers].deleted = FALSE;
+    numHandlers = numHandlers + 1;
+    return TRUE;
+}
+
+void
+RemoveBlockAndWakeupHandlers (blockHandler, wakeupHandler, blockData)
+    BlockHandlerProcPtr blockHandler;
+    WakeupHandlerProcPtr wakeupHandler;
+    pointer blockData;
+{
+    int            i;
+
+    for (i = 0; i < numHandlers; i++)
+       if (handlers[i].BlockHandler == blockHandler &&
+           handlers[i].WakeupHandler == wakeupHandler &&
+           handlers[i].blockData == blockData)
+       {
+           if (inHandler)
+           {
+               handlerDeleted = TRUE;
+               handlers[i].deleted = TRUE;
+           }
+           else
+           {
+               for (; i < numHandlers - 1; i++)
+                   handlers[i] = handlers[i+1];
+               numHandlers--;
+           }
+           break;
+       }
+}
+
+void
+InitBlockAndWakeupHandlers ()
+{
+    xfree (handlers);
+    handlers = (BlockHandlerPtr) 0;
+    numHandlers = 0;
+    sizeHandlers = 0;
+}
+
+/*
+ * A general work queue.  Perform some task before the server
+ * sleeps for input.
+ */
+
+WorkQueuePtr           workQueue;
+static WorkQueuePtr    *workQueueLast = &workQueue;
+
+void
+ProcessWorkQueue()
+{
+    WorkQueuePtr    q, *p;
+
+    p = &workQueue;
+    /*
+     * Scan the work queue once, calling each function.  Those
+     * which return TRUE are removed from the queue, otherwise
+     * they will be called again.  This must be reentrant with
+     * QueueWorkProc.
+     */
+    while (q = *p)
+    {
+       if ((*q->function) (q->client, q->closure))
+       {
+           /* remove q from the list */
+           *p = q->next;    /* don't fetch until after func called */
+           xfree (q);
+       }
+       else
+       {
+           p = &q->next;    /* don't fetch until after func called */
+       }
+    }
+    workQueueLast = p;
+}
+
+void
+ProcessWorkQueueZombies()
+{
+    WorkQueuePtr    q, *p;
+
+    p = &workQueue;
+    while (q = *p)
+    {
+       if (q->client && q->client->clientGone)
+       {
+           (void) (*q->function) (q->client, q->closure);
+           /* remove q from the list */
+           *p = q->next;    /* don't fetch until after func called */
+           xfree (q);
+       }
+       else
+       {
+           p = &q->next;    /* don't fetch until after func called */
+       }
+    }
+    workQueueLast = p;
+}
+
+Bool
+#if NeedFunctionPrototypes
+QueueWorkProc (
+    Bool       (*function)(
+#if NeedNestedPrototypes
+               ClientPtr       /* pClient */,
+               pointer         /* closure */
+#endif
+               ),
+    ClientPtr  client,
+    pointer    closure)
+#else
+QueueWorkProc (function, client, closure)
+    Bool       (*function)();
+    ClientPtr  client;
+    pointer    closure;
+#endif
+{
+    WorkQueuePtr    q;
+
+    q = (WorkQueuePtr) xalloc (sizeof *q);
+    if (!q)
+       return FALSE;
+    q->function = function;
+    q->client = client;
+    q->closure = closure;
+    q->next = NULL;
+    *workQueueLast = q;
+    workQueueLast = &q->next;
+    return TRUE;
+}
+
+/*
+ * Manage a queue of sleeping clients, awakening them
+ * when requested, by using the OS functions IgnoreClient
+ * and AttendClient.  Note that this *ignores* the troubles
+ * with request data interleaving itself with events, but
+ * we'll leave that until a later time.
+ */
+
+typedef struct _SleepQueue {
+    struct _SleepQueue *next;
+    ClientPtr          client;
+    ClientSleepProcPtr  function;
+    pointer            closure;
+} SleepQueueRec, *SleepQueuePtr;
+
+static SleepQueuePtr   sleepQueue = NULL;
+
+Bool
+ClientSleep (client, function, closure)
+    ClientPtr  client;
+    ClientSleepProcPtr function;
+    pointer    closure;
+{
+    SleepQueuePtr   q;
+
+    q = (SleepQueuePtr) xalloc (sizeof *q);
+    if (!q)
+       return FALSE;
+
+    IgnoreClient (client);
+    q->next = sleepQueue;
+    q->client = client;
+    q->function = function;
+    q->closure = closure;
+    sleepQueue = q;
+    return TRUE;
+}
+
+Bool
+ClientSignal (client)
+    ClientPtr  client;
+{
+    SleepQueuePtr   q;
+
+    for (q = sleepQueue; q; q = q->next)
+       if (q->client == client)
+       {
+           return QueueWorkProc (q->function, q->client, q->closure);
+       }
+    return FALSE;
+}
+
+void
+ClientWakeup (client)
+    ClientPtr  client;
+{
+    SleepQueuePtr   q, *prev;
+
+    prev = &sleepQueue;
+    while ( (q = *prev) )
+    {
+       if (q->client == client)
+       {
+           *prev = q->next;
+           xfree (q);
+           if (client->clientGone)
+               CloseDownClient(client);
+           else
+               AttendClient (client);
+           break;
+       }
+       prev = &q->next;
+    }
+}
+
+Bool
+ClientIsAsleep (client)
+    ClientPtr  client;
+{
+    SleepQueuePtr   q;
+
+    for (q = sleepQueue; q; q = q->next)
+       if (q->client == client)
+           return TRUE;
+    return FALSE;
+}
+
+/*
+ *  Generic Callback Manager
+ */
+
+/* ===== Private Procedures ===== */
+
+static int numCallbackListsToCleanup = 0;
+static CallbackListPtr **listsToCleanup = NULL;
+
+static Bool 
+#if NeedFunctionPrototypes
+_AddCallback(
+    CallbackListPtr *pcbl,
+    CallbackProcPtr callback,
+    pointer         data)
+#else
+_AddCallback(pcbl, callback, data)
+    CallbackListPtr *pcbl;
+    CallbackProcPtr callback;
+    pointer         data;
+#endif
+{
+    CallbackPtr     cbr;
+
+    cbr = (CallbackPtr) xalloc(sizeof(CallbackRec));
+    if (!cbr)
+       return FALSE;
+    cbr->proc = callback;
+    cbr->data = data;
+    cbr->next = (*pcbl)->list;
+    cbr->deleted = FALSE;
+    (*pcbl)->list = cbr;
+    return TRUE;
+}
+
+static Bool 
+#if NeedFunctionPrototypes
+_DeleteCallback(
+    CallbackListPtr *pcbl,
+    CallbackProcPtr callback,
+    pointer         data)
+#else
+_DeleteCallback(pcbl, callback, data)
+    CallbackListPtr *pcbl;
+    CallbackProcPtr callback;
+    pointer         data;
+#endif
+{
+    CallbackListPtr cbl = *pcbl;
+    CallbackPtr     cbr, pcbr;
+
+    for (pcbr = NULL, cbr = cbl->list;
+        cbr != NULL;
+        pcbr = cbr, cbr = cbr->next)
+    {
+       if ((cbr->proc == callback) && (cbr->data == data))
+           break;
+    }
+    if (cbr != NULL)
+    {
+       if (cbl->inCallback)
+       {
+           ++(cbl->numDeleted);
+           cbr->deleted = TRUE;
+       }
+       else
+       {
+           if (pcbr == NULL)
+               cbl->list = cbr->next;
+           else
+               pcbr->next = cbr->next;
+           xfree(cbr);
+       }
+       return TRUE;
+    }
+    return FALSE;
+}
+
+static void 
+#if NeedFunctionPrototypes
+_CallCallbacks(
+    CallbackListPtr    *pcbl,
+    pointer        call_data)
+#else
+_CallCallbacks(pcbl, call_data)
+    CallbackListPtr    *pcbl;
+    pointer        call_data;
+#endif
+{
+    CallbackListPtr cbl = *pcbl;
+    CallbackPtr     cbr, pcbr;
+
+    ++(cbl->inCallback);
+    for (cbr = cbl->list; cbr != NULL; cbr = cbr->next)
+    {
+       (*(cbr->proc)) (pcbl, cbr->data, call_data);
+    }
+    --(cbl->inCallback);
+
+    if (cbl->inCallback) return;
+
+    /* Was the entire list marked for deletion? */
+
+    if (cbl->deleted)
+    {
+       DeleteCallbackList(pcbl);
+       return;
+    }
+
+    /* Were some individual callbacks on the list marked for deletion?
+     * If so, do the deletions.
+     */
+
+    if (cbl->numDeleted)
+    {
+       for (pcbr = NULL, cbr = cbl->list; (cbr != NULL) && cbl->numDeleted; )
+       {
+           if (cbr->deleted)
+           {
+               if (pcbr)
+               {
+                   cbr = cbr->next;
+                   xfree(pcbr->next);
+                   pcbr->next = cbr;
+               } else
+               {
+                   cbr = cbr->next;
+                   xfree(cbl->list);
+                   cbl->list = cbr;
+               }
+               cbl->numDeleted--;
+           }
+           else /* this one wasn't deleted */
+           {
+               pcbr = cbr;
+               cbr = cbr->next;
+           }
+       }
+    }
+}
+
+static void
+#if NeedFunctionPrototypes
+_DeleteCallbackList(
+    CallbackListPtr    *pcbl)
+#else
+_DeleteCallbackList(pcbl)
+    CallbackListPtr    *pcbl;
+#endif
+{
+    CallbackListPtr cbl = *pcbl;
+    CallbackPtr     cbr, nextcbr;
+    int i;
+
+    if (cbl->inCallback)
+    {
+       cbl->deleted = TRUE;
+       return;
+    }
+
+    for (i = 0; i < numCallbackListsToCleanup; i++)
+    {
+       if ((listsToCleanup[i] = pcbl) != 0)
+       {
+           listsToCleanup[i] = NULL;
+           break;
+       }
+    }
+
+    for (cbr = cbl->list; cbr != NULL; cbr = nextcbr)
+    {
+       nextcbr = cbr->next;
+       xfree(cbr);
+    }
+    xfree(cbl);
+    *pcbl = NULL;
+}
+
+static CallbackFuncsRec default_cbfuncs =
+{
+    _AddCallback,
+    _DeleteCallback,
+    _CallCallbacks,
+    _DeleteCallbackList
+};
+
+/* ===== Public Procedures ===== */
+
+Bool
+CreateCallbackList(pcbl, cbfuncs)
+    CallbackListPtr  *pcbl;
+    CallbackFuncsPtr cbfuncs;
+{
+    CallbackListPtr  cbl;
+    int i;
+
+    if (!pcbl) return FALSE;
+    cbl = (CallbackListPtr) xalloc(sizeof(CallbackListRec));
+    if (!cbl) return FALSE;
+    cbl->funcs = cbfuncs ? *cbfuncs : default_cbfuncs;
+    cbl->inCallback = 0;
+    cbl->deleted = FALSE;
+    cbl->numDeleted = 0;
+    cbl->list = NULL;
+    *pcbl = cbl;
+
+    for (i = 0; i < numCallbackListsToCleanup; i++)
+    {
+       if (!listsToCleanup[i])
+       {
+           listsToCleanup[i] = pcbl;
+           return TRUE;
+       }    
+    }
+
+    listsToCleanup = (CallbackListPtr **)xnfrealloc(listsToCleanup,
+               sizeof(CallbackListPtr *) * (numCallbackListsToCleanup+1));
+    listsToCleanup[numCallbackListsToCleanup] = pcbl;
+    numCallbackListsToCleanup++;
+    return TRUE;
+}
+
+Bool 
+AddCallback(pcbl, callback, data)
+    CallbackListPtr *pcbl;
+    CallbackProcPtr callback;
+    pointer         data;
+{
+    if (!pcbl) return FALSE;
+    if (!*pcbl)
+    {  /* list hasn't been created yet; go create it */
+       if (!CreateCallbackList(pcbl, (CallbackFuncsPtr)NULL))
+           return FALSE;
+    }
+    return ((*(*pcbl)->funcs.AddCallback) (pcbl, callback, data));
+}
+
+Bool 
+DeleteCallback(pcbl, callback, data)
+    CallbackListPtr *pcbl;
+    CallbackProcPtr callback;
+    pointer         data;
+{
+    if (!pcbl || !*pcbl) return FALSE;
+    return ((*(*pcbl)->funcs.DeleteCallback) (pcbl, callback, data));
+}
+
+void 
+CallCallbacks(pcbl, call_data)
+    CallbackListPtr    *pcbl;
+    pointer        call_data;
+{
+    if (!pcbl || !*pcbl) return;
+    (*(*pcbl)->funcs.CallCallbacks) (pcbl, call_data);
+}
+
+void
+DeleteCallbackList(pcbl)
+    CallbackListPtr    *pcbl;
+{
+    if (!pcbl || !*pcbl) return;
+    (*(*pcbl)->funcs.DeleteCallbackList) (pcbl);
+}
+
+void 
+InitCallbackManager()
+{
+    int i;
+
+    for (i = 0; i < numCallbackListsToCleanup; i++)
+    {
+       DeleteCallbackList(listsToCleanup[i]);
+    }
+    if (listsToCleanup) xfree(listsToCleanup);
+
+    numCallbackListsToCleanup = 0;
+    listsToCleanup = NULL;
+}