/*{{{}}}*/
/*{{{  #includes*/
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define KEYTAB_C

#include "keybind.h"
#include <h/rcformat.h>
#include <h/envvar_str.h>
#include <lib/ori_rc_lib.h>
#include <lib/ori_add_lib.h>
/*}}}  */

/*{{{  overload prefix handling*/
typedef struct overload_entry
 { char *name;
   int len;
   struct overload_entry *next;
 } overload_entry;
private overload_entry *overloads=0;

/*{{{  overload_prefix*/
public void overload_prefix(void)
{
  begin_parse();
  for (;;)
   { switch (get_full_token())
      { case END: break;
        /*{{{  NAME: a new mode*/
        case NAME:
         { overload_entry *x;
           /*{{{  check, if conflicting*/
           { int l;
             for (x=overloads,l=ustrlen(tk_string);x;x=x->next)
                if (!strncmp((char*)tk_string,x->name,(x->len<l)?x->len:l))
                   m_exit(F_OVER_CON,tk_string,x->name);
           }
           /*}}}  */
           /*{{{  new node*/
           x=kbd_malloc(sizeof(overload_entry)+ustrlen(tk_string)+1);
           strcpy(x->name=(char*)(x+1),(char*)tk_string);
           x->len=ustrlen(tk_string);
           x->next=overloads;
           overloads=x;
           if (verbose_level>0)
              fprintf(stderr,F_OVER_PRE,tk_string);
           /*}}}  */
           continue;
         }
        /*}}}  */
        /*{{{  error*/
        default:
           m_exit(M_OVER_ERR);
        /*}}}  */
      }
     break;
   }
  end_parse(M_WANTEND);
}
/*}}}  */
/*{{{  cut_overload*/
public unsigned char const *cut_overload(unsigned char const *name)
{ overload_entry *x;

  for (x=overloads;x;x=x->next)
     if (!strncmp((char*)name,x->name,x->len))
        return(name+x->len);

  return(name);
}
/*}}}  */
/*}}}  */
/*{{{  mouse handling*/
typedef struct
 { unsigned char* name;
   unsigned char* code;
   int code_lg;
 } mouse_supports;
private boolean mouse=False;
private mouse_supports m_list[]=
 /*{{{  init values*/
 { {(unsigned char*)"scann",(unsigned char*)0,0},
#   ifdef XTERM
      { (unsigned char*)XTERM_MOUSE_NAME,(unsigned char*)XTERM_MOUSE_CODE,XTERM_MOUSE_LG },
#   endif
#   ifdef MGR
      { (unsigned char*)MGR_MOUSE_NAME,(unsigned char*)MGR_MOUSE_CODE,MGR_MOUSE_LG },
#   endif
#   ifdef OS_MOUSE_TAG
      { (unsigned char*)OS_MOUSE_NAME,(unsigned char*)OS_MOUSE_CODE,OS_MOUSE_LG },
#   endif
   { (unsigned char*)NO_MOUSE,(unsigned char*)0,0 },
   { (unsigned char*)0,(unsigned char*)0,0 }
 };
 /*}}}  */
public unsigned char *m_name=(unsigned char*)DEFAULT_MOUSE;

/*{{{  init_mouse*/
private void init_mouse(void)
{ mouse_supports *x=m_list;

  do
     if (!strncmp((char*)x->name,(char*)m_name,ustrlen(x->name)))
        return;
  while ((x++)->name);

  /*{{{  error exit*/
  fprintf(stderr,"unknown mouse set\n");
  fprintf(stderr,M_MICE);
  show_mice(stderr);
  fputc('\n',stderr);
  kbd_exit(1);
  /*}}}  */
}
/*}}}  */
/*{{{  show_mice*/
public void show_mice(FILE *f)
{ mouse_supports *x=m_list;
  boolean first=True;

  fprintf(f,"%s(",m_name);
  while (x->name) { fprintf(f,first?"%s":",%s",(x++)->name);first=False; }
  fprintf(f,")");
}
/*}}}  */
/*{{{  mouse_code*/
public void mouse_code(void)
{ tokens token;
  int i=0;
  TOKEN map[MAX_MOUSE_BUTTONS];

  /*{{{  multiple mouse?*/
  if (mouse)
     m_exit(M_MOUSE);
  mouse=True;
  /*}}}  */
  while ((token=get_full_token())!=END)
   /*{{{  handle a button*/
   { if (token==OPCODE)
      /*{{{  opcode*/
      { if ((map[i]=tk_key->num)!=O_NOP)
           write_bind
            ( tk_key->name,
              (unsigned char*)(REF_COUNT_BASE+i),
              mouse_kbd
            );
      }
      /*}}}  */
     else if (token==MACRO && ustrlen(tk_string)==1)
      /*{{{  simple*/
      { unsigned char simple[3];

        simple[0]='\"';
        simple[1]=map[i]=tk_string[0];
        simple[2]='\0';
        write_bind
         ( simple,
           (unsigned char*)(REF_COUNT_BASE+i),
           mouse_kbd
         );
      }
      /*}}}  */
     else if (token==OPERATION)
      /*{{{  operation*/
      { TOKEN k;

        if (tk_operation->place>=0)
           k=(TOKEN)((int)O_EXE_MACRO+tk_operation->place);
        else if (tk_operation->length==1)
           k=tk_operation->ops[0];
        else
           m_exit(M_NODEBOUND);
        if ((map[i]=k)!=O_NOP)
           write_bind
            ( tk_operation->op_name,
              (unsigned char*)(REF_COUNT_BASE+i),
              mouse_kbd
            );
      }
      /*}}}  */
     else
      /*{{{  error*/
        m_exit(M_NOCOMMAND);
      /*}}}  */
     if (++i==MAX_MOUSE_BUTTONS)
      /*{{{  memory-crash*/
        m_exit(M_NOMEMORY);
      /*}}}  */
   }
   /*}}}  */
  write_buttons_rc(map,i);
}
/*}}}  */
/*}}}  */
/*{{{  alias handling*/
/*{{{  ALIAS_LIST*/
typedef struct ALIAS_LIST
 { const unsigned char *key_name;
   struct acl
    { int length;
      struct acl *next;
      TOKEN codes[1];
    } *code;
   struct ALIAS_LIST *next;
 } ALIAS_LIST;
/*}}}  */
private const ALIAS_LIST init_al_list={ undef,0,0 };
private ALIAS_LIST *alias_list=(ALIAS_LIST*)&init_al_list;

/*{{{  create an alias entry*/
private void creat_alias(unsigned char *name,int lg,TOKEN *code)
{ ALIAS_LIST *ap;

  for (ap=alias_list;;ap=ap->next)
     if (!ap)
      /*{{{  unknown alias -> new node*/
      {
        /*{{{  new node at head of alias list*/
        ap=kbd_malloc(sizeof(ALIAS_LIST));
        ap->next=alias_list;
        alias_list=ap;
        /*}}}  */
        /*{{{  no sequences till now*/
        ap->code=0;
        /*}}}  */
        /*{{{  name in node*/
        ap->key_name=mstrcpy(name);
        /*}}}  */
        break;
      }
      /*}}}  */
     else if (!ustrcmp(name,ap->key_name))
      /*{{{  break, alias uses more than one sequence*/
      { if (warning)
         { error_po();
           fprintf(stderr,F_2ALIAS,name);
         }
        break;
      }
      /*}}}  */
  /*{{{  code in node*/
  { struct acl *x;

    x=kbd_malloc(sizeof(struct acl)+(lg-1)*sizeof(TOKEN));
    x->next=ap->code;
    ap->code=x;
    x->length=lg;
    memcpy(x->codes,code,lg*sizeof(TOKEN));
  }
  /*}}}  */
  if (verbose_level>1)
     fprintf(stderr,F_ALIAS,name);
}
/*}}}  */
/*{{{  init alias list*/
public void init_alias(void)
{ TOKEN esc=033;

  creat_alias((unsigned char*)"esc",1,&esc);
}
/*}}}  */
/*}}}  */
/*{{{  keytables*/
/*{{{  types*/
/*{{{  KEY_LONG*/
typedef struct kl_struct
 { union
    { int id;
      struct kl_struct *hash;
    } x;
   TOKEN code;
   TOKEN key;
   struct kl_struct *next;
   struct kl_struct *n_level;
 } KEY_LONG;
/*}}}  */
/*{{{  TERMINALS*/
typedef struct t_lst
 { unsigned char tname[NAME_LG+1];
   int id;
   struct t_lst *next;
 } TERMINALS;
/*}}}  */
/*{{{  KEYTABLE*/
typedef struct kt
 { int id;
   unsigned char mode[NAME_LG+1];
   boolean def;
   struct ktbl
    { TERMINALS *t;
      KEY_LONG *top_level;
      int mark;
      struct ktbl *next;
    } keys;
   struct kt *next;
 } KEYTABLE;
/*}}}  */
/*{{{  KEYSEQUENCE*/
typedef struct
 { unsigned char protocol[NAME_LG+1];
   struct ksl
    { TERMINALS *t;
      int lg;
      TOKEN codes[ALIAS_LG];
      struct ksl *next;
    } *key_list;
 } KEYSEQUENCE;
/*}}}  */
/*}}}  */
/*{{{  variables*/
/* do not change DEF_TERM, origami needs the empty word for detecting */
/* the default table! */
/* do not change DEF_MODE_ID, origami uses this table as startup table! */
#define DEF_TERM ""
#define DEF_TERM_ID 0
#define DEF_MODE ""
#define DEF_MODE_ID 0

private unsigned char const def_term_name[]=DEF_TERM;
private TERMINALS const def_terminal={ DEF_TERM,DEF_TERM_ID,0 };
private TERMINALS *term_list= (TERMINALS*)&def_terminal;
private int last_used_term_id=DEF_TERM_ID;

private unsigned char const def_mode_name[]=DEF_MODE;
private KEYTABLE *key_table_list=0;
private int last_ktbl_id=DEF_MODE_ID;

private boolean ab_set=False;
private boolean def_kbd_used=False;
private int modes_used;
private int bind_help_id=usr_kbd;
/*}}}  */

/*{{{  struct ksl mallocing*/
private struct ksl *ksl_f_list=0;

/*{{{  ks_malloc*/
private struct ksl *ks_malloc(void)
{
  struct ksl *k;

  if ((k=ksl_f_list))
     ksl_f_list=k->next;
  else
     k=kbd_malloc(sizeof(struct ksl));

  return(k);
}
/*}}}  */
/*{{{  ks_free*/
private void ks_free(struct ksl *kl)
{
  kl->next=ksl_f_list;
  ksl_f_list=kl;
}
/*}}}  */
/*{{{  ksl_free*/
private void ksl_free(struct ksl *kl)
{
  for (;kl;)
   { struct ksl *kl1;

     kl1=kl->next;
     ks_free(kl);
     kl=kl1;
   }
}
/*}}}  */
/*}}}  */
/*{{{  TERMINALS mallocing*/
private TERMINALS *t_f_list=0;

/*{{{  t_malloc*/
private TERMINALS *t_malloc(void)
{
  TERMINALS *t;

  if ((t=t_f_list))
     t_f_list=t->next;
  else
     t=kbd_malloc(sizeof(TERMINALS));

  return(t);
}
/*}}}  */
/*{{{  t_free*/
private void t_free(TERMINALS *t)
{
  t->next=t_f_list;
  t_f_list=t;
}
/*}}}  */
/*{{{  tl_free*/
private void tl_free(TERMINALS *t)
{
  while (t)
   { TERMINALS *tx;

     tx=t->next;
     t_free(t);
     t=tx;
   }
}
/*}}}  */
/*}}}  */

/*{{{  name2terminal*/
private TERMINALS *name2terminal(unsigned char const * const term)
{
  TERMINALS *t;

  for (t=term_list;;t=t->next)
     if (!t)
      /*{{{  define new terminal*/
      { t=t_malloc();
        ustrcpy(t->tname,term);
        t->id=++last_used_term_id;
        t->next=term_list;
        term_list=t;
        break;
      }
      /*}}}  */
     else if (!ustrcmp(term,t->tname))
        break;

  return(t);
}
/*}}}  */
/*{{{  name2mode*/
private KEYTABLE *name2mode(unsigned char const*const s)
{
  KEYTABLE *x;

  for (x=key_table_list;;x=x->next)
     if (!x || !ustrcmp(s,x->mode))
        break;

  return(x);
}
/*}}}  */
/*{{{  id2mode*/
private KEYTABLE *id2mode(int id)
{
  KEYTABLE *x;

  for (x=key_table_list;;x=x->next)
     if (!x)
        m_exit(M_CRASH);
     else if (id==x->id)
        break;

  return(x);
}
/*}}}  */
/*{{{  get_term_token*/
private tokens get_term_token(void)
{ tokens x=get_full_token();

  switch (x)
   { case DEFAULT:
        ustrcpy(tk_string,def_mode_name);
        x=NAME;
        break;
     case OPCODE:
     case OPERATION:
     case VARIABLE:
        if (isalnum(tk_string[0]))
           x=NAME;
     default:
        break;
   }
  return(x);
}
/*}}}  */

/*{{{  KEY_LONG node handling*/
private KEY_LONG *k_freed=0;

/*{{{  key_alloc*/
private KEY_LONG *key_alloc(void)
{
  KEY_LONG *x;

  if (k_freed)
   /*{{{  use freed node*/
   { x=k_freed;
     k_freed=k_freed->next;
   }
   /*}}}  */
  else
   /*{{{  use new one*/
   { static KEY_LONG *km;
     static int key_malloced=0;
#    define KEY_PAKET 128

     if (!key_malloced)
      /*{{{  get paket of key structs*/
      { km=kbd_malloc(KEY_PAKET*sizeof(KEY_LONG));
        key_malloced=KEY_PAKET;
      }
      /*}}}  */
     x=km++;
     key_malloced--;
   }
   /*}}}  */
  x->next=x->n_level=0;
  x->code=x->key=0;

  return(x);
}
/*}}}  */
/*{{{  key_free*/
private void key_free(KEY_LONG * const x)
{
  x->next=k_freed;
  k_freed=x;
}
/*}}}  */
/*{{{  key_copy*/
private KEY_LONG *key_copy(KEY_LONG *p)
{ KEY_LONG *n;

  if (!p)
     return(0);
  n=key_alloc();
  *n = *p;
  if (n->next)
     n->next=key_copy(p->next);
  if (n->n_level)
      n->n_level=key_copy(p->n_level);

  return(n);
}
/*}}}  */
/*}}}  */

/*{{{  init_ktb*/
private struct ktbl *find_term(KEYTABLE * const k,TERMINALS const * const t);

private void init_ktb
 ( unsigned char const*const mode,
   unsigned char const*const term
 )
{
  TERMINALS *t;
  struct ktbl *kl;
  KEYTABLE *x;

  for (x=key_table_list;;x=x->next)
     if (!x)
      /*{{{  get a new table for mode*/
      {
        /*{{{  get new space*/
        x=kbd_malloc(sizeof(KEYTABLE));
        x->next=key_table_list;
        key_table_list=x;
        /*}}}  */
        x->id=last_ktbl_id++;
        /*{{{  init data for the mode/default table*/
        ustrcpy(x->mode,mode);
        x->def=False;
        x->keys.t=(TERMINALS*)&def_terminal;
        x->keys.top_level=key_alloc();
        x->keys.mark=bind_help_id++;
        x->keys.next=0;
        if (bind_help_id&0x8000)
           m_exit(M_T_MAX);
        /*}}}  */
        break;
      }
      /*}}}  */
     else if (!ustrcmp(mode,x->mode))
        break;
  for (t=name2terminal(term),kl= &x->keys;;kl=kl->next)
     if (!kl)
      /*{{{  define a new table for current terminal*/
      { kl=kbd_malloc(sizeof(struct ktbl));
        kl->t=t;
        kl->top_level=key_copy
                       ( find_term
                          ( x,
                            (TERMINALS*)&def_terminal
                          )->top_level
                       );
        kl->mark=bind_help_id++;
        kl->next=x->keys.next;
        x->keys.next=kl;
        break;
      }
      /*}}}  */
     else if (kl->t->id==t->id)
        break;
}
/*}}}  */
/*{{{  find_term*/
private struct ktbl *find_term(KEYTABLE * const k,TERMINALS const * const t)
{
  struct ktbl *x;

  for (x= &k->keys;;x=x->next)
     if (!x)
      { init_ktb(k->mode,t->tname);
        x=find_term(k,t);
        break;
      }
     else if (x->t->id==t->id)
        break;

  return(x);
}
/*}}}  */

/*{{{  init_keytables*/
public void init_keytables(void)
{
  init_mouse();
  init_ktb(def_mode_name,def_term_name);
}
/*}}}  */
/*{{{  token2mode*/
public int token2mode(tokens t)
{
  KEYTABLE *k;

  if (t!=OPERATION || !(k=name2mode((unsigned char const*const)tk_string)))
     return(-1);
  else
     return(k->id);
}
/*}}}  */

/*{{{  define_k_modes*/
public void define_k_modes(void)
{
  boolean begin_end_used=False;

  if (def_kbd_used)
     m_exit(M_KBD_KEY);
  for (;;)
   { switch (get_full_token())
      {
        /*{{{  END:  end loop*/
        case END:
           break;
        /*}}}  */
        /*{{{  BEGIN:mark list read*/
        case BEGIN:
           if (begin_end_used)
              goto mkbd_error;
           begin_end_used=True;
           continue;
        /*}}}  */
        /*{{{  NAME: a new mode*/
        case NAME:
         { int mode_number;

           /*{{{  set mode number*/
           { KEYTABLE *k;

             k=name2mode(tk_string);
             mode_number=k?k->id:-1;
           }
           /*}}}  */
           if (mode_number==-1)
            { KEYTABLE *k;

              if (modes_used)
                 init_ktb(tk_string,def_term_name);
              mode_number=modes_used;
              k=id2mode(mode_number);
              ustrcpy(k->mode,tk_string);
              /*{{{  verbose?*/
              if (verbose_level>0)
                 fprintf(stderr,F_KBDS,k->mode,modes_used);
              /*}}}  */
              /*{{{  gen kbd switch macro*/
              { TOKEN t[2];

                t[0]=M_SW_KBD;
                t[1]=modes_used;
                verbose_level--;
                creat_op(k->mode,True,2,t,-1,False,0);
                verbose_level++;
              }
              /*}}}  */
              /*{{{  maybe gen rc comment*/
              if (commenting && !dest_mac)
               { char buff[NAME_LG+NAME_LG];
                 char *tp=buff;

                 sprintf(buff,"%s is mode %d",tk_string,mode_number);
                 rc_put_c(RC_COMMENT,rc.fp);
                 do rc_put_c(*tp,rc.fp); while (*tp++);
               }
              /*}}}  */
              modes_used++;
            }
           continue;
         }
        /*}}}  */
        /*{{{  error*/
        default:
        mkbd_error:
           m_exit(M_KBD_ERR);
        /*}}}  */
      }
     break;
   }
  if (begin_end_used)
     end_parse(M_WANTEND);
}
/*}}}  */
/*{{{  check_k_modes*/
public void check_k_modes(void)
{
  int i;

  for (i=modes_used;i--;)
   { KEYTABLE *k;

     k=id2mode(i);
     if (!k->def)
        m_exit(F_KBD_MISS,k->mode);
   }
}
/*}}}  */

/*{{{  add_keyseq*/
/*{{{  decode*/
private unsigned char *decode(TOKEN keynumber)
{ KEYNAME *seek = bindings;
  int off;
  static unsigned char name[NAME_LG];

  /*{{{  store offset to first fix-macro*/
  if (keynumber>=O_CALL_FIX)
   { off=keynumber-O_CALL_FIX+1;
     keynumber=O_CALL_FIX;
   }
  else
     off=0;
  /*}}}  */
  while (seek->name)
    if ((seek->num)==(TOKEN) keynumber)
      /*{{{  return the name*/
      if (off)
       { sprintf((char*)name,"%d=%s %d",keynumber,seek->name,off);
         return(name);
       }
      else
       { sprintf((char*)name,"%d=%s",keynumber,seek->name);
         return(name);
       }
      /*}}}  */
    else
      seek++;
  sprintf((char*)name,F_NOKEYNUMBER,keynumber);
  return(name);
}
/*}}}  */
/*{{{  add_key*/
private KEY_LONG *add_key(TOKEN key,KEY_LONG *current,TOKEN op)
{
  KEY_LONG *x;

  for (x=current->n_level;;x=x->next)
     if (!x)
      /*{{{  get new node*/
      { x=key_alloc();
        x->key=key;
        x->next=current->n_level;
        current->n_level=x;
        break;
      }
      /*}}}  */
     else if (x->key == key)
      /*{{{  key already in list, end search or error*/
      { if (x->code)
           if (x->code==op)
            /*{{{  maybe warn*/
            { if (warning)
               { error_po();
                 fprintf(stderr,F_DBL_BIND,decode(op));
               }
            }
            /*}}}  */
           else
            /*{{{  return error*/
            { error_po();
              fprintf(stderr,F_ISMASKED,decode(x->code));
              x=0;
            }
            /*}}}  */
        break;
      }
      /*}}}  */

  return (x);
}
/*}}}  */
/*{{{  set_key_code*/
private boolean set_key_code (TOKEN code,KEY_LONG *current)
{
  if (current->n_level)
   { error_po();
     fprintf(stderr,F_MASKS,decode(code));
     return(True);
   }
  current->code = code;

  return (False);
}
/*}}}  */

private void add_keyseq(struct ktbl *kt,TOKEN k,int lg,TOKEN *s)
{ KEY_LONG *key_point;

  if (kt)
   { key_point=kt->top_level;
     for (;lg;lg--)
        if (!(key_point=add_key(*s++,key_point,((lg>1)?0:k))))
           goto error_out;
     if (set_key_code(k,key_point))
        goto error_out;
   }
  return;
error_out:
    m_exit
     ( F_CUR_TERM,
       kt->t->id==DEF_TERM_ID
        ? (unsigned char*)"default"
        : kt->t->tname
     );
}
/*}}}  */
/*{{{  parse_keysequence*/
/*{{{  add_k_c*/
private void add_k_c(struct ksl *k,TOKEN c)
{
  int l;

  if ((l=k->lg++)==ALIAS_LG)
     m_exit(M_LONG_KEY);
  k->codes[l]=c;
}
/*}}}  */
/*{{{  add_kl_c*/
private void add_kl_c(struct ksl *kl,TOKEN c)
{
  for (;kl;kl=kl->next)
     add_k_c(kl,c);
}
/*}}}  */
/*{{{  add_k_alias*/
private struct ksl *add_k_alias(struct ksl *k,ALIAS_LIST *a)
{
  struct acl *c,*cn;

  for (c=a->code;;c=cn,k=k->next)
   { cn=c->next;
     if (cn)
      /*{{{  double current sequence for next alias version*/
      { struct ksl *x;

        x=ks_malloc();
        *x = *k;
        k->next=x;
      }
      /*}}}  */
     /*{{{  append string*/
     { TOKEN *s;
       int  lg;

       for (lg=c->length,s=c->codes;lg;s++,lg--)
          add_k_c(k,*s);
     }
     /*}}}  */
     if (!cn)
        return(k);
   }
}
/*}}}  */
/*{{{  add_kl_alias*/
private void add_kl_alias(struct ksl *k,ALIAS_LIST *a)
{
  while (k)
   { if (!(k=add_k_alias(k,a)))
        kbd_exit(1);
     k=k->next;
   }
}
/*}}}  */

private void parse_keysequence(TERMINALS *t,KEYSEQUENCE *k)
{
  char *p;
  tokens token;

  /*{{{  check for valid term*/
  if (!t || t->id==-1)
     t=0;
  /*}}}  */
  /*{{{  build empty KEYSEQUENCE list for given terms*/
  { TERMINALS *tx;
    struct ksl *kl;

    (p=(char*)k->protocol)[0]='\0';
    for (tx=t,k->key_list=0;tx;tx=tx->next)
     { kl=ks_malloc();
       kl->t=tx;
       kl->lg=0;
       kl->next=k->key_list;
       k->key_list=kl;
     }
  }
  /*}}}  */
  begin_parse();
  for (;;)
   { switch ((token=get_keycode_token()))
      {
        /*{{{  all*/
        case MULT:
           add_kl_c(k->key_list,keytaball);
           strcat(p,"* ");
           continue;
        /*}}}  */
        /*{{{  hex or normal char*/
        case HEX:
        case CHAR:
           add_kl_c(k->key_list,tk_char);
           /*{{{  generate protocol of this binding*/
           if (token==HEX)
            { strcat(p,(char*)tk_string);
              strcat(p," ");
            }
           else if (tk_char==127)
              strcat(p,"C-? ");
           else if (tk_char==' ')
              strcat(p,"space ");
           else if ((unsigned int)tk_char>(unsigned int)' ')
            { char x[3];

              x[0]=(char)tk_char;
              x[1]=' ';
              x[2]='\0';
              strcat(p,x);
            }
           else
            { char x[5];

              x[0]='C';
              x[1]='-';
              x[2]=CTRL_CHARS[tk_char];
              x[3]=' ';
              x[4]='\0';
              strcat(p,x);
            }
           /*}}}  */
           continue;
        /*}}}  */
        /*{{{  meta*/
        case META:
           add_kl_c(k->key_list,27);
           strcat(p,"M-");
           continue;
        /*}}}  */
        /*{{{  alias*/
        case DOLLAR:
         { ALIAS_LIST *ap;

           ap=alias_list;
           while (ap && ustrcmp(ap->key_name,tk_string))
              ap=ap->next;
           if (ap)
            /*{{{  alias found -> normal alias, copy to all sequences*/
              add_kl_alias(k->key_list,ap);
            /*}}}  */
           else
            /*{{{  handle as terminal-alias*/
            { struct ksl *kl;

              for (kl=k->key_list;kl;)
               { unsigned char an[NAME_LG];

                 /*{{{  create correct name*/
                 ustrcpy(an,kl->t->tname);
                 strcat((char*)an,"@");
                 ustrcat(an,tk_string);
                 /*}}}  */
                 ap=alias_list;
                 while (ap && ustrcmp(ap->key_name,an))
                    ap=ap->next;
                 if (ap)
                  /*{{{  alias found*/
                  { if (!(kl=add_k_alias(kl,ap)))
                       kbd_exit(1);
                    kl=kl->next;
                  }
                  /*}}}  */
                 else
                  /*{{{  don't bind this term-alias!*/
                  { if (warning)
                     { error_po();
                       fprintf(stderr,F_MISALIAS,an);
                     }
                    if (kl==k->key_list)
                     { k->key_list=kl->next;
                       ks_free(kl);
                       kl=k->key_list;
                     }
                    else
                     { struct ksl *kx;

                       for (kx=k->key_list;;kx=kx->next)
                          if (kx->next==kl)
                           { kx->next=kl->next;
                             ks_free(kl);
                             kl=kx->next;
                             break;
                           }
                     }
                  }
                  /*}}}  */
               }
            }
            /*}}}  */
           strcat(p,(char*)tk_string);
           strcat(p," ");
           continue;
         }
        /*}}}  */
        /*{{{  )*/
        case END: break;
        /*}}}  */
        /*{{{  errors*/
        default:
           m_exit(M_WANTEND);
        case ERROR:
           kbd_exit(1);
        /*}}}  */
      }
     break;
   }
}
/*}}}  */
/*{{{  keydef_body*/
private void keydef_body(KEYTABLE *kt,TERMINALS *tl,boolean ab_allowed)
{ KEYSEQUENCE k;
  TOKEN operation;
  unsigned char *n;
  struct ksl *kl;

  /*{{{  get binding object*/
  switch (get_full_token())
   { default:
     invalid_bind_object:
         m_exit(M_NOCOMMAND);
     /*{{{  OPCODE, use table values*/
     case OPCODE:
        n=(unsigned char*)tk_key->name;
        operation=tk_key->num;
        break;
     /*}}}  */
     /*{{{  MACRO, generate simple string from tk_string*/
     case MACRO:
        if (ustrlen(tk_string)==1)
         { static unsigned char simple_char_name[3];

           simple_char_name[0]='\"';
           simple_char_name[1]=tk_string[0];
           simple_char_name[2]='\0';
           operation=tk_macro[0];
           n=simple_char_name;
           break;
         }
        else
           goto invalid_bind_object;
     /*}}}  */
     /*{{{  OPERATION: use deffun or defmac value*/
     case OPERATION:
        if (tk_operation->place<0)
         { if (tk_operation->length!=1)
              m_exit(M_NODEBOUND);
           else
              operation= *(tk_operation->ops);
         }
        else
           operation=(TOKEN)((int)O_EXE_MACRO+tk_operation->place);
        n=tk_operation->op_name;
        break;
     /*}}}  */
     /*{{{  MINUS: key-not-bound*/
     case MINUS:
        operation=keytabknb;
        n=0;
        break;
     /*}}}  */
   }
  /*}}}  */
  /*{{{  get sequence*/
  parse_keysequence(tl,&k);
  end_parse(M_KEYEND);
  /*}}}  */
  if (operation==O_BREAK)
     if (ab_allowed)
      /*{{{  handle abort*/
      {
        /*{{{  incorrect sequence*/
        if
         (    !k.key_list
           || k.key_list->lg!=1
           || k.key_list->codes[0]>=O_NOP
           || k.key_list->codes[0]<'\0'
           || !n
         )
           m_exit(M_NOABORT);
        /*}}}  */
        /*{{{  double*/
        if (ab_set)
           m_exit(M_DUPAB);
        /*}}}  */
        general_abort_key=k.key_list->codes[0];
        write_bind(n,k.protocol,abort_kbd);
        ab_set=True;
      }
      /*}}}  */
     else
      /*{{{  error*/
        m_exit(M_KBD_KEY);
      /*}}}  */
  else
   /*{{{  normal key*/
   { if (ab_allowed)
        if (modes_used)
         /*{{{  multiple keytables used -> crash*/
           m_exit(M_KBD_KEY);
         /*}}}  */
        else
           def_kbd_used=True;
     /*{{{  copy all not already used terminal-trees*/
     for (kl=k.key_list;kl;kl=kl->next)
        find_term(kt,kl->t);
     /*}}}  */
     /*{{{  add sequence to trees*/
     for (kl=k.key_list;kl;kl=kl->next)
      { struct ktbl *ktb;

        ktb=find_term(kt,kl->t);
        add_keyseq(ktb,operation,kl->lg,kl->codes);
      }
     /*}}}  */
     if (n)
      /*{{{  write to helpfile*/
      { TERMINALS *tp;

        for (tp=term_list;tp;tp=tp->next)
           for (kl=k.key_list;kl;kl=kl->next)
            { if (kl->t->id==tp->id)
               { struct ktbl *ktb;

                 ktb=find_term(kt,kl->t);
                 write_bind(n,k.protocol,ktb->mark);
                 break;
               }
            }
      }
      /*}}}  */
   }
   /*}}}  */
  ksl_free(k.key_list);
}
/*}}}  */
/*{{{  terminal_body*/
private void terminal_body(KEYTABLE *ktb)
{
  TERMINALS *term_l;
  boolean no_mode_set;

  if (!ktb)
   { no_mode_set=True;
     ktb=id2mode(DEF_MODE_ID);
   }
  else
     no_mode_set=False;
  /*{{{  get correct terminals*/
  switch (get_term_token())
   { case NAME:
     case NUMBER:
      /*{{{  single terminal*/
        term_l=t_malloc();
        *term_l= *name2terminal(tk_string);
        term_l->next=0;
        break;
      /*}}}  */
     case BEGIN:
      /*{{{  list of terminals*/
      { for (term_l=0;;)
         { switch (get_term_token())
            { case END:
                 break;
              case NAME:
              case NUMBER:
               /*{{{  copy name*/
               { TERMINALS *x;

                 x=t_malloc();
                 *x= *name2terminal(tk_string);
                 x->next=term_l;
                 term_l=x;
                 continue;
               }
               /*}}}  */
              default:
                 goto term_expected;
            }
           break;
         }
        break;
       }
      /*}}}  */
     default:
     term_expected:
        m_exit(M_T_NAME);
   }
  if (verbose_level>3)
   /*{{{  show all opened terminals*/
   { TERMINALS *x;

     for (x=term_l;x && x->id!=-1;x=x->next)
        fprintf(stderr,F_T_START,x->tname);
   }
   /*}}}  */
  /*}}}  */
  for (;;)
   { switch (get_full_token())
      { case END:
           break;
        case BEGIN:
           switch (get_full_token())
            { case KEYDEF:
                 keydef_body(ktb,term_l,False);
                 continue;
              case KEYALIAS:
                 if (no_mode_set)
                  /*{{{  do the alias*/
                  { unsigned char name[NAME_LG+1];
                    KEYSEQUENCE k;
                    struct ksl *kl;

                    name_parse(name,M_EXPALIAS,True);
                    parse_keysequence(term_l,&k);
                    for (kl=k.key_list;kl;kl=kl->next)
                      { unsigned char tname[2*NAME_LG+1];

                        ustrcpy(tname,kl->t->tname);
                        strcat((char*)tname,"@");
                        ustrcat(tname,name);
                        if (ustrlen(tname)>NAME_LG)
                           m_exit(M_CRASH);
                        creat_alias(tname,kl->lg,kl->codes);
                      }
                    ksl_free(k.key_list);
                    end_parse(M_WANTEND);
                    continue;
                  }
                  /*}}}  */
              default:
                 break;
            }
        default:
            m_exit(M_KBD_BEGIN);
      }
     break;
   }
 tl_free(term_l);
 if (verbose_level>3)
  /*{{{  show all opened terminals*/
  { TERMINALS *x;

    for (x=term_l;x && x->id!=-1;x=x->next)
       fprintf(stderr,M_T_END,x->tname);
  }
  /*}}}  */
}
/*}}}  */
/*{{{  def_keybind_code*/
public void def_keybind_code(void)
{
  keydef_body(name2mode(def_mode_name),term_list,True);
}
/*}}}  */
/*{{{  set_alias*/
public void set_alias(void)
{ unsigned char name[NAME_LG];
  KEYSEQUENCE k;

  name_parse(name,M_EXPALIAS,True);
  parse_keysequence((TERMINALS*)&def_terminal,&k);
  if (k.key_list)
     creat_alias(name,k.key_list->lg,k.key_list->codes);
}
/*}}}  */
/*{{{  mode_code*/
public void mode_code(void)
{
  tokens t;
  int mode_number;
  KEYTABLE *ktb;

  if (!modes_used)
     m_exit(M_KBD_KEY);
  /*{{{  get correct kbd-no*/
  if ((mode_number=token2mode(get_full_token()))<0)
     kbd_exit(1);
  ktb=id2mode(mode_number);
  if (mode_number && !ktb->def)
     init_ktb(ktb->mode,def_term_name);
  /*}}}  */
  if (verbose_level>0)
     fprintf(stderr,F_BEG_MODE,ktb->mode);
  ktb->def=True;
  while ((t=get_full_token())!=END)
     switch (t)
      { case BEGIN:
           switch(get_full_token())
            { case KEYDEF:
                 keydef_body(ktb,term_list,False);
                 break;
              case TERMINAL:
                 terminal_body(ktb);
                 break;
              default:
                 goto mode_error;

            }
           break;
        default:
        mode_error:
           m_exit(M_KBD_BEGIN);
      }
  if (verbose_level>0)
     fprintf(stderr,F_END_MODE,ktb->mode);
}
/*}}}  */
/*{{{  terminal_cmd*/
public void terminal_cmd(void)
{
  terminal_body(0);
}
/*}}}  */

/*{{{  write_kbds*/
/*{{{  write_t_ktbs*/
typedef enum { FAR_NEXT_KEY=O_NOP,WRITTEN_KEY,TOP_KEY } special_key;
#define BASE_ID 0
#define CLEARED_ID (BASE_ID-1)
private int joined;

/*{{{  opti_id_ktb*/
/*{{{  ktb_set_id*/
/*{{{  comment*/
/* genereate unique identifiers for all nodes, shared next-nodes will
 * lead to far-next-nodes, to gurantee, that next-nodes are directly
 * after their parent!
 * returns the number of used nodes!
 */
/*}}}  */
/*{{{  ktb_clear_id*/
private void ktb_clear_id(KEY_LONG * const n)
{
  if (n)
   { n->x.id=CLEARED_ID;
     ktb_clear_id(n->next);
     ktb_clear_id(n->n_level);
   }
}
/*}}}  */
/*{{{  rec_ktb_set_id*/
private int rec_ktb_set_id(KEY_LONG * const n,int base)
{
  if (base&0x8000)
     m_exit(M_TOOMANYKEYS);
  if (n && n->x.id==CLEARED_ID)
   { n->x.id=base++;
     if (n->next)
      { if (n->next->x.id!=CLEARED_ID)
         /*{{{  generate far-next-node*/
         { KEY_LONG *c;

           c=key_alloc();
           c->x.id=CLEARED_ID;
           c->key=FAR_NEXT_KEY;
           c->n_level=n->next;
           n->next=c;
           joined--;
         }
         /*}}}  */
        base=rec_ktb_set_id(n->next,base);
      }
     base=rec_ktb_set_id(n->n_level,base);
   }

  return(base);
}
/*}}}  */

private int ktb_set_id(KEY_LONG * const ktb)
 {
   ktb_clear_id(ktb);
   return(rec_ktb_set_id(ktb,BASE_ID));
 }
/*}}}  */
/*{{{  sort_ktb*/
private void sort_ktb(KEY_LONG * const n,boolean const full)
 {
   if (n)
    { if (full)
         sort_ktb(n->n_level,True);
      if (n->next)
       { if (full)
            sort_ktb(n->next,True);
         if
          (    n->key<TOP_KEY
            && (    n->key==keytaball
                 || (n->key>n->next->key && n->next->key!=keytaball)
               )
          )
          /*{{{  change nodes*/
          { KEY_LONG x;

            x = *n;
            *n = *(x.next);
            *(x.next) = x;
            x.next->next=n->next;
            n->next=x.next;
            sort_ktb(n->next,False);
          }
          /*}}}  */
       }
    }
 }
/*}}}  */
#ifndef NO_OPTI
   /*{{{  join_tree*/
   /* transform the keytable tree into an directed-acyclic-graph,
    * sharing common sub-trees
    */
   /*{{{  ktb hashing*/
   private KEY_LONG *hash[O_NOP];

   /*{{{  ktb_init_hash*/
   /*{{{  ktb_r_i_h*/
   private void ktb_r_i_h(KEY_LONG * const n)
    {
      if (n)
       { ktb_r_i_h(n->n_level);
         if (n->key>=0 && n->key<O_NOP)
          { n->x.hash=hash[n->key];
            hash[n->key]=n;
          }
         ktb_r_i_h(n->next);
       }
    }
   /*}}}  */

   private void ktb_init_hash(KEY_LONG * const n)
    {
      /*{{{  empty lists*/
      { int i;

        for (i=O_NOP;--i>=0;hash[i]=0);
      }
      /*}}}  */
      ktb_r_i_h(n);
    }
   /*}}}  */
   /*{{{  ktb_find_hash*/
   private KEY_LONG *ktb_find_hash(KEY_LONG const * const n)
    {
      KEY_LONG *x;

      if (n->key>=0 && n->key<O_NOP)
       { for (x=n->x.hash;x;x=x->x.hash)
            if
             (    x!=n
               && x->code==n->code
               && x->next==n->next
               && x->n_level==n->n_level
             )
               break;
       }
      else
         x=0;

      return(x);
    }
   /*}}}  */
   /*{{{  ktb_del_hash*/
   private void ktb_del_hash(KEY_LONG const * const n)
    {
      if (n->key>=0 && n->key<O_NOP)
         if (hash[n->key]==n)
            hash[n->key]=n->x.hash;
         else
          { KEY_LONG *x;

            for (x=hash[n->key];x;x=x->x.hash)
               if (x->x.hash==n)
                { x->x.hash=n->x.hash;
                  break;
                }
          }
    }
   /*}}}  */
   /*}}}  */
   /*{{{  rec_join_tree*/
   private boolean rec_join_tree(KEY_LONG * const n)
    {
      boolean ret;

      ret=False;
      if (n)
       { KEY_LONG *c;

         /*{{{  maybe use copy of n_level node*/
         if (n->n_level)
          { ret=rec_join_tree(n->n_level);
            if ((c=ktb_find_hash(n->n_level)))
             { ktb_del_hash(n->n_level);
               key_free(n->n_level);
               n->n_level=c;
               joined++;
               ret=True;
             }
          }
         /*}}}  */
         /*{{{  maybe use copy of next node*/
         if (n->next)
          { ret|=rec_join_tree(n->next);
            if ((c=ktb_find_hash(n->next)))
             /*{{{  replace next by found copy*/
             { ktb_del_hash(n->next);
               key_free(n->next);
               n->next=c;
               joined++;
               ret=True;
             }
             /*}}}  */
          }
         /*}}}  */
       }
      return(ret);
    }
   /*}}}  */

   private void join_tree(KEY_LONG * const ktb)
    {
      joined=0;
      ktb_init_hash(ktb);
      while (rec_join_tree(ktb));
    }
   /*}}}  */
#endif

private int opti_id_ktb(KEY_LONG * const ktb)
 {
   joined=0;
   /*{{{  set top_level key's to special values, and sort level entries*/
   { KEY_LONG *t;
     int m;

     for (m=TOP_KEY,t=ktb;t;t->key=m,m++,t=t->next)
        sort_ktb(t->n_level,True);
   }
   /*}}}  */
#  ifndef NO_OPTI
      join_tree(ktb);
#  endif

   return(ktb_set_id(ktb));
 }
/*}}}  */
/*{{{  write_single_keynode*/
private void write_single_keynode(KEY_LONG *n)
 {
   if (n && n->key!=WRITTEN_KEY && !dest_mac)
    { if (ab_set && n->key==general_abort_key)
         m_exit(M_ABORTUSED);
      /*{{{  create and write compressed keytab node*/
      { KEY ks;

        ks.flags=NOT_USED;
        ks.code=n->code;
        if (n->key==keytaball)
         { ks.key='\0';
           ks.flags|=MAGIC_KEY;
         }
        else
           ks.key=n->key;
        if (n->next)
           ks.flags|=NEXT_USED;
        if (n->n_level)
         { ks.flags|=LEVEL_USED;
           if (n->key==FAR_NEXT_KEY)
            { ks.flags|=FAR_NEXT;
              ks.key=0;
            }
           ks.code=n->n_level->x.id-n->x.id;
         }
        put_key_data(&ks,rc.fp);
      }
      /*}}}  */
      n->key=WRITTEN_KEY;
      write_single_keynode(n->next);
      write_single_keynode(n->n_level);
    }
 }
/*}}}  */

private void write_t_ktbs(TERMINALS const * const t)
 {
   KEY_LONG *top,*tail;
   int nodes;

   /*{{{  generate single tree for all modes*/
   { int m;
     for (top=0,m=0;m<modes_used;m++)
      { KEYTABLE const *k;
        struct ktbl *x;

        /*{{{  get ktbl for mode/terminal, adding mouse keys*/
        k=id2mode(m);
        x=find_term((KEYTABLE*)k,t);
        /*{{{  add mouse for this terminal*/
        { mouse_supports *m_ptr;
          unsigned char const *m_n;

          /*{{{  m_ptr=current mouse, or default*/
          for (m_ptr=m_list,m_n=(t->id!=DEF_TERM_ID)?t->tname:m_name;;)
           { if
              (    m_ptr->name
                && strncmp((char*)m_ptr->name,(char*)m_n,ustrlen(m_ptr->name))
              )
                m_ptr++;
             else if (!(m_ptr->name) && (m_n==t->tname))
              { m_n=m_name;m_ptr=m_list; }
             else
                break;
           }
          /*}}}  */
          if
           (    m_ptr
             && m_ptr->code_lg
           )
           { TOKEN codes[ALIAS_LG];
             int i;

             for (i=0;i<m_ptr->code_lg;i++)
                codes[i]=m_ptr->code[i];
             add_keyseq(x,K_MOUSE,m_ptr->code_lg,codes);
           }
        }
        /*}}}  */
        /*}}}  */
        /*{{{  append to tree for mode*/
        if (top)
           tail->next=x->top_level;
        else
           top=x->top_level;
        tail=x->top_level;
        /*}}}  */
      }
   }
   /*}}}  */
   nodes=opti_id_ktb(top);
   /*{{{  write the keytable*/
   rc_put_c(RC_DEFKEY,rc.fp);
   /*{{{  write name*/
   { unsigned char const *term;

     term=t->tname;
     do
        rc_put_c(*term,rc.fp);
     while (*term++);
   }
   /*}}}  */
   rc_put_w(nodes,rc.fp);
   /*{{{  write help marks*/
   { int m;

     for (m=0;m<modes_used;m++)
        rc_put_w(find_term(id2mode(m),t)->mark,rc.fp);
   }
   /*}}}  */
   if (verbose_level>0)
      fprintf(stderr,F_NODES,t->tname,nodes,joined);
   write_single_keynode(top);
   /*}}}  */
 }
/*}}}  */

public void write_kbds(void)
 {
   TERMINALS *t;

   if (def_kbd_used)
      modes_used=1;
   ktb_count=modes_used;
   for (t=term_list;t;t=t->next)
      if (t->id!=DEF_TERM_ID)
         write_t_ktbs(t);
   write_t_ktbs(&def_terminal);
 }
/*}}}  */
/*}}}  */
