.if n .pH port.chap10 @(#)chap10	40.4
.\" Porting manual: Miscellaneous NPSI Ch. 10
.\"
.\" Ps = POSTSCRIPT
.\" Sd = NeWS
.\" Cs = CSCRIPT
.ds Ps P\s-2OST\s+2S\s-2CRIPT\s+2
.ds Sd \s-2Ne\h'-0.2n'WS\s+2
.ds Cs \CS\s-2CRIPT\s+2
.BK "Porting the X11/NeWS Server"
.CH "Miscellaneous NPSI Functions" 10
.H 1 "Miscellaneous NPSI Functions" 
.H 2 "Introduction"
In the previous sections we described a number of NPSI functions that need 
to be implemented in order to handle covered I/O muxing, networking 
and keyboard/mouse handling.  This section describes the miscellaneous NPSI 
functions that need to be implemented to complete the system interface.  
In addition,
we will describe some optional functions which can be stubbed out, and 
some data structures declared in the NPSI code which the rest of
the server relies on.
.H 2 "Optional Routines"
The routines listed below are optional and can be stubbed out. Note that 
.UI osi_availinputbytes() 
should return -1. The return values for the others is irrelevant.
.BL
.LI
.UI osi_availinputbytes()                 
.LI
.UI ringBell()               
.LI
.UI open_pipe()                          
.LI
.UI osi_unlockdevice()                    
.LI
.UI osi_lockdevice()                   
.LI
.UI osi_initscreen()                      
.LI
.UI osi_setcolors()                    
.LE
.H 2 "Data Structures"
The server relies on the several data structures being declared
in the NPSI code.  These are listed below.
.SS
SCREENE CurrentInputScreene;
REF ConstrainCanvas;
unsigned char sun_red[256], sun_green[256], sun_blue[256];
.SE
.H 2 "Additional NPSI Functions"
In addition to the routines previously mentioned in this chapter,
the following additional NPSI routines must also be implemented.
.BL
.LI
.UI rint()
.LI
.UI restore_keyboard()
.LI
.UI currenttime()
.LI
.UI osi_spawn_process()
.LI
.UI raw_keyboard()                 
.LI
.UI osi_setinitiallocation()
.LI
.UI initialize_sysVOSI()
.LI
.UI CatastrophicSignalHandler()
.LI
.UI getsocketpeername()
.LI
.UI getsocketlocaladdress()
.LI
.UI getenv()
.LI
.UI setenv()
.LE
.P
A description of each of these functions is provided below.
.H 3 "\f5rint()\fP"                                
.P
.UI rint() 
rounds off the input value to the nearest integer.
.H 3 "\f5restore_keyboard\fP"
.UI restore_keyboard() 
is called when the server exits and the keyboard needs to be restored to its
normal operational mode.  In some implementations,
the keyboard is grabbed by the server so that the server has exclusive control
of it. In the System V version, closing the keyboard releases it 
back to the system, so no other action is necessary.  The code for 
.UI restore_keyboard() 
is shown below.  It is recommended that this code be included in the system.
.SS
    for (i=0; i<screenInfo.numScreenes;i++) {
        raster = cs_scrn_scene(screenInfo.screene[i])->display;
        cmap = Get_Raster(raster, RAS_CMAP);
        Set_Cmap(cmap, CMAP_FUNC, set_cmap);
        Set_Raster(raster, RAS_CMAP, cmap);
    }
.SE
.H 3 "\f5currenttime\fP"
.UI currenttime
is used extensively in the server to time stamp events.
If the time is not correct, 
you may experience problems with event distribution.
.P
.UI currenttime() 
is a BSD derived library function which doesn't exist on
most System V platforms.  It has been implemented by using 
.UI gettimeofday ,
also a BSD derived library function.  
These functions are implemented for System V as follows:
.SS
fract
currenttime(tv)
    struct timeval *tv;
{
    fract           ret;
    struct timeval  now;
    long            sec, 
                    usec;
    if (tv == 0) {
        tv = &now;
        gettimeofday(tv, 0); 
    }
    sec = tv->tv_sec - startup_time.tv_sec;
    usec = tv->tv_usec - startup_time.tv_usec;
    if (usec < 0) {
        usec += 1000000;
        sec -= 1;
    }
    ret = sec * ((1 << 16) / 60) /* fraction(sec, 60) */ ;
    ret += usec/(60000000 >>16) /*fraction((usec/1000), 60000)*/;
    return (ret);
}

gettimeofday(tv, flag)
  struct timeval *tv;
  int flag;
{
        static long basetime=0;
        static long basetime_hz;
        struct tms times_buf;
        register long ticks = times(&times_buf);
        register unsigned long tmp;

        if (tv == NULL) {
                errno = EFAULT;
                return -1;
        }

        if ((basetime == 0) || (ticks <= basetime_hz)) {
                /* either wrap around or first time */

                if (time((long *) &basetime) == -1) {
                        basetime = 0;
                        errno = EFAULT;
                        return -1;
                }
                basetime_hz = ticks;
        }

        tmp = ticks - basetime_hz;
        tv->tv_sec  = basetime + tmp / HZ;
        tv->tv_usec = (tmp % HZ) * (1000000 / HZ);

} /* end of gettimeofday */
.SE
.H 3 "\f5osi_spawn_process()\fP"
.UI osi_spawn_process() 
is used in the server by the forkunix primitive,
which spawns a new process to execute a program.
.UI osi_spawn_process 
relies on the 
.UI vfork() 
and 
.UI exec() 
system calls. If 
.UI vfork() 
is not present on your system, use 
.UI fork() .
.P
.UI osi_spawn_process() 
first checks to see if there are any special characters
(like I/O redirection, wild-card characters, etc.) in the command to execute.  
If so, it executes the shell to run the program.
Otherwise, it executes the program directly
after first parsing the arguments into a 
.UI "char**"
argument list.
It also sets up standard input, output and error 
.UI /dev/null .
The code for 
.UI osi_spawn_process() 
is shown below.
.SS
osi_spawn_process(sp, len)
    register char *sp;
    int len;
{
    char            combuf[400];
    char           *args[100],  *getenv();
    register char  *dp;
    register char **ap;
    register        cnt;
    int             err = 0;

    if (len <= 0)	/* Don't fork null process */
        return 1;

    cs_cursorin();
    for (dp = sp, cnt = len; --cnt >= 0; dp++)
        switch (*dp) {
          case '!':
          case '"':
          case '$':
          case '&':
          case '(':
          case ')':
          case '*':
          case ';':
          case '<':
          case '>':
          case '?':
          case '[':
          case '\'':
          case '\\':
          case ']':
          case '`':
          case '{':
          case '|':
          case '}':
          case '~':
            cnt = -1;
        }
    ap = args;
    if (cnt == -1) { /* no special characters, don't invoke the
                     * shell */
        dp = combuf;
        while (len>0 && ap<&args[sizeof args/sizeof args[0]-1])
        {
            *ap++ = dp;
            while (dp < combuf + sizeof combuf - 1 && 
                *sp > ' ' && --len >= 0)
                *dp++ = *sp++;
            *dp++ = 0;
            while (*sp <= ' ' && --len >= 0)
                sp++;
        }
    } else {
        static char    *shell;

        (void) strncpy(combuf, sp, len);
        combuf[len] = 0;
        if (shell == 0 && (shell = (char *) getenv("SHELL")) == 0)
            shell = "/bin/sh";
        *ap++ = shell;
        *ap++ = "-c";
        *ap++ = combuf;
    }

    *ap++ = 0;

    if (vfork() == 0) {
	if (setpgrp() < 0) 
	    perror("setpgrp during fork");
        if (close(0) < 0)
	    perror("close");
        if (open("/dev/null", 2) < 0)
	    perror("open");
        if (dup2(0, 1) < 0)
	    perror("dup2");
        if (dup2(0, 2) < 0)
	    perror("dup2");
        if ((int) signal (SIGCLD, SIG_DFL) < 0)
	    perror("signal: SIGCLD");
        if ((int) signal (SIGPIPE, SIG_DFL) < 0)
	    perror("signal: SIGPIPE");
        if ((int) signal (SIGFPE, SIG_DFL) < 0)
	    perror("signal: SIGFPE");
/*
	It seems safe enough to take out the third arguement because
	execvp doesn't accept one according to the manuals. Think I'll
	leave this note in just in case though. alb. July 20/89
        execvp(args[0] ,args, environ);
*/
        execvp(args[0] ,args);
	/*
         * Beware: I'm depending on vfork semantics to propogate the error
         * code.
         */
        err = 1;
	_exit(0252); 
    }
/*
    more timer stuff - alb

    if (setitimer(ITIMER_VIRTUAL, &old_t, &new_t)) {
	perror("set_timeout_alarm");
    }
*/
    return err;
}

.SE
.H 3 \f5osi_setinitiallocation()\fP
This routine is used to set the server's idea of the initial location of the
cursor. The implementation is shown below.
.SS
void
osi_setinitiallocation(x, y)
    short x, y;
{
    static int calledbefore = 0;
    extern short eventq_last_x, eventq_last_y;

    if (! calledbefore) {
        eventq_last_x = x;
        eventq_last_y = y;
        calledbefore = 1;
    }
}
.SE

.H 3 "\f5initialize_sysVOSI\fP"
.P
.UI initialize_sysVOSI 
is the initialization routine for the NPSI code.
It defines certain operators in the system dictionary and initializes data
structures.  
It also traps most signals and directs them to the
signal handler 
.UI CatastrophicSignalHandler() .  
This is described below.
.P
Note the following code used to initialize objects used in presenting
events to the server:
.SS

    motion_keyword =     add_kwd_name("MouseDragged");
    but1_keyword =       add_kwd_name("LeftMouseButton");
    but2_keyword =       add_kwd_name("MiddleMouseButton");
    but3_keyword =       add_kwd_name("RightMouseButton");
    but4_keyword =       add_kwd_name("4thMouseButton");
    but5_keyword =       add_kwd_name("5thMouseButton");
    raw_motion_keyword = add_kwd_name("RawMouseDragged");
    raw_but1_keyword =   add_kwd_name("RawLeftMouseButton");
    raw_but2_keyword =   add_kwd_name("RawMiddleMouseButton");
    raw_but3_keyword =   add_kwd_name("RawRightMouseButton");
    up_keyword =         add_kwd_name("UpTransition");
    down_keyword =       add_kwd_name("DownTransition");
.SE
.UI add_kwd_name() ,
in 
.UI nucleus/keywords.c , 
is called to add each name to the server's
keyword database.  The pointer to the object returned is saved in a variable
local to the NPSI code for future use in places like 
.UI ProcessInputEvent()
(see section on keyboard and mouse event handling).
.P
There is also code which defines new operators for the server.  
These operators can then be used by any \*(Ps or NeWS code.  
For example, 
.UI startkeyboardandmouse 
is invoked by the server initialization \*Ps code.
The code shown below is used to perform the operator initialization.
.SS
define_operator("getkeyboardtranslation", 
                getkeyboardtranslation, 0, 0, 1);
define_operator("getmousetranslation", 
                getmousetranslation, 0, 0, 1);
define_operator("keyboardtype", keyboardtype, 0, 0, 1);
define_operator("setkeyboardtranslation",
            setkeyboardtranslation_primitive, 0, 1, 0);
define_operator("setmousetranslation",
                setmousetranslation, 0, 1, 0);
define_operator("startkeyboardandmouse",
                startkeyboardandmouse, 0, 0, 0);
define_operator("getenv", getenv_primitive, 0, 1, 1);
define_operator("putenv", putenv_primitive, 0, 2, 0);
define_operator("localhostnamearray",
                localhostnamearr, 0, 0, 1);
define_operator("getsocketpeername",
                getsocketpeername, 0, 1, 1);
define_operator("getsocketlocaladdress",
                getsocketlocaladdress, 0, 1, 1); 
define_operator("enumeratefontdicts",
                enumeratefontdicts, 0, 0, 0);
define_operator("exitreally", exitreally, 0, 0, 0);
.SE
The System V version of 
.UI initialize_sysVOSI 
is included below in
its entirety.
.SS
initialize_sysVOSI()
{
    int i;
    
    news_pid = getpid();

    init_scene_clipping_matrix();

    for (i = 0; i < MAX_FDS; i++)
        select_read_mask[i].fd /*=select_write_mask[i].fd*/ = -1;
    vsdev = -1;
    raw_keyboard = 0;
    raw_mouse = 0;

    motion_keyword = add_kwd_name("MouseDragged");
    but1_keyword =   add_kwd_name("LeftMouseButton");
    but2_keyword =   add_kwd_name("MiddleMouseButton");
    but3_keyword =   add_kwd_name("RightMouseButton");
    but4_keyword =   add_kwd_name("4thMouseButton");
    but5_keyword =   add_kwd_name("5thMouseButton");
    raw_motion_keyword = add_kwd_name("RawMouseDragged");
    raw_but1_keyword =   add_kwd_name("RawLeftMouseButton");
    raw_but2_keyword =   add_kwd_name("RawMiddleMouseButton");
    raw_but3_keyword =   add_kwd_name("RawRightMouseButton");
    up_keyword =          add_kwd_name("UpTransition");
    down_keyword =          add_kwd_name("DownTransition");
    define_operator("getkeyboardtranslation", 
                getkeyboardtranslation, 0, 0, 1);
    define_operator("getmousetranslation", 
                getmousetranslation, 0, 0, 1);
    define_operator("keyboardtype", keyboardtype, 0, 0, 1);
    define_operator("setkeyboardtranslation",
                setkeyboardtranslation_primitive, 0, 1, 0);
    define_operator("setmousetranslation",
                setmousetranslation, 0, 1, 0);
    define_operator("startkeyboardandmouse",
                startkeyboardandmouse, 0, 0, 0);
    define_operator("getenv", getenv_primitive, 0, 1, 1);
    define_operator("putenv", putenv_primitive, 0, 2, 0);
    define_operator("localhostnamearray",
                localhostnamearr, 0, 0, 1);
    define_operator("getsocketpeername",
                getsocketpeername, 0, 1, 1);
    define_operator("getsocketlocaladdress",
                getsocketlocaladdress, 0, 1, 1); 
    define_operator("enumeratefontdicts",
                enumeratefontdicts, 0, 0, 0);
    define_operator("exitreally", exitreally, 0, 0, 0);
    if ((int) signal(SIGPIPE, SIG_IGN) < 0)
        perror("signal: SIGPIPE");
    if ((int) signal(SIGFPE, SIG_IGN) < 0)
        perror("signal: SIGFPE");

#ifndef DEBUG
    if ((int) signal(SIGBUS, CatastrophicSignalHandler) < 0)
        perror("signal: SIGBUS");
    if ((int) signal(SIGSEGV, CatastrophicSignalHandler) < 0)
        perror("signal: SIGSEGV");
#endif

    if (uname(&ubuf) == -1)
        perror("uname");
    strcpy(my_hostname, ubuf.nodename);

    /* fill hosts <-> inet addr cache */
    {
      struct sockaddr_in addr;

      hlist_pntr = gethostbyname("localhost");
      bcopy((char *) hlist_pntr->h_addr, (char *)&addr.sin_addr,
            (int)hlist_pntr->h_length);

      strcpy(hosts_cache[next].inet_num,inet_ntoa(addr.sin_addr));
      strcpy(hosts_cache[next++].name, hlist_pntr->h_name);

      hlist_pntr = gethostbyname(my_hostname);
      bcopy((char *) hlist_pntr->h_addr, (char *)&addr.sin_addr,
            (int)hlist_pntr->h_length);
      strcpy(hosts_cache[next].inet_num,inet_ntoa(addr.sin_addr));
      strcpy(hosts_cache[next++].name, hlist_pntr->h_name);
    }

    openkeyboardandmouse();
}
.SE

.H 3 "\f5CatastrophicSignalHandler()\fP"
.UI CatastrophicSignalHandler() 
is called by the operating system when
a SIGBUS or SIGSEGV is directed to the server. 
This usually means the server has crashed.  The implementation code 
is shown below.
.SS
static void
CatastrophicSignalHandler(sig, code, scp)
        int sig;
        int code;
        struct sigcontext *scp;
{
    restore_keyboard();
    write(2, "NeWS: unexpected ", 17);
    switch (sig) {
    case SIGBUS: write(2, "SIGBUS ", 7); break;
    case SIGSEGV: write(2, "SIGSEGV ", 8); break;
    }
    write(2, "signal received\n", 16);
    abort(0);
    exit(1);
}
.SE

.H 3 "\f5getsocketpeername\fP"
.UI getsocketpeername() 
is a NeWS operator which places a string containing the
name of the peer machine on the other side of a network connection on the 
interpreter's operand stack.  
On Sun and other BSD derived systems, 
there is a system call named
.UI getpeername()
which can be called to return this information.
.P
On other systems,
determining the name of the peer system may be more involved.
.H 3 "\f5getsocketlocaladdress()\fP"
.UI getsocketlocaladdress() 
is used to determine the name of the system associated with the local socket.
As with 
.UI getsocketpeername() , 
it places an string containing the name on the operand stack.
On Sun and other BSD systems, the 
.UI getsockname() 
system call is used to determine this information.
.P
As with 
.UI getsocketpeername ,
this may be more involved on other systems.
.H 3 "\f5getenv_primitive\fP"
.P
.UI getenv_primitive 
implements the functionality of the 
.UI getenv() 
C library call, which retrieves the string associated with the 
environment variable.
.H 3 "\f5putenv_primitive\fP"
.P
.UI putenv_primitive 
performs the inverse function of 
.UI getenv_primitive.
That is, it places variables and their associated value in the environment.
On Sun and other BSD systems, 
there is a 
.UI setenv 
library call to perform this function.
.P
On other systems, it is done directly.
The System V version is implemented in several subroutines which are presented below.  .SS
/*
 * Implement setenv and unsetenv
 */
extern	char **environ, *calloc();
static char **
blkend(up)
        register char **up;
{

        while (*up)
                up++;
        return (up);
}
 
static
blklen(av)
        register char **av;
{
        register int i = 0;

        while (*av++)
                i++;
        return (i);
}

static char **
blkcpy(oav, bv)
        char **oav;
        register char **bv;
{
        register char **av = oav;

        while (*av++ = *bv++)
                continue;
        return (oav);
}

static char **
blkcat(up, vp)
        char **up, **vp;
{

        blkcpy(blkend(up), vp);
        return (up);
}

static
blkfree(av0)
        char **av0;
{
        register char **av = av0;

        while (*av)
                xfree(*av++);
        xfree((char *)av0);
}

static char *
strspl(cp, dp)
        register char *cp, *dp;
{
        register char *ep = calloc(1, strlen(cp) + strlen(dp)+1);
        strcpy(ep, cp);
        strcat(ep, dp);
        return (ep);
}
static char **
blkspl(up, vp)
        register char **up, **vp;
{
        register char **wp = (char **) calloc(
            blklen(up) + blklen(vp) + 1, sizeof (char **));

        blkcpy(wp, up);
        return (blkcat(wp, vp));
}
static
xfree(cp)
        char *cp;
{
        extern char end[];

        if (cp >= end && cp < (char *) &cp)
                free(cp);
}

setenv(name, value)
        char *name, *value;
{
        register char **ep = environ;
        register char *cp, *dp;
        char *blk[2], **oep = ep;

        for (; *ep; ep++) {
            for (cp=name, dp=*ep; *cp && *cp == *dp; cp++, dp++)
                    continue;
            if (*cp != 0 || *dp != '=')
                    continue;
            cp = strspl("=", value);
            xfree(*ep);
            *ep = strspl(name, cp);
            xfree(cp);
            return;
    }
        blk[0] = strspl(name, "="); blk[1] = 0;
        environ = blkspl(environ, blk);
        xfree((char *)oep);
        setenv(name, value);
}
unsetenv(name)
        char *name;
{
        register char **ep = environ;
         register char *cp, *dp;
        char **oep = ep;

        for (; *ep; ep++) {
            for (cp=name, dp=*ep; *cp && *cp == *dp; cp++, dp++)
                   continue;
            if (*cp != 0 || *dp != '=')
                   continue;
            cp = *ep;
            *ep = 0;
            environ = blkspl(environ, ep+1);
            *ep = cp;
            xfree(cp);
            xfree((char *)oep);
            return;
    }
}
.SE
.H 2 "Debugging Input Handling Code"
You should be able to set up an environment in which you can
get keyboard and mouse events and see that the event logger
gets them of whether or not you have the graphics
interface (\*(Cs and Shapes) working.
.P
You should trace to see that events are being presented
to the NPSI code properly 
.UI (EventsHaveSelected()) ,
that they are being processed properly as described above,
and that they are being enqueued properly 
.UI (enqueueEvent()) .
.P
Once they're queued, you should then check to see if they're
being distributed 
.UI (distributeevent()) .
.P
If all you have working is the event logger,
you can't do much with the events except look at all of them.
However, once you have Shapes and \*(Cs working and linked in to the server,
you should be able to create canvases and express interests for
specific events and see that they're being delivered to those canvases.
.P
.H 2 "Event Logging"
The file 
.UI eventlog.ps
defines a procedure called
.B eventlog ,
which does two things.  First, it turns the logging of event distribution 
on and off.  Second, it defines a dictionary called
.B UnloggedEvents ,
which defines those events to be excluded from the log record.  
.I Logging
means that a copy of each event is printed as it is taken out of the
event queue for distribution.  This is useful for debugging the server
and for clients that use events heavily.  The fields of the event that
are printed are 
.B 
Serial, TimeStamp, Location, Name, Action, Cancas, Process, Keystate,
.ft 1
and
.B ClientData.
.P 
The 
.B Journal 
application uses the event logging mechanism to allow the
user to record and play back user actions.  Refer to the NeWS Programming
Manual for further details on the event logger.

