.if n .pH port.chap09 @(#)chap09	40.5
.\" Porting manual: Keyboard & Mouse Input chapter 9
.\"
.\" 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 "Keyboard and Mouse Input" 9
.H 1 "Keyboard and Mouse Input" 
.H 2 "Introduction"
The next step in porting the \*(Sd I/O and System Interface is to get the
Mouse and Keyboard Subsystem working.  This involves handling keyboard and
Mouse ``events''.  An event is a change in the state of the keyboard
(that is, a change in a key from up to down or vice versa) or a change in 
the mouse (either in position or buttons).  
.P
When an event occurs, the system 
must first queue the event to the server.  This is done by
taking the event from the device driver, translating the event into
a form expected by the server, and finally queueing the event to the server.
Once the event is in the server, the server must deliver the event to a 
process thread.  
.P
In addition to the event handling code, a number of external interfaces and 
additional NPSI NeWS operators are required to get the Mouse and
Keyboard working.
Some of these are used internally to the server only.
Others implement operators used by the NeWS/\*(Ps code.
.P
This chapter is divided into the following major subsections:
.BL
.LI
Writing the Device Driver
.LI
Converting Events from the Driver to the Form Acceptable by the Server
.LI
Queueing Events to the Server
.LI
Event Distribution in the Server
.LI
NPSI NeWS operators for Input Handling
.LE
.H 2 " Writing the Device Driver"
.P
You may need to write a device driver to interface the mouse
and keyboard to the server.
The exact nature of the interface is at your discretion,
but you should make the driver place events in a queue which
is addressable by a user process (i.e., the server).
.P
The interface code you write for the server must
correspond to the manner in which the device driver presents events.
On System V systems, this driver should be a streams driver,
so that the 
.UI poll(2)
system call will work with it.
.H 2 "Driver to Server Event Conversion"
Before the server can process an event, the event must be converted to a form 
that the server can read.  Before describing this conversion
process (a process involving the routine
.UI EventsHaveSelected() 
), some basic rules which must be followed in
passing events to the server must be discussed.
These are presented in the next section.
.H 3 "Rules for Formulation of Events to Pass to Server
.AL 1
.LI
Separate events must be submitted to 
.UI enqueueEvent() 
for the following occurences:
.AL a
.LI 
Keyboard transitions if keyboard is in raw mode;
ASCII codes if the keyboard is in translated mode.
.LI 
Pointer motion.
.LI 
Mouse buttons (one event for each button transition)
.P
The key here is 
.I "separate events."  
The driver may present
more than one of these individual items as one event to the NPSI
code.  For example, the driver may present mouse motion and mouse
button transitions in one event to the NPSI code.  The NPSI code
must convert that single event into multiple events which get presented
to the server.
.LE
.LI
Set mandatory fields in the 
.UI corpus 
structure sent to the server.
.P
Events are passed to the server in a 
.UI "struct corpus" 
in 
.UI server/include/NeWS/PostScript.h 
(see below).
The 
.UI corpus 
structure is in part a union.
One of the elements of that union is a structure called 
.UI event .
Certain of the fields in this sub-structure must be set according
to certain rules.  These are presented below.
.AL a
.LI 
\f5corpus.event.cancelmotionhint\fP.
Set to TRUE if event is a keystroke class event
(either a mouse button or keyboard state transition).
.LI 
\f5corpus.event.keyboard_event\fP.
Set to TRUE if the event is a keyboard transition,
if the keyboard is in raw mode or an ASCII character,
if the keyboard is in translated mode.
Set to FALSE if the event is a mouse event
(either motion or button change).
.LI 
\f5corpus.event.motion_event\fP.
Set to TRUE if the event is a mouse motion event.
.LI
\f5corpus.event.action\fP.
Set if the event is a keystroke class event.
If the keystroke transition is up, set to 
.UI up_keyword .
If the keystroke transition is down, set to 
.UI down_keyword .
Note that 
.UI up_keyword 
and 
.UI down_keyword 
are
.UI "struct objects" 
which are defined in the NPSI code.
.LI 
\f5corpus.event.time\fP.
Set to the current time when the event occurred.
This needn't be actual wall clock time, but should
be relative time.  In other words, the time clock could
start at 0 when the server starts to run, 
and be increased by 1 with every clock tick.
.P
The other consideration here is that the time stamps
must be strictly increasing.  If events come in quickly,
it is possible for them to be processed in the same clock
tick.  If this is the case, subsequent events should be
given strictly increasing time stamps.
.LI 
.B corpus.event.loc_x,
.B corpus.event.loc_y
.B corpus.event.delta_x,
.B corpus.event.delta_y.

If the mouse delivers position information in
relative terms, 
.UI corpus.event.delta_x,y 
should be set with that information.
If the mouse delivers position information in
absolute terms, 
.UI corpus.event.loc_x,y 
should be set with that information.
.LI
\f5corpus.event.locationnotset\fP.
If the mouse delivers position information in relative terms,
.UI locationnotset 
should be set to TRUE, otherwise FALSE.
.LI
\f5corpus.event.name\fP.
Set according to the type of event.  See information in
description of 
.UI ProcessInputEvents().
.LE
.LE
.P
.SS
struct corpus {
    ENTITY_FIELDS         /* header and ref info */

    union {
        .
        .
        .
        struct {          /* == event_type */
            struct object name;
            struct object action;
            struct object client_data;
            struct object runnable_match;
            struct object canvas;
            REF process;   /*  process interested in this
                            *  event, or intended recipient*/
            REF inter;   /* interest matched by this event */
            REF kstate;          /* array of down keys */
            REF next;            /* next event in queue or
                                  * next interest on the
                                  * processes interest list */
            MatchMask nameMask;         /* name match bits */
            MatchMask actionMask; /* action match bits */
            short loc_x;          /* locator position */
            short loc_y;
            short delta_x;
            short delta_y;
            unsigned  serial;    /* event #, or interest's last
                                  * match */
            fract time;          /* minutes since startup */
            fract priority;      /* applies only to interests */
            unsigned is_queued:1; /* event is in the eventq */
            unsigned is_interest:1;/*event in an interest list*/
            unsigned is_exclusive:1;/*interest gobbles matching
                                      * events */
            unsigned pre_child:1;  /* interest matches ahead of
                                   * interests on child canvases*/
            unsigned name_match:2; /* was /AnyValue specified in
                                  * an interest name or action */
            unsigned action_match:2; /* and how */
            unsigned cancelmotionhint:1;/*PointerMotionHint-X11*/
            unsigned damage_interest:1; /*interested in damage?*/
            unsigned motion_event:1;
            unsigned locationnotset:1;
            unsigned keyboard_event:1;
            unsigned pad:3;
        } event;
    .
    .
    .
    } corpus;
};
.SE
.P
.H 3 "\f4EventsHaveSelected()\fP"
Both the Sun and System V code relating to input event handling are similarly 
structured.  Both have a subroutine called
.UI EventsHaveSelected()
which is called when 
.UI WaitForInput()
indicates that an input event may have occurred.
.P
Note that this implies that it must be possible to multiplex on
the file descriptor associated with the input device
and that the device indicates that it is ready
when input events have occurred.
.P
Unless the device acts as a streams device, special actions must be
taken to make sure the
.UI poll(2) 
system call works and can be unblocked.  In the reference port, this is 
done using the 
.UI SIGTRAP 
signal.
.P
As a point of clarification,
note that when we speak of the file descriptor associated with the input
device we are implying that the device was opened as a node in the file system
at some point, and that there is a file descriptor which was returned
by the 
.UI open 
call.
.P
We don't, however, read the file descriptor to obtain events.
Rather, for the sake of efficiency,
the device driver places them in a buffer which is mapped into
the \*(Sd server's address space.
.P
So, 
.UI add_selectable_file() 
is called to add the file descriptor associated with the
input device to the multiplex list.
(This is usually done in 
.UI startkeyboardandmouse() 
described below.)
.UI WaitForInput() 
then multiplexes on the input device.
In the AT&T implementation, the keyboard and mouse driver is not a
streams driver, so the 
.UI poll()
system call does not return when events become available.  Instead,
the driver delivers a signal to the server process when events are
available.  These are trapped by the routine 
.UI catch_xsig()
in 
.UI kbms_attic.c .
.SS
int
catch_xsig(sig)
int        sig;
{
        if (sig != SIGTRAP && sig != SIGILL)
             signal(sig, catch_xsig);

        io_ready = 1;

        if (jmp_flag==1)
           longjmp(sig_ret_jmp, 1);

        return 0;
}
.SE

The signal SIGTRAP is used since it doesn't reset to the default
handling when it fires.
.P
Note that 
.UI WaitForInput()
does a 
.UI setjmp()
right before calling 
.UI poll() .  
This system call always returns 0.  However, when the matching 
.UI longjmp()
is made in 
.UI catch_xsig() ,
the code after the return from 
.UI setjmp()
is executed, but this time the
.UI setjmp() 
appears to return 1.
.P
Further, the 
.UI poll()
call in 
.UI WaitForInput() 
is preceded by setting a global variable,
.UI jmp_flag ,
to 1.
The routine
.UI catch_xsig
does 
.UI longjmp()
only if 
.UI jmp_flag
is 1.
.UI jmp_flag
is reset to 0 after the 
.UI poll() 
call returns.  This prevents execution of the code in 
.UI WaitForInput()
when events become available and when we're not polling for more input.
When it becomes ready, 
.UI EventsHaveSelected()
is called.
.SS
    set_alarm(now);

    pol_ret = -1;
    sig_ret = setjmp(sig_ret_jmp);
    jmp_flag = 0;

    if (sig_ret == 0)

    	EventsHaveSelected(1);

        jmp_flag = 1;
        poll_ret = poll(select_read_mask, select_read_count + 1, 
                 (process_set || io_ready) ? 0 : timeout);
        jmp_flag = 0;

}
        if (poll_ret < 0 ) return;
.SE

The exact implementation of 
.UI EventsHaveSelected() 
varies from system to system. The purpose, however, is the same:
to interpret input events from the keyboard and mouse and
translate them into terms acceptable to the server.
.P
In the System V implementation,
.UI EventsHaveSelected() 
consists of a while loop which cycles through
each entry in the input queue. For each entry, it determines
whether the event was a keystroke or a mouse event.
If it is a keyboard event,
.UI PostKeyboardEvent 
is called.
If it is a mouse event, 
.UI PostMouseEvent 
is called.
Below we show the 
.UI EventsHaveSelected()
routine.
.SS
void
EventsHaveSelected(flag)
char flag;
{
    register xqEvent    * pE;
    register int        nevents;
    int          collapsedEvent = 0;

    if(queue == NULL)
    {
#ifdef DEBUG
        fprintf(stderr, "Queue is still NULL\n");
#endif
        return;
    }

	while (queue->xq_tail!=queue->xq_head)
    {
        queue->xq_sigenable = 0;
        pE = &queue->xq_events[queue->xq_head];

        if (pE->xq_type == XQ_KEY)
            PostKeyboardEvent(pE);
        else {
            PostMouseEvent(pE);
        }
        
        queue->xq_head++;
        queue->xq_head %= qLimit;
        if (flag)
            queue->xq_sigenable = 1;
}
.SE
.P
.H 1 "Keyboard Events"
.P
The handling of keyboard events depends on whether
the keyboard is in raw or translated mode.
In raw mode, the events queued to the server consist only
of up and down transitions of keys.
In translated mode,
the events queued to the server consist of
ASCII characters.
.P
Some keyboards and drivers support a translated mode, and some do not.
In the case of the AT&T 6386, the translated mode is not supported,
but it is a fairly straightforward matter to support this in software,
as is discussed below.
.P
It should also be possible to support a system which
provides only a translated mode,
but that is beyond the scope of this document.
.P
In the AT&T 6386 version,
.UI PostKeyboardEvent() 
is called by
.UI EventsHaveSelected() .
The 
.UI PostKeyboardEvent()
routine is described below.
.SS
static
PostKeyboardEvent(pE)
xqEvent *pE;
{
    struct inputevent event;
    register int c = pE->xq_code;


        gettimeofday(&event.ie_time);

    c &= 0xff;
    if (kbmode == K_RAW) {   /* RAW mode */
        if (c & 0x80) {	     /* key is UP */
            keystate[c & 0x7f] = Up;
            kb_event(c, &event);
        } else {             /* key is DOWN */
            if (keystate[c] == Up) {
                keystate[c] = Down;
                kb_event(c, &event);
            } 
        }
    } else {                /* TRANS mode */
        if (c & 0x80) {			/* key is UP */
            keystate[c & 0x7f] = Up;
        } else {			/* key is DOWN */
            switch(c) {
            case LSHIFT:
            case RSHIFT:
            case CTRLKEY:
            case ALTKEY:
                keystate[c] = Down;
                break;
            case CAPS:
                caps_lock_on = (caps_lock_on ? 0 : 1);
                break;
            case NUMLOCK:
                num_lock_on = (num_lock_on ? 0 : 1);
                break;
            default:
                keystate[c] = Down;
            }
        }
        kb_event(c, &event);
    }
}
.SE

If the keyboard is in raw mode,
the code to see if the hardware event corresponds to an up or down transition.
If it is an up transition,
the code keeps track of the new state in an array called
.UI keystate[] ,
and generates an event to be sent to the server by calling 
.UI kb_event().
.P
If the transition is a down transition,
we check to see if the last state of the key was up.
If it was, we set the new state in 
.UI keystate[] 
to down and generate an event by calling 
.UI kb_event() .
.P
Processing is a bit more complicated if the keyboard is in translated mode.
First, if the key is now up, this is noted in the 
.UI keystate[] 
array.
.P
If the transition is down, a somewhat more complex processing is done.
The code keeps track of the state of the CAPS LOCK and NUM LOCK keys.
These keys act as toggles; every time they're pressed,
the state of their operation changes.
Thus, whether they're up or down is irrelevant;
what matters is how many times they've been pressed.
.P
For all other keys, the new state is noted in the
.UI keystate[] 
array.
.UI Kb_event()
is then called to generate an event to be sent to the server.
.SS
/*
 * Make keyboard event packet.
 */

static
kb_event(code, ev)
unsigned char code;
struct inputevent *ev;
{
    int newskey;
    int key_is_down;


    key_is_down = (code & 0x80) ? Up : Down;
    code &= 0x7f;

    if (kbmode == D_RAW) {
        newskey = code;
    } else {
        newskey = ATtoNeWSKeys(code);  
    }

    ev->ie_code = newskey;
    ev->ie_flags = (key_is_down ? 0 : IE_NEGEVENT);
    ev->ie_shiftmask = 0;
    ev->ie_locx = cursor.cur_x;
    ev->ie_locy = cursor.cur_y;
    ProcessInputEvent(ev);
}
.SE
If the keyboard is in a raw state, the code corresponding
to the key just pressed is put into an 
.UI inputevent 
structure.  This structure is used locally in the NPSI code.
Otherwise, the routine 
.UI ATtoNeWSKeys() 
is called to translate
the current keyboard state to an ASCII character code.
The 
.UI ie_shiftmask 
field is unused, and set to 0.
The 
.UI ie_flags 
field is set to 0 if the key transition is down, otherwise it's set to  
.UI IE_NEGEVENT
which equals 1.
.\".L IE_NEGEVENT 
.\"is a holdover from Sun systems, in particular
.\"SunView.  
This constant is defined in 
.UI server/include/ref/events.h.
.P
The cursor location is also placed in the 
.UI inputevent 
structure.
.P
.UI ProcessInputEvent() 
is then called to further process the event and queue it to the server.
This is discussed in detail later in the section describing mouse events 
which also pass through
.UI ProcessInputEvent() .
.P
.UI ATtoNeWSKeys() 
is fairly straightforward and is not described
in detail here.  It checks to see what shift-type keys
(e.g., left or right shift key, CTRL, etc.) are down, and then uses
translation tables in 
.UI kbtbl.c 
to determine the ASCII code for
the keystroke.
.SS
ATtoNeWSKeys(key)
register key;
{
    register currentkey;
    register newskey;

    if (keystate[key] == Down) {
        if (keystate[CTRLKEY] == Down)
            newskey = CtrlKeys[key];
        else if (keystate[LSHIFT] == Down || keystate[RSHIFT] 
                        == Down) {
            if ((currentkey = 1 << key) & numkey) {
                if (num_lock_on) 
                    newskey = NeWSKeys[key];
                  else
                    newskey = ShiftKeys[key];
            } else {
                if (caps_lock_on) 
                    newskey = NeWSKeys[key];
                else
                    newskey = ShiftKeys[key];
            }
        } else {
            if ((currentkey = 1 << key) & numkey) {
                if (num_lock_on)
                    newskey = ShiftKeys[key];
                else
                    newskey = NeWSKeys[key];
            } else {
                if (caps_lock_on) 
                    newskey = ShiftKeys[key];
                else
                    newskey = NeWSKeys[key];
            }
        }
    } else
        printf("key sync");

    return(newskey);
}
.SE
.H 3 "Mouse Events"
.UI EventsHaveSelected() 
calls 
.UI PostMouseEvent()
if the event the driver presented is a mouse event.
.UI PostMouseEvent()
is described below.
.SS
static
PostMouseEvent(pE)
xqEvent	*pE;
{
        unsigned char ch = pE->xq_code;
        register int button;
        register int button_has_changed;
        struct inputevent event;
        

        cursor.cur_x += (short) pE->xq_x;	
                 /* update the mouse position */
        cursor.cur_y += (short) pE->xq_y;

        gettimeofday(&event.ie_time);

        /* check limits */
        if (cursor.cur_x < 0)
            cursor.cur_x = 0;
        if (cursor.cur_x >cursor.max_x)
            cursor.cur_x = (cursor.max_x - 1);
        if (cursor.cur_y < 0)
            cursor.cur_y = 0;
        if (cursor.cur_y > cursor.max_y)
            cursor.cur_y = (cursor.max_y - 1);

        /* check for motion */
        if (cursor.last_x != cursor.cur_x || 
                        cursor.last_y != cursor.cur_y) {
            ms_event(LOC_MOVE, 0, &event);
            cursor.last_x = cursor.cur_x;
            cursor.last_y = cursor.cur_y;
        }

        /* Do we need to generate a button event ? */
        button = pE->xq_code&07;
        button_has_changed = button ^ cursor.last_button;
        if (button_has_changed != 0) 
        {
            cursor.last_button = button;
            if (button_has_changed & RIGHT_BUTTON) {
               ms_event(MS_RIGHT,(button & RIGHT_BUTTON),&event);
            }
            if (button_has_changed & MIDDLE_BUTTON) {
               ms_event(MS_MIDDLE,(button&MIDDLE_BUTTON),&event);
            }
            if (button_has_changed & LEFT_BUTTON) {
                ms_event(MS_LEFT, (button & LEFT_BUTTON), &event);
            }
        }
}
.SE

The first thing 
.UI PostMouseEvent() 
does is update the server's idea of
where the cursor is.  The cursor in this context means the
graphic displayed on the frame buffer which indicates the mouse's
position.
.SS
        cursor.cur_x += (short) pE->xq_x; 
                /* update the mouse position */
        cursor.cur_y += (short) pE->xq_y;
.SE

For some systems, the position of the cursor is under the
software control of a user level process.
It is up to the user level process to get all mouse events,
keep track of the current mouse location, and tell the
device driver where the new mouse position is when that
position changes.
.P
This is not the case when using the standard AT&T keyboard and
mouse driver, but on systems where it is necessary it
is done using an 
.UI ioctl(2) 
call on the device.
This can be done in the routine 
.UI SetCursorPosition() ,
which is a stub in the reference code.
.P
After updating the local idea of the cursor's location,
the code time stamps the event by calling 
.UI gettimeofday() .
On BSD-derived systems, this is a library function.
For the System V implementation,
it is a new function.
It is not discussed in detail here, as it is
relatively straightforward:
.SS
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
After time stamping the event,
the code then checks to make sure it is within the bounds of the
framebuffer. If it's not,
it sets it so that it is at the maximum value or at zero:
.SS
        /* check limits */
        if (cursor.cur_x < 0)
            cursor.cur_x = 0;
        if (cursor.cur_x >cursor.max_x)
            cursor.cur_x = (cursor.max_x - 1);
        if (cursor.cur_y < 0)
            cursor.cur_y = 0;
        if (cursor.cur_y > cursor.max_y)
            cursor.cur_y = (cursor.max_y - 1);
.SE
Next,
the code checks to see if motion has occurred by comparing the
new cursor location with the last location:
.SS
        /* check for motion */
        if (cursor.last_x != cursor.cur_x || cursor.last_y 
                                        != cursor.cur_y) {
            ms_event(LOC_MOVE, 0, &event);
            cursor.last_x = cursor.cur_x;
            cursor.last_y = cursor.cur_y;
        }
.SE
If it has,
.UI ms_event()
is called to generate an event packet and the last cursor
location is updated.
Finally, the code determines if any of the mouse buttons
have changed.  If it has,
.UI ms_event() 
is again called to generate a mouse event.
Note that one event can result in multiple event packets
if motion occurs along with one or more button changes.
.SS
    /* Do we need to generate a button event ? */
    button = pE->xq_code&07;
    button_has_changed = button ^ cursor.last_button;
    if (button_has_changed != 0) 
    {
        cursor.last_button = button;
        if (button_has_changed & RIGHT_BUTTON) {
            ms_event(MS_RIGHT, (button & RIGHT_BUTTON), &event);
        }
        if (button_has_changed & MIDDLE_BUTTON) {
            ms_event(MS_MIDDLE, (button & MIDDLE_BUTTON), &event);
        }
        if (button_has_changed & LEFT_BUTTON) {
            ms_event(MS_LEFT, (button & LEFT_BUTTON), &event);
        }
    }
}
.SE

.UI ms_event() 
is similar to 
.UI kb_event() ,
in that it sets up the 
.UI "struct inputevent" 
and passes it to 
.UI ProcessInputEvent() .
.SS
/*
 * Make mouse event packet
 */
static
ms_event(code, button_up, ev)
int code;
int button_up;
register struct inputevent *ev;
{
 
    if (code == LOC_MOVE) {
        ev->ie_code = LOC_MOVE;
        ev->ie_flags = 0; 
    } else {
        ev->ie_code = code;
        ev->ie_flags = (button_up ? IE_NEGEVENT : 0);
    }
    ev->ie_shiftmask = 0; 
    ev->ie_locx = cursor.cur_x;
    ev->ie_locy = cursor.cur_y;
    ProcessInputEvent(ev);
}
.SE

The last critical element in handling input events is
.UI ProcessInputEvent() .
This is a fairly complicated routine whose purpose is to translate
raw input events presented in an 
.UI inputevent 
structure
into event structures which can be consumed by the server.
A good deal of this code should be fairly portable,
so you should be able to use it as a starting point
for your implementation.
.UI ProcessInputEvent() 
is described below.
.SS
static
ProcessInputEvent(se)
    struct inputevent *se;
{
    REF ev;
    register struct corpus *b;
    register int    code;
    register enum {
        unknown, keystroke, motion
    }                    class;
    struct object   name;

    switch (se->ie_code) {
    case LOC_MOVE:
        class = motion;
        name=raw_mouse ? raw_motion_keyword : motion_keyword;
        break;
    case MS_LEFT:
        name = raw_mouse ? raw_but1_keyword : but1_keyword;
        class = keystroke;
        break;
    case MS_MIDDLE:
        name = raw_mouse ? raw_but2_keyword : but2_keyword;
        class = keystroke;
        break;
    case MS_RIGHT:
        name = raw_mouse ? raw_but3_keyword : but3_keyword;
        class = keystroke;
        break;
    default:
        if ((code = se->ie_code) > META_LAST)
            class = unknown;
        else {
            class = keystroke;
            if (raw_keyboard)
                code += DEVIDKBD;
        }
        set_fixed_object(&name, fracti(code));
        break;
    }

    /*
     * we need to change *all* events if we're contrained, 
     * not just motion
     */
    if (!is_ref_zero(ConstrainCanvas)) {
        /* XXX maybe later, should investigate whether we
         * need to warp the coordinates in motion events
         * which are already queued
         */
        (void)osi_constraintocanvas(ConstrainCanvas, 
                                &se->ie_locx, &se->ie_locy);
    }

    ev = pnew(NO_POOL, event_type, 0);
    b = corpus_of(ev);

    b->corpus.event.loc_x = se->ie_locx;
    b->corpus.event.loc_y = se->ie_locy;

    {
        /*
         * we need strictly increasing time stamps.  We have 
         * millisecond resolution, so this shouldn't bite us
         *  unless we have very fast event generation
         */
        static unsigned last_time;

        b->corpus.event.time = currenttime(&(se->ie_time));
        if (b->corpus.event.time <= last_time)
            b->corpus.event.time = last_time + 1;
        last_time = b->corpus.event.time;
    }

    b->corpus.event.name = name;

    switch (class) {
      case keystroke:
        b->corpus.event.cancelmotionhint = TRUE;
        b->corpus.event.action = (se->ie_flags & IE_NEGEVENT) ? 
          up_keyword : down_keyword;
        if (b->corpus.event.name.type == fixed_type)
            b->corpus.event.keyboard_event = TRUE;
        break;
      case motion:
        if (raw_mouse) {
            /* XXX not modified for multiple screens
             * raw_mouse isn't really used by anything
             */
            static int  lastx,
                        lasty;
            register int newx,
                        newy;
            int cx, cy;

            newx = se->ie_locx;
            newy = se->ie_locy;
            cs_getcursorpos (&cx, &cy);
            b->corpus.event.loc_x = cx + newx - lastx;
            b->corpus.event.loc_y = cy + newy - lasty;
            lastx = newx;
            lasty = newy;
        }
        else {
            cs_movecursor((short) se->ie_locx,
              (short) se->ie_locy);
            CheckCrossings(b->corpus.event.time);
        }
        clear_object(&(b->corpus.event.action));

        b->corpus.event.motion_event = TRUE;
        if (collapsePtrMotion) {
            enqueueEvent(ev, collapsePtrMotion);
            return;
        }
        break;
    default:
        clear_object(&(b->corpus.event.action));
        break;
    }
    enqueueEvent(ev, FALSE);
}
.SE

The first thing 
.UI ProcessInputEvent() 
does is to assign a "name" to the event.
.SS
    switch (se->ie_code) {
    case LOC_MOVE:
        class = motion;
        name = raw_mouse ? raw_motion_keyword : motion_keyword;
        break;
    case MS_LEFT:
        name = raw_mouse ? raw_but1_keyword : but1_keyword;
        class = keystroke;
        break;
    case MS_MIDDLE:
        name = raw_mouse ? raw_but2_keyword : but2_keyword;
        class = keystroke;
        break;
    case MS_RIGHT:
        name = raw_mouse ? raw_but3_keyword : but3_keyword;
        class = keystroke;
        break;
    default:
        if ((code = se->ie_code) > META_LAST)
            class = unknown;
        else {
            class = keystroke;
            if (raw_keyboard)
                code += DEVIDKBD;
        }
        set_fixed_object(&name, fracti(code));
        break;
    }
.SE

This name depends on whether the event is a mouse event or
a keyboard event.
For mouse events, there are 
struct objects
corresponding
to each type, one each for the different buttons and for motion.
For keyboard events, the name is assigned the code of the keystroke.
.P
Note that for raw keyboard events, the constant 
.UI DEVIDKBD 
is added to the code.  This code is defined in 
.UI server/NeWS/input.h 
and is used in the processing of events in
.UI add_match_bit() 
.UI (nucleus/inputdist.c).
In addition to setting up the name for the event,
this code also determines the class of the event.
There are two classes, keystroke and motion.
Note that mouse button events have a class of keystroke.
The setting of the class is used in processing later.
.P
Next,
the code handles constraining the mouse motion to a particular
canvas.
This supports an X11 feature which is not discussed in detail here.
The routine
.UI osi_constraintocanvas()
is called to constrain the pointer motion to a particular canvas.
.P
In short, this is a feature which a client can invoke which restrains
the mouse's motion to a particular canvas.
.SS
    /*
     * we need to change *all* events if we're contrained, not 
     * just motion
     */
    if (!is_ref_zero(ConstrainCanvas)) {
        /* XXX maybe later, should investigate whether we
         * need to warp the coordinates in motion events
         * which are already queued
         */
        (void)osi_constraintocanvas(ConstrainCanvas, 
                             &se->ie_locx, &se->ie_locy);
    }
.SE

Next,
the code allocates a structure to hold the event to be passed to the
server.
.SS
    ev = pnew(NO_POOL, event_type, 0);
    b = corpus_of(ev);
.SE

Then, it sets the location of the event.
.SS
    b->corpus.event.loc_x = se->ie_locx;
    b->corpus.event.loc_y = se->ie_locy;
.SE

After that,
it ensures that the time stamp on the event is greater than all previously
submitted events. This is done because the code in 
.UI enqueueEvent() 
relies on the time stamps being in strictly increasing order.
.SS
{
    /*
     * we need strictly increasing time stamps.  We have 
     * millisecond resolution, so this shouldn't bite us 
     * unless we have *very* fast event generation
     */
        static unsigned last_time;

        b->corpus.event.time = currenttime(&(se->ie_time));
        if (b->corpus.event.time <= last_time)
            b->corpus.event.time = last_time + 1;
        last_time = b->corpus.event.time;
    }
.SE

Then, the name is assigned to the event.
.SS
    b->corpus.event.name = name;
.SE

Next, processing specific to the class of event is done.
.SS
    switch (class) {
      case keystroke:
        b->corpus.event.cancelmotionhint = TRUE;
        b->corpus.event.action =
           (se->ie_flags&IE_NEGEVENT) ? up_keyword : down_keyword;
        if (b->corpus.event.name.type == fixed_type)
            b->corpus.event.keyboard_event = TRUE;
        break;
.SE

If the event is a keystroke,
the 
.UI cancelmotionhint 
bit field is set.
This is used by the X11 code and must be set.
Also note that the 
.UI keyboard_event 
bit field is set if the event was a keystroke, and NOT a mouse button.
Also note that the action field is set to either 
.UI up_keyword 
or
.UI down_keyword 
depending on whether the 
.UI IE_NEGEVENT 
bit is set
in the flags field of the 
.UI inputevent 
struct.
Recall that this was set in 
.UI kb_event() 
or 
.UI ms_event() .
.SS
    switch (class) {
    .
    .
    .
    case motion:
        if (raw_mouse) {
            /* XXX not modified for multiple screens
             * raw_mouse isn't really used by anything
             */
            static int  lastx,
                        lasty;
            register int newx,
                        newy;
            int cx, cy;

            newx = se->ie_locx;
            newy = se->ie_locy;
            cs_getcursorpos (&cx, &cy);
            b->corpus.event.loc_x = cx + newx - lastx;
            b->corpus.event.loc_y = cy + newy - lasty;
            lastx = newx;
            lasty = newy;
        }
        else {
            cs_movecursor((short) se->ie_locx,(short)se->ie_locy);
            CheckCrossings(b->corpus.event.time);
        }
        clear_object(&(b->corpus.event.action));

        b->corpus.event.motion_event = TRUE;
        if (collapsePtrMotion) {
            enqueueEvent(ev, collapsePtrMotion);
            return;
        }
        break;
    default:
        clear_object(&(b->corpus.event.action));
        break;
    }
    enqueueEvent(ev, FALSE);
}
.SE

The other class is motion
(if the class was other than keystroke or motion,
the event is thrown away).
.P
For motion events,
if the mouse is in raw mode,
the x and y location are set by determining the current mouse
location and adding in the delta from the event and the last position.
.P
Otherwise, the \*(Cs's idea of the mouse position is
updated by calling 
.UI cs_movecursor() 
and 
.UI CheckCrossings() 
is called
to see if the mouse is now over a new canvas.
.P
The action field of the event is cleared,
and then the 
.UI motion_event 
bit field is set.
.P
Finally, if events are being collapsed 
.UI enqueueEvent() 
is called indicating that collapsing should be done,
and we return from 
.UI ProcessInputEvent .
Event collapsing is a feature which can be turned on and off by clients.  
If it is on and the new event matches one in the queue,
the new event is thrown away and the one in the queue is updated
to reflect the new information.
.P
Otherwise, 
.UI enqueueEvent() 
is called outside the switch statement without collapsing.

.H 2 "Queueing Events to the Server"
Event queueing is the process that the system uses to receive events
and queue them up to the server.
The routine 
.UI enqueueEvent.c 
in 
.UI nucleus/sched.c 
is the routine called by the 
NPSI code (
.UI ProcessInputEvent ) 
to enqueue an event for consumption by the server.
.SS
void
enqueueEvent(evt, collapse_motion)
    REF evt;
    int collapse_motion;
{
    register struct corpus *event = corpus_of(evt);
    register struct corpus *q;
    register fract        time = event->corpus.event.time;

#ifdef PERFORMANCE
    int count = 0;

    queue_length++;
#endif

#ifdef EVENT_DEBUG
        check_q();
#endif
    assert(event->type==event_type);
    assert(!event->corpus.event.is_queued);
    event->corpus.event.is_interest = 0;
    event->corpus.event.is_exclusive = 0;
    event->corpus.event.serial = 0;
    if (last_event_queued &&
        event->corpus.event.time >= 
                last_event_queued->corpus.event.time) {
        q = last_event_queued;
        assert(last_event_queued->refcount > 0);
        assert(last_event_queued->corpus.event.is_queued);
    } else {
        q = corpus_of(eventq);
    }
    if (q == 0 || time < q->corpus.event.time) {
        event->corpus.event.next = eventq;
        eventq = evt;        
        last_event_queued = event;
        event->corpus.event.is_queued = 1;
    } else {
        while (corpus_of((q->corpus.event.next)) &&
               corpus_of((q->corpus.event.next))
                   ->corpus.event.time <= time) {
#ifdef PERFORMANCE
                count++;
#endif
            assert(!q->corpus.event.is_interest);
            assert(q->corpus.event.is_queued);
            q = corpus_of((q->corpus.event.next));
        }
#ifdef PERFORMANCE
        queue_compares += count;
        queue_event_count++;
#endif
        event->corpus.event.next = q->corpus.event.next;
        if (collapse_motion && collapsePtrMotion
         && (event->corpus.event.locationnotset==
                q->corpus.event.locationnotset)
         && equal_object(&event->corpus.event.name, 
                &q->corpus.event.name)
         && equal_object(&event->corpus.event.action, 
                &q->corpus.event.action)){
            if (event->corpus.event.locationnotset) {
                q->corpus.event.delta_x += 
                        event->corpus.event.delta_x;
                q->corpus.event.delta_y += 
                        event->corpus.event.delta_y;
            } else {
                q->corpus.event.loc_x=event->corpus.event.loc_x;
                q->corpus.event.loc_y=event->corpus.event.loc_y;
            }
            decref(evt);
#ifdef PERFORMANCE
            queue_length--;
#endif
        } else {
            q->corpus.event.next = evt;
                last_event_queued = event;
                event->corpus.event.is_queued = 1;
        }
    }
#ifdef EVENT_DEBUG
        check_q();
#endif
}
.SE

.UI enqueueEvent()
takes two parameters.  One is the 
.UI REF 
containing the event to enqueue,
and the other is a flag indicating whether the event should be collapsed
into another one if it matches one already in the queue.
.P
First,
.UI enqueueEvent() 
clears some fields. 
The field
.UI is_interest 
is used when matching interests with the event.
The 
.UI serial 
field is initialized when the event is distributed in 
.UI RunScheduler() .
.SS
    event->corpus.event.is_interest = 0;
    event->corpus.event.is_exclusive = 0;
    event->corpus.event.serial = 0;
.SE

Next, the code goes about inserting the new event in the queue.
The variable 
.UI last_event_queued 
points into the queue at the point of the last insertion.
It may be null if the event it originally pointed to has been distributed.
It is assumed that events in the queue are in time stamp order, so
if 
.UI last_event_queued 
is not null and the time stamp on the new event is
greater than the one to which
.UI last_event_queued 
pointed,
.UI last_event_queued 
can be the place to insert the new event.
.SS
    if (last_event_queued &&
        event->corpus.event.time >= 
                last_event_queued->corpus.event.time) {
        q = last_event_queued;
.SE
.P
The next 
.UI if 
statement 
.UI "(if (q = 0...))" 
will then fail,
and proceed with the 
.UI else 
part:
.SS
    if (q == 0 || time < q->corpus.event.time) {
    .
    .
    .
    } else {
        while (corpus_of((q->corpus.event.next)) &&
               corpus_of((q->corpus.event.next))
                        ->corpus.event.time <= time) {
            q = corpus_of((q->corpus.event.next));
        }
        event->corpus.event.next = q->corpus.event.next;
.SE

This starts at 
.UI last_event_queued 
and searches for the point at which
to insert the new event, that is, the last event in the queue whose time is
less than the new event.
.P
All this presupposes that 
.UI last_event_queued 
is set.
If not, 
the test on 
.UI last_event_queued 
fails and 
.UI q 
is set to
.UI eventq , 
which points to the beginning of the event queue.
.SS
    if (last_event_queued &&
        event->corpus.event.time >= 
                last_event_queued->corpus.event.time) {
    .
    .
    .
    } else {
        q = corpus_of(eventq);
    }
.SE

If the queue is empty 
.UI "(q = 0)"
or the time of the new event is
before the time of the first event in the queue, 
the new event becomes the first event in the queue.
.SS
    if (q == 0 || time < q->corpus.event.time) {
        event->corpus.event.next = eventq;
        eventq = evt;        
            last_event_queued = event;
        event->corpus.event.is_queued = 1;
    } else {
    .
    .
    .
    }
.SE

Otherwise,
the same while loop as described above is executed,
but this time it starts at the beginning of the queue.
.SS
    if (q == 0 || time < q->corpus.event.time) {
    .
    .
    .
    } else {
        while (corpus_of((q->corpus.event.next)) &&
               corpus_of((q->corpus.event.next))
                        ->corpus.event.time <= time) {
            assert(!q->corpus.event.is_interest);
            assert(q->corpus.event.is_queued);
            q = corpus_of((q->corpus.event.next));
        }
        event->corpus.event.next = q->corpus.event.next;
.SE

Once the entry is inserted into the queue,
a check is made to see if we should collapse the
new event with one already in the queue.
.SS
if (collapse_motion && collapsePtrMotion
  && (event->corpus.event.locationnotset==
  q->corpus.event.locationnotset)
  && equal_object(&event->corpus.event.name, 
  &q->corpus.event.name)
  && equal_object(&event->corpus.event.action, 
  &q->corpus.event.action)){
    if (event->corpus.event.locationnotset) {
        q->corpus.event.delta_x += 
        event->corpus.event.delta_x;
        q->corpus.event.delta_y += 
        event->corpus.event.delta_y;
    } else {
        q->corpus.event.loc_x = event->corpus.event.loc_x;
        q->corpus.event.loc_y = event->corpus.event.loc_y;
    }
    decref(evt);
} else {
    q->corpus.event.next = evt;
    last_event_queued = event;
    event->corpus.event.is_queued = 1;
}
.SE

If the new event is the same as the one we're inserting next to,
the new event is thrown away via 
.UI decref(evt)
and the existing event is updated to reflect the information in
the new event.
.P
Otherwise, the new event is inserted into the queue.
.P
This ends 
.UI enqueueEvent .
The events are dequeued in 
.UI RunScheduler 
in
.UI nucleus/sched.c .
.H 2 "Event Distribution in the Server"
Once an event is collected from the kernel and queued on the event queue 
.UI (eventq) ,
.UI RunScheduler() 
in
.UI nucleus/sched.c  
calls 
.UI distributeevents() 
to handle event distribution in the Server.
The code below from 
.UI RunScheduler()
shows the beginning of the event distribution.
.SS
if (input_queue_lock.count <= 0) {
    REF ev;

       ev = eventq;
       e = corpus_of((ev));
    while (distribution_count == 0
       && (e) != 0
       && e->corpus.event.time <= now) {
#ifdef PERFORMANCE
        if (queue_length > max_queue_length)
                max_queue_length = queue_length;
        queue_length--;
#endif
    eventq = e->corpus.event.next;
    if (e == last_event_queued)
        last_event_queued = (struct corpus *) 0;
    zero_ref(e->corpus.event.next);
    if (!e->corpus.event.serial)
        e->corpus.event.serial=++event_serial_number;
    if (e->corpus.event.time > last_event_time)
        last_event_time = e->corpus.event.time;
    if (raw_keyboard)
        determine_keystate(e);
    e->corpus.event.is_queued = 0;
    distributeevent(&ev);
    decref(last_event_keystate);
    last_event_keystate = e->corpus.event.kstate;
    incref(last_event_keystate);
  /*
 * Now see if this event is the one that the eventdist
 * waiters are waiting on
 */
    if (equal_ref(ev, process_wait_event)) {
        register struct execution_environment *ee, *next;

            ee = process_eventdist_waiters;
            while (ee) {
                next = ee->eventdist_next;
                ee->eventdist_next = 0;
                enqueue_process(ee);
                ee = next;
            }
            process_eventdist_waiters=
                (struct execution_environment *)0;
            decref(process_wait_event);
                zero_ref(process_wait_event);
        }
        decref(ev);
        ev = eventq;
        e = corpus_of(ev);

      }
}
.SE

Events are only distributed if a client hasn't locked the input queue 
.UI "(input_queue_lock.count <= 0)."
If the input queue isn't locked, events are distributed in a 
.UI while 
loop which runs as long as there are events in
the queue 
.UI "((e) != 0)" and their time stamps are before the current time,
and as long as no events have been distributed this time through
the loop 
.UI "(distribution_count == 0)."
.SS
            while (distribution_count == 0
                   && (e) != 0
                   && e->corpus.event.time <= now) {
.SE

This last test on 
.UI distribution_count 
is used to enforce an element of 
NeWS semantics: a process thread to which an event is delivered must be
allowed to run before other events are delivered to it or any other thread.
Note also that shuttle events (see below in the description of
.UI distributeevent() 
and repeating keys can be in the queue and have
an event time which is greater than the current time.
.P
When an event is actually delivered (by 
.UI deliverevent() 
in
.UI nucleus/input.c , 
.UI distribution_count 
is incremented.  This may not
happen with every event, but once an event is delivered,
the 
.UI while 
loop terminates and the process thread is allowed to run.
.P
Inside the 
.UI while 
loop, the event is first removed from the queue 
.UI "(eventq = e->corpus.event.next)" .
The event is also assigned a serial number.
.SS
    if (!e->corpus.event.serial)
        e->corpus.event.serial = ++event_serial_number;
.SE

We also keep track of the time of the last event:
.SS
if (e->corpus.event.time > last_event_time)
    last_event_time = e->corpus.event.time;
.SE

If the keyboard is in raw mode, we determine whether a key is up or
down. 
.SS
if (raw_keyboard)
    determine_keystate(e);
.SE

This is done by calling 
.UI determine_keystate() 
in 
.UI nucleus/input.c .
Note that the code above builds the keystate field in the event.
Then, we set the 
.UI is_queued 
field of the event to 0 since the event is
no longer queued, and call 
.UI distributeevent() :
.SS
    e->corpus.event.is_queued = 0;
    distributeevent(&ev);
.SE

When the event has been distributed, we keep track of the last event
keystate.  This global is used by the 
.B lasteventkeystate 
NeWS operator
(see 
.UI lasteventkeystate_primitive() 
in 
.UI input.c).
.SS
    decref(last_event_keystate);
    last_event_keystate = e->corpus.event.kstate;
    incref(last_event_keystate);
.SE

The whole goal of event distribution is to deliver an incoming event to
a process thread which has expressed an interest in the corresponding type
of event.  Process threads (that is, clients) express interests by using the
.B expressinterest 
NeWS operator.  Refer to the NeWS Programmers Guide for details.
.P
Event distribution is handled in the source file 
.UI nucleus/input.c .
The process of distributing an event begins in 
.UI distributeevent() .
.P
The first thing 
.UI distributeevent() 
does is call 
.UI setEventMatchMask()
.UI (nucleus/inputdist.c) .  
This routine sets some fields in the event
structure which are used internally to compare events with interests.
.P
The bits used in these fields are defined in 
.UI $DESTDIR/usr/etc/NeWS/matchbits.ps .  
This file is read on initialization by 
.UI init.ps .
.P
The next thing 
.UI distributeevent() 
does is set the location at which
the event occurred if it's not already set.
Generally speaking, the location is not set when the event is first distributed.
.SS
    if (event->corpus.event.locationnotset) {
        event->corpus.event.locationnotset = FALSE;
        eventq_last_x += event->corpus.event.delta_x;
        eventq_last_y += event->corpus.event.delta_y;
        event->corpus.event.loc_x = eventq_last_x;
        event->corpus.event.loc_y = eventq_last_y;

        last_event_x = fracti(event->corpus.event.loc_x);
        last_event_y = fracti(event->corpus.event.loc_y);
    }
.SE

It is assumed that
if the event being presented is a motion event (that is, the
cursor moved),
then
the code in the NPSI layer, which sets up the event, presents
not the absolute location of the event,
but rather a relative change in position.
The code in 
.UI input.c 
keeps track of what it thinks the pointing device's
absolute location is in 
.UI eventq_last_x 
and 
.UI eventq_last_y .
Any delta from the last event is added to the absolute location,
and then the absolute location is placed in the event structure.
.P
If the code in the NPSI layer presents absolute locations, then
.UI event->corpus.event.locationnotset 
is FALSE, and 
.UI event->corpus.event.loc_x 
and 
.UI event->corpus.event.loc_y
are set with the absolute location.
Otherwise, the change in location is presented in 
.UI event->corpus.event.delta_x
and 
.UI event->corpus.event.delta_y.  
The code in 
.UI input.c 
keeps track of
what it thinks the pointing device's absolute location is in 
.UI eventq_last_x
and 
.UI eventq_last_y .  
The change is added to the last absolute location,
and the absolute location is placed in the event in
.UI event->corpus.event.loc_x 
and 
.UI event->corpus.event.loc_y .
Note that 
.UI event->corpus.event.locationnotset 
is set to FALSE indicating the event now
contains an absolute location.
.P
The code also keeps track of the last event position in 
.UI last_event_x
and  
.UI last_event_y . 
These are used by the NeWS operators
.B lasteventx 
and 
.B lasteventy.
.P
Next, if the event is a motion event, the event is put in the motion buffer.
X11 Clients can request the contents of this buffer so they can
see where the mouse has been moving.
.SS
    if (event->corpus.event.motion_event) {
        motion_event = TRUE;
        event->corpus.event.motion_event = FALSE;
#ifndef NEWS_ONLY
        /*
         * put event in motion buffer
         */
        motion_buf[motion_head].x = event->corpus.event.loc_x;
        motion_buf[motion_head].y = event->corpus.event.loc_y;
        motion_buf[motion_head].time = event->corpus.event.time;
        motion_head++;
        if (motion_head >= MOTIONBUFFERSIZE)
            motion_head = 0;
#endif
    }
.SE

Then, the code handles repeating of depressed keys.
.SS
    /* repeat keys */
    if (event->corpus.event.keyboard_event
      && (find_object_in_dictionary(event->corpus.event.name, 
                        repeat_keys))) {
        repeater_changed(event);
        event->corpus.event.keyboard_event = FALSE;
    } else if (event == corpus_of(shuttle)) {
        shuttle_arrived(event);
        return;
    }
.SE

If the event is a keyboard event (i.e., a keystroke), then 
.UI repeater_changed() 
(
.UI nucleus/repeat.c ) 
is called.  This checks to see if the key was already down.
If it was, and has been for a while,
a \f2shuttle\fP event is generated.
This tells the event distribution code to send another character to
the interested thread.
When 
.UI distributeevent() 
is called with the shuttle event,
.UI shuttle_arrived() 
.UI (nucleus/repeat.c) 
is called.
.UI Shuttle_arrived() 
figures out what keys are down and calls
.UI distributeevent() 
with a keyboard event corresponding to those keys.
It also requeues a shuttle event for the future.
.P
After handling repeating of keys, 
.UI distributeevent 
then sends
the event to the event logger process if it is active.
The event logger is a debugging tool which is described later in this 
chapter.
.SS
    if (event_logger) {
        REF ev;
        struct corpus *e;

        ev = pnew(NO_POOL, event_type, 0);
        e = corpus_of(ev);
        corpus_copy(event, e, event);
        zero_ref(e->corpus.event.next);
        event_inc_refs(&ev);
        deliverevent(&ev, event_logger);
    }
.SE

Finally, the code distributes the event to the appropriate destination:
either a specific \*(Sd canvas, an array of canvases,
or a canvas which is an entry in a dictionary.  This is done by
switching on the type of the canvas associated with the event.
.SS
    cvobj = event->corpus.event.canvas;
    assert(cvobj.type != packedarray_type);
    switch (cvobj.type) {
        case null_type: {
.SE

Events which come from the input device always have a canvas type of
.UI null_type , 
which is handled as follows:
.SS
case null_type: {
  REF cv;

  cv=cs_locatecanvasinscene(cs_scrn_scene(CurrentInputScreene),
                            event->corpus.event.loc_x,
                            event->corpus.event.loc_y);
  assert(!is_ref_zero(cv));
  distributeToCanvas(event, (XNCANVAS) entity_addr(cv), FALSE);
  break;
}
.SE

First, 
.UI cs_locatecanvasinscene 
is called to determine which canvas was
underneath the mouse at the time of the event.
Then 
.UI distributeToCanvas 
is called to distribute the event to that
canvas according to NeWS semantics.
.P
The other cases mentioned above (array of canvases, canvas in
dictionary, specific XNeWS canvas) are not discussed in detail here,
though they are handled in a similar fashion.
Events generated by using the 
.UI sendevent 
NeWS operator will have
one of these canvas types.
.P
.UI distributeToCanvas() 
is used to distribute events to a specific
canvas according to NeWS semantics.
Basically, this means that canvases which are ancestors of the current canvas
(the one over which the event occurred) are given first crack at
the event starting with the root canvas (or frame buffer) and working
down to the current canvas.
.P
If the event still hasn't been delivered,
.UI distributeToCanvas() 
tries to deliver it to each ancestor in the
opposite order: parent, grandparent, great-grandparent, etc.,
all the way to the root canvas.
.P
For more information on this, refer to the NeWS Programmer's Guide.
.SS
distributeToCanvas(event, cvent, canvas_specified)
    register struct corpus *event;
    register XNCANVAS cvent;
    int canvas_specified;
{
    register int interested;

    /*
     * <=0 means continue with search
     */
    if (preChildWalkTree(event, cvent) <= 0) {
        /*
         * this loop only gets executed ONCE
         * if canvas_specified is true
         */
        do {
            cvPostInterest(cvent, event, interested);
            if ((interested && search_ilists(event, cvent, FALSE))
                    || canvas_specified
                    || cvent->eventsconsumed == AllEvents)
                break;

            cvPostAncesInterest(cvent, event, interested);
            cvent = (XNCANVAS) entity_addr(cvent->parent);
        } while (cvent != 0 && interested);
    }
    return;
}
.SE

.UI distributeToCanvas() 
calls 
.UI preChildWalkTree() 
to deliver the event to
ancestors starting with the root canvas.
Then the 
.UI do-while 
loop tries to deliver the event to the
current canvas and then ancestors
in order of proximity to the current canvas until the root is reached.
.P
.UI cvPostInterest 
and 
.UI cvPostAncesInterest 
are macros in the following directory:
.DS I UI
$SRCROOT/usr.bin/xnews/xnews/server/include/nucleus/inputdist.h.
.DE
.UI cvPostInterest 
checks to see if the canvas has a post-child interest.
.UI cvPostAncesInterest
checks to see if any ancestors of the canvas has
a post-child interest. These are implemeted as macros for performance
considerations.
.P
The routine 
.UI search_ilists() 
is called by 
.UI distributeToCanvas()
to search the interest lists
of a canvas.
If a match is found,
the routine 
.UI deliverevent() 
is called to actually deliver the event to the
process thread associated with the canvas.
.P
.UI Search_ilists 
is also called by 
.UI preChildWalkTree() .
.SS
/*
 * walk tree calling search_ilists for possibly interesting 
 * canvases. return 1 if the search should stop here, 0 if 
 * the search should continue, and -1 if the pre-child 
 * search should stop (but the post-child should continue).
 */
static int
preChildWalkTree(ev, cv)
    struct corpus *ev;
    register XNCANVAS cv;
{
    register int interested, desc_interested;
    register XNCANVAS cvent;

    /* Search preceding interests on parents */
    cvent = (XNCANVAS) entity_addr(cv->parent);
    if ((cvent != 0) && (desc_interested = 
        preChildWalkTree(ev, cvent)))
        return desc_interested;

    cvPreInterest(cv, ev, interested);
    if (interested && search_ilists(ev, cv, TRUE))
        return 1;
    cvPreDescInterest(cv, ev, desc_interested);
    return (desc_interested ? 0 : -1);
}
.SE

.UI preChildWalkTree() 
works by calling itself recursively until the
root is reached.  It then
uses 
.UI cvPreInterest 
and 
.UI cvPreDescInterest ,
also macros in 
.UI inputdist.h ,
to see if the current canvas or any
descendants have an interest matching the event.
If a match may exist, 
.UI search_ilists 
is then called to
find out for sure.  Note we say ``may''. This is because the macro 
may ``return'' an indication that an interest exists when none does.
.SS
/*
 *  Walk interest-lists looking for a match. If pre-child, 
 *  walk root down to here, including this canvas' pre-child
 *  interests at tail of recursion, but leaving its post-child
 *  interests. If post-child, just look at this canvas'
 *  post-child.  Return whether to stop here interests.
 */
static int
search_ilists(ev, cv, pre_child)
    struct corpus *ev;
    XNCANVAS cv;
    int pre_child;
{
    register int    matched = 0, may_match;
    EvConsCell *int_cell;
    REF e;
    register struct corpus *inc;

    if (pre_child) {
        int_cell = cv->preInterest;
    } else {
        int_cell = cv->postInterest;
    }

    while (int_cell) {
        inc = int_cell->interest;
        assert(inc->type == event_type);
        assert(inc->corpus.event.is_interest);
        interestMatch(inc, ev, may_match);
        if (!may_match) {
            int_cell = int_cell->next;
            continue;
        }
        e = is_interesting(int_cell, ev, cv);
        if (e.type == event_type) {
#ifdef        FULLDEBUG /* This ifdef is for debugging */
    {
        struct corpus *evc = corpus_of(e);
        /* name need not be the same */
        /* action need not be the same */
        /* runnable_match need not be equal */
        assert(evc->corpus.event.canvas == 
                    ev->corpus.event.canvas);
        assert(equal_ref(evc->corpus.event.process,
                                inc->corpus.event.process);
        assert(evc->corpus.event.inter.type == event_type);
        assert(evc->corpus.event.kstate.type == null_type
            || evc->corpus.event.kstate.type == array_type);
        assert(corpus_of(evc->corpus.event.next) == 0);
        /* pos */
        assert(evc->corpus.event.serial == 
                inc->corpus.event.serial);
        /* time */
        /* priority */
        assert(!evc->corpus.event.is_interest);
        assert(!evc->corpus.event.is_exclusive);
        assert(!evc->corpus.event.is_queued);
        /* name_match */
        /* action_match */
    }
#endif

            deliverevent(&e,
                (struct execution_environment *)
                        entity_addr(inc->corpus.event.process));
            distribution_count++;
            if (inc->corpus.event.is_exclusive)
                return 1;
            matched = 1;
        }
        int_cell = int_cell->next;
    }
    /*
     * pre-child interests do not consume events
     */
    return (!pre_child
            && (matched && cv->eventsconsumed == MatchedEvents
                || cv->eventsconsumed == AllEvents));
}
.SE

.UI Deliverevent 
is called by 
.UI search_ilists 
if a match is found.
It attaches the event to the process thread's event queue.
If the process was asleep, it awakens it by calling 
.UI enqueue_process().
.SS
deliverevent(ev, ee)
    REF *ev;
    register struct execution_environment *ee;
{
    register struct corpus *b = corpus_of(*ev);

    assert(ev->type == event_type);
    assert(b->type == event_type);

    if (corpus_of(ee->eq_head) == NULL) {  /* queue was empty */
        ee->eq_head = *ev;
        ee->eq_tail = b;
        if (ee->event == input_event_wait)
            enqueue_process(ee);
        else if (ee->event > 0 && ee->interruptible_iowait) {
            dequeue_io_waiter(ee, ((ee->event & 
                        ~event_write_flag) - 1));
            enqueue_process(ee);
        }
    } else {
        zero_ref(b->corpus.event.next);
        ee->eq_tail->corpus.event.next = *ev;
        ee->eq_tail = b;
    }
}
.SE
.H 2 "NeWS Operators"
In addition to implementing these operators, 
you will also have to add code to define the following operators in
the \*(Sd dictionary.
Other initialization may also be required.
Refer to the reference code as an example.
You will also have to arrange for this initialization routine to
be called as part of 
.UI apply_initialization().

.H 3 "\f4startkeyboardandmouse(ee)\fP"
This routine does whatever it takes to initialize the keyboard and mouse.
This usually involves opening the device or devices associated with
the keyboard and mouse and performing some 
.UI ioctls 
on the device to tell it to start delivering events.
The System V implementation uses two other routines,
.UI openkeyboardandmouse 
and 
.UI initkeyboardandmouse .
.SS
initkeyboardandmouse()
{
    struct vt_mode        vtmode;
    struct termio        ttyparms;
    struct kd_quemode        qmode;


    if ((indev = open ("/dev/mouse", O_RDONLY)) < 0) {
            Error("Cannot open /dev/mouse");
            exit(1);
    }

    if (xsig == 0)
            xsig = XSIG;
    qmode.qsize = 500;
    qmode.signo = xsig;
    if (ioctl(vt_fd, KDQUEMODE, &qmode) == -1) {
            Error("KDQUEMODE failed");
            exit(1);
    }


    queue = (xqEventQueue *)qmode.qaddr;
    qLimit = queue->xq_size;
    qfull = queue->xq_size - 1;

    signal(xsig, catch_xsig);

    queue->xq_sigenable = 1;
}

startkeyboardandmouse(ee)
register struct execution_environment *ee;
{
    key_t         	key;
    int 		shmid;
    struct cursor 	*cp = &cursor;
    register int	xmax, ymax;
    static int		keyboardandmousestarted;



    if (vt_fd < 0) {
        perror("startkeyboardandmouse:  display device not open");
        return;
    }

    /*
	This is intended to be the equivalent of the code in the sun
	version (npsi/os/sun/sun_dev.c) which checks to see if
	osprivate(CurrentInputScrene)->fd > 0;  that is,  has
	startkeyboardandmouse been called already?
     */

    if (keyboardandmousestarted)
        return;
    else
        keyboardandmousestarted = 1;

    initkeyboardandmouse();

    /* initialize the cursor */

    /*
        This code is necessary to tell the event q driver where
        the cursor is so when it posts events it specifies the
        correct position.
    */

    cp->max_x = xmax = screene_get_width(CurrentInputScreene);
    cp->max_y = ymax = screene_get_height(CurrentInputScreene);
    cp->cur_x = xmax/2;
    cp->cur_y = ymax/2;
    cp->last_button = 7;
    positioncursor(cp->cur_x, cp->cur_y);

    /*
   Update the local idea of the cursor position (curx,y).
     */

    curx = xmax/2;
    cury = ymax/2;
    lastx = 0;
    lasty = 0;
    setmouseposition(curx, cury);

    kbmode = K_XLATE;

/* Setup tty parameters for all */
/* terminal emulators that will start */
    {
        int         tty_fd;
        tty_fd = open("/dev/tty", O_RDWR, 0);
        if (tty_fd < 0)
            (void) ttysw_saveparms(2);  /* Try stderr */
        else {
            (void) ttysw_saveparms(tty_fd);
            (void) close(tty_fd);
        }
    }

    cs_enablecursor();
    {
        struct object *value;
        DICT *devdict;
        struct object false;

        value =
            find_object_in_dictionary(add_kwd_name("devicedict"),
                                  system_dictionary);
        devdict = dict_of(*value);
        set_typed_object(&false, boolean_type);
        false.value.fixed = fracti(0);
        define_object_in_dictionary(add_kwd_name("SunView?"), 
                                false, devdict, (ENTITY *)0);
    }

    return;
}



openkeyboardandmouse()
{
    if (vt_fd  >= 0)
                return;

    if (openvt() < 0)
    {
        perror("open:  Could not open display");
        exit(1);
    }
}
.SE

.H 3 "\f4setkeyboardtranslation_primitive(ee)\fP"
This routine is used to set the keyboard translation mode,
either to raw or translated mode.
.SS
setkeyboardtranslation_primitive(ee)
    register struct execution_environment *ee;
{
    register struct object *optop = ee->optop - 1;
    register enum kbd_translation desired;

    if (optop->type != boolean_type) {
        ee->error_code = typecheck_error_code;
        return;
    }
    desired = optop->value.fixed ? NORMAL_KEYBOARD : 
             UNENCODED_KEYBOARD;
        if (!setkeyboardtranslation(desired)) {
        ee->error_code = rangecheck_error_code;
        return;
    }
    raw_keyboard = (desired == UNENCODED_KEYBOARD);
    ee->optop--;
}

setkeyboardtranslation(arg)
    enum kbd_translation arg;
{
    switch (arg) {
    case UNENCODED_KEYBOARD:
        raw_keyboard = 1;
        kbmode = K_RAW;
        break;
    case NORMAL_KEYBOARD:
        raw_keyboard = 0;
        kbmode = K_XLATE;
        break;
    }

    return 1;
}
.SE

.H 3 "\f4getkeyboardtranslation(ee)\fP"
This routine places a code corresponding to
the current keyboard translation mode on the operand stack.
If the mode is raw, the code is 0, otherwise 1.
.SS
getkeyboardtranslation(ee)
    register struct execution_environment *ee;
{
    register struct object *optop = ee->optop - 1;
    int             result;

    result = (raw_keyboard ? 0 : 1);
    set_typed_object(ee->optop, boolean_type);
    /*
     * changed result to fracti(result) nara 
     * 890201 based on sun_dev.c
    */
    ee->optop->value.fixed = fracti(result);
    ee->optop++;
}
.SE
.H 3 "\f4getmousetranslation(ee)\fP"
This routine places a code corresponding to
the current translation mode on the operand stack.
If the mode is raw, the code is 0, otherwise 1.
.SS
getmousetranslation(ee)
    register struct execution_environment *ee;
{
    set_typed_object(ee->optop, boolean_type);
    /*
     * changed !raw_mouse to fracti(!raw_mouse) 
     * nara 890201 based on sun_dev.c
    */
    ee->optop->value.fixed = fracti(!raw_mouse);
    ee->optop++;
}
.SE
.H 3 \f4setmousetranslation(ee)\fP
This routine can be stubbed out.
.H 3 "\f4setkeyboardtranslation_primitive(ee)\fP"
This routine is used to set the mouse translation mode,
either to raw or translated mode.
.H 3 "\f4keyboardtype(ee)\fP"
.P
This routine places on the operand stack a code corresponding to
the type of keyboard.
This code is used in the initialization \*(Sd/\*(Ps
code to read in a specific keyboard translation table.
The code is in 
.UI \s-1$SRCROOT/usr.bin/xnews/xnews/etc/NeWS/keyboard.ps\s+1 
and
.UI \s-1$SRCROOT/usr.bin/xnews/xnews/etc/NeWS/keybdinit.ps\s+1 .
.P
The code in these files may need to be modified to add a new keyboard
type if what you have doesn't correspond to the code for
the existing keyboard types.
.P
This code reads in a keyboard translation file,
which you may also have to write.
Examples are in
.UI \s-1$SRCROOT/usr.bin/xnews/xnews/etc/NeWS/sun?_keys.ps\s+1 .
.SS
keyboardtype(ee)
    register struct execution_environment *ee;
{
    register struct object *optop = ee->optop - 1;
    int             result;

    result = KB_AT386;
    set_fixed_object(ee->optop, fracti(result));
    ee->optop++;
}
.SE

.H 1 "Server Internal Routines"
.P
In addition to the functionality required to take events from the
device driver, translate them to NeWS terms and queue them to the server,
a number of external interfaces are required.
Some of these are used internally to the server only.
Others implement operators used by NeWS/\*(Ps code.  These interfaces
are described below.

\f4osi_setcursorposition() {}\fP
.P
This routine takes three
parameters:  a pointer to the screen structure representing the
frame buffer, and the x and y coordinates to which to move the cursor.
This routine should do whatever it takes to notify the NPSI layer
code of the new cursor location (such as updating local variables
which keep track of the location).
.P
It should also generate a mouse event.
In the AT&T version, this is done
by calling 
.UI setmouseposition() ,
which in turn calls 
.UI ProcessInputEvent() .
.SS
void
osi_setcursorposition(scr, x, y)
    SCREENE scr;
    short     x, y;  /* integer framebuffer coordinates */
{
    if (!is_ref_zero(ConstrainCanvas))
        (void)osi_constraintocanvas(ConstrainCanvas, (short *)&x, 
            (short *)&y);
    setmouseposition(x, y);
    positioncursor(x, y); /* Tell the event q driver where
                             the cursor is berg 890127. */
    if (scr != CurrentInputScreene)
        CurrentInputScreene = scr;
    cs_movecursor((short) x, (short) y);
    CheckCrossings(last_event_time);
}
.SE
.H 3 "\f4osi_isscreen() {}\fP"
This takes one parameter, a file name.
It should return 1 if the file name corresponds to
the frame buffer, otherwise 0.
.P
It is called by
.UI createdevice() 
( 
.UI nucleus/canvasdict.c )
which is invoked by the 
.B createdevice 
operator to determine what type of device is being initialized.
.SS
/*
 * osi_isscreen()
 *
 * Decide whether the char string is the name of the
 * framebuffer device
 */
int
osi_isscreen(fn)
        char *fn;
{
/* this code replaces the previous incarnation below */
                return(1);
        return(0);

}
.SE
.H 3 "\f4osi_ismouse() {}\fP"
This takes one parameter, a file name.
It should return 1 if the file name corresponds to
the mouse, otherwise 0.
It is also called by
.UI createdevice() 
(
.UI nucleus/canvasdict.c 
).
.SS
int
osi_ismouse(fn)
        char *fn;
{
        if (!strncmp(fn, "/dev/mouse", 10))
                return(1);

        return(0);
}
.SE
.H 3 "\f4osi_iskbd() {}\fP"
This takes one parameter, a file name.
It should return 1 if the file name corresponds to
the keyboard, otherwise 0.
It is also called by
.UI createdevice() 
(
.UI nucleus/canvasdict.c
).
.SS
int
osi_iskbd(fn)
        char *fn;
{
        if (!strncmp(fn, "/dev/kbd", 8))
                return(1);

        return(0);
}
.SE
.H 3 "\f4osi_lw_setcursorposition() {}\fP"
.P
This subroutine is obsolete, and can be stubbed out.
.H 3 "\f4osi_devicecontrol() {}\fP"
.P
This routine can also be stubbed out, as it applies mainly to Sun systems.



