v12i080: X server patch for sun4's, Part01/01

Viktor Dukhovni slcpi!viktor at uunet.UU.NET
Mon May 6 15:08:30 AEST 1991


Submitted-by: slcpi!viktor at uunet.UU.NET (Viktor Dukhovni)
Posting-number: Volume 12, Issue 80
Archive-name: r4.sun.patch/part01

[ moderator's note: don't just blindly apply this -- read it first! ]

	I had posted this in response to a question on comp.unix.internals,
it has been pointed out to me that:

1)	The patch for "mivaltree.c" was broken and unnecessary.  (so
I am excluding it now)

2)	I should post to a group where the patch is likely to at least be
archived.

	Also in rushing out a response to the original question
I ommitted the README file from the code by:
	Robert Viduya		   robert at shangri-la.gatech.edu
on which the NumLock and Audio support are based.

	The iconization unser SunView is mine,  and I changed Robert's
code for greater reuse of code between CapsLock and NumLock,  and to allow
a per user bell file whose name is obtained from the environment.
	Also the #ifdef macros were changed to test for presense of
functionality rather than release level (which defines the available 
functionality in sun.cf)  The patch for sun.cf is not strictly
limited to the above functions,  season to taste!

	Here is the origina README:  (patch below)
=================================
The files in this package are my changes to the X.V11R4 Sun server (upto
patch 11).  They implement two things: support for the numeric keypad on
type 4 keyboards and support for the audio device on the SS1 for use as
a bell.  The two changes are don't rely on each other and with minor
modifications to the source, either may be removed.  All changes in the
source files are #ifdef'ed.

The numeric keypad support should work under either SunOS 4.0 and 4.1, but
I've only tested it under 4.1.  The only difference between the two OS
releases (as far as the code is concerned) is that under 4.1, the Num Lock
led on the keyboard turns on and off as appropriate.  NumLock support is
implemented by grabbing the numlock key and using it to toggle a numlock
flag.  This means that X clients won't ever see the numlock key being hit
(real minor loss, imho).  Whenever a key on the numeric keypad is hit, the
flag is queried and if clear, they are passed through to the normal key
processing unmolested.  If the numlock flag is set, they are remapped to
a new set of scan codes that aren't used by the keyboard.  The mapping
is as follows:

Key		Numlock off                     Numlock on

=,R4		0x2d=NoSymbol,R4                0x80=KP_Equal,R4
/,R5		0x2e=NoSymbol,R5                0x81=KP_Divide,R5
*,R6		0x2f=NoSymbol,R6                0x82=KP_Multiply,R6
.,Del		0x32=Delete,NoSymbol            0x83=KP_Decimal,NoSymbol
7,Home,R7	0x44=Home,R7                    0x84=KP_7,R7
8,Up,R8		0x45=Up,R8                      0x85=KP_8,R8
9,PgUp,R9	0x46=Prior,R9                   0x86=KP_9,R9
-		0x47=NoSymbol,NoSymbol		0x87=KP_Subtract,NoSymbol
Enter		0x5a=NoSymbol,NoSymbol          0x88=KP_Enter,NoSymbol
4,Left,R10	0x5b=Left,R10                   0x89=KP_4,R10
5,R11		0x5c=NoSymbol,R11               0x8a=KP_5,R11
6,Right,R12	0x5c=Right,R12                  0x8b=KP_6,R12
0,Ins		0x5e=Insert,NoSymbol            0x8c=KP_0,NoSymbol
1,End,R13	0x70=End,R13                    0x8d=KP_1,R13
2,Down,R14	0x71=Down,R14                   0x8e=KP_2,R14
3,PgDn,R15	0x72=Next,R15                   0x8f=KP_3,R15
+		0x7d=NoSymbol,NoSymbol          0x90=KP_Add,NoSymbol

Here's the rational.  The Sun server maps two symbols onto each key, but
the numeric keypad has at most three symbols per key.  If numlock is
set, then a key's primary symbol is the one at the very top of the key
(the numeric one).  If numlock is clear, then a key's primary symbol is
the one below that (the cursor movement one).  In any case, the key's
secondary symbol is always the one on the front of the key (the R1-R15
ones).

Of course, if the mapping isn't satisfactory to a given user, he can use
xmodmap to remap the scan codes to something he likes while still having
the advantage of using the numlock key to toggle the keypad.

The second change, that of using /dev/audio for replacing the keyboard
bell, isn't implemented as cleanly as I like mainly because X itself
provides fairly minimal control of the bell.  X allows the user to
adjust the volume, pitch and duration of the bell.  Mac's, as a point of
comparison, allow users to replace the bell sound with any digitized
sound they desire, leading to all sorts of weird bells like toilet
flushes and PeeWee Herman screams.  Although I didn't want anything
weird like that, I did want a nice bell sound with a sharp attack and a
ringing decay.  So I kludged in a way for me to do that.  The code was
written explicitely for SunOS 4.1 (it uses /usr/lib/libaudio.a that can
be made by stuff in /usr/demo/SOUND).  I can pretty much guarantee that
it won't work under 4.0 without changes.  The code expects a file called
$(LIBDIR)/serverbell.au to exist and must contain the sound the user
wants for the bell (on my system, I leave the file world-writable so I
can replace the sound anytime I want to).  This file is stat'ed
everytime a request to ring the bell comes in and if the time stamp has
been changed, is closed and re-opened so that a user can replace the
sound on the fly.  If any error occurs, like not being able to access
the audio device or the sound file or the sound file not being correctly
structured, then the code will fallback to ringing the normal bell on
the keyboard.

Using the xset command, the user can adjust the base volume of the played
sound file.  I say base volume because the XBell Xlib function has a
volume parameter that is applied to the base volume to produce the final
volume.  Adjusting the base volume is probably desired because some X
applications (like emacs) call XBell with a parameter of 100%.

Also using the xset command, the user can set the pitch to either a zero
or non-zero value.  Setting it to a non-zero value (default is 400 when
the server starts up) makes the server use /dev/audio for a bell (if it
can).  Setting it to zero forces the server to use the normal bell on
the keyboard.  The server will leave /dev/audio open if the pitch is
non-zero.  Since /dev/audio is an exclusive access device, this has the
effect of preventing any other use of the it.  Setting the pitch to zero
allows the user to tell the server to close the device, presumably
because the user wants to do something else with it.  Setting the pitch
has no effect whatsoever on the sound played.

The xset command's third bell parameter is duration which has no effect
on the sound played (unlike the keyboard bell where duration specifies
how long the bell is on).  Whenever an XBell request comes in, the sound
file is played from beginning to end.  This avoids "choppy" sounds that
occur when the duration specified is shorter than the sound file.
Fortunately, the sound file is played in parallel to code execution in
the server (as a result of the fact that the server leaves the audio
device open all the time) so the server can keep running while the sound
file plays to completion.

Anyway, the changes are in four files which need to be copied to their
appropriate locations in the X source tree.  They are:

	sun.cf		belongs in	./config/sun.cf
	sunKeyMap.c	belongs in	./server/ddx/sun/sunKeyMap.c
	sunKbd.c	belongs in	./server/ddx/sun/sunKbd.c
	Imakefile	belongs in	./server/Imakefile

You might want to diff them with their original files before copying
them.

			robert

--
Robert Viduya					   robert at shangri-la.gatech.edu
Office of Computing Services
Georgia Institute of Technology					 (404) 894-6296
Atlanta, Georgia	30332-0275
==================================

*** /tmp/,RCSt1a01935	Wed Apr 24 22:11:45 1991
--- ./server/ddx/sun/sunKbd.c	Tue Oct 23 14:15:44 1990
***************
*** 48,53 ****
--- 48,57 ----
  
  
  #define NEED_EVENTS
+ #ifdef SUN_AUDIO_FILE
+ #include <multimedia/libaudio.h>
+ #include <multimedia/audio_device.h>
+ #endif
  #include "sun.h"
  #include <stdio.h>
  #include "Xproto.h"
***************
*** 55,61 ****
  #include "inputstr.h"
  
  typedef struct {
!     int	    	  trans;          	/* Original translation form */
  } SunKbPrivRec, *SunKbPrivPtr;
  
  extern CARD8 *sunModMap[];
--- 59,71 ----
  #include "inputstr.h"
  
  typedef struct {
!     int		trans;		/* Original translation form */
! #ifdef SUN_AUDIO_FILE
!     int		audio_fd;	/* /dev/audio fd */
!     int		sound_fd;	/* fd of sound file */
!     struct stat	sound_stat;	/* stat info of sound file */
!     Bool	using_audio;	/* true if using /dev/audio else normal bell */
! #endif
  } SunKbPrivRec, *SunKbPrivPtr;
  
  extern CARD8 *sunModMap[];
***************
*** 93,98 ****
--- 103,169 ----
      &sysKbCtrl,			/* Initial full duration = .25 sec. */
  };
  
+ /* XXX:  Must be a clean way of not hardwiring these! */
+ #define KB_SUN4_CODE_MASK	0x7F
+ #define KB_SUN4_MAX_CODE	0x7F
+ #define KB_SUN4_NUM_LOCK_KCODE	(0x61+MIN_KEYCODE)
+ #define KB_SUN4_CAPS_LOCK_KCODE	(0x76+MIN_KEYCODE)
+ 
+ #ifdef SUN_NUMLOCK
+ /*
+  * The following table is the list of scancodes for keys affected by
+  * the numlock switch.  The order MUST match the order of simulated
+  * numlock keys defined at the end of the keymap in sunKeyMap.c
+  */
+ static char		numlock_keys[] = {
+     0x2d, 0x2e, 0x2f, 0x32, 0x44, 0x45, 0x46, 0x47, 0x5a,
+     0x5b, 0x5c, 0x5d, 0x5e, 0x70, 0x71, 0x72, 0x7d, 0x00
+ };
+ #endif
+ 
+ #ifdef SUN_LED_SUPPORT
+ /*
+  * set_kbd_led
+  */
+ static void
+ set_kbd_led (pKeyboard, flag, mask)
+ DevicePtr	pKeyboard;
+ Bool		flag;
+ long		mask;
+ {
+     KbPrivPtr		kbp = (KbPrivPtr) pKeyboard->devicePrivate;
+     int 		kbdOpenedHere;
+     char		led;
+     bool		old;
+ 
+ 
+     kbdOpenedHere = ( kbp->fd < 0 );
+     if ( kbdOpenedHere ) {
+ 	kbp->fd = open("/dev/kbd", O_RDWR, 0);
+ 	if (kbp->fd < 0) {
+ 	    ErrorF("set_numlock_flag: can't open keyboard");
+ 	    return;
+ 	}
+     }	
+  
+     (void) ioctl (kbp->fd, KIOCGLED, &led);
+     old = ((led & mask) == mask);
+ 
+     if (flag != old) {
+ 	if (flag)
+ 	    led |= mask;
+ 	else
+ 	    led &= ~mask;
+ 	(void) ioctl (kbp->fd, KIOCSLED, &led);
+     }
+ 
+     if ( kbdOpenedHere ) {
+ 	(void) close(kbp->fd);
+ 	kbp->fd = -1;
+     }
+ }
+ #endif
+ 
  /*-
   *-----------------------------------------------------------------------
   * sunKbdProc --
***************
*** 167,173 ****
                          perror( "ioctl KIOCGETKEY" );
  			FatalError("Can't KIOCGETKEY on fd %d\n", kbdFd);
                      }
!                     if (key.kio_entry != HOLE)
                          sysKbPriv.type = KB_SUN4;
                  }
  #endif
--- 238,244 ----
                          perror( "ioctl KIOCGETKEY" );
  			FatalError("Can't KIOCGETKEY on fd %d\n", kbdFd);
                      }
!                     if ( ((long)key.kio_entry) != HOLE)
                          sysKbPriv.type = KB_SUN4;
                  }
  #endif
***************
*** 208,213 ****
--- 279,292 ----
  		sunKeySyms[sysKbPriv.type].maxKeyCode += offset;
  		sysKbPriv.offset = offset;
  	    }
+ #ifdef SUN_LED_SUPPORT
+ 	    if (sysKbPriv.type == KB_SUN4)
+ 		set_kbd_led (pKeyboard, FALSE, ~0L);
+ #endif
+ #ifdef SUN_AUDIO_FILE
+ 	    sunKbPriv.audio_fd = sunKbPriv.sound_fd = -1;
+ 	    sunKbPriv.using_audio = FALSE;
+ #endif
  	    InitKeyboardDeviceStruct(
  		    pKeyboard,
  		    &(sunKeySyms[sysKbPriv.type]),
***************
*** 229,234 ****
--- 308,314 ----
  		 * when DEVICE_CLOSE was executed.
  		 * Translation is set/reset upon receipt of
  		 * KBD_USE/KBD_DONE input events (sunIo.c)
+ 		 * Or When Meta-L2 suspends the server.
  		 */
  		if (deviceOffKbdState != TR_UNDEFINED) {
  		    if (sunChangeKbdTranslation(pKeyboard,
***************
*** 235,240 ****
--- 315,333 ----
  				deviceOffKbdState == TR_UNTRANS_EVENT) < 0) {
  			FatalError("Can't set (SW) keyboard translation\n");
  		    }
+ 
+ #ifdef SUN_LED_SUPPORT
+ 		    if (sysKbPriv.type == KB_SUN4) {
+ 			set_kbd_led (pKeyboard, FALSE, ~0L) ;
+ 			/* Set LEDs to match Logical state */
+ 			if (BitIsOn(((DeviceIntPtr)pKeyboard)->key->down,
+ 				    KB_SUN4_NUM_LOCK_KCODE))
+ 			    set_kbd_led (pKeyboard, TRUE, LED_NUM_LOCK) ;
+ 			if (BitIsOn(((DeviceIntPtr)pKeyboard)->key->down, 
+ 				    KB_SUN4_CAPS_LOCK_KCODE))
+ 			    set_kbd_led (pKeyboard, TRUE, LED_CAPS_LOCK) ;
+ 		    }
+ #endif
  		}
  		AddEnabledDevice(windowFd);
  #endif SUN_WINDOWS
***************
*** 254,259 ****
--- 347,354 ----
  		AddEnabledDevice(kbdFd);
  	    }
  	    pKeyboard->on = TRUE;
+ 	    /* Enable Processing in sunWakeupHandler */
+ 	    autoRepeatReady = 1;
  	    break;
  
  	case DEVICE_CLOSE:
***************
*** 326,335 ****
      int	    	  loudness;	    /* Percentage of full volume */
      DevicePtr	  pKeyboard;	    /* Keyboard to ring */
  {
!     KbPrivPtr	  pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
!     int	  	  kbdCmd;   	    /* Command to give keyboard */
!     int	 	  kbdOpenedHere; 
!  
      if (loudness == 0) {
   	return;
      }
--- 421,516 ----
      int	    	  loudness;	    /* Percentage of full volume */
      DevicePtr	  pKeyboard;	    /* Keyboard to ring */
  {
!     KbPrivPtr		pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
!     SunKbPrivPtr	psPriv = (SunKbPrivPtr) pPriv->devPrivate;
!     int			kbdCmd;   	    /* Command to give keyboard */
!     int			kbdOpenedHere; 
! 
! 
! #ifdef SUN_AUDIO_FILE
! #define savecopy(dst,src) \
! 	((dst=(src)), dst = (dst ? strcpy(malloc(strlen(dst)+1),dst) : NULL))
! 
!     extern char *	getenv() ;
! 
!     static Audio_hdr	dev_hdr, sound_hdr;
!     double		gain;
!     struct stat		st;
!     char		buf[1024];
!     int			nr;
!     static char *	audio_file = NULL;
! 
!     if (psPriv->using_audio) {
! 
! 	/* open the /dev/audio device */
! 	if (psPriv->audio_fd < 0) {
! 	    if ((psPriv->audio_fd = open ("/dev/audio",
! 					  O_WRONLY | O_NDELAY)) < 0) {
! 		if (errno != EBUSY)	/* hard error, don't retry later */
! 		    psPriv->using_audio = FALSE;	
! 		goto use_kybd_bell;
! 	    }
! 	}
! 
! 	if (audio_get_play_config(psPriv->audio_fd,&dev_hdr) != AUDIO_SUCCESS) {
! 	    /* not an audio device */
! 	    (void) close (psPriv->audio_fd);
! 	    psPriv->audio_fd = -1;
! 	    psPriv->using_audio = FALSE;
! 	    goto use_kybd_bell;
! 	}
! 
! 	/* open the sound file */
! 	if (psPriv->sound_fd < 0) {
! reopen_sound:
! 	    if ( !audio_file && !savecopy(audio_file,getenv(SUN_AUDIO_FILE)) ||
! 	         (psPriv->sound_fd = open (audio_file, O_RDONLY, 0)) < 0 ) {
! 		/* can't open, try again later (maybe too expensive?) */
! 		psPriv->sound_fd = -1;
! 		goto use_kybd_bell;
! 	    }
! 	    (void) fstat(psPriv->sound_fd, &(psPriv->sound_stat));
! 	}
! 	else {	/* stat the file to see if it may have changed */
! 	    if (stat (audio_file, &st) == -1) {
! 		(void) close (psPriv->sound_fd);
! 		psPriv->sound_fd = -1;
! 		goto use_kybd_bell;
! 	    }
! 	    if (st.st_mtime != psPriv->sound_stat.st_mtime) {
! 		(void) close (psPriv->sound_fd);	/* changed, go reopen */
! 		goto reopen_sound;
! 	    }
! 	}
! 	
! 	/* set the volume */
! 	gain = ((double) loudness) / 100.0;
! 	(void) audio_set_play_gain (psPriv->audio_fd, &gain);	/* ignore errors */
! 	
! 	/* process the audio file header */
! 	(void) lseek (psPriv->sound_fd, 0L, 0);
! 	if (audio_read_filehdr (psPriv->sound_fd, &sound_hdr, (char *) 0, 0) != AUDIO_SUCCESS)
! 	    goto use_kybd_bell;
! 	if (audio_cmp_hdr (&dev_hdr, &sound_hdr) != 0) {
! 	    if (audio_drain (psPriv->audio_fd, FALSE) != AUDIO_SUCCESS)
! 		goto use_kybd_bell;
! 	    if (audio_set_play_config (psPriv->audio_fd, &sound_hdr) != AUDIO_SUCCESS)
! 		goto use_kybd_bell;
! 	}
! 
! 	/* write the sound */
! 	while ((nr = read (psPriv->sound_fd, buf, sizeof (buf))) >= 0) {
! 	    if (nr == 0)
! 		break;
! 	    (void) write (psPriv->audio_fd, buf, nr);
! 	}
! 
! 	return;
!     }
! 
! use_kybd_bell:
! #endif
! 
      if (loudness == 0) {
   	return;
      }
***************
*** 386,394 ****
      DevicePtr	  pKeyboard;	    /* Keyboard to alter */
      KeybdCtrl     *ctrl;
  {
!     KbPrivPtr	  pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
!     int     	  kbdClickCmd = ctrl->click ? KBD_CMD_CLICK : KBD_CMD_NOCLICK;
!     int	 	  kbdOpenedHere; 
  
      kbdOpenedHere = ( pPriv->fd < 0 );
      if ( kbdOpenedHere ) {
--- 567,579 ----
      DevicePtr	  pKeyboard;	    /* Keyboard to alter */
      KeybdCtrl     *ctrl;
  {
!     KbPrivPtr		pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
!     SunKbPrivPtr	psPriv = (SunKbPrivPtr) ((KbPrivPtr)pKeyboard->devicePrivate)->devPrivate;
!     int			kbdClickCmd = ctrl->click ? KBD_CMD_CLICK : KBD_CMD_NOCLICK;
!     int			kbdOpenedHere;
! #ifdef SUN_AUDIO_FILE
!     int			kbdBellPitch = ctrl->bell_pitch;
! #endif
  
      kbdOpenedHere = ( pPriv->fd < 0 );
      if ( kbdOpenedHere ) {
***************
*** 403,408 ****
--- 588,606 ----
   	ErrorF("Failed to set keyclick");
  	goto bad;
      }
+ 
+ #ifdef SUN_AUDIO_FILE
+     if (!kbdOpenedHere && kbdBellPitch == 0) {	/* 0 pitch means release /dev/audio, use bell */
+ 	if (psPriv->audio_fd >= 0) {
+ 	    (void) audio_drain (psPriv->audio_fd, FALSE);
+ 	    (void) close (psPriv->audio_fd);
+ 	    psPriv->audio_fd = -1;
+ 	}
+ 	psPriv->using_audio = FALSE;
+     }
+     else
+ 	psPriv->using_audio = TRUE;
+ #endif
   
      *pPriv->ctrl = *ctrl;
  
***************
*** 443,449 ****
      static Firm_event	evBuf[MAXEVENTS];   /* Buffer for Firm_events */
  
      pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
!     nBytes = read (pPriv->fd, evBuf, sizeof(evBuf));
  
      if (nBytes < 0) {
  	if (errno == EWOULDBLOCK) {
--- 641,650 ----
      static Firm_event	evBuf[MAXEVENTS];   /* Buffer for Firm_events */
  
      pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
!     if (pKeyboard->on)
! 	nBytes = read (pPriv->fd, evBuf, sizeof(evBuf));
!     else
! 	nBytes = 0;
  
      if (nBytes < 0) {
  	if (errno == EWOULDBLOCK) {
***************
*** 501,508 ****
      BYTE		key;
      CARD8		keyModifiers;
  
      if (autoRepeatKeyDown && fe->id == AUTOREPEAT_EVENTID) {
- 	pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
  	if (pPriv->ctrl->autoRepeat != AutoRepeatModeOn) {
  		autoRepeatKeyDown = 0;
  		return;
--- 702,709 ----
      BYTE		key;
      CARD8		keyModifiers;
  
+     pPriv = (KbPrivPtr) pKeyboard->devicePrivate;
      if (autoRepeatKeyDown && fe->id == AUTOREPEAT_EVENTID) {
  	if (pPriv->ctrl->autoRepeat != AutoRepeatModeOn) {
  		autoRepeatKeyDown = 0;
  		return;
***************
*** 539,545 ****
  	return;
      }
  
!     key = (fe->id & 0x7F) + sysKbPriv.offset;
      keyModifiers = ((DeviceIntPtr)pKeyboard)->key->modifierMap[key];
      if (autoRepeatKeyDown && (keyModifiers == 0) &&
  	((fe->value == VKEY_DOWN) || (key == autoRepeatEvent.u.u.detail))) {
--- 740,765 ----
  	return;
      }
  
! 
! #ifdef SUN_NUMLOCK
!     if (pPriv->type == KB_SUN4) {
! 	char	*cp, *index ();
! 
! 	key = fe->id & KB_SUN4_CODE_MASK;
! 	if (BitIsOn(((DeviceIntPtr)pKeyboard)->key->down, 
! 	    KB_SUN4_NUM_LOCK_KCODE) &&
! 	    (cp = index (numlock_keys, key)) != NULL)
! 	    fe->id = KB_SUN4_MAX_CODE + 1 + (cp - numlock_keys);
! 	else
! 	    fe->id &= KB_SUN4_CODE_MASK;
!     }
!     else
! 	fe->id &= KB_SUN4_CODE_MASK;
!     key = fe->id + sysKbPriv.offset;
! #else
!     key = (fe->id & KB_SUN4_CODE_MASK) + sysKbPriv.offset;
! #endif
! 
      keyModifiers = ((DeviceIntPtr)pKeyboard)->key->modifierMap[key];
      if (autoRepeatKeyDown && (keyModifiers == 0) &&
  	((fe->value == VKEY_DOWN) || (key == autoRepeatEvent.u.u.detail))) {
***************
*** 558,568 ****
      xE.u.u.type = ((fe->value == VKEY_UP) ? KeyRelease : KeyPress);
      xE.u.u.detail = key;
  
!     if (keyModifiers & LockMask) {
! 	if (xE.u.u.type == KeyRelease)
! 	    return; /* this assumes autorepeat is not desired */
! 	if (BitIsOn(((DeviceIntPtr)pKeyboard)->key->down, key))
! 	    xE.u.u.type = KeyRelease;
      }
  
      if ((xE.u.u.type == KeyPress) && (keyModifiers == 0)) {
--- 778,807 ----
      xE.u.u.type = ((fe->value == VKEY_UP) ? KeyRelease : KeyPress);
      xE.u.u.detail = key;
  
! #ifndef SUN_NUMLOCK
!     if (keyModifiers & LockMask)
! #else
!     if (keyModifiers & LockMask ||
! 	pPriv->type == KB_SUN4 && key == KB_SUN4_NUM_LOCK_KCODE)
! #endif
!     {
! #ifdef SUN_LED_SUPPORT
! 	int ledmask = (keyModifiers & LockMask) ? LED_CAPS_LOCK : LED_NUM_LOCK;
! #endif
! 	if (xE.u.u.type == KeyRelease ||
! 	    BitIsOn(((DeviceIntPtr)pKeyboard)->key->down, key)) {
! 	    if ( xE.u.u.type == KeyRelease )
! 		return;
! 	    else
! 		xE.u.u.type = KeyRelease;
! #ifdef SUN_LED_SUPPORT
! 	    if ( pPriv->type == KB_SUN4 )
! 		    set_kbd_led(pKeyboard, FALSE, ledmask) ;
! 	} else {
! 	    if ( pPriv->type == KB_SUN4 )
! 		set_kbd_led (pKeyboard, TRUE, ledmask);
! #endif
! 	}
      }
  
      if ((xE.u.u.type == KeyPress) && (keyModifiers == 0)) {
***************
*** 621,626 ****
--- 860,866 ----
      int		kbdOpenedHere;
  
      static struct timeval lastChngKbdTransTv;
+     static int firsttime = 1;
      struct timeval tv;
      struct timeval lastChngKbdDeltaTv;
      int lastChngKbdDelta;
***************
*** 629,639 ****
       * Workaround for SS1 serial driver kernel bug when KIOCTRANS ioctl()s
       * occur too closely together in time.
       */
      gettimeofday(&tv, (struct timezone *) NULL);
!     tvminus(lastChngKbdDeltaTv, tv, lastChngKbdTransTv);
!     lastChngKbdDelta = TVTOMILLI(lastChngKbdDeltaTv);
      if (lastChngKbdDelta < 750) {
  	struct timeval wait;
  
  	/*
           * We need to guarantee at least 750 milliseconds between
--- 869,890 ----
       * Workaround for SS1 serial driver kernel bug when KIOCTRANS ioctl()s
       * occur too closely together in time.
       */
+     /*
+      * Is this still needed ???  What OS Versions???
+      */
+ #ifdef KIOCTRANS_HACK
      gettimeofday(&tv, (struct timezone *) NULL);
!     if (firsttime) {
! 	lastChngKbdTransTv = tv;
! 	lastChngKbdDelta   = 0;
! 	firsttime = 0;
!     } else {
! 	tvminus(lastChngKbdDeltaTv, tv, lastChngKbdTransTv);
! 	lastChngKbdDelta = TVTOMILLI(lastChngKbdDeltaTv);
!     }
      if (lastChngKbdDelta < 750) {
  	struct timeval wait;
+ 	int    mask = sigmask(SIGIO);       
  
  	/*
           * We need to guarantee at least 750 milliseconds between
***************
*** 641,650 ****
  	 */
  	wait.tv_sec = 0;
  	wait.tv_usec = (750L - lastChngKbdDelta) * 1000L;
!         (void) select(0, (int *)0, (int *)0, (int *)0, &wait);
          gettimeofday(&tv, (struct timezone *) NULL);
      }
      lastChngKbdTransTv = tv;
      
  
      pPriv = (KbPrivPtr)pKeyboard->devicePrivate;
--- 892,905 ----
  	 */
  	wait.tv_sec = 0;
  	wait.tv_usec = (750L - lastChngKbdDelta) * 1000L;
! 
! 	mask = sigblock(mask) ;
!         (void) select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &wait);
! 	(void) sigsetmask(mask) ;
          gettimeofday(&tv, (struct timezone *) NULL);
      }
      lastChngKbdTransTv = tv;
+ #endif
      
  
      pPriv = (KbPrivPtr)pKeyboard->devicePrivate;
***************
*** 792,802 ****
--- 1047,1078 ----
      register struct inputevent *se;
  {   
      Firm_event	fe;
+     int		loc;
+     static	Meta_On, Meta_hold;
  
      fe.time = event_time(se);
      fe.id = event_id(se);
      fe.value = (event_is_up(se) ? VKEY_UP : VKEY_DOWN);
  
+     if (fe.id > 0) {
+ 	loc = (fe.id - 1) * sunKeySyms[sysKbPriv.type].mapWidth;
+ 
+ 	if( sunKeySyms[sysKbPriv.type].map[loc] == XK_Meta_L ||
+ 	   sunKeySyms[sysKbPriv.type].map[loc] == XK_Meta_R ) /* Meta */
+ 	{
+ 		Meta_hold = fe.id;
+ 		Meta_On = (fe.value == VKEY_DOWN);
+ 	}
+ 	if( sunKeySyms[sysKbPriv.type].map[loc] == XK_L2 ) /* L2 */
+ 	{
+ 		if( fe.value == VKEY_DOWN && Meta_On )
+ 		{
+ 			stopme();
+ 			fe.id = Meta_hold;
+ 			fe.value = VKEY_UP;
+ 		}
+ 	}
+     }
      sunKbdProcessEvent (pKeyboard, &fe);
  }
  #endif SUN_WINDOWS
*** /tmp/,RCSt1a01938	Wed Apr 24 22:11:46 1991
--- ./server/ddx/sun/sun.h	Tue Oct 23 14:14:59 1990
***************
*** 248,259 ****
  extern struct timeval autoRepeatDeltaTv;
  
  #define tvminus(tv, tv1, tv2)	/* tv = tv1 - tv2 */ \
- 		if ((tv1).tv_usec < (tv2).tv_usec) { \
- 			(tv1).tv_usec += 1000000; \
- 			(tv1).tv_sec -= 1; \
- 		} \
  		(tv).tv_usec = (tv1).tv_usec - (tv2).tv_usec; \
! 		(tv).tv_sec = (tv1).tv_sec - (tv2).tv_sec;
  
  #define tvplus(tv, tv1, tv2)	/* tv = tv1 + tv2 */ \
  		(tv).tv_sec = (tv1).tv_sec + (tv2).tv_sec; \
--- 248,259 ----
  extern struct timeval autoRepeatDeltaTv;
  
  #define tvminus(tv, tv1, tv2)	/* tv = tv1 - tv2 */ \
  		(tv).tv_usec = (tv1).tv_usec - (tv2).tv_usec; \
! 		(tv).tv_sec = (tv1).tv_sec - (tv2).tv_sec; \
! 		if ((tv).tv_usec < 0) { \
! 			(tv).tv_usec += 1000000; \
! 			(tv).tv_sec -= 1; \
! 		}
  
  #define tvplus(tv, tv1, tv2)	/* tv = tv1 + tv2 */ \
  		(tv).tv_sec = (tv1).tv_sec + (tv2).tv_sec; \
*** /tmp/,RCSt1a01941	Wed Apr 24 22:11:47 1991
--- ./server/ddx/sun/sunKeyMap.c	Tue Oct 23 14:16:28 1990
***************
*** 350,356 ****
          XK_F7,          NoSymbol,               /* 0x10 */
          XK_F8,          NoSymbol,               /* 0x11 */
          XK_F9,          NoSymbol,               /* 0x12 */
! 	XK_Alt_L,	NoSymbol,		/* 0x13 */
          NoSymbol,       NoSymbol,               /* 0x14 */
          XK_R1,          XK_Pause,               /* 0x15 */
          XK_R2,          NoSymbol,               /* 0x16 */
--- 350,356 ----
          XK_F7,          NoSymbol,               /* 0x10 */
          XK_F8,          NoSymbol,               /* 0x11 */
          XK_F9,          NoSymbol,               /* 0x12 */
!         XK_Alt_L,       NoSymbol,               /* 0x13 */
          NoSymbol,       NoSymbol,               /* 0x14 */
          XK_R1,          XK_Pause,               /* 0x15 */
          XK_R2,          NoSymbol,               /* 0x16 */
***************
*** 376,384 ****
          XK_quoteleft,   XK_asciitilde,          /* 0x2a */
          XK_BackSpace,   NoSymbol,               /* 0x2b */
          NoSymbol,       NoSymbol,               /* 0x2c */
!         XK_R4,          XK_KP_Equal,		/* 0x2d */
!         XK_R5,          XK_KP_Divide,		/* 0x2e */
!         XK_R6,          XK_KP_Multiply,		/* 0x2f */
          NoSymbol,       NoSymbol,               /* 0x30 */
          XK_L5,          NoSymbol,               /* 0x31 */
          XK_Delete,      XK_KP_Decimal,          /* 0x32 */
--- 376,384 ----
          XK_quoteleft,   XK_asciitilde,          /* 0x2a */
          XK_BackSpace,   NoSymbol,               /* 0x2b */
          NoSymbol,       NoSymbol,               /* 0x2c */
!         XK_R4,          XK_KP_Equal,            /* 0x2d */
!         XK_R5,          XK_KP_Divide,           /* 0x2e */
!         XK_R6,          XK_KP_Multiply,         /* 0x2f */
          NoSymbol,       NoSymbol,               /* 0x30 */
          XK_L5,          NoSymbol,               /* 0x31 */
          XK_Delete,      XK_KP_Decimal,          /* 0x32 */
***************
*** 425,431 ****
          XK_Left,        XK_KP_4,                /* 0x5b */
          XK_R11,         XK_KP_5,                /* 0x5c */
          XK_Right,       XK_KP_6,                /* 0x5d */
!         XK_Insert,       XK_KP_0,                /* 0x5e */
          XK_L9,          NoSymbol,               /* 0x5f */
          NoSymbol,       NoSymbol,               /* 0x60 */
          XK_L10,         NoSymbol,               /* 0x61 */
--- 425,431 ----
          XK_Left,        XK_KP_4,                /* 0x5b */
          XK_R11,         XK_KP_5,                /* 0x5c */
          XK_Right,       XK_KP_6,                /* 0x5d */
!         XK_Insert,      XK_KP_0,                /* 0x5e */
          XK_L9,          NoSymbol,               /* 0x5f */
          NoSymbol,       NoSymbol,               /* 0x60 */
          XK_L10,         NoSymbol,               /* 0x61 */
***************
*** 459,464 ****
--- 459,486 ----
          XK_KP_Add,      XK_KP_Add,              /* 0x7d */
          NoSymbol,       NoSymbol,               /* 0x7e */
          NoSymbol,       NoSymbol,               /* 0x7f */
+ #ifdef SUN_NUMLOCK
+         /* these entries are faked scan codes for keys that are affected
+          * by the numlock key.  The order must match the table in sunKbd.c
+          */
+         XK_KP_Equal,    XK_R4,                  /* 0x2d => 0x80 */
+         XK_KP_Divide,   XK_R5,                  /* 0x2e => 0x81 */
+         XK_KP_Multiply, XK_R6,                  /* 0x2f => 0x82 */
+         XK_KP_Decimal,  NoSymbol,               /* 0x32 => 0x83 */
+         XK_KP_7,        XK_R7,                  /* 0x44 => 0x84 */
+         XK_KP_8,        XK_R8,                  /* 0x45 => 0x85 */
+         XK_KP_9,        XK_R9,                  /* 0x46 => 0x86 */
+         XK_KP_Subtract, NoSymbol,               /* 0x47 => 0x87 */
+         XK_KP_Enter,    NoSymbol,               /* 0x5a => 0x88 */
+         XK_KP_4,        XK_R10,                 /* 0x5b => 0x89 */
+         XK_KP_5,        XK_R11,                 /* 0x5c => 0x8a */
+         XK_KP_6,        XK_R12,                 /* 0x5d => 0x8b */
+         XK_KP_0,        NoSymbol,               /* 0x5e => 0x8c */
+         XK_KP_1,        XK_R13,                 /* 0x70 => 0x8d */
+         XK_KP_2,        XK_R14,                 /* 0x71 => 0x8e */
+         XK_KP_3,        XK_R15,                 /* 0x72 => 0x8f */
+         XK_KP_Add,      NoSymbol,               /* 0x7d => 0x90 */
+ #endif
  };
  
  
***************
*** 473,479 ****
--- 495,505 ----
  #endif
      Sun2Map,		1,	0x7a,	2,
      Sun3Map,		1,	0x7a,	2,
+ #ifdef SUN_NUMLOCK
+     Sun4Map,		1,	0x90,	2,
+ #else
      Sun4Map,		1,	0x7d,	2,
+ #endif
  };
  
  #define	cT	(ControlMask)
*** /tmp/,RCSt1a01944	Wed Apr 24 22:11:48 1991
--- ./server/ddx/sun/sunInit.c	Tue Oct 23 14:15:36 1990
***************
*** 689,691 ****
--- 689,786 ----
  
      return (fd);
  }
+ 
+ /*
+  * stopme - stop window for suntools
+  */
+ #ifdef	SUN_WINDOWS
+ #include    "validate.h"
+ #include    "windowstr.h"
+ #include    "dix.h"
+ extern WindowPtr *WindowTable;
+ 
+ stopme()
+ {
+     DevicePtr	pKbd;
+     int		i;
+     void	ExposeEveryone();
+ 
+     if( !sunUseSunWindows())
+ 	return;
+ 
+     /*
+      * on our way out -- shut down keyboard and mouse
+      * and remove the window
+      */
+     pKbd = LookupKeyboardDevice();
+     sunKbdProc(pKbd,  DEVICE_OFF);
+     sunMouseProc(pKbd, DEVICE_OFF);
+     win_remove(windowFd);
+ 
+     sunNonBlockConsoleOff((char *)0) ;
+     kill(0, SIGSTOP);
+ 
+     /*
+      * we're back, restore the window, and turn on the
+      * keyboard and mouse
+      * reset nonblocking mode on stderr.
+      */
+     i = fcntl(2, F_GETFL, 0);
+     if (i >= 0)
+         i = fcntl(2, F_SETFL, i | FNDELAY);
+     if (i < 0) {
+         perror("fcntl");
+         ErrorF("InitOutput: can't put stderr in non-block mode\n");
+     }
+     win_insert(windowFd);
+     sunMouseProc(pKbd, DEVICE_ON);
+     sunKbdProc(pKbd, DEVICE_ON);
+ 
+ 
+     /*
+      * refresh all screens
+      */
+     for( i = 0; i < screenInfo.numScreens; )
+         ExposeEveryone(WindowTable[i++]);
+ }
+ 
+ /*
+  * Cause a repaint of area slightly larger than window 
+  * by covering with temp window, and destroying the latter.  
+  * Shamelessly copied from the TileScreenSaver() code in dix/window.c
+  */
+ static void
+ ExposeEveryone(pWin)
+     WindowPtr pWin;
+ {
+     int       result;
+     XID       attributes[2];
+     Mask      mask = CWBackPixmap|CWOverrideRedirect;
+     WindowPtr tempWin;		
+     ScreenPtr pScreen = pWin->drawable.pScreen ;
+     Window    wid     = pWin->drawable.id;
+     
+ 
+ #define FUDGE 32
+ 
+     attributes[0] = None;
+     attributes[1] = xTrue;
+     tempWin = 
+ 	 CreateWindow(FakeClientID(0), pWin,
+ 	      (short)-FUDGE, (short)-FUDGE,
+ 	      (unsigned short)(pScreen->width + FUDGE),
+ 	      (unsigned short)(pScreen->height + FUDGE),
+ 	      0, InputOutput, mask, attributes, 0, serverClient,
+ 	      wVisual(pWin), &result);
+ 
+     if (!tempWin)
+ 	return;
+ 
+     if (!AddResource(tempWin->drawable.id, RT_WINDOW, (pointer)tempWin))
+ 	return;
+ 
+     MapWindow(tempWin, serverClient);
+     FreeResource(tempWin->drawable.id,RT_NONE) ;
+     DeleteWindow(tempWin,serverClient) ;
+ }
+ #endif	SUN_WINDOWS
*** /tmp/,RCSt1a01947	Wed Apr 24 22:11:48 1991
--- ./server/Imakefile	Mon Sep 24 15:15:44 1990
***************
*** 230,235 ****
--- 230,238 ----
  #if defined(UseSunWindowsInServer) && UseSunWindowsInServer
  SUNWINDOWSLIBS = -lsunwindow -lpixrect
  #endif
+ #if defined(UseSunAudioInServer) && UseSunAudioInServer
+ SUNAUDIOLIBS = -laudio
+ #endif
  #define need_dix
  #define need_ddx_snf
  #define need_ddx_mi
***************
*** 240,246 ****
  SUNDIRS = dix ddx/snf ddx/mi ddx/mfb ddx/cfb ddx/sun os/4.2bsd
  SUNOBJS = ddx/sun/sunInit.o $(FONTUTIL)
  SUNLIBS = $(SUN) $(CFB) $(DIX) $(BSD) $(SNF) $(MFB) $(MI) $(EXTENSIONS)
! SUNSYSLIBS = $(SYSLIBS) $(SUNWINDOWSLIBS)
  XsunDIRS = $(SUNDIRS)
  
  ServerTarget(Xsun,$(EXTDIR) $(FONTUTILDIR) $(SUNDIRS),$(SUNOBJS),$(SUNLIBS),$(SUNSYSLIBS))
--- 243,249 ----
  SUNDIRS = dix ddx/snf ddx/mi ddx/mfb ddx/cfb ddx/sun os/4.2bsd
  SUNOBJS = ddx/sun/sunInit.o $(FONTUTIL)
  SUNLIBS = $(SUN) $(CFB) $(DIX) $(BSD) $(SNF) $(MFB) $(MI) $(EXTENSIONS)
! SUNSYSLIBS = $(SUNAUDIOLIBS) $(SYSLIBS) $(SUNWINDOWSLIBS)
  XsunDIRS = $(SUNDIRS)
  
  ServerTarget(Xsun,$(EXTDIR) $(FONTUTILDIR) $(SUNDIRS),$(SUNOBJS),$(SUNLIBS),$(SUNSYSLIBS))
*** /tmp/,RCSt1a01950	Wed Apr 24 22:11:49 1991
--- ./clients/xinit/Imakefile	Mon Oct  1 17:18:13 1990
***************
*** 1,4 ****
--- 1,8 ----
+ #ifdef SunArchitecture
+         DEFINES = SunWindowsDefines ConnectionFlags -DBINDIR=\"$(BINDIR)\"
+ #else
          DEFINES = ConnectionFlags -DBINDIR=\"$(BINDIR)\"
+ #endif
          DEPLIBS = $(DEPXMULIB) $(DEPXLIB)
  LOCAL_LIBRARIES = $(XMULIB) $(XLIB)
            SRCS1 = xinit.c 
*** /tmp/,RCSt1a01953	Wed Apr 24 22:11:49 1991
--- ./clients/xinit/xinit.c	Sun Oct  7 03:31:52 1990
***************
*** 141,146 ****
--- 141,150 ----
      return;
  }
  
+ #ifdef sun
+ static char	*under_sunview;
+ #endif
+ 
  main(argc, argv)
  int argc;
  register char **argv;
***************
*** 175,181 ****
  		 * that means SunWindows isn't running, so we should pass 
  		 * the -C flag to xterm so that it sets up a console.
  		 */
! 		if ( getenv("WINDOW_PARENT") == NULL )
  		    *cptr++ = "-C";
  #endif /* sun */
  	} else {
--- 179,185 ----
  		 * that means SunWindows isn't running, so we should pass 
  		 * the -C flag to xterm so that it sets up a console.
  		 */
! 		if ( (under_sunview=getenv("WINDOW_PARENT")) == NULL )
  		    *cptr++ = "-C";
  #endif /* sun */
  	} else {
***************
*** 290,298 ****
  	signal(SIGUSR1, sigUsr1);
  	if ((serverpid = startServer(server)) > 0
  	 && (clientpid = startClient(client)) > 0) {
! 		pid = -1;
! 		while (pid != clientpid && pid != serverpid)
  			pid = wait(NULL);
  	}
  	signal(SIGQUIT, SIG_IGN);
  	signal(SIGINT, SIG_IGN);
--- 294,315 ----
  	signal(SIGUSR1, sigUsr1);
  	if ((serverpid = startServer(server)) > 0
  	 && (clientpid = startClient(client)) > 0) {
! #ifndef SUN_WINDOWS
! 		for (pid = -1;  pid != clientpid && pid != serverpid;)
  			pid = wait(NULL);
+ #else
+ 		for(pid=-1; pid != clientpid && pid != serverpid;) 
+ 		{
+ 			pid = wait3(&status, WUNTRACED, NULL) ;
+ 			if ( under_sunview && pid == serverpid )
+ 				if (WIFSTOPPED(status)) {
+ 					(void)sigblock(SIGCHLD) ;
+ 					(void)kill(0,SIGSTOP) ;
+ 					(void)kill(serverpid,SIGCONT) ;
+ 					pid = -1;
+ 				}
+ 		}
+ #endif
  	}
  	signal(SIGQUIT, SIG_IGN);
  	signal(SIGINT, SIG_IGN);
*** /tmp/,RCSt1a01956	Wed Apr 24 22:11:50 1991
--- ./config/sun.cf	Mon Oct 15 21:42:41 1990
***************
*** 10,18 ****
  #define OSMinorVersion    1
  #define HasSaberC	  NO		/* for machines that have it */
  #define HasNdbm		  YES
! #define XsunServer 	  Xsun
  
! #define HasGcc 		  NO  	/* VERY USEFUL for server on Sun3 */
  
  #define SystemV           NO
  #define HasPutenv YES
--- 10,24 ----
  #define OSMinorVersion    1
  #define HasSaberC	  NO		/* for machines that have it */
  #define HasNdbm		  YES
! #define XsunServer	  Xsun
! #define ManPath		  /usr/local/man
! #define ManSuffix	  1
  
! #ifdef sparc
! #define HasGcc NO
! #else
! #define HasGcc YES 	/* VERY USEFUL for server on Sun3 */
! #endif
  
  #define SystemV           NO
  #define HasPutenv YES
***************
*** 42,47 ****
--- 48,54 ----
  
  /* you may find one or both of these options useful on your system */
  /* #define DefaultCCOptions -f68881 -pipe */
+ #define DefaultCCOptions -pipe
  
  #define LibraryCCOptions /* don't want special floating point */
  #define LibraryDefines  /**/
***************
*** 53,59 ****
--- 60,70 ----
  
  #if OSMajorVersion == 3 && OSMinorVersion <= 2
  #define OptimizedCDebugFlags /* as nothing */
+ #else
+ #ifdef SparcArchitecture
+ #define OptimizedCDebugFlags	-O4
  #endif
+ #endif
  
  #define UseSunWindowsInServer	YES	/* link in SunWindows support? */
  
***************
*** 63,68 ****
--- 74,106 ----
  #define SunWindowsDefines /* as nothing */
  #endif
  
+ #define UseSunAudioInServer	YES
+ #define UseSunNumlock		YES	/* enable numlock support on type4 kbds? */
+ 
+ #if UseSunNumlock
+ #if OSMajorVersion >= 4 && OSMinorVersion >= 1
+ #define SunNumlockDefines	-DSUN_NUMLOCK -DSUN_LED_SUPPORT
+ #else
+ #define SunNumlockDefines	-DSUN_NUMLOCK
+ #endif
+ #else
+ #define SunNumlockDefines	/* as nothing */
+ #endif
+ 
+ #if UseSunAudioInServer
+ #if OSMajorVersion >= 4 && OSMinorVersion >= 1
+ #define UseSunAudioInServer	YES	/* use /dev/audio for bell? */
+ #else
+ #define UseSunAudioInServer	NO
+ #endif
+ #endif
+ 
+ #if UseSunAudioInServer
+ #define SunAudioDefines -DSUN_AUDIO_FILE=\"XSUNBELLFILE\"
+ #else
+ #define SunAudioDefines	/* as nothing */
+ #endif
+ 
  /* define this as you like for server compilation */
  #if OSMajorVersion >= 4 || defined(SparcArchitecture)
  #define AllocateLocalDefines -DINCLUDE_ALLOCA_H
***************
*** 70,78 ****
  #define AllocateLocalDefines /**/
  #endif
  
! #define ExtensionDefines -DSHAPE -DMITSHM -DMULTIBUFFER -DMITMISC
  
! #define ServerDefines -DXDMCP SunWindowsDefines ExtensionDefines AllocateLocalDefines
  
  #if OSMajorVersion >= 4 && OSMinorVersion >= 0
  #define SetTtyGroup YES
--- 108,116 ----
  #define AllocateLocalDefines /**/
  #endif
  
! #define ExtensionDefines -DMULTIBUFFER
  
! #define ServerDefines -DXDMCP SunNumlockDefines SunWindowsDefines SunAudioDefines ExtensionDefines AllocateLocalDefines
  
  #if OSMajorVersion >= 4 && OSMinorVersion >= 0
  #define SetTtyGroup YES
--
        Viktor Dukhovni <viktor at shearson.com>       : ARPA
                <...!uunet!shearson.com!viktor>     : UUCP
        388 Greenwich St., 11th floor, NY, NY 10013 : US-Post
                +1-(212)-464-3793                   : VOICE

--
Dan Heller
O'Reilly && Associates       Z-Code Software    Comp-sources-x:
Senior Writer                President          comp-sources.x at uunet.uu.net
argv at ora.com                 argv at zipcode.com



More information about the Comp.sources.x mailing list