/******************************************************************/
/* --  $Header: /cec/src/nestor/servers/R3/shXlib/io.c,v 1.2 90/05/14 08:57:28 altenhof Exp $ 
/* --  $Log:	io.c,v $
 * Revision 1.2  90/05/14  08:57:28  altenhof
 * Fixed two BUGS: 
 * * Overwriting of event masks in XChWAttrs.c ( don't make any sense).
 * * Removal of resources that are still used; e.g. a pixmap in a gc.
 * 
 * 
 * Revision 1.1.1.1  90/04/23  10:12:28  spanachi
 * First version of 'shared X' library (X11R3). 
 * 
 * Revision 1.1  90/04/23  10:12:25  spanachi
 * Initial revision
 * 
 * Revision 1.2  90/04/04  12:44:38  altenhof
 * Mod: more "flexible' select
 * 
 * Revision 1.1  90/04/04  09:39:34  altenhof
 * Initial revision
 * 
 * Revision 1.3  89/11/28  13:52:49  neideck
 * Trying to fix up bugs with select
 * 
 * Revision 1.2  89/11/24  09:02:10  neideck
 * *** empty log message ***
 * 
 * Revision 1.1  89/11/03  14:45:18  neideck
 * Original send to X consortium
 * 
 * Revision 1.8  89/09/01  15:31:24  michael
 * FIXED BUG : Don't continue, if select returns with an error. You've heard
 * about xfig ? Well, it decides to set an alarm during select with timeout = 0
 * to make it's text cursor blink !!
 * 
 * Revision 1.7  89/08/23  11:48:19  michael
 * *** empty log message ***
 * 
 * Revision 1.6  89/08/15  10:43:50  michael
 * XmuXselect can be customized to run in two different versions under
 * ChalkPassing mode ( defensive or optimistic).
 * FIXED BUG :
 * Cleaning thee masks got caught in a loop if you specified nfds as 0 !
 * 
 * Revision 1.5  89/08/10  18:44:18  michael
 * Changes : XmuX's version of ioctl system call added ( you guess why ? Well
 * read the code of xterm !)
 * XuXselect has been changed to work correctly under ChalkPassing mode
 * (we may change it again)
 * 
 * Revision 1.4  89/07/20  20:27:08  michael
 * Have you seen this wonderful "multiplex" version of select ?
 * 
 * Revision 1.3  89/07/07  08:44:39  michael
 * *** empty log message ***
 * 
 * Revision 1.2  89/06/26  15:20:16  michael
 * Add two new functions :
 *  XmuXWaitForReadables -> XmuX's extension to Xlib's XWaitForReadable
 *  XmuXselect           -> XmuX's version of the select system call 
 * 
 * Revision 1.1  89/06/07  17:25:12  michael
 * Initial revision
 * 
/*****************************************************************
 * -- i/o functions
 *
 *   ReadRequest (macro in io.h) , XmuXWriteToServer , 
 *   XmuXWaitForReadables , XmuXSelect
 *****************************************************************/

#define NEED_EVENTS
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <X11/Xos.h>
#include "Xlibint.h"
#include "XmuXlibint.h"
#include "multiplex.h"
#ifndef MAXSELFD
/* This defines the maximum number of file descriptors allowed
 * in a select(2) call.  On some systems (e.g. Ultrix) it is
 * defined in /usr/include/sys/types.h
 */
#define MAXSELFD NOFILE
#endif

#ifndef MY_OWN_SELECT
#define sElEcT(a,b,c,d,e) select(a,b,c,d,e)
#endif

#define OtherServers( fd ) \
	     ConnectionTranslation[ fd ]->other_servers

#define ValidConnection( fd ) \
             ( ConnectionTranslation[ fd ] != NullServer )

extern int errno;
extern void _XWaitForWritable( ) ;
extern (*_XIOErrorFunction)( ) ;

/*
 * -- here we are, making our ugly select faster !
 * -- won't work if we still use application contexts (assuming applications
 * -- may use more than one display)
 */

static int num_of_servers = 0 ;


static xReq _dummy_request = {
	0, 0, 0
};

/*********************************************************
 * -- XmuXWriteToServer : write data to the server
 *    
 * -- That's the place where we do the actual "writes" on a socket.
 * -- This is normally done by Xlib in _XFlush and _XSend.
 * -- We have modified these two routines to allow MULTIPLEXING.
 * -- Here you find the code that was replaced.
 *********************************************************/

void
XmuXWriteToServer ( dpy , buffer , size )

Display *dpy ;
char    *buffer ;
int      size ;

{   register int todo , write_stat ;
    register char *bufindex ;

    todo = size ;
    bufindex = buffer ;
    /* 
     * -- While write has not writen the entire buffer, keep looping
     * -- until the entire buffer is written. bufindex will be incremented
     * -- and size decremented as data is written out.
     * -- WriteToServer is defined in Xlibos.h which is included via
     * -- Xlibint.h
     */
    while( size )
    {
	errno = 0 ;
	write_stat = WriteToServer( dpy->fd , bufindex , todo ) ;
	if( write_stat >= 0 )
	{
	    size -= write_stat ;
	    todo  = size ;
	    bufindex += write_stat ;
#ifdef EWOULDBLOCK
	}
	else
	    if( errno == EWOULDBLOCK )
	    {
		_XWaitForWritable( dpy ) ;
#endif
#ifdef SUNSYSV
	    }
	    else
		if( errno == 0 )
		{
		    _XWaitForWritable( dpy ) ;
#endif
#ifdef EMSGSIZE
		}
		else
		    if( errno == EMSGSIZE )
		    {
			todo >>= 1 ;
#endif
		    }
		    else
		    {
			/* -- write failed ! errno set by write system call -- */
			( *_XIOErrorFunction )( dpy ) ;
		    }
    }
}

/*********************************************************
 * -- XmuXFlushConnections : write data to all MULTIPLEX'ed  servers
 *    
 * -- Loop through the list of dpy's "other servers" and call
 * -- XmuXWriteToServer for this displays.
 *********************************************************/

void
XmuXFlushConnections( dpy )

Display *dpy ;

{   register int i ;
    register Display *next_dpy ;

    for( i = 1 ; ( next_dpy = XmuXGetDisplay( dpy , i ) ) != NULL ; i++ )
    {
	/* -- flush the output queue -- */
	XmuXWriteToServer( next_dpy , next_dpy->buffer ,
			   next_dpy->bufptr - next_dpy->buffer ) ;
	next_dpy->bufptr = next_dpy->buffer ;
	next_dpy->last_req = ( char * ) &_dummy_request ;
    }
}

/********************
* -- XmuXWaitForReadables -- XmuX's version of Xlib's XWaitForReadable, with two
* -- additional arguments: 
* --  size    -> we want to read at least size (e.g. sizeof( xEvent ) ) bytes
* --  pending -> array containing the number of pending bytes for each 
* --             investigated socket.
********************/
void
XmuXWaitForReadables( dpy , size , pending )

Display *dpy ;
int      size ;        /* we want to read al least size bytes 
		          from any socket */
long     pending[ ] ;  /* returns the number of pending bytes on each individual 
			  socket */

{   unsigned long r_mask[ MSKCNT ] , readables[ MSKCNT ] ;
    int           i , fd , pend , result ;
    Bool          got_one = False ;

    CLEARBITS( readables ) ;
    /* 
     * -- Check ALL associated sockets !
     */
    COPYBITS( OtherServers( dpy->fd ) , readables ) ;
    BITSET( readables , dpy->fd ) ;
    /* -- loop until there's at least one socket with #size bytes pending -- */
    do
    {
	do
	{
	    COPYBITS( readables , r_mask ) ;
	    result = sElEcT( MAXSOCKS , r_mask , NULL , NULL , NULL ) ;
	    if( result == -1 && errno != EINTR )
		( *_XIOErrorFunction )( dpy ) ;
	}
	while( result <= 0 ) ;

	for( i = 0 ; i < MSKCNT ; i++ )
	    while( r_mask[ i ] )
	    {
		fd = ffs( r_mask[ i ] ) - 1 + ( i << 5 ) ;
		BITCLEAR( r_mask , fd ) ;
		pending[ fd ] = 0 ;
		
		/* -- how much can we read from this socket -- */
		if( BytesReadable( fd , ( char * ) &pend ) < 0 )
		    ( *_XIOErrorFunction )( MuxDisplay( fd ) ) ;
		else
		    pending[ fd ] = pend ;
		if( pend >= size )
		    got_one = True ;
	    }
    }
    while( !got_one ) ;
}
#ifdef DONT_READ_X_SOCKETS
Bool
HaveReadEvents( fd ) 

int fd ;

{   register Display *dpy ,
	             *dflt_dpy ;
    register long     pend ;
             long     pend_not_register ;
    register int      old_qlen ;
             char     buf[ BUFSIZE ] ;
    register xEvent  *ev ;         

#ifndef DONT_MASKALL
    return( 1 ) ;
#else
    dpy = MuxDisplay( fd ) ;
    dflt_dpy = XmuXPrimaryDisplayFromConnection( fd ) ;
    old_qlen = dflt_dpy->qlen ;
    /* find out how much data can be read */
    if (BytesReadable(dpy->fd, (char *) &pend_not_register) < 0)
	(*_XIOErrorFunction)(dpy);
    pend = pend_not_register;
    
    /* must read at least one xEvent; if none is pending, then
       we'll just block waiting for it */
    if (pend < SIZEOF(xEvent)) pend = SIZEOF(xEvent);
    
    /* but we won't read more than the max buffer size */
    if (pend > BUFSIZE) pend = BUFSIZE;
    
    /* round down to an integral number of XReps */
    pend = (pend / SIZEOF(xEvent)) * SIZEOF(xEvent);
    
    _XRead (dpy, buf, pend);

    /* no space between comma and type or else macro will die */
    STARTITERATE (ev,xEvent, buf, (pend > 0),(pend -= SIZEOF(xEvent))) 
    {
	if (ev->u.u.type == X_Error)
	    _XError (dpy, (xError *) ev);
	else		
	    /* it's an event packet; enqueue it in default queue */
	    _XEnq (dpy, ev);
    }
    ENDITERATE
    return( old_qlen != dflt_dpy->qlen ) ;

#endif
}

#endif

#define BIT_ISSET( i , mask )  \
         ( ( mask[ MASKIDX( i ) ] ) & ( BITMASK( i ) ) )
#define SEL_MASK( i ) \
          ConnectionTranslation[ i ]->other_servers

#ifdef DONT_READ_X_SOCKETS
#define WaitUntilDoomsday( timeout )  ( timeout == ( struct timeval * ) NULL )
#endif

#define HAS_CHALK( i ) \
    ( XmuXHasChalk( MuxDisplay( i ) ) )

#define FD_ZERO_INDEXED( fd_set , i ) \
          (fd_set)->fds_bits[ i ] = 0L 
#define SIZEOFFDMASK  ( sizeof( fd_mask ) << 3 ) 

/********************
* -- XmuXselect : XmuX's version of the select system call (aha !)
* -- Xt Intrinsics use the select system call to block if no events are 
* -- pending. 
* -- Since we do not tell the application that it is multiplexed, it will call
* -- select without the multiplex'ed sockets in its masks. This implies, that
* -- select would not return if something happened on these "transparent"
* -- sockets. 
* -- We may  use a chalkpass mode, and there are two ways to
* -- handle this case :
* -- DEFENSIVE way :
* --  -->only the ChalkHolder connection may appear in the new mask; 
* --     otherwise XmuXselect may return, butthe corresponding events 
* --     are thrown away (what a nice deadlock!) 
* --  Disadvantage : "Tabula rasa" on multiplex'ed displays, i.e. application
* --    blocks in XmuXselect and the necessary Expose events on the mux'ed
* --    displays have not yet been delivered. You have do "wake up" the
* --    application by doing something on the Chalk display.
* -- OPTIMISTIC way :
* --  --> allow all associated connections to appear in the READ mask.
* --      If something has happened on non-chalk sockets try to enque these
* --      events. Return depends on the result of this enqueing and the 
* --      selection timer : If there were only throw-away events we clear
* --      the bit in the read mask and decrement the nfound variable.
* --      If the application says "I can wait" by passing NULL as the timeout
* --      argument and nfound has decrement to zero, we're just looping.
* --  "Disadvantage" ( May turn to an error ) : We assume that applications
* --     do not try to do their own "event reading" by calling read, but rely
* --     on the Xlib routines.
********************/

#ifdef MY_OWN_SELECT             
int
select( nfds , readfds , writefds , exceptfds , timeout )
#else
int
SeLeCt( nfds , readfds , writefds , exceptfds , timeout )
#endif

int nfds ;
fd_set *readfds , *writefds , *exceptfds ;
struct timeval *timeout ;		
		
{   fd_set XmuXreadfds ,
	   XmuXwritefds ,
	   XmuXexceptfds ;
    /* 
     * -- if we "wait until doomsday" , we need a save set for subsequent
     * -- select calls
     * -- AND we need it to decrement nfound correctly!
     */
    fd_set saved_rmask ,
           saved_wmask ,
           saved_emask ;
    register int i , j , sel_fd , XmuXnfds , nfound ;

#ifndef MULTIPLE_DISPLAYS
    
    if( num_of_servers <= 1 || !nfds )
      return( sElEcT( nfds , readfds , writefds , exceptfds , timeout ) ) ;

#endif

    /*
     * -- Sometimes C reminds me of assembler...
     */
    FD_ZERO( &XmuXreadfds ) ;
    FD_ZERO( &XmuXwritefds ) ;
    FD_ZERO( &XmuXexceptfds ) ;
    FD_ZERO( &saved_rmask ) ;
    FD_ZERO( &saved_wmask ) ;
    FD_ZERO( &saved_emask ) ;

    XmuXnfds = nfds ;

    /*
     * -- Augment the fd masks (and increment XmuXnfds !) if necessary
     */
    for( i = 0 ; i < nfds ; i++ )
    {
	if( readfds )
	{
	    if( FD_ISSET( i , readfds ) )
	    {
		FD_SET( i , &XmuXreadfds ) ;
		/* -- are there other interesting sockets -- */
		if( ValidConnection( i ) )
		{
#ifndef DONT_READ_X_SOCKETS 
		    /* -- defensive way clears non-chalk bits -- */
		    if( !HAS_CHALK( i ) )
			FD_CLR( i , &XmuXreadfds ) ;
#endif
		    for( j = 0 ; j < MAXSELFD ; j++ )
#ifdef DONT_READ_X_SOCKETS	
			if( BIT_ISSET( j , SEL_MASK( i ) ) )
#else
			if( BIT_ISSET( j , SEL_MASK( i ) ) && HAS_CHALK( j ) )
#endif
			{
			    FD_SET( j , &XmuXreadfds ) ;
			    XmuXnfds = ( j >= XmuXnfds ) ? j+1 : XmuXnfds ;
			}
		 }
	      }
	  }
        
	if( writefds )
	    if( FD_ISSET( i , writefds ) )
		FD_SET( i , &XmuXwritefds ) ;
	if( exceptfds )
	    if( FD_ISSET( i , exceptfds ) )
	      FD_SET( i , &XmuXexceptfds ) ;
    }
    
    /*
     * -- clear the original masks !!! 
     * -- WATCH OUT : There are sneaky devils like xterm that pass
     * -- integers as masks ( a 32-bit mask ), but assume that it still
     * -- works! 
     */
    for( i = 0 ; i <= MASKIDX( nfds ) ; i++ )
    {
	if( readfds ) 
	    FD_ZERO_INDEXED( readfds , i ) ; /* -- ha ha ! -- */
	if( writefds ) 
	    FD_ZERO_INDEXED( writefds , i ) ; /* -- ha ha ! -- */
	if( exceptfds ) 
	    FD_ZERO_INDEXED( exceptfds , i ) ; /* -- ha ha ! -- */
    }


    /* -- set the save set -- */
    bcopy( ( char * ) &XmuXreadfds , 
	   ( char * ) &saved_rmask , sizeof( fd_set ) ) ;
    bcopy( ( char * ) &XmuXwritefds , 
	   ( char * ) &saved_wmask , sizeof( fd_set ) ) ;
    bcopy( ( char * ) &XmuXexceptfds , 
	   ( char * ) &saved_emask , sizeof( fd_set ) ) ;

#ifdef DONT_READ_X_SOCKETS
    while( True ) 
    {
    /* -- restore the masks -- */
    bcopy( ( char * )&saved_rmask , 
	   ( char * ) &XmuXreadfds , sizeof( fd_set ) ) ;
    bcopy( ( char * ) &saved_wmask , 
	   ( char * ) &XmuXwritefds , sizeof( fd_set ) ) ;
    bcopy( ( char * ) &saved_emask , 
	   ( char * ) &XmuXexceptfds ,sizeof( fd_set ) ) ;
#endif
    
    /* -- call select with augmented masks and incremented nfds -- */

    nfound = sElEcT( XmuXnfds , &XmuXreadfds , &XmuXwritefds , 
		     &XmuXexceptfds , timeout ) ;

    if( nfound < 0 )
    {
	XmuXdebug( debug_events , "select failed , errr = %d\n" , errno ) ;
	return( nfound ) ;
    }
    /*
     * -- translate the result back to fds that are known by the caller
     */
    XmuXdebug( debug_events , "XmuXselect " ) ;
    for( i = 0 ; i < XmuXnfds ; i++ )
    {
	if( FD_ISSET( i , &XmuXreadfds ) )
	{
	    if( !readfds )
	    {
		readfds = ( fd_set * ) Xmalloc( sizeof( fd_set ) ) ;
		FD_ZERO( readfds ) ;
	    }
	    XmuXdebug( debug_events , "r%d " , i ) ;
#ifdef DONT_READ_X_SOCKETS	    
	    if( ValidConnection( i ) )
	    {
		if( HaveReadEvents( i ) )
		    FD_SET( ConnectionTranslation[ i ]->prim_server , 
			    readfds ) ;
	    }
	    else
		FD_SET( i , readfds ) ;
#else
	    if( ValidConnection( i ) )
		sel_fd =
                    ConnectionTranslation[ i ]->prim_server ;
                else
                    sel_fd = i ;
	    FD_SET( sel_fd , readfds ) ;
#endif
	}

	if( FD_ISSET( i , &XmuXwritefds ) )
	{
	    XmuXdebug( debug_events , "w%d " , i ) ;
	    if( !writefds )
	    {
		writefds = ( fd_set * ) Xmalloc( sizeof( fd_set ) ) ;
		FD_ZERO( writefds ) ;
	    }
	    FD_SET( i , writefds ) ;
	}

	if( FD_ISSET( i , &XmuXexceptfds ) )
	{
	    XmuXdebug( debug_events , "e%d " , i ) ;
	    if( !exceptfds )
	    {
		exceptfds = ( fd_set * ) Xmalloc( sizeof( fd_set ) ) ;
		FD_ZERO( exceptfds ) ;
	    }
	    FD_SET( i , exceptfds ) ;
	}
    }
    /* 
     * -- recalculate nfound
     */
    nfound = 0 ;
    for( i = 0 ; i < nfds ; i++ )
      if( ( readfds && FD_ISSET( i , readfds) ) || 
	  ( writefds && FD_ISSET( i , writefds ) ) ||
	  ( exceptfds && FD_ISSET( i , exceptfds ) ) )
	nfound++ ;

    XmuXdebug( debug_events , " found %d \n" , nfound ) ;

#ifdef DONT_READ_X_SOCKETS    
    if( ( nfound != 0 ) || !WaitUntilDoomsday( timeout ) )
      {
	return( nfound ) ;
      }
    } /* while( True ) */
#else
    return( nfound ) ;
#endif

}
 
#undef BIT_ISSET    

/* -- xterm drives me crazy !!! -- */

int 
XmuXioctl( d , request , argp )

int d , request ;
char *argp ;

{   register Display *chalk_dpy ;
    register int result ;

    if( ValidConnection( d ) )
    {
	chalk_dpy = XmuXChalkDisplay( MuxDisplay( d ) ) ;
	d = chalk_dpy->fd ;
    }
    XmuXdebug( debug_events , "XmuXioctl on socket %d, " , d ) ;

    result = ioctl( d , request , argp ) ;

    XmuXdebug( debug_events , "argp = %d\n" , argp ? (int) *argp : 0 ) ;
    
    return( result ) ;
}

void 
XmuXIncServers( n ) 

int n ;

{
    num_of_servers += n  ;
}

void
XmuXDecServers( n )

int n ;

{ 
    num_of_servers -= n ;
    if( num_of_servers < 0 ) 
      num_of_servers = 0 ;
}
