File:  [The Machine Emulator] / tme / generic / keyboard.c
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 16:42:56 2018 UTC (3 years, 9 months ago) by root
Branches: heeltoe, fredette, MAIN
CVS tags: tme-0_8heeltoe, tme-0_8, HEAD
tme-0.8

/* $Id: keyboard.c,v 1.1.1.3 2018-04-24 16:42:56 root Exp $ */

/* generic/keyboard.c - generic keyboard implementation support: */

/*
 * Copyright (c) 2003 Matt Fredette
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <tme/common.h>
_TME_RCSID("$Id: keyboard.c,v 1.1.1.3 2018-04-24 16:42:56 root Exp $");

/* includes: */
#include <tme/generic/keyboard.h>
#include <tme/hash.h>
#include <tme/misc.h>
#include <stdlib.h>
#include <errno.h>

/* macros: */

/* the shortest possible time, in milliseconds, between two human
   transitions on the same key: */
#define TME_KEYBOARD_SHORTEST_DOUBLE_MSEC	(80)

/* input stage zero uses a slightly expanded set of event types: */
#define TME_KEYBOARD_EVENT_IN0_RELEASE_USER	(0)
#define TME_KEYBOARD_EVENT_IN0_PRESS_USER	(1)
#define TME_KEYBOARD_EVENT_IN0_RELEASE_AUTO	(2)
#define TME_KEYBOARD_EVENT_IN0_PRESS_AUTO	(3)
#if TME_KEYBOARD_EVENT_RELEASE != TME_KEYBOARD_EVENT_IN0_RELEASE_USER
#error "TME_KEYBOARD_EVENT_RELEASE must be 0"
#endif
#if TME_KEYBOARD_EVENT_PRESS != TME_KEYBOARD_EVENT_IN0_PRESS_USER
#error "TME_KEYBOARD_EVENT_PRESS must be 1"
#endif

/* this macro turns an input stage zero pressed value into the
   corresponding release event type: */
#define TME_KEYBOARD_IN0_RELEASE_EVENT(pressed)	((pressed) ^ 1)

/* these macros evaluate to nonzero iff a keyval is pressed in the
   different stages: */
#define TME_KEYBOARD_PRESSED_IN0(keysym)	\
  ((keysym)->tme_keysym_state_in0_pressed)
#define TME_KEYBOARD_PRESSED_IN1(keysym)	\
  ((keysym)->tme_keysym_state_in1_keymode.tme_keymode_state_pressed)
#define _TME_KEYBOARD_PRESSED_IN2(keysym, prev)	\
  ((keysym)->tme_keysym_state_in2_pressed	\
   || (!(keysym)->tme_keysym_state_in2_released	\
       && prev))
#define TME_KEYBOARD_PRESSED_IN2(keysym)	\
  _TME_KEYBOARD_PRESSED_IN2(keysym, TME_KEYBOARD_PRESSED_IN1(keysym))
#define _TME_KEYBOARD_PRESSED_OUT0(keysym, prev)\
  ((keysym)->tme_keysym_state_out0_pressed	\
   || (!(keysym)->tme_keysym_state_out0_released\
       && prev))
#define TME_KEYBOARD_PRESSED_OUT0(keysym)	\
  _TME_KEYBOARD_PRESSED_OUT0(keysym, TME_KEYBOARD_PRESSED_IN2(keysym))
#define TME_KEYBOARD_PRESSED_OUT1(keycode)	\
  ((keycode)->tme_keycode_state_keymode.tme_keymode_state_pressed)

/* types: */

struct tme_keyboard_buffer_int;
struct tme_keysym_state;

/* keymode state: */
struct tme_keymode_state {

  /* keys that may be autorepeating are kept on a linked list: */
  struct tme_keymode_state *tme_keymode_state_next;

  /* the state for this keysym.  technically, since output stage one
     deals in keycodes, this really should be a void * and point to
     the keysym state for input stage one, and point to the keycode
     state for output stage one.  

     however, avoiding the void * allows us to avoid some function
     pointer casting, and in the output stage one case we don't care
     which of the many keysyms that may map to the same keycode is
     stored here - we just immediately grab the keycode state out of
     that keysym: */
  struct tme_keysym_state *tme_keymode_state_keysym;

  /* the keymode mode: */
  int tme_keymode_state_mode;

  /* this is nonzero iff the key is pressed in the physical sense: */
  int tme_keymode_state_pressed;

  /* the last time this key was released: */
  tme_uint32_t tme_keymode_state_last_release;

  /* this is nonzero iff a genuine release should be ignored: */
  int tme_keymode_state_ignore_release;
};

/* a keymode stage: */
struct tme_keymode_stage {

  /* the global keymode: */
  int tme_keymode_stage_global_mode;

  /* the list of keymode states for keys that must not autorepeat: */
  struct tme_keymode_state *tme_keymode_stage_no_autorepeats;

  /* the next stage: */
  int (*tme_keymode_stage_next) _TME_P((struct tme_keyboard_buffer_int *,
					struct tme_keysym_state *,
					tme_uint32_t));
};

/* keycode state: */
struct tme_keycode_state {

  /* the keycode: */
  tme_keyboard_keyval_t tme_keycode_state_keycode;

  /* the keycode keymode state: */
  struct tme_keymode_state tme_keycode_state_keymode;
};

/* keysym state.  one of these is kept for every keysym controlled by
   one or more input or output stages: */
struct tme_keysym_state {

  /* this keysym: */
  tme_keyboard_keyval_t tme_keysym_state_keysym;

  /* input stage zero: */

  /* if greater than TME_KEYBOARD_MODIFIER_NONE, this is the modifier
     that this keysym is attached to in input stage zero: */
  int tme_keysym_state_in0_modifier;

  /* the keysym states for all keys attached to the same modifier in
     input stage zero are kept on a linked list: */
  struct tme_keysym_state *tme_keysym_state_in0_modifier_next;

  /* this is nonzero iff the keysym is being pressed in input stage
     zero.  it's really either FALSE, or
     TME_KEYBOARD_EVENT_IN0_PRESS_USER, or
     TME_KEYBOARD_EVENT_IN0_PRESS_AUTO, which is why the last two are
     nonzero: */
  unsigned int tme_keysym_state_in0_pressed;

  /* the last time this keysym was pressed in input stage zero.  this
     is a time in milliseconds: */
  tme_uint32_t tme_keysym_state_in0_press_time;

  /* input stage one: */
  
  /* the input stage one keymode state: */
  struct tme_keymode_state tme_keysym_state_in1_keymode;

  /* input stage two: */

  /* this is nonzero iff the keysym is being released in input stage
     two: */
  unsigned int tme_keysym_state_in2_released;

  /* this is nonzero iff the keysym is being pressed in input stage
     two: */
  unsigned int tme_keysym_state_in2_pressed;

  /* output stage zero: */

  /* if non-NULL, this is the keycode that this keysym is mapped to in
     output stage zero: */
  struct tme_keycode_state *tme_keysym_state_out0_keycode;

  /* if this keysym is not attached to any modifier on the output
     stage zero, it may require that certain output side modifiers be
     set or clear in order for the keycode to mean the given keysym: */
  tme_keyboard_modifiers_t tme_keysym_state_out0_modifiers_set;
  tme_keyboard_modifiers_t tme_keysym_state_out0_modifiers_clear;

  /* iff greater than TME_KEYBOARD_MODIFIER_NONE, this is the modifier
     that this keysym is attached to in output stage zero: */
  int tme_keysym_state_out0_modifier;

  /* the keysym states for all keys attached to the same modifier in
     output stage zero are kept on a linked list: */
  struct tme_keysym_state *tme_keysym_state_out0_modifier_next;

  /* this is nonzero iff the keysym is being released in output stage
     zero: */
  unsigned int tme_keysym_state_out0_released;

  /* this is nonzero iff the keysym is being pressed in output stage
     zero: */
  unsigned int tme_keysym_state_out0_pressed;

  /* if this keysym is pressed in the output stage zero but required
     output stage zero modifier changes to be so, this is the list of
     those changes.  since most keyboards generate the same keysym
     using the same modifier(s), this list will usually be empty: */
  struct tme_keysym_state **tme_keysym_state_out0_keysyms;
  unsigned int *tme_keysym_state_out0_press_flags;

  /* output stage one: */
  
  /* this is nonzero iff the next release seen by output stage one 
     will not affect the output modifiers mask: */
  int tme_keysym_state_out1_ignore_release;
};

/* keyboard macros are necessary because it's almost certain that the
   keyboard you want to emulate has keysyms that your keyboard doesn't
   have.  one keyboard macro takes a *sequence* of one or more pressed
   keysyms to a *set* of one or more released and pressed keysyms.

   all keysym macros are kept in a single tree, where a single branch
   represents the next keysym in the sequences for one or more macros.

   a node in the macros tree is active iff the keysyms on the path
   from the root node have been pressed in sequence and remain
   pressed.  if there are any macros at all, the root node is always
   active: */
struct tme_keyboard_macro {

  /* a pointer up to our parent node, and the keysym on the branch
     from our parent to us.  for the root node, these are NULL and
     TME_KEYBOARD_KEYVAL_UNDEF, respectively: */
  struct tme_keyboard_macro *tme_keyboard_macro_parent;
  tme_keyboard_keyval_t tme_keyboard_macro_keysym;

  /* all active nodes are on a list.  the root node is always active,
     and it must be the last node on this list - making the is-active
     test for all other nodes as simple as testing this pointer
     against NULL: */
  struct tme_keyboard_macro *tme_keyboard_macro_active_next;

  /* non-leaf nodes branch out by keysym: */
  tme_hash_t tme_keyboard_macro_branches;

  /* leaf nodes contain the set of keysyms that the recognized
     sequence maps to.  in addition to presses of one or more new
     keysyms, this will normally include releases of some or all of
     the keysyms in the original sequence: */
  unsigned int tme_keyboard_macro_length;
  struct tme_keysym_state **tme_keyboard_macro_keysyms;
  unsigned int *tme_keyboard_macro_press_flags;
};

/* an internal keyboard buffer: */
struct tme_keyboard_buffer_int {

  /* the public keyboard buffer.  this must be first: */
  struct tme_keyboard_buffer tme_keyboard_buffer;
#define tme_keyboard_buffer_int_size tme_keyboard_buffer.tme_keyboard_buffer_size
#define tme_keyboard_buffer_int_head tme_keyboard_buffer.tme_keyboard_buffer_head
#define tme_keyboard_buffer_int_tail tme_keyboard_buffer.tme_keyboard_buffer_tail
#define tme_keyboard_buffer_int_events tme_keyboard_buffer.tme_keyboard_buffer_events
#define tme_keyboard_buffer_int_log_handle tme_keyboard_buffer.tme_keyboard_buffer_log_handle

  /* the keysyms state, common to all stages: */
  tme_hash_t tme_keyboard_buffer_int_keysyms_state;

  /* input stage zero: */

  /* this is nonzero iff input stage zero has modifier information,
     and it's actually the mask of modifiers that we have keysyms for: */
  unsigned int tme_keyboard_buffer_int_in0_have_modifiers;

  /* the lists of keysyms that are attached to input stage zero
     modifiers: */
  struct tme_keysym_state *tme_keyboard_buffer_int_in0_modkeys[TME_KEYBOARD_MODIFIER_MAX + 1];

  /* the current input stage zero modifiers mask: */
  tme_keyboard_modifiers_t tme_keyboard_buffer_int_in0_modifiers;

  /* the current input stage zero pressed keycodes, mapped to their
     corresponding struct tme_keysym_states: */
  tme_hash_t tme_keyboard_buffer_int_in0_keycodes;

  /* input stage one: */

  /* the input stage one keymode stage: */
  struct tme_keymode_stage tme_keyboard_buffer_int_in1_keymode_stage;

  /* input stage two: */

  /* this is NULL iff input stage two is a passthrough, else this is
     the list of active nodes in the input stage two keysym macros
     tree: */
  struct tme_keyboard_macro *tme_keyboard_buffer_int_in2_macros_active;

  /* the root of the input stage two keysym macros tree: */
  struct tme_keyboard_macro tme_keyboard_buffer_int_in2_macros_root;

  /* output stage zero: */

  /* this is nonzero iff output stage zero is just a passthrough: */
  unsigned int tme_keyboard_buffer_int_out0_passthrough;

  /* the output stage zero keycodes: */
  tme_hash_t tme_keyboard_buffer_int_out0_keycodes;

  /* this is nonzero iff the output stage zero lock modifier is to be
     treated as caps lock: */
  int tme_keyboard_buffer_int_out0_lock_is_caps;

  /* any output stage zero modifier that the Num_Lock keysym is
     attached to: */
  int tme_keyboard_buffer_int_out0_mod_num_lock;

  /* the lists of keysyms that are output stage zero modifiers: */
  struct tme_keysym_state *tme_keyboard_buffer_int_out0_modkeys[TME_KEYBOARD_MODIFIER_MAX + 1];

  /* the current output stage zero modifiers mask: */
  tme_keyboard_modifiers_t tme_keyboard_buffer_int_out0_modifiers;

  /* output stage one: */

  /* the output stage one keymode stage: */
  struct tme_keymode_stage tme_keyboard_buffer_int_out1_keymode_stage;
};

/* prototypes: */
static int _tme_keyboard_buffer_in2 _TME_P((struct tme_keyboard_buffer_int *, 
					    struct tme_keysym_state *,
					    tme_uint32_t));
static int _tme_keyboard_buffer_out1_bottom _TME_P((struct tme_keyboard_buffer_int *,
						    struct tme_keysym_state *,
						    tme_uint32_t));

/* this is for debugging only: */
#if 0
static void
_tme_keyboard_debug(const struct tme_keyboard_buffer_int *buffer,
		    const char *stage,
		    tme_keyboard_keyval_t keyval,
		    int is_press,
		    tme_uint32_t event_time)
{
  struct tme_log_handle *handle;
  const char *string;
  extern const char *_tme_gtk_keyboard_keyval_name _TME_P((tme_keyboard_keyval_t));

  handle = buffer->tme_keyboard_buffer_int_log_handle;
  if (handle == NULL) {
    return;
  }

  string = _tme_gtk_keyboard_keyval_name(keyval);
  if (string == NULL) {
    string = "???";
  }

  tme_log(handle, 100, TME_OK,
	  (handle,
	   "%s event: time %lu key %lu (%s) %s",
	   stage,
	   (unsigned long) event_time,
	   (unsigned long) keyval,
	   string,
	   (is_press
	    ? "press"
	    : "release")));
}
#else
#define _tme_keyboard_debug(b, s, k, p, t) \
  do { } while(/* CONSTCOND */ 0)	  
#endif

/* this creates a new keyboard buffer: */
struct tme_keyboard_buffer *
tme_keyboard_buffer_new(unsigned int size)
{
  struct tme_keyboard_buffer_int *buffer;
  struct tme_keymode_stage *stage;
  struct tme_keyboard_macro *root;
  int modifier;

  /* round the buffer size up to a power of two: */
  if (size & (size - 1)) {
    do {
      size &= (size - 1);
    } while (size & (size - 1));
    size <<= 1;
  }

  /* allocate the buffer: */
  buffer = tme_new0(struct tme_keyboard_buffer_int, 1);

  /* set the buffer size: */
  buffer->tme_keyboard_buffer_int_size = size;

  /* set the head and tail pointers: */
  buffer->tme_keyboard_buffer_int_head = 0;
  buffer->tme_keyboard_buffer_int_tail = 0;

  /* allocate the buffer events: */
  buffer->tme_keyboard_buffer_int_events
    = tme_new(struct tme_keyboard_event, size);

  /* for now there is no log handle: */
  buffer->tme_keyboard_buffer_int_log_handle = NULL;

  /* create the common keysyms state: */
  buffer->tme_keyboard_buffer_int_keysyms_state
    = tme_hash_new(tme_direct_hash,
		   tme_direct_compare,
		   (tme_hash_data_t) NULL);

  /* input stage zero begins with no modifiers: */
  buffer->tme_keyboard_buffer_int_in0_have_modifiers = FALSE;

  /* initialize the input stage zero modifier keys lists: */
  for (modifier = 0;
       modifier <= TME_KEYBOARD_MODIFIER_MAX;
       modifier++) {
    buffer->tme_keyboard_buffer_int_in0_modkeys[modifier] = NULL;
  }

  /* initialize the input stage zero modifiers mask: */
  buffer->tme_keyboard_buffer_int_in0_modifiers = 0;

  /* initialize the input stage one keycodes hash: */
  buffer->tme_keyboard_buffer_int_in0_keycodes
    = tme_hash_new(tme_direct_hash,
		   tme_direct_compare,
		   (tme_hash_data_t) NULL);

  /* initialize the input stage one keymode stage: */
  stage = &buffer->tme_keyboard_buffer_int_in1_keymode_stage;
  stage->tme_keymode_stage_global_mode = 0;
  stage->tme_keymode_stage_no_autorepeats = NULL;
  stage->tme_keymode_stage_next = _tme_keyboard_buffer_in2;

  /* input stage two begins with no macros, so not even the root of
     the macros tree is active: */
  buffer->tme_keyboard_buffer_int_in2_macros_active = NULL;
  
  /* create the root of the input stage two keysym macros tree: */
  root = &buffer->tme_keyboard_buffer_int_in2_macros_root;
  root->tme_keyboard_macro_parent = NULL;
  root->tme_keyboard_macro_keysym = TME_KEYBOARD_KEYVAL_UNDEF;
  root->tme_keyboard_macro_active_next = NULL;
  root->tme_keyboard_macro_branches
    = tme_hash_new(tme_direct_hash,
		   tme_direct_compare,
		   (tme_hash_data_t) NULL);

  /* output stage zero begins as a passthrough: */
  buffer->tme_keyboard_buffer_int_out0_passthrough = TRUE;

  /* initialize the output stage zero keycodes: */
  buffer->tme_keyboard_buffer_int_out0_keycodes
    = tme_hash_new(tme_direct_hash,
		   tme_direct_compare,
		   (tme_hash_data_t) NULL);

  /* the output stage zero lock modifier is assumed to be a Shift lock: */
  buffer->tme_keyboard_buffer_int_out0_lock_is_caps
    = FALSE;

  /* initialize the output stage zero modifier that the Num_Lock
     keysym is attached to: */
  buffer->tme_keyboard_buffer_int_out0_mod_num_lock
    = TME_KEYBOARD_MODIFIER_NONE;

  /* initialize the output stage zero modifier keys lists: */
  for (modifier = 0;
       modifier <= TME_KEYBOARD_MODIFIER_MAX;
       modifier++) {
    buffer->tme_keyboard_buffer_int_out0_modkeys[modifier] = NULL;
  }

  /* initialize the output stage zero modifiers mask: */
  buffer->tme_keyboard_buffer_int_out0_modifiers = 0;

  /* initialize the output stage one keymode stage: */
  stage = &buffer->tme_keyboard_buffer_int_out1_keymode_stage;
  stage->tme_keymode_stage_global_mode = 0;
  stage->tme_keymode_stage_no_autorepeats = NULL;
  stage->tme_keymode_stage_next = _tme_keyboard_buffer_out1_bottom;

  /* done: */
  return (&buffer->tme_keyboard_buffer);
}

/* this destroys an entry in the common keysyms state: */
static void
_tme_keysym_state_destroy(tme_hash_data_t __keysym,
			  tme_hash_data_t _keysym,
			  void *_junk)
{
  struct tme_keysym_state *keysym;

  /* recover the keysym state: */
  keysym = (struct tme_keysym_state *) _keysym;

  /* if this keysym state has output stage zero keysym changes, free
     them: */
  if (keysym->tme_keysym_state_out0_keysyms != NULL) {
    tme_free(keysym->tme_keysym_state_out0_keysyms);
    tme_free(keysym->tme_keysym_state_out0_press_flags);
  }

  /* free the state itself: */
  tme_free(keysym);
}

/* this recursively destroys the input stage two keysym macros tree: */
static void
_tme_keyboard_macro_destroy(tme_hash_data_t _keysym,
			    tme_hash_data_t _macro, 
			    void *_junk)
{
  struct tme_keyboard_macro *macro;

  /* get this macro: */
  macro = (struct tme_keyboard_macro *) _macro;

  /* if this is a leaf node: */
  if (macro->tme_keyboard_macro_branches == NULL) {

    /* free the keysyms and flags: */
    tme_free(macro->tme_keyboard_macro_keysyms);
    tme_free(macro->tme_keyboard_macro_press_flags);
  }

  /* otherwise, recurse: */
  else {
    tme_hash_foreach(macro->tme_keyboard_macro_branches,
		     _tme_keyboard_macro_destroy,
		     NULL);
    tme_hash_destroy(macro->tme_keyboard_macro_branches);
  }

  /* free this tree node: */
  tme_free(macro);
}

/* this destroys an entry in the output stage zero keycodes state: */
static void
_tme_keycode_state_destroy(tme_hash_data_t __keycode,
			   tme_hash_data_t _keycode,
			   void *_junk)
{
  struct tme_keycode_state *keycode;

  /* recover the keycode state: */
  keycode = (struct tme_keycode_state *) _keycode;

  /* free the state itself: */
  tme_free(keycode);
}

/* this destroys a keyboard buffer: */
void
tme_keyboard_buffer_destroy(struct tme_keyboard_buffer *_buffer)
{
  struct tme_keyboard_buffer_int *buffer;

  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* free the events: */
  tme_free(buffer->tme_keyboard_buffer_int_events);

  /* destroy the common keysyms state: */
  tme_hash_foreach(buffer->tme_keyboard_buffer_int_keysyms_state,
		   _tme_keysym_state_destroy,
		   NULL);
  tme_hash_destroy(buffer->tme_keyboard_buffer_int_keysyms_state);

  /* destroy the input stage two keysym macros tree: */
  tme_hash_foreach(buffer->tme_keyboard_buffer_int_in2_macros_root.tme_keyboard_macro_branches,
		   _tme_keyboard_macro_destroy,
		   NULL);
  tme_hash_destroy(buffer->tme_keyboard_buffer_int_in2_macros_root.tme_keyboard_macro_branches);

  /* destroy the output stage zero keycodes: */
  tme_hash_foreach(buffer->tme_keyboard_buffer_int_out0_keycodes,
		   _tme_keycode_state_destroy,
		   NULL);
  tme_hash_destroy(buffer->tme_keyboard_buffer_int_out0_keycodes);

  /* destroy the buffer itself: */
  tme_free(buffer);
}

/* this gets the state for a keysym, creating a new state if one
   doesn't exists yet: */
static struct tme_keysym_state *
_tme_keysym_state_get(struct tme_keyboard_buffer_int *buffer,
		      tme_keyboard_keyval_t _keysym)
{
  struct tme_keysym_state *keysym;

  /* look up the state for this keysym: */
  keysym
    = ((struct tme_keysym_state *)
       tme_hash_lookup(buffer->tme_keyboard_buffer_int_keysyms_state,
		       tme_keyboard_hash_data_from_keyval(_keysym)));

  /* if the state doesn't exist, allocate it: */
  if (keysym == NULL) {
    keysym = tme_new0(struct tme_keysym_state, 1);

    /* initialize all fields that might not be properly initialized as
       all-bits-zero: */
    keysym->tme_keysym_state_keysym = _keysym;
    keysym->tme_keysym_state_in0_modifier = TME_KEYBOARD_MODIFIER_NONE;
    keysym->tme_keysym_state_in1_keymode.tme_keymode_state_keysym = keysym;
    keysym->tme_keysym_state_out0_keycode = NULL;
    keysym->tme_keysym_state_out0_modifier = TME_KEYBOARD_MODIFIER_NONE;
    keysym->tme_keysym_state_out0_keysyms = NULL;
    keysym->tme_keysym_state_out0_press_flags = NULL;

    /* insert this state into the hash: */
    tme_hash_insert(buffer->tme_keyboard_buffer_int_keysyms_state,
		    tme_keyboard_hash_data_from_keyval(_keysym),
		    (tme_hash_data_t) keysym);
  }

  /* done: */
  return (keysym);
}

/* this changes the set of keysyms that are attached to an input stage
   zero modifier: */
int
tme_keyboard_buffer_in_modifier(struct tme_keyboard_buffer *_buffer,
				int modifier,
				const tme_keyboard_keyval_t *modkeys)
{
  struct tme_keyboard_buffer_int *buffer;
  struct tme_keysym_state *mod_keysym, **_mod_keysym;
  tme_keyboard_keyval_t keysym;

  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* this must be a valid modifier: */
  assert (modifier > TME_KEYBOARD_MODIFIER_NONE
	  && modifier <= TME_KEYBOARD_MODIFIER_MAX);

  /* remove all currently attached keysyms from this modifier: */
  for (mod_keysym = buffer->tme_keyboard_buffer_int_in0_modkeys[modifier];
       mod_keysym != NULL;
       mod_keysym = mod_keysym->tme_keysym_state_in0_modifier_next) {
    mod_keysym->tme_keysym_state_in0_modifier
      = TME_KEYBOARD_MODIFIER_NONE;
  }

  /* attach all of these new keysyms to this modifier: */
  _mod_keysym = &buffer->tme_keyboard_buffer_int_in0_modkeys[modifier];
  for (; (keysym = *(modkeys++)) != TME_KEYBOARD_KEYVAL_UNDEF; ) {
    mod_keysym = _tme_keysym_state_get(buffer, keysym);
    mod_keysym->tme_keysym_state_in0_modifier = modifier;
    *_mod_keysym = mod_keysym;
    _mod_keysym = &mod_keysym->tme_keysym_state_in0_modifier_next;
  }
  *_mod_keysym = NULL;

  /* input stage zero now has modifier information: */
  buffer->tme_keyboard_buffer_int_in0_have_modifiers
    |= (1 << modifier);

  return (TME_OK);
}

/* this changes a keysym's input stage one keymode: */
int
tme_keyboard_buffer_in_mode(struct tme_keyboard_buffer *_buffer,
			    tme_keyboard_keyval_t _keysym, int mode)
{
  struct tme_keyboard_buffer_int *buffer;
  struct tme_keysym_state *keysym;
  
  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* there's no such thing as a global input keymode: */
  assert (_keysym != TME_KEYBOARD_KEYVAL_UNDEF);

  /* TME_KEYBOARD_MODE_UNLOCK and TME_KEYBOARD_MODE_LOCK cannot be
     combined with any other bits: */
  if ((mode
       & (TME_KEYBOARD_MODE_UNLOCK
	  | TME_KEYBOARD_MODE_LOCK))
      && (mode
	  & (mode - 1))) {
    return (EINVAL);
  }
  
  /* none of the TME_KEYBOARD_MODE_FLAG_NO_AUTOREPEATS,
     TME_KEYBOARD_MODE_FLAG_NO_RELEASES, and
     TME_KEYBOARD_MODE_FLAG_LOCK_SOFT flags can be set
     without TME_KEYBOARD_MODE_PASSTHROUGH: */
  if ((mode
       & (TME_KEYBOARD_MODE_FLAG_NO_AUTOREPEATS
	  | TME_KEYBOARD_MODE_FLAG_NO_RELEASES
	  | TME_KEYBOARD_MODE_FLAG_LOCK_SOFT))
      && !(mode
	   & TME_KEYBOARD_MODE_PASSTHROUGH)) {
    return (EINVAL);
  }

  /* you cannot specify that an input key must not release, or
     that it soft locks: */
  if (mode
      & (TME_KEYBOARD_MODE_FLAG_NO_RELEASES
	 | TME_KEYBOARD_MODE_FLAG_LOCK_SOFT)) {
    return (EINVAL);
  }

  /* look up this keysym and set the input stage one mode: */
  keysym = _tme_keysym_state_get(buffer, _keysym);
  keysym->tme_keysym_state_in1_keymode.tme_keymode_state_mode = mode;
  return (TME_OK);
}

/* this adds an input stage two keysym macro: */
int
tme_keyboard_buffer_in_macro(struct tme_keyboard_buffer *_buffer,
			     const tme_keyboard_keyval_t *keysyms_lhs,
			     const tme_keyboard_keyval_t *keysyms_rhs)
{
  struct tme_keyboard_buffer_int *buffer;
  unsigned int count_lhs, count_rhs;
  unsigned int keysym_i, keysym_j, keysym_count;
  tme_keyboard_keyval_t keysym;
  struct tme_keysym_state **keysyms;
  unsigned int *press_flags;
  int rc;
  struct tme_keyboard_macro *macro, *macro_next;

  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* count the number of keysyms on both sides: */
  for (count_lhs = 0;
       keysyms_lhs[count_lhs] != TME_KEYBOARD_KEYVAL_UNDEF;
       count_lhs++);
  for (count_rhs = 0;
       keysyms_rhs[count_rhs] != TME_KEYBOARD_KEYVAL_UNDEF;
       count_rhs++);

  /* there must be some left-hand side and some right-hand side: */
  if (count_lhs == 0
      || count_rhs == 0) {
    return (EINVAL);
  }

  /* create the final keysyms and press-flags arrays for the macro.  any
     keysym on the left hand side that is also on the right hand side
     becomes a press, else a release, and any keysym on the right hand
     side that isn't on the left hand side becomes a press: */
  keysyms = tme_new(struct tme_keysym_state *, count_lhs + count_rhs);
  press_flags = tme_new(unsigned int, count_lhs + count_rhs);
  keysym_count = 0;
  for (keysym_i = 0; 
       keysym_i < count_lhs; 
       keysym_i++) {
    keysym = keysyms_lhs[keysym_i];
  
    /* see if this keysym is on the right hand side: */
    for (keysym_j = 0;
	 keysym_j < count_rhs;
	 keysym_j++) {
      if (keysym == keysyms_rhs[keysym_j]) {
	break;
      }
    }

    /* set this keysym and press-flags: */
    keysyms[keysym_count] = _tme_keysym_state_get(buffer, keysym);
    press_flags[keysym_count] = (keysym_j < count_rhs);
    keysym_count++;
  }
  for (keysym_j = 0;
       keysym_j < count_rhs;
       keysym_j++) {
    keysym = keysyms_rhs[keysym_j];

    /* see if this keysym is on the left hand side: */
    for (keysym_i = 0;
	 keysym_i < count_lhs;
	 keysym_i++) {
      if (keysym == keysyms_lhs[keysym_i]) {
	break;
      }
    }

    /* set this keysym and press-flags: */
    if (keysym_i == count_lhs) {
      keysyms[keysym_count] = _tme_keysym_state_get(buffer, keysym);
      press_flags[keysym_count] = TRUE;
      keysym_count++;
    }
  }
  
  /* the last keysym in any macro's right hand side must be a press: */
  if (!press_flags[keysym_count - 1]) {
    tme_free(keysyms);
    tme_free(press_flags);
    return (EINVAL);
  }
  
  /* add this keysym macro to the macros tree.  this macro's sequence
     (i.e., its left-hand side) cannot be strictly longer, or strictly
     shorter, or the same as any existing macro's sequence: */
  macro = &buffer->tme_keyboard_buffer_int_in2_macros_root;
  rc = TME_OK;
  for (keysym_i = 0;
       ;
       keysym_i++) {

    /* if we handled all left-hand side keysyms: */
    if (keysym_i == count_lhs) {
      
      /* if this node is already a non-leaf node, then this macro's
	 sequence is strictly shorter than an existing macro's
	 sequence: */
      if (macro->tme_keyboard_macro_branches != NULL) {
	rc = EEXIST;
      }

      /* otherwise, if this node is already a leaf node, then this
	 macro's sequence is the same as an existing macro's sequence: */
      else if (macro->tme_keyboard_macro_length > 0) {
	rc = EEXIST;
      }

      /* stop no matter what: */
      break;
    }

    /* if this node has no branch set: */
    if (macro->tme_keyboard_macro_branches == NULL) {

      /* if this node is already a leaf node, then this macro's
	 sequence is strictly longer than an existing macro's
	 sequence: */
      if (macro->tme_keyboard_macro_length > 0) {
	rc = EEXIST;
	break;
      }

      /* otherwise, create a branch set for this node: */
      macro->tme_keyboard_macro_branches
	= tme_hash_new(tme_direct_hash,
		       tme_direct_compare,
		       (tme_hash_data_t) NULL);
    }
      
    /* get the keysym: */
    keysym = keysyms_lhs[keysym_i];

    /* look up this keysym in the branch set for this node: */
    macro_next
      = ((struct tme_keyboard_macro *)
	 tme_hash_lookup(macro->tme_keyboard_macro_branches,
			 tme_keyboard_hash_data_from_keyval(keysym)));

    /* if this keysym is a new branch, create a new macros tree node: */
    if (macro_next == NULL) {
      macro_next = tme_new0(struct tme_keyboard_macro, 1);
      macro_next->tme_keyboard_macro_parent = macro;
      macro_next->tme_keyboard_macro_keysym = keysym;
      tme_hash_insert(macro->tme_keyboard_macro_branches, 
		      tme_keyboard_hash_data_from_keyval(keysym),
		      (tme_hash_data_t) macro_next);
    }

    /* advance in the tree: */
    macro = macro_next;
  }

  /* if this sequence couldn't be added to the sequences tree: */
  if (rc != TME_OK) {
    tme_free(keysyms);
    tme_free(press_flags);
    return (rc);
  }

  /* finish this leaf node in the macros tree: */
  macro->tme_keyboard_macro_length = keysym_count;
  macro->tme_keyboard_macro_keysyms = keysyms;
  macro->tme_keyboard_macro_press_flags = press_flags;

  /* if this is the first keysym macro added, set the root of the
     keysym macros tree as active, making input stage two no longer a
     passthrough: */
  if (buffer->tme_keyboard_buffer_int_in2_macros_active
      == NULL) {
    buffer->tme_keyboard_buffer_int_in2_macros_active
      = &buffer->tme_keyboard_buffer_int_in2_macros_root;
  }

  return (TME_OK);
}

/* this adds a single output stage zero keysym map entry: */
int
tme_keyboard_buffer_out_map(struct tme_keyboard_buffer *_buffer,
			    _tme_const struct tme_keyboard_map *map)
{
  struct tme_keyboard_buffer_int *buffer;
  struct tme_keysym_state *keysym;
  struct tme_keycode_state *keycode;
  struct tme_keymode_state *keymode;
  int modifier;
  tme_keyboard_modifiers_t modifiers_set, modifiers_clear;
  
  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* the keysym must be defined: */
  assert (map->tme_keyboard_map_keysym
	  != TME_KEYBOARD_KEYVAL_UNDEF);

  /* get the state for this keysym: */
  keysym = _tme_keysym_state_get(buffer, map->tme_keyboard_map_keysym);

  /* this keysym must not already have an output side keycode: */
  if (keysym->tme_keysym_state_out0_keycode != NULL) {
    return (EEXIST);
  }

  /* lookup this keycode: */
  keycode
    = ((struct tme_keycode_state *)
       tme_hash_lookup(buffer->tme_keyboard_buffer_int_out0_keycodes,
		       tme_keyboard_hash_data_from_keyval(map->tme_keyboard_map_keycode)));

  /* if this keycode is new, allocate, initialize and add a structure
     for it: */
  if (keycode == NULL) {

    /* allocate the keycode state: */
    keycode = tme_new0(struct tme_keycode_state, 1);

    /* initialize any parts of the keycode state that might not be
       properly initialized as all-bits-zero.  note that the keysym
       stored in the keycode keymode state is the first keysym mapped
       to the keycode: */
    keycode->tme_keycode_state_keycode = map->tme_keyboard_map_keycode;
    keymode = &keycode->tme_keycode_state_keymode;
    keymode->tme_keymode_state_keysym = keysym;

    /* add the keycode structure: */
    tme_hash_insert(buffer->tme_keyboard_buffer_int_out0_keycodes,
		    tme_keyboard_hash_data_from_keyval(map->tme_keyboard_map_keycode),
		    (tme_hash_data_t) keycode);
  }

  /* set this keycode on this keysym: */
  keysym->tme_keysym_state_out0_keycode = keycode;
     
  /* if this keysym is attached to an output side modifier: */
  modifier = map->tme_keyboard_map_modifier;
  if (modifier != TME_KEYBOARD_MODIFIER_NONE) {

    /* attach this keysym to the output side modifier: */
    keysym->tme_keysym_state_out0_modifier
      = modifier;
    keysym->tme_keysym_state_out0_modifier_next
      = buffer->tme_keyboard_buffer_int_out0_modkeys[modifier];
    buffer->tme_keyboard_buffer_int_out0_modkeys[modifier]
      = keysym;

    /* dispatch on any special keysym note: */
    switch (map->tme_keyboard_map_keysym_note) {
    default: assert(FALSE);
    case TME_KEYBOARD_KEYSYM_NOTE_UNDEF:
      break;
    case TME_KEYBOARD_KEYSYM_NOTE_CAPS_LOCK:
      if (modifier == TME_KEYBOARD_MODIFIER_LOCK) {
	buffer->tme_keyboard_buffer_int_out0_lock_is_caps = TRUE;
      }
      break;
    case TME_KEYBOARD_KEYSYM_NOTE_SHIFT_LOCK:
      break;
    case TME_KEYBOARD_KEYSYM_NOTE_NUM_LOCK:
      buffer->tme_keyboard_buffer_int_out0_mod_num_lock = modifier;
      break;
    }

    /* this keysym cannot require any output side modifiers to be set
       or clear: */
    assert (map->tme_keyboard_map_modifiers_set == 0
	    && map->tme_keyboard_map_modifiers_clear == 0);
  }

  /* remember the output stage zero modifiers that must be set or
     clear for this mapping to work: */
  modifiers_set = map->tme_keyboard_map_modifiers_set;
  modifiers_clear = map->tme_keyboard_map_modifiers_clear;
  assert ((modifiers_set & modifiers_clear) == 0);

  /* if this keysym is lowercase, it also requires the shift modifier
     to be clear: */
  if (modifiers_clear
      & (1 << TME_KEYBOARD_MODIFIER_LOCK)) {
    modifiers_clear
      |= (1 << TME_KEYBOARD_MODIFIER_SHIFT);
  }

  keysym->tme_keysym_state_out0_modifiers_set = modifiers_set;
  keysym->tme_keysym_state_out0_modifiers_clear = modifiers_clear;

  /* output stage zero is no longer a passthrough: */
  buffer->tme_keyboard_buffer_int_out0_passthrough = FALSE;

  return (TME_OK);
}

/* this changes a keycode's output stage one mode: */
int
tme_keyboard_buffer_out_mode(struct tme_keyboard_buffer *_buffer,
			     tme_keyboard_keyval_t _keycode, int mode)
{
  struct tme_keyboard_buffer_int *buffer;
  struct tme_keycode_state *keycode;
  
  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* TME_KEYBOARD_MODE_UNLOCK and TME_KEYBOARD_MODE_LOCK cannot be
     combined with any other bits: */
  if ((mode
       & (TME_KEYBOARD_MODE_UNLOCK
	  | TME_KEYBOARD_MODE_LOCK))
      && (mode
	  & (mode - 1))) {
    return (EINVAL);
  }
  
  /* none of the TME_KEYBOARD_MODE_FLAG_NO_AUTOREPEATS,
     TME_KEYBOARD_MODE_FLAG_NO_RELEASES, and
     TME_KEYBOARD_MODE_FLAG_LOCK_SOFT flags can be set
     without TME_KEYBOARD_MODE_PASSTHROUGH: */
  if ((mode
       & (TME_KEYBOARD_MODE_FLAG_NO_AUTOREPEATS
	  | TME_KEYBOARD_MODE_FLAG_NO_RELEASES
	  | TME_KEYBOARD_MODE_FLAG_LOCK_SOFT))
      && !(mode
	   & TME_KEYBOARD_MODE_PASSTHROUGH)) {
    return (EINVAL);
  }

  /* you cannot specify that an output key must be unlocked: */
  if (mode & TME_KEYBOARD_MODE_UNLOCK) {
    return (EINVAL);
  }

  /* if we are setting the mode on a particular keycode: */
  if (_keycode != TME_KEYBOARD_KEYVAL_UNDEF) {
    keycode
      = ((struct tme_keycode_state *)
	 tme_hash_lookup(buffer->tme_keyboard_buffer_int_out0_keycodes,
			 tme_keyboard_hash_data_from_keyval(_keycode)));
    if (keycode == NULL) {
      return (ENOENT);
    }
    keycode->tme_keycode_state_keymode.tme_keymode_state_mode = mode;
  }

  /* otherwise, we are setting the mode on the whole keyboard: */
  else {

    /* you can't set TME_KEYBOARD_MODE_GLOBAL at the global level: */
    if (mode == TME_KEYBOARD_MODE_GLOBAL) {
      return (EINVAL);
    }

    /* set the output stage one global mode: */
    buffer->tme_keyboard_buffer_int_out1_keymode_stage
      .tme_keymode_stage_global_mode = mode;
  }

  return (TME_OK);
}

/* this fixes the keyboard's output stage zero modifiers when they get
   out of sync with the emulated software reading the output keyboard: */
tme_keyboard_modifiers_t
tme_keyboard_buffer_out_modifiers(struct tme_keyboard_buffer *_buffer,
				  tme_keyboard_modifiers_t modifiers_clear,
				  tme_keyboard_modifiers_t modifiers_set)
{
  struct tme_keyboard_buffer_int *buffer;
  
  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* update the modifiers: */
  return (buffer->tme_keyboard_buffer_int_out0_modifiers
	  = ((buffer->tme_keyboard_buffer_int_out0_modifiers
	      & ~modifiers_clear)
	     | modifiers_set));
}

/* this parses a single keysym macro: */
int
tme_keyboard_parse_macro(const char *string,
			 tme_keyboard_keysym_lookup_t keysym_lookup,
			 void *keysym_lookup_private,
			 tme_keyboard_keyval_t **_keysyms_lhs,
			 tme_keyboard_keyval_t **_keysyms_rhs)
{
  char **tokens;
  int tokens_count, equals_token;
  int token_i;
  tme_keyboard_keyval_t keysym, *keysyms_lhs, *keysyms_rhs;
  struct tme_keyboard_lookup lookup;
  unsigned int count_lhs, count_rhs;
  int rc;

  /* tokenize this line: */
  tokens = tme_misc_tokenize(string, '#', &tokens_count);
  keysyms_lhs = tme_new(tme_keyboard_keyval_t, tokens_count);
  keysyms_rhs = tme_new(tme_keyboard_keyval_t, tokens_count);
  count_lhs = 0;
  count_rhs = 0;

  /* start the lookup structure: */
  lookup.tme_keyboard_lookup_context_length = 0;
  lookup.tme_keyboard_lookup_context = NULL;

  /* all of the tokens must be valid keysyms, except for a single
     mandatory "=" token, which must not be the first or last token: */
  equals_token = -1;
  rc = TME_OK;
  for (token_i = 0;
       token_i < tokens_count;
       token_i++) {
    
    /* check for an "=" token: */
    if (!strcmp(tokens[token_i], "=")) {
      if (equals_token >= 0
	  || token_i == 0
	  || token_i + 1 == tokens_count) {
	rc = EINVAL;
	break;
      }
      equals_token = token_i;
      continue;
    }

    /* a token on the left hand side must be a keysym that the 
       caller can generate directly: */
    if (equals_token < 0) {

      /* get the keysym for this token: */
      lookup.tme_keyboard_lookup_string = tokens[token_i];
      lookup.tme_keyboard_lookup_flags = TME_KEYBOARD_LOOKUP_FLAG_OK_DIRECT;
      keysym = (*keysym_lookup)(keysym_lookup_private, &lookup);
      if (keysym == TME_KEYBOARD_KEYVAL_UNDEF) {
	rc = ENOENT;
	break;
      }
      keysyms_lhs[count_lhs++] = keysym;
    }

    /* otherwise, a token on the right hand side is either a keysym
       that the caller can generate directly, or the caller must
       be able to allocate a unique value for it: */
    else {

      /* get the keysym for this token: */
      lookup.tme_keyboard_lookup_string = tokens[token_i];
      lookup.tme_keyboard_lookup_flags = (TME_KEYBOARD_LOOKUP_FLAG_OK_DIRECT
					  | TME_KEYBOARD_LOOKUP_FLAG_OK_ALLOC
					  | TME_KEYBOARD_LOOKUP_FLAG_OK_ALLOC_NOW);
      keysym = (*keysym_lookup)(keysym_lookup_private, &lookup);
      assert (keysym != TME_KEYBOARD_KEYVAL_UNDEF);
      keysyms_rhs[count_rhs++] = keysym;
    }
  }

  /* if this macro didn't parse correctly: */
  if (rc != TME_OK) {
    tme_free_string_array(tokens, -1);
    tme_free(keysyms_lhs);
    tme_free(keysyms_rhs);
    return (rc);
  }

  /* finish the sides of the macro: */
  keysyms_lhs[count_lhs] = TME_KEYBOARD_KEYVAL_UNDEF;
  keysyms_rhs[count_rhs] = TME_KEYBOARD_KEYVAL_UNDEF;

  /* done: */
  *_keysyms_lhs = keysyms_lhs;
  *_keysyms_rhs = keysyms_rhs;
  tme_free_string_array(tokens, -1);
  return (TME_OK);
}

/* this parses a single keysym map entry: */
int
tme_keyboard_parse_map(const char *string,
		       tme_keyboard_keysym_lookup_t keysym_lookup,
		       void *keysym_lookup_private,
		       struct tme_keyboard_map *map)
{
  char **tokens, *p1, c;
  int tokens_count;
  int token_i;
  tme_keyboard_keyval_t keycode;
  int modifier, attached_modifier;
  tme_keyboard_modifiers_t modifiers_set, modifiers_clear;
  struct tme_keyboard_lookup lookup;
  int rc;

  /* tokenize this line: */
  tokens = tme_misc_tokenize(string, '#', &tokens_count);
  rc = TME_OK;

  /* there must be at least three tokens.  the second token must be an
     equals sign, and the third token must be an integer that isn't
     TME_KEYBOARD_KEYVAL_UNDEF: */
  if (tokens_count < 3
      || strcmp(tokens[1], "=")
      || ((keycode = strtoul(tokens[2], &p1, 0))
	  == TME_KEYBOARD_KEYVAL_UNDEF)
      || p1 == tokens[2]
      || *p1 != '\0') {
    rc = EINVAL;
  }
  
  /* any tokens after the third must all be modifier names: */  
  else {
    attached_modifier = TME_KEYBOARD_MODIFIER_NONE;
    modifiers_set = 0;
    modifiers_clear = 0;
    for (token_i = 3;
	 token_i < tokens_count;
	 token_i++) {

      /* a token might be prefixed with '+' or '!': */
      p1 = tokens[token_i];
      c = *p1;
      if (c == '+'
	  || c == '!') {
	p1++;
      }

      /* turn this token into a real modifier: */
      if (!strcmp(p1, "shift")) {
	modifier = TME_KEYBOARD_MODIFIER_SHIFT;
      }
      else if (!strcmp(p1, "lock")) {
	modifier = TME_KEYBOARD_MODIFIER_LOCK;
      }
      else if (!strcmp(p1, "control")) {
	modifier = TME_KEYBOARD_MODIFIER_CONTROL;
      }
      else if (!strcmp(p1, "mod1")) {
	modifier = TME_KEYBOARD_MODIFIER_MOD1;
      }
      else if (!strcmp(p1, "mod2")) {
	modifier = TME_KEYBOARD_MODIFIER_MOD2;
      }
      else if (!strcmp(p1, "mod3")) {
	modifier = TME_KEYBOARD_MODIFIER_MOD3;
      }
      else if (!strcmp(p1, "mod4")) {
	modifier = TME_KEYBOARD_MODIFIER_MOD4;
      }
      else if (!strcmp(p1, "mod5")) {
	modifier = TME_KEYBOARD_MODIFIER_MOD5;
      }
      else {
	rc = EINVAL;
	break;
      }
    
      /* if a modifier is prefixed with '+', it must be the only
	 modifier token in the map entry, and it indicates that the
	 keycode is attached to that modifier in this map: */
      if (c == '+') {

	/* if this is not the only modifier token in the map entry: */
	if (tokens_count != 4) {
	  rc = EINVAL;
	  break;
	}

	attached_modifier = modifier;
      }
      
      /* otherwise, if a modifier name is prefixed with '!', this
	 modifier must be clear for this keycode to mean this keysym: */
      else if (c == '!') {
	modifiers_clear |= (1 << modifier);
      }

      /* otherwise, this modifier must be set for this keycode to mean
	 this keysym: */
      else {
	modifiers_set |= (1 << modifier);
      }
    }

    /* the modifiers that must be set and the modifiers that must be
       clear cannot overlap: */
    if (modifiers_set & modifiers_clear) {
      rc = EINVAL;
    }

    /* if no error has been encountered yet: */
    if (rc == TME_OK) {

      /* make the keyboard map entry.  we very deliberately set all
	 bytes not allocated to structure members to all-bits-zero, so
	 that identical keysym contexts truly are identical: */
      memset(map, 0, sizeof(*map));
      map->tme_keyboard_map_keycode = keycode;
      map->tme_keyboard_map_modifier = attached_modifier;
      map->tme_keyboard_map_modifiers_set = modifiers_set;
      map->tme_keyboard_map_modifiers_clear = modifiers_clear;

      /* take note of a special keysym: */
      if (!strcmp(tokens[0], "Caps_Lock")) {
	map->tme_keyboard_map_keysym_note
	  = TME_KEYBOARD_KEYSYM_NOTE_CAPS_LOCK;
      }
      else if (!strcmp(tokens[0], "Shift_Lock")) {
	map->tme_keyboard_map_keysym_note
	  = TME_KEYBOARD_KEYSYM_NOTE_SHIFT_LOCK;
      }
      else if (!strcmp(tokens[0], "Num_Lock")) {
	map->tme_keyboard_map_keysym_note
	  = TME_KEYBOARD_KEYSYM_NOTE_NUM_LOCK;
      }
      else {
	map->tme_keyboard_map_keysym_note
	  = TME_KEYBOARD_KEYSYM_NOTE_UNDEF;
      }

      /* the caller must be able to either directly generate this
	 keysym or have allocated a value for it already (because a
	 macro was previously added than can generate it).  if neither
	 is true, the lookup function must return
	 TME_KEYBOARD_KEYVAL_UNDEF, but this function still does not
	 fail.  it is up to the caller to not add a map entry with an
	 undefined keysym: */
      lookup.tme_keyboard_lookup_string
	= tokens[0];
      lookup.tme_keyboard_lookup_flags
	= (TME_KEYBOARD_LOOKUP_FLAG_OK_DIRECT
	   | TME_KEYBOARD_LOOKUP_FLAG_OK_ALLOC);
      lookup.tme_keyboard_lookup_context_length
	= sizeof(*map);
      lookup.tme_keyboard_lookup_context
	= (tme_uint8_t *) map;
      map->tme_keyboard_map_keysym
	= (*keysym_lookup)(keysym_lookup_private, &lookup);
    }
  }

  /* free the tokens: */
  tme_free_string_array(tokens, -1);

  /* return the parsed map: */
  return (rc);
}

/* this adds an event to the event buffer: */
static int
_tme_keyboard_buffer_copyin(struct tme_keyboard_buffer_int *buffer,
			    _tme_const struct tme_keyboard_event *event)
{
  unsigned int buffer_head, buffer_size_mask;

  buffer_head = buffer->tme_keyboard_buffer_int_head;
  buffer_size_mask = buffer->tme_keyboard_buffer_int_size - 1;

  /* if the buffer is full: */
  if (((buffer_head + 1) & buffer_size_mask)
      == buffer->tme_keyboard_buffer_int_tail) {
    return (EAGAIN);
  }

  /* put this event into the buffer: */
  buffer->tme_keyboard_buffer_int_events[buffer_head]
    = *event;

  /* advance the head: */
  buffer->tme_keyboard_buffer_int_head
    = (buffer_head + 1) & buffer_size_mask;

  return (TME_OK);
}

/* this adds a difference to an event time, avoiding a result of
   TME_KEYBOARD_EVENT_TIME_UNDEF: */
static tme_uint32_t
_tme_keyboard_event_time_diff(tme_uint32_t event_time,
			      tme_int32_t diff)
{
  event_time += (tme_uint32_t) diff;
  if (event_time == TME_KEYBOARD_EVENT_TIME_UNDEF) {
    assert (diff != 0);
    event_time += (diff < 0 ? -1 : 1);
  }
  return (event_time);
}

/* this subtracts event_time1 from event_time0, handling the wrapping
   of the tme_uint32_t milliseconds values as best as possible: */
static tme_int32_t
_tme_keyboard_event_time_subtract(tme_uint32_t event_time0,
				  tme_uint32_t event_time1)
{
  tme_uint32_t event_time0_less_least;
  tme_uint32_t event_time_diff_unwrapped;
  tme_uint32_t event_time_diff_wrapped;
  
  /* if the two times are equal: */
  if (event_time0 == event_time1) {
    return (0);
  }

  /* calculate the unwrapped and wrapped differences between the
     two times: */
  if (event_time0 < event_time1) {
    event_time_diff_unwrapped = event_time1 - event_time0;
    event_time_diff_wrapped = 0 - event_time_diff_unwrapped;
  }
  else {
    event_time_diff_unwrapped = event_time0 - event_time1;
    event_time_diff_wrapped = 0 - event_time_diff_unwrapped;
  }

  /* calculate the event time that is the most in the past without
     being confused as being later than event_time0: */
  event_time0_less_least
    = (event_time0 - (((tme_uint32_t) -1) >> 1));

  /* if event_time0_less_least is literally greater than event_time0,
     the event time space that represents "less than event_time0" is
     not a single region in the tme_uint32_t space: */
  if (event_time0_less_least < event_time0) {
    return ((event_time0_less_least <= event_time1
	     && event_time1 < event_time0)
	    /* event_time1 is less than event_time0: */
	    ? event_time_diff_unwrapped
	    /* event_time1 is greater than event_time0: */
	    : (event_time1 > event_time0
	       ? 0 - event_time_diff_unwrapped
	       : 0 - event_time_diff_wrapped));
  }

  /* otherwise, the event time space that represents "less than
     event_time0" is two regions in the tme_uint32_t space: */
  else {
    return ((event_time0_less_least <= event_time1
	     || event_time1 < event_time0)
	    /* event_time1 is less than event_time0: */
	    ? (event_time1 < event_time0
	       ? event_time_diff_unwrapped
	       : event_time_diff_wrapped)
	    /* event_time1 is greater than event_time0: */
	    : 0 - event_time_diff_unwrapped);
  }
}

/* this runs a keymode stage.  this is used for input stage one,
   and output stage one.  roughly, a keymode stage tries to
   guarantee that a key has a specific press/release behavior
   (or "mode") for later stages.  the particular modes are described
   in the case below: */
static int
_tme_keymode_stage(struct tme_keyboard_buffer_int *buffer,
		   struct tme_keymode_stage *stage,
		   struct tme_keymode_state *keymode,
		   int is_press,
		   tme_uint32_t event_time)
{
  struct tme_keymode_state **_auto_keymode, *auto_keymode;
  int pressed_old, mode;
  int rc;

  /* check all keys on the no-autorepeats list: */
  for (_auto_keymode = &stage->tme_keymode_stage_no_autorepeats;
       (auto_keymode = *_auto_keymode) != NULL; ) {

    /* if the time of the last release from the earlier stages is
       undefined, that means that the last event from the earlier
       stages was a press: */
    if (auto_keymode->tme_keymode_state_last_release
	== TME_KEYBOARD_EVENT_TIME_UNDEF) {

      /* if the key we're checking happens to be the key that the
	 earlier stages are calling us for: */
      if (auto_keymode == keymode) {

	/* this must be a release on this key: */
	assert (!is_press);

	/* set the release time on this key: */
	auto_keymode->tme_keymode_state_last_release = event_time;

	/* we've taken care of this call: */
	keymode = NULL;
      }
    }

    /* otherwise, the last event from the earlier stages was a release.
       if enough time has elapsed since then without a press from
       earlier stages: */
    else if (_tme_keyboard_event_time_subtract(event_time,
					       auto_keymode->tme_keymode_state_last_release)
	     > TME_KEYBOARD_SHORTEST_DOUBLE_MSEC) {

      /* now we're sure that the key has genuinely released.
	 remove this key from the autorepeats list: */
      *_auto_keymode = auto_keymode->tme_keymode_state_next;
      auto_keymode->tme_keymode_state_next = NULL;

      /* if we're supposed to ignore the genuine release: */
      if (auto_keymode->tme_keymode_state_ignore_release) {
	auto_keymode->tme_keymode_state_ignore_release = FALSE;
      }

      /* otherwise, we're not supposed to ignore the genuine release: */
      else {

	/* flip the pressed state of the key: */
	auto_keymode->tme_keymode_state_pressed
	  = !auto_keymode->tme_keymode_state_pressed;

	/* if this key is now pressed, or if we're allowed to
	   pass releases, run the next stage: */
	mode = auto_keymode->tme_keymode_state_mode;
	if (mode == TME_KEYBOARD_MODE_GLOBAL) {
	  mode = stage->tme_keymode_stage_global_mode;
	}
	if (auto_keymode->tme_keymode_state_pressed
	    || !(mode
		 & TME_KEYBOARD_MODE_FLAG_NO_RELEASES)) {
	  rc = (*stage->tme_keymode_stage_next)
	    (buffer,
	     auto_keymode->tme_keymode_state_keysym,
	     _tme_keyboard_event_time_diff(event_time, -1));
	  assert (rc == TME_OK);
	}
      }

      /* continue now - by removing this key from the autorepeats
	 list we've already advanced our position in the list for
	 the next iteration: */
      continue;
    }

    /* otherwise, if the key we're checking happens to be the key that
       the earlier stages are calling us for: */
    else if (auto_keymode == keymode) {

      /* this must be a press on this key: */
      assert (is_press);

      /* now we're sure that this key is autorepeating.  clear
	 the last-release time: */
      auto_keymode->tme_keymode_state_last_release
	= TME_KEYBOARD_EVENT_TIME_UNDEF;

      /* we've taken care of this call: */
      keymode = NULL;
    }
      
    /* continue: */
    _auto_keymode = &auto_keymode->tme_keymode_state_next;
  }
      
  /* return now if we already finished processing this event: */
  if (keymode == NULL) {
    return (TME_OK);
  }

  /* get this key's mode: */
  mode = keymode->tme_keymode_state_mode;
  if (mode == TME_KEYBOARD_MODE_GLOBAL) {
    mode = stage->tme_keymode_stage_global_mode;
  }
  
  /* remember if this key was pressed in this stage before: */
  pressed_old = keymode->tme_keymode_state_pressed;

  /* unlock mode unlocks a key by turning every transition into a
     press transition and a release transition.  think of a Caps Lock
     key on an input keyboard that *physically* locks down when you
     press it down - this is a nice property, because there's no
     mistaking that when you get a key press event, you know the key
     is both physically and *semantically* down until you get a key
     release, at which time you know just the opposite.
     
     however, this is a best case.  many keyboards have Caps Lock, Num
     Lock, and other keys where their semantic pressed/release state
     doesn't match their physical pressed/release state.  to make
     everything uniform, we need to bring the best-case keyboards down
     to the worst-case level: */
  if (mode == TME_KEYBOARD_MODE_UNLOCK) {

    /* we must not have this key as pressed: */
    assert (!pressed_old);

    /* press this key and run the next stage: */
    keymode->tme_keymode_state_pressed = TRUE;
    rc = (*stage->tme_keymode_stage_next)
      (buffer,
       keymode->tme_keymode_state_keysym,
       _tme_keyboard_event_time_diff(event_time, -1));
    assert (rc == TME_OK);
    
    /* release this key, and make sure the next stage
       gets run at the end of this function: */
    keymode->tme_keymode_state_pressed = FALSE;
    pressed_old = TRUE;
  }

  /* lock mode makes the key lock in the physical sense, like the
     best-case Caps Lock described above.  since our events from
     earlier stages are worst-case, we need to ignore autorepeats, and
     each press/release pair must toggle our notion of whether or not
     the key is physically pressed: */
  else if (mode == TME_KEYBOARD_MODE_LOCK) {

    /* this must be a press: */
    assert (is_press);

    /* put this key on the no-autorepeats list: */
    keymode->tme_keymode_state_last_release
      = TME_KEYBOARD_EVENT_TIME_UNDEF;
    keymode->tme_keymode_state_next
      = stage->tme_keymode_stage_no_autorepeats;
    stage->tme_keymode_stage_no_autorepeats = keymode;
    
    /* if we have the key as pressed (locked): */
    if (pressed_old) {
      
      /* don't ignore the genuine release, when it is determined to
	 have happened.  if this mode has
	 TME_KEYBOARD_MODE_FLAG_NO_RELEASES set, the genuine release
	 code will handle it then: */
      keymode->tme_keymode_state_ignore_release = FALSE;
    }
    
    /* otherwise, we have the key as released (unlocked): */
    else {
      
      /* press this key: */
      keymode->tme_keymode_state_pressed = TRUE;
      
      /* ignore the genuine release, when it is determined
	 to have happened: */
      keymode->tme_keymode_state_ignore_release = TRUE;
    }
  }

  /* passthrough mode generally passes events through: */
  else {

    assert (!pressed_old != !is_press);

    /* if this is a press: */
    if (is_press) {

      /* press this key: */
      keymode->tme_keymode_state_pressed = TRUE;

      /* if we must not pass through autorepeats: */
      if (mode & TME_KEYBOARD_MODE_FLAG_NO_AUTOREPEATS) {

	/* put this key on the no-autorepeats list: */
	keymode->tme_keymode_state_last_release
	  = TME_KEYBOARD_EVENT_TIME_UNDEF;
	keymode->tme_keymode_state_next
	  = stage->tme_keymode_stage_no_autorepeats;
	stage->tme_keymode_stage_no_autorepeats = keymode;

	/* don't ignore the genuine release, when it is
	   determined to have happened.  if this mode has
	   TME_KEYBOARD_MODE_FLAG_NO_RELEASES set, the 
	   genuine release code will handle it then: */
	keymode->tme_keymode_state_ignore_release = FALSE;
      }
    }

    /* otherwise, this is a release: */
    else {

      /* release this key: */
      keymode->tme_keymode_state_pressed = FALSE;

      /* if this mode has TME_KEYBOARD_MODE_FLAG_NO_RELEASES set,
	 we'll handle it below when we go to run the next stage: */
    }
  }

  /* stop processing this event now if the key's pressed state
     in this stage has not changed, or if the key is now released
     and we're not allowed to pass releases.  otherwise, run the
     next stage: */
  return ((keymode->tme_keymode_state_pressed
	   ? pressed_old
	   : (!pressed_old
	      || (mode
		  & TME_KEYBOARD_MODE_FLAG_NO_RELEASES)))
	  ? TME_OK
	  : ((*stage->tme_keymode_stage_next)
	     (buffer,
	      keymode->tme_keymode_state_keysym,
	      event_time)));
}

/* this is the bottom half of output stage one.  this half does
   nothing except finally buffer an event for a keycode, and update
   the output modifiers mask: */
static int
_tme_keyboard_buffer_out1_bottom(struct tme_keyboard_buffer_int *buffer,
				 struct tme_keysym_state *keysym,
				 tme_uint32_t event_time)
{
  struct tme_keycode_state *keycode;
  struct tme_keyboard_event event_buffer;
  int modifier;
  int is_press;

  /* get the keycode: */
  keycode = keysym->tme_keysym_state_out0_keycode;

  /* get whether or not this is a press: */
  is_press = TME_KEYBOARD_PRESSED_OUT1(keycode);

  _tme_keyboard_debug(buffer,
		      "out1-bottom",
		      keysym->tme_keysym_state_keysym,
		      is_press,
		      event_time);

  /* if this keysym is attached to a modifier, update the output
     modifiers mask: */
  /* XXX there might be a cleaner way to work the output modifier
     mask.  it's suspicious that stages zero and one need to share the
     modifiers mask and the keysym to modifier mapping: */
  modifier = keysym->tme_keysym_state_out0_modifier;
  if (modifier != TME_KEYBOARD_MODIFIER_NONE) {

    /* if this is a press: */
    if (is_press) {

      /* if the modifier is currently clear: */
      if (!(buffer->tme_keyboard_buffer_int_out0_modifiers
	    & (1 << modifier))) {

	/* set the modifier: */
	buffer->tme_keyboard_buffer_int_out0_modifiers
	  |= (1 << modifier);

	/* iff this keysym soft-locks, the next release will not
	   affect the output modifiers mask: */
	keysym->tme_keysym_state_out1_ignore_release
	  = (keycode->tme_keycode_state_keymode.tme_keymode_state_mode
	     & TME_KEYBOARD_MODE_FLAG_LOCK_SOFT);
      }

      /* otherwise, the modifier is currently set: */
      else {
	
	/* nothing to do.  even if this keysym soft-locks, we won't
	   clear the modifier until the release: */
      }
    }

    /* otherwise, this is a release: */
    else {

      /* if we're supposed to ignore this release: */
      if (keysym->tme_keysym_state_out1_ignore_release) {
	keysym->tme_keysym_state_out1_ignore_release = FALSE;
      }

      /* otherwise, if the modifier is currently set: */
      else if (buffer->tme_keyboard_buffer_int_out0_modifiers
	       & (1 << modifier)) {

	/* clear the modifier: */
	buffer->tme_keyboard_buffer_int_out0_modifiers
	  &= ~(1 << modifier);
      }

      /* otherwise, the modifier is currently clear: */
      else {

	/* nothing to do.  a release would never set a modifier: */
      }
    }
  }

  /* finally buffer this keycode: */
  event_buffer.tme_keyboard_event_type
    = (is_press
       ? TME_KEYBOARD_EVENT_PRESS
       : TME_KEYBOARD_EVENT_RELEASE);
  event_buffer.tme_keyboard_event_keyval
    = keycode->tme_keycode_state_keycode;
  event_buffer.tme_keyboard_event_keycode = 0;
  event_buffer.tme_keyboard_event_time
    = event_time;
  event_buffer.tme_keyboard_event_modifiers
    = buffer->tme_keyboard_buffer_int_out0_modifiers;
  return (_tme_keyboard_buffer_copyin(buffer, &event_buffer));
}

/* this is the top half of output stage one.  it is the last output
   stage, that gives each keycode the specific behavior that it must
   have on the output keyboard: */
static int
_tme_keyboard_buffer_out1(struct tme_keyboard_buffer_int *buffer,
			  struct tme_keysym_state *keysym,
			  tme_uint32_t event_time)
{
  struct tme_keycode_state *keycode;

  _tme_keyboard_debug(buffer,
		      "out1-top",
		      keysym->tme_keysym_state_keysym,
		      TME_KEYBOARD_PRESSED_OUT0(keysym),
		      event_time);

  /* get the keycode state: */
  keycode = keysym->tme_keysym_state_out0_keycode;

  /* run the keymode stage function: */
  return (_tme_keymode_stage(buffer,
			     &buffer->tme_keyboard_buffer_int_out1_keymode_stage,
			     &keycode->tme_keycode_state_keymode,
			     TME_KEYBOARD_PRESSED_OUT0(keysym),
			     event_time));
}

/* this is output stage zero.  at this point, we have the final
   keysyms from the input keyboard, and this stage maps those keysyms
   to keycodes on the output keyboard.  

   sometimes, the input keyboard may generate keysyms for which the
   output keyboard requires that certain modifiers be set or clear, at
   a time when the output modifiers mask isn't suitable.  for example,
   this can happen when the input keyboard can generate a certain
   keysym without shifting, when the output keyboard requires
   shifting.

   when this happens, this stage is responsible for simulating presses
   or releases of modifiers on the output keyboard as needed to make
   sure that the keysym is properly obtained when the mapped keycode
   is pressed, and for undoing those changes when the mapped keycode
   is released: */
static int
_tme_keyboard_buffer_out0(struct tme_keyboard_buffer_int *buffer,
			  struct tme_keysym_state *keysym,
			  tme_uint32_t event_time)
{
  struct tme_keyboard_event event_buffer;
  tme_keyboard_modifiers_t modifiers, modifiers_set, modifiers_clear;
  int num_lock_on;
  int is_press, mod_pressed_old, modifier;
  struct tme_keysym_state *mod_keysym, **keysyms;
  unsigned int *press_flags;
  int keysym_count;
  int modifier_stuck;
  int rc;

  _tme_keyboard_debug(buffer,
		      "out0",
		      keysym->tme_keysym_state_keysym,
		      TME_KEYBOARD_PRESSED_IN2(keysym),
		      event_time);

  /* get whether or not this is a press from earlier stages: */
  is_press = TME_KEYBOARD_PRESSED_IN2(keysym);

  /* if output stage zero is a passthrough, just buffer an event for
     the keysym and we're done processing this event: */
  if (buffer->tme_keyboard_buffer_int_out0_passthrough) {

    /* otherwise, simply buffer this event: */
    event_buffer.tme_keyboard_event_type
      = (is_press
	 ? TME_KEYBOARD_EVENT_PRESS
	 : TME_KEYBOARD_EVENT_RELEASE);
    event_buffer.tme_keyboard_event_keyval
      = keysym->tme_keysym_state_keysym;
    event_buffer.tme_keyboard_event_keycode = 0;
    event_buffer.tme_keyboard_event_time
      = event_time;
    event_buffer.tme_keyboard_event_modifiers
      = 0;
    return (_tme_keyboard_buffer_copyin(buffer, &event_buffer));
  }

  /* if this keysym is not mapped to a keycode on the output keyboard,
     we don't have to process this event any more: */
  if (keysym->tme_keysym_state_out0_keycode == NULL) {
    return (TME_OK);
  }
  
  /* if this is a press from earlier stages: */
  if (is_press) {

    /* this keysym can't have any changes attached to it already: */
    assert (keysym->tme_keysym_state_out0_keysyms == NULL);
    keysyms = NULL;
    press_flags = NULL;
    keysym_count = 0;

    /* get the current output stage zero modifiers: */
    modifiers = buffer->tme_keyboard_buffer_int_out0_modifiers;

    /* get the sets of modifiers that must be clear and set for this
       keysym: */
    modifiers_clear = keysym->tme_keysym_state_out0_modifiers_clear;
    modifiers_set = keysym->tme_keysym_state_out0_modifiers_set;
    assert ((modifiers_clear & modifiers_set) == 0);
    assert ((modifiers_clear | modifiers_set) == 0
	    || (keysym->tme_keysym_state_out0_modifier
		== TME_KEYBOARD_MODIFIER_NONE));

    /* if this keysym is uppercase, but the current modifiers are such
       that a lowercase keysym *might* be generated: */
    if ((modifiers_set & (1 << TME_KEYBOARD_MODIFIER_LOCK))
	&& !(modifiers & (1 << TME_KEYBOARD_MODIFIER_LOCK))) {

      /* if shift is down, we must be generating uppercase keysyms, so
	 forget about the lock modifier in this instance: */
      if (modifiers & (1 << TME_KEYBOARD_MODIFIER_SHIFT)) {
	modifiers_set &= ~(1 << TME_KEYBOARD_MODIFIER_LOCK);
      }

      /* otherwise, neither shift nor lock are down, so we are
	 generating lowercase keysyms.  since this keyboard might not
	 have a lock modifier at all (and since it seems more
	 reasonable to do it this way anyways), require the shift
	 modifier to be set instead of the lock modifier in this
	 instance: */
      else {
	modifiers_set &= ~(1 << TME_KEYBOARD_MODIFIER_LOCK);
	modifiers_set |= (1 << TME_KEYBOARD_MODIFIER_SHIFT);
      }
    }
      
    /* if this output keyboard has a Num_Lock key, and this keysym is
       sensitive to the Num_Lock setting, but the appropriate Num_Lock
       setting is already in effect, forget about Num_Lock for this
       instance: */
    modifier = buffer->tme_keyboard_buffer_int_out0_mod_num_lock;
    if (modifier != TME_KEYBOARD_MODIFIER_NONE
	&& ((modifiers_clear
	     | modifiers_set)
	    & (1 << modifier))) {

      /* determine what Num_Lock setting is currently in effect.  it
	 is active if Num_Lock is pressed and there is no shifting
	 active, or if Num_Lock is released and there is shifting
	 active.  NB that if the lock modifier is attached only to a
	 Shift_Lock, that modifier also counts as shifting: */
      num_lock_on
	= (

	   /* this term is 0 iff Num_Lock is pressed, else 1: */
	   !(modifiers
	     & (1 << modifier))

	   !=

	   /* this term is 0 iff shifting is active, else 1: */
	   !(modifiers
	     & ((1 << TME_KEYBOARD_MODIFIER_SHIFT)
		| (buffer->tme_keyboard_buffer_int_out0_lock_is_caps
		   ? 0
		   : (1 << TME_KEYBOARD_MODIFIER_LOCK)))));

      /* if this keysym requires Num_Lock to be clear, but it is
	 not in effect, forget about Num_Lock for this instance: */
      if ((modifiers_clear & (1 << modifier))
	  && !num_lock_on) {
	modifiers_clear &= ~(1 << modifier);
      }

      /* if this keysym requires Num_Lock to be set, but it is
	 in effect, forget about Num_Lock for this instance: */
      if ((modifiers_set & (1 << modifier))
	  && num_lock_on) {
	modifiers_set &= ~(1 << modifier);
      }
    }

    /* finish the set of modifiers that must be clear and set for this
       keysym, but aren't: */
    modifiers_clear &= modifiers;
    modifiers_set &= ~modifiers;

    /* try to generate release events for all keysyms attached to
       output modifiers that are set, but that need to be clear: */
    if (modifiers_clear != 0) {
      
      /* loop over the modifiers that need clearing: */
      for (modifier = 0;
	   modifier <= TME_KEYBOARD_MODIFIER_MAX;
	   modifier++) {
	if (!(modifiers_clear & (1 << modifier))) {
	  continue;
	}
	
	/* try to release all of the keysyms attached to this modifier
	   that are pressed: */
	modifier_stuck = FALSE;
	for (mod_keysym = buffer->tme_keyboard_buffer_int_out0_modkeys[modifier];
	     mod_keysym != NULL;
	     mod_keysym = mod_keysym->tme_keysym_state_out0_modifier_next) {
	
	  /* XXX this is broken for modifiers that soft-lock. */

	  /* ignore this keysym if it isn't pressed: */
	  if (!TME_KEYBOARD_PRESSED_OUT0(mod_keysym)) {
	    continue;
	  }

	  /* grow the keysyms and press flags arrays.  the keysyms
	     array always has one extra entry, which will be filled
	     with NULL to terminate the array: */
	  if (keysym_count == 0) {
	    keysyms = tme_new(struct tme_keysym_state *, 2);
	    press_flags = tme_new(unsigned int, 1);
	  }
	  else {
	    keysyms = tme_renew(struct tme_keysym_state *, keysyms, keysym_count + 2);
	    press_flags = tme_renew(unsigned int, press_flags, keysym_count + 1);
	  }
	  
	  /* add this keysym to those arrays: */
	  keysyms[keysym_count] = mod_keysym;
	  press_flags[keysym_count] = FALSE;
	  keysym_count++;

	  /* release this modifier key.  if this actually
	     releases the key, run the next stage: */
	  mod_keysym->tme_keysym_state_out0_released++;
	  if (!TME_KEYBOARD_PRESSED_OUT0(mod_keysym)) {
	    rc = _tme_keyboard_buffer_out1(buffer,
					   mod_keysym,
					   _tme_keyboard_event_time_diff(event_time, -1));
	    assert (rc == TME_OK);
	  }

	  /* otherwise, this modifier is stuck on: */
	  else {
	    modifier_stuck = TRUE;
	  }
	}

	/* if we were able to release all keysyms attached to this
	   modifier, clear the modifier in the output keyboard mask: */
	if (!modifier_stuck) {
	  buffer->tme_keyboard_buffer_int_out0_modifiers
	    &= ~(1 << modifier);
	}
      }
    }

    /* try to generate press events for single keysyms attached to
       output modifiers that are clear, but that need to be set: */
    if (modifiers_set != 0) {
      
      /* loop over the modifiers that need setting: */
      for (modifier = 0;
	   modifier <= TME_KEYBOARD_MODIFIER_MAX;
	   modifier++) {
	if (!(modifiers_set & (1 << modifier))) {
	  continue;
	}
	
	/* try to press a single keysym attached to this modifier: */
	mod_keysym = buffer->tme_keyboard_buffer_int_out0_modkeys[modifier];
	assert (mod_keysym != NULL);
	
	/* this keysym can't pressed - if it is, the modifier
	   should be set: */
	assert (!TME_KEYBOARD_PRESSED_OUT0(mod_keysym));

	/* grow the keysyms and press flags arrays.  the keysyms
	   array always has one extra entry, which will be filled
	   with NULL to terminate the array: */
	if (keysym_count == 0) {
	  keysyms = tme_new(struct tme_keysym_state *, 2);
	  press_flags = tme_new(unsigned int, 1);
	}
	else {
	  keysyms = tme_renew(struct tme_keysym_state *, keysyms, keysym_count + 2);
	  press_flags = tme_renew(unsigned int, press_flags, keysym_count + 1);
	}
	  
	/* add this keysym to those arrays: */
	keysyms[keysym_count] = mod_keysym;
	press_flags[keysym_count] = TRUE;
	keysym_count++;

	/* press this modifier key.  this must actually press the key,
	   so run the next stage: */
	mod_keysym->tme_keysym_state_out0_pressed++;
	assert (TME_KEYBOARD_PRESSED_OUT0(mod_keysym));
	rc = _tme_keyboard_buffer_out1(buffer,
				       mod_keysym,
				       _tme_keyboard_event_time_diff(event_time, -1));
	assert (rc == TME_OK);

	/* set the modifier: */
	buffer->tme_keyboard_buffer_int_out0_modifiers
	  |= (1 << modifier);
      }
    }

    /* remember any changes we made: */
    if (keysyms != NULL) {
      keysyms[keysym_count] = NULL;
    }
    keysym->tme_keysym_state_out0_keysyms = keysyms;
    keysym->tme_keysym_state_out0_press_flags = press_flags;

    /* run the next stage: */
    return (_tme_keyboard_buffer_out1(buffer,
				      keysym,
				      event_time));
  }

  /* otherwise, this is a release: */
  else {
  
    /* run the next stage: */
    rc = _tme_keyboard_buffer_out1(buffer,
				   keysym,
				   event_time);
    assert (rc == TME_OK);

    /* if this keysym tried to make any modifier changes, undo them: */
    keysyms = keysym->tme_keysym_state_out0_keysyms;
    press_flags = keysym->tme_keysym_state_out0_press_flags;
    if (keysyms != NULL) {

      /* loop over all of the modifiers we changed: */
      for (; (mod_keysym = *(keysyms++)) != NULL; ) {

	/* see if this modifier is pressed now: */
	mod_pressed_old = TME_KEYBOARD_PRESSED_OUT0(mod_keysym);

	/* undo the change we made to this modifier: */
	if (*(press_flags++)) {
	  mod_keysym->tme_keysym_state_out0_pressed--;
	}
	else {
	  mod_keysym->tme_keysym_state_out0_released--;
	}

	/* if the state of this modifier has changed, run the next
	   stage: */
	if (TME_KEYBOARD_PRESSED_OUT0(mod_keysym) != mod_pressed_old) {
	  rc = _tme_keyboard_buffer_out1(buffer,
					 mod_keysym,
					 _tme_keyboard_event_time_diff(event_time, +1));
	  assert (rc == TME_OK);
	}
      }

      /* forget these modifier changes: */
      tme_free(keysym->tme_keysym_state_out0_keysyms);
      tme_free(keysym->tme_keysym_state_out0_press_flags);
      keysym->tme_keysym_state_out0_keysyms = NULL;
      keysym->tme_keysym_state_out0_press_flags = NULL;
    }
  }
      
  /* success: */
  return (TME_OK);
}

/* this is input stage two.  at this point, we have the input keyboard
   cleaned up to the point where we can have macros for generating
   keysyms that the input keyboard can't generate on its own: */
static int
_tme_keyboard_buffer_in2(struct tme_keyboard_buffer_int *buffer,
			 struct tme_keysym_state *keysym,
			 tme_uint32_t event_time)
{
  tme_keyboard_keyval_t _keysym;
  struct tme_keyboard_macro **_macro, *macro, *child, *parent;
  int is_press, pressed_old, sub_pressed_old;
  int update, keysym_i;
  struct tme_keysym_state *sub_keysym;
  int rc;

  _tme_keyboard_debug(buffer,
		      "in2",
		      keysym->tme_keysym_state_keysym,
		      TME_KEYBOARD_PRESSED_IN1(keysym),
		      event_time);

  /* get the keysym: */
  _keysym = keysym->tme_keysym_state_keysym;

  /* get whether or not this is a press from earlier stages: */
  is_press = TME_KEYBOARD_PRESSED_IN1(keysym);

  /* remember if this keysym was pressed in this stage before: */
  pressed_old = _TME_KEYBOARD_PRESSED_IN2(keysym, !is_press);

  /* visit all active macros tree nodes: */
  for (_macro = &buffer->tme_keyboard_buffer_int_in2_macros_active;
       (macro = *_macro) != NULL; ) {

    /* if this is a press, see if it advances this macro's sequence: */
    if (is_press) {

      /* ignore this macros tree node if it's a leaf, or if this
	 keysym is not a branch out of this node to some child node: */
      if (macro->tme_keyboard_macro_branches == NULL
	  || (child
	      = ((struct tme_keyboard_macro *)
		 tme_hash_lookup(macro->tme_keyboard_macro_branches,
				 tme_keyboard_hash_data_from_keyval(_keysym)))) == NULL) {
	_macro = &macro->tme_keyboard_macro_active_next;
	continue;
      }
	
      /* the child node cannot already be active.  if it is, that
	 means that earlier stages didn't give us a release for this
	 keysym: */
      assert (child->tme_keyboard_macro_active_next == NULL);
      
      /* make the child node active: */
      child->tme_keyboard_macro_active_next = macro;
      *_macro = child;
      _macro = &macro->tme_keyboard_macro_active_next;
      macro = child;
    }

    /* otherwise, this is a release, so see if this release cancels
       any sequence: */
    else {

      /* ignore this macros tree node if this is not a release of a
	 keysym somewhere on the path from the root to this node: */
      if (_keysym != macro->tme_keyboard_macro_keysym) {
	for (parent = macro->tme_keyboard_macro_parent;
	     parent != NULL;
	     parent = parent->tme_keyboard_macro_parent) {
	  if (_keysym == parent->tme_keyboard_macro_keysym) {
	    break;
	  }
	}
	if (parent == NULL) {
	  _macro = &macro->tme_keyboard_macro_active_next;
	  continue;
	}
      }
      
      /* make this macros tree node no longer active: */
      *_macro = macro->tme_keyboard_macro_active_next;
      macro->tme_keyboard_macro_active_next = NULL;
    }
    
    /* if this macro is not a leaf node, continue: */
    if (macro->tme_keyboard_macro_branches != NULL) {
      continue;
    }

    /* otherwise, this event has either activated or deactivated a
       macro.  either do or undo this macro's presses and releases: */
    update = (is_press ? 1 : -1);
    for (keysym_i = macro->tme_keyboard_macro_length;
	 keysym_i-- > 0; ) {

      /* get this keysym's state: */
      sub_keysym = macro->tme_keyboard_macro_keysyms[keysym_i];

      /* remember if this keysym was pressed in this stage before: */
      sub_pressed_old = TME_KEYBOARD_PRESSED_IN2(sub_keysym);

      /* update this keysym's state: */
      if (macro->tme_keyboard_macro_press_flags[keysym_i]) {
	sub_keysym->tme_keysym_state_in2_pressed += update;
      }
      else {
	sub_keysym->tme_keysym_state_in2_released += update;
      }

      /* if this keysym's pressed state in this stage has changed, 
	 run the next stage, unless this is the keysym that we
	 were called with, in which case we'll handle this later: */
      if (sub_keysym != keysym
	  && TME_KEYBOARD_PRESSED_IN2(sub_keysym) != sub_pressed_old) {
	rc = _tme_keyboard_buffer_out0(buffer, sub_keysym, event_time);
	assert (rc == TME_OK);
      }
    }
  }

  /* if this keysym's pressed state in this stage has changed, run
     the next stage, otherwise stop processing this event now: */
  return ((TME_KEYBOARD_PRESSED_IN2(keysym) != pressed_old)
	  ? _tme_keyboard_buffer_out0(buffer, keysym, event_time)
	  : TME_OK);
}

/* this is input stage one.  at this point, the input keyboard is
   slightly cleaned up - consecutive presses have had a release
   inserted in between, releases of unpressed keys have been dropped,
   and as many inferred lost events as possible have been generated.

   this stage finishes cleaning up the input keyboard to the point
   where macros are useful.  usually, the only keysyms that will have
   specific behaviors enforced here will be keysyms that are used like
   modifiers in input stage two macros, but that don't behave like
   modifiers on the input keyboard (i.e., they autorepeat) and so
   aren't good for use in multiple-key macros: */
static int
_tme_keyboard_buffer_in1(struct tme_keyboard_buffer_int *buffer,
			 struct tme_keysym_state *keysym,
			 tme_uint32_t event_time)
{

  _tme_keyboard_debug(buffer,
		      "in1",
		      keysym->tme_keysym_state_keysym,
		      TME_KEYBOARD_PRESSED_IN0(keysym),
		      event_time);

  /* run the keymode stage function: */
  return (_tme_keymode_stage(buffer,
			     &buffer->tme_keyboard_buffer_int_in1_keymode_stage,
			     &keysym->tme_keysym_state_in1_keymode,
			     TME_KEYBOARD_PRESSED_IN0(keysym),
			     event_time));
}

/* this is the bottom half of input stage zero.  at this point, events
   inferred by modifier changes have been generated, but otherwise the
   input keyboard hasn't been cleaned up.

   this half drops releases of keys that we don't think are pressed,
   generates a release in between two consecutive presses, and tracks
   the input modifier mask: */
static int
_tme_keyboard_buffer_in0_bottom(struct tme_keyboard_buffer_int *buffer,
				struct tme_keysym_state *keysym,
				const struct tme_keyboard_event *event)
{
  struct tme_keysym_state *mod_keysym;
  struct tme_keyboard_event event_pseudo;
  int modifier, pressed_old;
  int rc;

  _tme_keyboard_debug(buffer,
		      "in0-bottom",
		      keysym->tme_keysym_state_keysym,
		      (event->tme_keyboard_event_type
		       & TME_KEYBOARD_EVENT_IN0_PRESS_USER),
		      event->tme_keyboard_event_time);

  /* NB: event->tme_keyboard_event_modifiers is always the modifiers
     *mask from immediately before* the event, i.e., if this event is
     *for a modifier keysym, it does not reflect any change the press
     *or release of this keysym will cause: */

  /* remember if this keysym was pressed in this stage before: */
  pressed_old = TME_KEYBOARD_PRESSED_IN0(keysym);

  /* see if this keysym is attached to any known modifier: */
  modifier = (buffer->tme_keyboard_buffer_int_in0_have_modifiers
	      ? keysym->tme_keysym_state_in0_modifier
	      : TME_KEYBOARD_MODIFIER_NONE);

  /* dispatch on the event type: */
  switch (event->tme_keyboard_event_type) {
    
    /* an automatic input stage zero press: */
  case TME_KEYBOARD_EVENT_IN0_PRESS_AUTO:
    /* this keysym must not be pressed at all: */
    assert (!pressed_old);
    /* FALLTHROUGH */
    
    /* a user input stage zero press: */
  case TME_KEYBOARD_EVENT_IN0_PRESS_USER:

    /* if this keysym was already pressed, inject a release first -
       you can't press a key twice without releasing it in between: */
    if (pressed_old) {

      /* make the pseudoevent.  assume that the release we dropped
	 happened as soon as humanly possible after the press, but
	 never after this new press: */
      event_pseudo.tme_keyboard_event_type
	= TME_KEYBOARD_IN0_RELEASE_EVENT(keysym->tme_keysym_state_in0_pressed);
      event_pseudo.tme_keyboard_event_time
	= _tme_keyboard_event_time_diff(keysym->tme_keysym_state_in0_press_time,
					TME_KEYBOARD_SHORTEST_DOUBLE_MSEC);
      if (_tme_keyboard_event_time_subtract(event_pseudo.tme_keyboard_event_time,
					    event->tme_keyboard_event_time) <= 0) {
	event_pseudo.tme_keyboard_event_time
	  = _tme_keyboard_event_time_diff(event->tme_keyboard_event_time, -1);
      }
      event_pseudo.tme_keyboard_event_modifiers
	= buffer->tme_keyboard_buffer_int_in0_modifiers;

      /* recurse with this pseudoevent: */
      rc = _tme_keyboard_buffer_in0_bottom(buffer,
					   keysym,
					   &event_pseudo);
      assert (rc == TME_OK);
    }
    
    /* this keysym is now pressed: */
    keysym->tme_keysym_state_in0_pressed = event->tme_keyboard_event_type;
    keysym->tme_keysym_state_in0_press_time = event->tme_keyboard_event_time;

    /* set any modifier in the mask: */
    assert (buffer->tme_keyboard_buffer_int_in0_modifiers
	    == event->tme_keyboard_event_modifiers);
    if (modifier != TME_KEYBOARD_MODIFIER_NONE) {
      buffer->tme_keyboard_buffer_int_in0_modifiers |= (1 << modifier);
    }
    break;
	
    /* an automatic input stage zero release :*/
  case TME_KEYBOARD_EVENT_IN0_RELEASE_AUTO:
    assert (pressed_old == TME_KEYBOARD_EVENT_IN0_PRESS_AUTO);
    /* FALLTHROUGH */

    /* a user input stage zero release: */
  case TME_KEYBOARD_EVENT_IN0_RELEASE_USER:
      
    /* if this keysym wasn't already pressed, stop processing this
       event: */
    if (!pressed_old) {
      return (TME_OK);
    }

    /* this keysym is no longer pressed: */
    keysym->tme_keysym_state_in0_pressed = FALSE;

    /* if this keysym is attached to a modifier: */
    if (modifier != TME_KEYBOARD_MODIFIER_NONE) {

      /* if we can find no keysym attached to this modifier that is
	 still pressed, clear the modifier in the mask: */
      for (mod_keysym = buffer->tme_keyboard_buffer_int_in0_modkeys[modifier];
	   mod_keysym != NULL;
	   mod_keysym = mod_keysym->tme_keysym_state_in0_modifier_next) {
	if (mod_keysym->tme_keysym_state_in0_pressed) {
	  break;
	}
      }
      if (mod_keysym == NULL) {
	buffer->tme_keyboard_buffer_int_in0_modifiers &= ~(1 << modifier);
      }
    }
    break;
      
  default:
    abort();
  }

  /* run the next stage: */
  return (_tme_keyboard_buffer_in1(buffer,
				   keysym,
				   event->tme_keyboard_event_time));
}

/* this is the top half of input stage zero.  this gets raw
   events from the input keyboard.  if we get a modifiers mask with
   each event, and we know which input keyboard keysyms are attached
   to which modifiers, we can often infer presses and releases of
   these keysyms when our caller has missed those events.  

   For example, this works well under X11 when the Caps_Lock or
   Num_Lock key "locks" and its state changes while our window doesn't
   have focus.  When we do regain focus, we don't get press or release
   events for those changes, but we can get the current modifiers
   mask: */
static int
_tme_keyboard_buffer_in0(struct tme_keyboard_buffer_int *buffer,
			 const struct tme_keyboard_event *event)
{
  struct tme_keysym_state *keysym, *mod_keysym, *other_keysym;
  struct tme_keyboard_event event_pseudo;
  int modifier;
  tme_keyboard_modifiers_t modifiers, modifiers_set, modifiers_clear;
  int rc;

  _tme_keyboard_debug(buffer,
		      "in0-top",
		      event->tme_keyboard_event_keyval,
		      (event->tme_keyboard_event_type
		       == TME_KEYBOARD_EVENT_PRESS),
		      event->tme_keyboard_event_time);

  assert (event->tme_keyboard_event_time
	  != TME_KEYBOARD_EVENT_TIME_UNDEF);

  /* look up this keysym: */
  keysym
    = ((struct tme_keysym_state *)
       tme_hash_lookup(buffer->tme_keyboard_buffer_int_keysyms_state,
		       tme_keyboard_hash_data_from_keyval(event->tme_keyboard_event_keyval)));


  /* if input stage zero has modifier information: */
  if (buffer->tme_keyboard_buffer_int_in0_have_modifiers) {

    /* NB: event->tme_keyboard_event_modifiers is always the modifiers
       mask from immediately *before* the event, i.e., if this event
       is for a modifier keysym, it does not reflect any change this
       press or release of this keysym will cause to that mask: */
    modifiers = event->tme_keyboard_event_modifiers;

    /* get the mask of modifiers that been cleared and set unbeknownst
       to us before this event: */
    modifiers_clear =
      (buffer->tme_keyboard_buffer_int_in0_modifiers
       & ~modifiers
       & buffer->tme_keyboard_buffer_int_in0_have_modifiers);
    modifiers_set =
      (modifiers
       & ~buffer->tme_keyboard_buffer_int_in0_modifiers
       & buffer->tme_keyboard_buffer_int_in0_have_modifiers);

    /* generate the appropriate release events for all keysyms
       attached to modifiers that have been cleared: */
    if (modifiers_clear != 0) {

      /* loop over the modifiers that need clearing: */
      for (modifier = 0;
	   modifier <= TME_KEYBOARD_MODIFIER_MAX;
	   modifier++) {
	if (!(modifiers_clear & (1 << modifier))) {
	  continue;
	}

	/* release all of the keysyms: */
	for (mod_keysym = buffer->tme_keyboard_buffer_int_in0_modkeys[modifier];
	     mod_keysym != NULL;
	     mod_keysym = mod_keysym->tme_keysym_state_in0_modifier_next) {

	  /* ignore this keysym if it isn't pressed: */
	  if (!mod_keysym->tme_keysym_state_in0_pressed) {
	    continue;
	  }

	  /* make the pseudoevent: */
	  event_pseudo.tme_keyboard_event_type
	    = TME_KEYBOARD_IN0_RELEASE_EVENT(mod_keysym->tme_keysym_state_in0_pressed);
	  event_pseudo.tme_keyboard_event_time
	    = _tme_keyboard_event_time_diff(event->tme_keyboard_event_time, -1);
	  event_pseudo.tme_keyboard_event_modifiers
	    = buffer->tme_keyboard_buffer_int_in0_modifiers;

	  /* call the bottom half with this pseudoevent: */
	  rc = _tme_keyboard_buffer_in0_bottom(buffer,
					       mod_keysym,
					       &event_pseudo);
	  assert (rc == TME_OK);
	}
      }

      /* all of the modifiers that needed clearing must now be clear: */
      assert ((buffer->tme_keyboard_buffer_int_in0_modifiers
	       & modifiers_clear) == 0);
    }

    /* generate a press event for a single keysym attached to each
       modifier that has been set: */
    if (modifiers_set != 0) {

      /* loop over the modifiers that need setting: */
      for (modifier = 0;
	   modifier <= TME_KEYBOARD_MODIFIER_MAX;
	   modifier++) {
	if (!(modifiers_set & (1 << modifier))) {
	  continue;
	}

	/* press the first keysym attached to this modifier: */
	mod_keysym = buffer->tme_keyboard_buffer_int_in0_modkeys[modifier];
	assert (mod_keysym != NULL);
	
	/* make the pseudoevent: */
	event_pseudo.tme_keyboard_event_type
	  = TME_KEYBOARD_EVENT_IN0_PRESS_AUTO;
	event_pseudo.tme_keyboard_event_time
	  = _tme_keyboard_event_time_diff(event->tme_keyboard_event_time, -1);
	event_pseudo.tme_keyboard_event_modifiers
	  = buffer->tme_keyboard_buffer_int_in0_modifiers;

	/* call the bottom half with this pseudoevent: */
	rc = _tme_keyboard_buffer_in0_bottom(buffer,
					     mod_keysym,
					     &event_pseudo);
	assert (rc == TME_OK);
      }

      /* all of the modifiers that needed setting must now be set: */
      assert ((buffer->tme_keyboard_buffer_int_in0_modifiers
	       & modifiers_set) == modifiers_set);
    }

    /* if the keysym in the event is undefined, we don't need to
       process this event any more.  events with an undefined keysym
       can be used whenever the caller thinks that it has dropped
       keyboard events, and wants to do what it can to update the
       keyboard, and it can at least get the true current modifiers
       mask.  this is common under X11: */
    if (event->tme_keyboard_event_keyval
	== TME_KEYBOARD_KEYVAL_UNDEF) {
      return (TME_OK);
    }
  }

  /* we must have a defined keysym: */
  assert (event->tme_keyboard_event_keyval
	  != TME_KEYBOARD_KEYVAL_UNDEF);

  /* if we have no state for the keysym in this event, this keysym
     isn't controlled by any of the input stages: */
  if (keysym == NULL) {

    /* if there are output stages, having no state for this keysym
       means that this keysym doesn't exist on the output keyboard.
       we can stop processing this event now: */
    if (!buffer->tme_keyboard_buffer_int_out0_passthrough) {
      return (TME_OK);
    }

    /* otherwise, simply buffer this event: */
    event_pseudo = *event;
    switch (event->tme_keyboard_event_type) {
    case TME_KEYBOARD_EVENT_IN0_PRESS_USER:
    case TME_KEYBOARD_EVENT_IN0_PRESS_AUTO:
      event_pseudo.tme_keyboard_event_type = TME_KEYBOARD_EVENT_PRESS;
      break;
    case TME_KEYBOARD_EVENT_IN0_RELEASE_USER:
    case TME_KEYBOARD_EVENT_IN0_RELEASE_AUTO:
      event_pseudo.tme_keyboard_event_type = TME_KEYBOARD_EVENT_RELEASE;
      break;
    default: abort();
    }
    event_pseudo.tme_keyboard_event_modifiers = 0;
    return (_tme_keyboard_buffer_copyin(buffer, &event_pseudo));
  }

  /* if this event has a keycode: */
  if (event->tme_keyboard_event_keycode
      != TME_KEYBOARD_KEYVAL_UNDEF) {

    /* see if this keycode is already pressed in this stage: */
    other_keysym
      = ((struct tme_keysym_state *)
	 tme_hash_lookup(buffer->tme_keyboard_buffer_int_in0_keycodes,
			 tme_keyboard_hash_data_from_keyval(event->tme_keyboard_event_keycode)));

    /* if this keycode is already pressed by another keysym in this
       stage, release the other keysym first: */
    if (other_keysym != NULL
	&& other_keysym != keysym
	&& TME_KEYBOARD_PRESSED_IN0(other_keysym)) {
      
      /* make the pseudoevent: */
      event_pseudo.tme_keyboard_event_type
	= TME_KEYBOARD_IN0_RELEASE_EVENT(other_keysym->tme_keysym_state_in0_pressed);
      event_pseudo.tme_keyboard_event_time
	= _tme_keyboard_event_time_diff(event->tme_keyboard_event_time, -1);
      event_pseudo.tme_keyboard_event_modifiers
	= buffer->tme_keyboard_buffer_int_in0_modifiers;
      
      /* call the bottom half with this pseudoevent: */
      rc = _tme_keyboard_buffer_in0_bottom(buffer,
					   other_keysym,
					   &event_pseudo);
      assert (rc == TME_OK);
    }

    /* if this is a press, remember that this keycode is pressed,
       else forget that this keycode is pressed: */
    if (event->tme_keyboard_event_type == TME_KEYBOARD_EVENT_PRESS) {
      tme_hash_insert(buffer->tme_keyboard_buffer_int_in0_keycodes,
		      tme_keyboard_hash_data_from_keyval(event->tme_keyboard_event_keycode),
		      (tme_hash_data_t) keysym);
    }
    else {
      tme_hash_remove(buffer->tme_keyboard_buffer_int_in0_keycodes,
		      tme_keyboard_hash_data_from_keyval(event->tme_keyboard_event_keycode));
    }
  }

  /* call the bottom half with this event: */
  return (_tme_keyboard_buffer_in0_bottom(buffer,
					  keysym,
					  event));
}

/* this copies a keyboard event into the buffer: */
int
tme_keyboard_buffer_copyin(struct tme_keyboard_buffer *_buffer,
			   const struct tme_keyboard_event *event)
{
  struct tme_keyboard_buffer_int *buffer;

  /* recover our data structure: */
  buffer = (struct tme_keyboard_buffer_int *) _buffer;

  /* run input stage zero: */
  return (_tme_keyboard_buffer_in0(buffer, event));
}

/* this copies a keyval out of a keyboard buffer: */
int
tme_keyboard_buffer_copyout(struct tme_keyboard_buffer *buffer,
			    struct tme_keyboard_event *event)
{
  unsigned int buffer_tail, buffer_size_mask;

  buffer_tail = buffer->tme_keyboard_buffer_tail;
  buffer_size_mask = buffer->tme_keyboard_buffer_size - 1;

  /* if the buffer is empty: */
  if (buffer_tail == buffer->tme_keyboard_buffer_head) {
    return (EAGAIN);
  }

  /* get an event out of the buffer: */
  *event = buffer->tme_keyboard_buffer_events[buffer_tail];

  /* advance the tail: */
  buffer->tme_keyboard_buffer_tail = (buffer_tail + 1) & buffer_size_mask;
  return (TME_OK);
}

unix.superglobalmegacorp.com