Source of commands.c


/* commands.c -- process X client commands for gemsvnc */

/* --------------------------------------------------------------------------------------------------------------------------------

   This file implements the command language for gemsvnc.  This language is used by X clients to control certain aspects of the
   gemsvnc rfb server.  These commands may be issued from stdin, in which case the response to them will be issued to stdout, or
   these commands may be issued from a socket that connects to a well known port.  (The socket interface is not yet implemented,
   but is a logical extrapolation of the stdin/stdout interface.)  The model is enquiry-response: when a command is issued, it
   will receive some sort of response.  The default SUCCESS response, in the absence of any other output, is "OK" followed by a
   newline.

   Where a <name> is called for, a regular expression is permitted, except in the case of the "new" command, in which case the
   <name> must be absolute.  In all other cases, if <name> is a regular expression, the command will be carried out on all names
   matching <name>.  

   The following commands are implemenmted:

       block <name>                      Do not allow the remote viewer to see region(s) <name>.  Show the region(s) as opaque 
                                         in the -block color (default orange).

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.

       guard <name>                      Do not allow the remote viewer to input keystrokes or mouse button presses into  
                                         region(s) <name>.  Show the region(s) as transparently tinted in the -guard color 
                                         (default green).

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.

       hold <name>                       Place region(s) <name> into the hold state.  Regions in the hold state maintain their 
                                         identity and position, but have no effect on the operation of the gemsvnc server.  This
                                         is a sort of "holding tank" for regionms that may not be fully configured, or that 
                                         temporarily need to be placed out of service.

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.

       image <name>                      Place region(s) <name> into the image state.  The video in these regions will be updated
                                         in their entirety whenever a change of any part of them is detected.  The intent is that
                                         such a region has an image in it, and so if any pixel of the image changes, then practically 
                                         all of the image is likely to have changed, so just update the entire region at once.

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.

       kill <name>                       Destroy the region(s) <name>.  This removes the region(s) and their name(s), thus making the
                                         name(s) available for re-use.

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.

       new <name>                        Create a new region named <name>.  In this case, <name> is *NOT* a regular expression.  Only
                                         a single region can be created with a single issuance of this command.  The region is at an 
                                         indeterminate location, and is placed into the hold state.

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NAME ALREADY IN USE      The name is already used by an existing rectangle.

       place <name> ulx uly lrx lry      Place the region(s) <name> at the specified location on the screen.  The region is always a
                                         rectangle, and is described by its upper left and lower right corners.  Each corner is
                                         described by its X and Y coordinates.

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.
                                                     BAD NUMBER FOR ULX       The upper left x coordinate is not a number.
                                                     BAD NUMBER FOR ULY       The upper left y coordinate is not a number.
                                                     BAD NUMBER FOR LRX       The lower right x coordinate is not a number.
                                                     BAD NUMBER FOR LRY       The lower right y coordinate is not a number.
                                                     RECTANGLE IS INSIDE-OUT  The upper left corner is on the wrong side of the lower right corner.
                                                     BAD REGULAR EXPRESSION   The <name> regular expression is ill-composed.

       show <name>                       Show the location and state of region(s) <name>.  This will output 1 line for each region.
                                         Each line will start with contain, in order, the name, state, and location, as follows:

                                              NAME STATE ULX ULY LRX LRY

                                         with a single space between each field.  This will be followed by the "OK" response.

                                         Responses:
                                                     OK                       The command was successful.
                                                     MISSING NAME             No <name> regular expression was provided.
                                                     GARBAGE AT END OF LINE   Non-whitespace after <name>.
                                                     NO NAMES MATCH           No names matched the regular expression <name>.
                                                     BAD REGULAR EXPRESSION   The <name> regular expression is ill-composed.

 -------------------------------------------------------------------------------------------------------------------------------- */


#include "rectangle.h"
#include "cmd_sockets.h"
#include <stdlib.h>
#include <stdio.h>                                               /* needed to define EOF */
#include <string.h>

#define FALSE (0)
#define TRUE (!FALSE)

void mark_tiles_in_rectangle_as_dirty(int ulx, int uly, int lrx, int lry);


rectangle* block_list;                                           /* the 4 lists for the 4 types of regions */
rectangle* guard_list;
rectangle* hold_list;
rectangle* image_list;

void init_commands(void) {                                       /* initializer for the command system */
  block_list = construct_empty_list_of_rectangles("BLOCK");
  guard_list = construct_empty_list_of_rectangles("GUARD");
  hold_list = construct_empty_list_of_rectangles("HOLD");
  image_list = construct_empty_list_of_rectangles("IMAGE");
}


static char* command;                                            /* pointer to the current command line */

char* next_token(void) {                                         /* parse the next token from the command */
  char* p;                                                       /* start of token */
  char* q;                                                       /* end of token */
  int len;
  int i;

  p = command;                                                   /* remainder of command line */
  if (p == NULL                                                  /* is there any line? */
      || *p == '\0') {                                           /* is line empty? */
    return NULL;                                                 /* if either is true, return no token */
  }

  while (*p == ' ') {                                            /* skip over leading blanks to find start of token */
    p++;
  }

  for (i = 0,                                                    /* skip over non-spaces or till end of string */
         q = p,
         len = strlen(p);
       i < len;                                                  /* don't go past end of string */
       i++,
         q++) {
    if (*q == ' ') {                                             /* found a space? */
      *q = '\0';                                                 /* yes, substitute a string terminator */
      command = q + 1;                                           /* remember where we left off */
      return p;                                                  /* return the token */
    }
  }
  command = NULL;                                                /* went till end of string, remember nothing is left over */
  if (p == q) {                                                  /* is the token empty? */
    return NULL;                                                 /* yes, return no token */
  } else {
    return p;                                                    /* no, return the token */
  }
}


static void block_cmd(void) {                                    /* Do not allow the remote viewer to see region */
  char* name;
  char* tail;
  int found = FALSE;
  rectangle* r;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  found = FALSE;
  while ((r = remove_named_rectangle_from_list(guard_list, name)) != NULL) { /* search other lists for name match and move to block_list */
    add_rectangle_to_list(block_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(hold_list, name)) != NULL) {
    add_rectangle_to_list(block_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(image_list, name)) != NULL) {
    add_rectangle_to_list(block_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


static void guard_cmd(void) {                                    /* Do not allow the remote viewer to input keystrokes or mouse button presses */
  char* name;
  char* tail;
  int found = FALSE;
  rectangle* r;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  found = FALSE;
  while ((r = remove_named_rectangle_from_list(block_list, name)) != NULL) { /* search other lists for name match and move to block_list */
    add_rectangle_to_list(guard_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(hold_list, name)) != NULL) {
    add_rectangle_to_list(guard_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(image_list, name)) != NULL) {
    add_rectangle_to_list(guard_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


static void hold_cmd(void) {                                     /* Place region(s) <name> into the hold state */
  char* name;
  char* tail;
  int found = FALSE;
  rectangle* r;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  found = FALSE;
  while ((r = remove_named_rectangle_from_list(block_list, name)) != NULL) { /* search other lists for name match and move to block_list */
    add_rectangle_to_list(hold_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(guard_list, name)) != NULL) {
    add_rectangle_to_list(hold_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(image_list, name)) != NULL) {
    add_rectangle_to_list(hold_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


static void image_cmd(void) {                                    /* Place region(s) <name> into the image state */
  char* name;
  char* tail;
  int found = FALSE;
  rectangle* r;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  found = FALSE;
  while ((r = remove_named_rectangle_from_list(block_list, name)) != NULL) { /* search other lists for name match and move to block_list */
    add_rectangle_to_list(image_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(guard_list, name)) != NULL) {
    add_rectangle_to_list(image_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(hold_list, name)) != NULL) {
    add_rectangle_to_list(image_list, r);
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    found = TRUE;
  }

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


static void kill_cmd(void) {                                     /* Destroy the region */
  char* name;
  char* tail;
  int found = FALSE;
  rectangle* r;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  found = FALSE;
  while ((r = remove_named_rectangle_from_list(block_list, name)) != NULL) { /* search all lists for name match and kill if found */
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    destruct_rectangle(r);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(guard_list, name)) != NULL) {
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    destruct_rectangle(r);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(hold_list, name)) != NULL) {
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    destruct_rectangle(r);
    found = TRUE;
  }
  while ((r = remove_named_rectangle_from_list(image_list, name)) != NULL) {
    mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
    destruct_rectangle(r);
    found = TRUE;
  }

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


static void new_cmd(void) {                                      /* Create a new region */
  char* name;                                                    /* the rectangle's name */
  char* tail;                                                    /* to test for no more tokens */
  rectangle* r;                                                  /* the rectangle we made */

  name = next_token();                                           /* get the name */
  if (name == NULL) {                                            /* was there one? */
    response("MISSING NAME\n");                                  /* no, complain and do nothing */
    return;
  }

  tail = next_token();                                           /* make sure no more tokens left */
  if (tail != NULL) {                                            /* were there any more? */
    response("GARBAGE AT END OF LINE\n");                        /* yes, complain and do nothing */
    return;
  }

  if (find_rectangle_on_list(block_list, name) != NULL           /* is the name available? */
      || find_rectangle_on_list(guard_list, name) != NULL
      || find_rectangle_on_list(hold_list, name) != NULL
      || find_rectangle_on_list(image_list, name) != NULL) {
    response("NAME ALREADY IN USE\n");                           /* no, complain and do nothing */
    return;
  }

  r = construct_rectangle(0, 0, 0, 0, name);                     /* yes, name available, so make the new rectangle */
  add_rectangle_to_list(hold_list, r);                           /* put it on the hold list */
  response("OK\n");                                              /* return the good response */
  return;
}


static void place_cmd(void) {                                    /* Place the region(s) <name> at the specified location on the screen */
  char* name;
  int ulx = 0;
  int uly = 0;
  int lrx = 1;
  int lry = 1;
  char* tail;
  char* endptr;
  int found = FALSE;
  rectangle* r;
  state_rectangle_list_iterator* i;
  static regex_t preg[4096];                                     /* compiled regular expression to match name */
  int errcode;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* get the upper left x coordinate */
  if (tail != NULL) {
    ulx = (int)strtol(tail, &endptr, 0);
    if (*endptr != '\0') {
      response("BAD NUMBER FOR ULX\n");
      return;
    }
  }
  tail = next_token();                                           /* get the upper left y coordinate */
  if (tail != NULL) {
    uly = (int)strtol(tail, &endptr, 0);
    if (*endptr != '\0') {
      response("BAD NUMBER FOR ULY\n");
      return;
    }
  }
  tail = next_token();                                           /* get the lower right x coordinate */
  if (tail != NULL) {
    lrx = (int)strtol(tail, &endptr, 0);
    if (*endptr != '\0') {
      response("BAD NUMBER FOR LRX\n");
      return;
    }
  }
  tail = next_token();                                           /* get the lower right y coordinate */
  if (tail != NULL) {
    lry = (int)strtol(tail, &endptr, 0);
    if (*endptr != '\0') {
      response("BAD NUMBER FOR LRY\n");
      return;
    }
  }

  if (ulx > lrx                                                  /* sanity check rectangle coordinates */
      || uly > lry) {
    response("RECTANGLE IS INSIDE-OUT\n");                       /* FIXME should we just swap corners here, or complain like this? */
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  errcode = regcomp(preg, name, 0);                              /* compile the regular expression */
  if (errcode != 0) {                                            /* any errors? */
    regfree(preg);                                               /* yes, free the compiled expression */
    response("BAD REGULAR EXPRESSION\n");                        /* say not found! */
    return;
  }

  found = FALSE;

  for (r = initial_rectangle_list_iterator(block_list, &i);      /* search the block list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      reposition_rectangle_on_list(block_list, r, ulx, uly, lrx, lry); /* yes, reposition the rectangle */
      mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  for (r = initial_rectangle_list_iterator(guard_list, &i);      /* search the guard list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      reposition_rectangle_on_list(guard_list, r, ulx, uly, lrx, lry); /* yes, reposition the rectangle */
      mark_tiles_in_rectangle_as_dirty(r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  for (r = initial_rectangle_list_iterator(hold_list, &i);       /* search the hold list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      reposition_rectangle_on_list(hold_list, r, ulx, uly, lrx, lry); /* yes, reposition the rectangle */
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  for (r = initial_rectangle_list_iterator(image_list, &i);      /* search the image list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      reposition_rectangle_on_list(image_list, r, ulx, uly, lrx, lry); /* yes, reposition the rectangle */
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  regfree(preg);                                                 /* free the compiled regular expression */  

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


static void show_cmd(void) {                                     /* Show the location and state of region */
  char* name;
  char* tail;
  int found = FALSE;
  rectangle* r;
  char line[256];
  state_rectangle_list_iterator* i;
  static regex_t preg[4096];                                     /* compiled regular expression to match name */
  int errcode;

  name = next_token();                                           /* get the regular expression to match names */
  if (name == NULL) {
    response("MISSING NAME\n");
    return;
  }

  tail = next_token();                                           /* make sure rest of line is empty */
  if (tail != NULL) {
    response("GARBAGE AT END OF LINE\n");
    return;
  }

  errcode = regcomp(preg, name, 0);                              /* compile the regular expression */
  if (errcode != 0) {                                            /* any errors? */
    regfree(preg);                                               /* yes, free the compiled expression */
    response("BAD REGULAR EXPRESSION\n");                        /* say not found! */
    return;
  }

  found = FALSE;

  for (r = initial_rectangle_list_iterator(block_list, &i);      /* search the block list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      sprintf(&line[0], "%s %s %d %d %d %d\n",                   /* yes, format the response */
              r->name, "block", r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      response(line);                                            /* send it to the enquirer */
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  for (r = initial_rectangle_list_iterator(guard_list, &i);      /* search the guard list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      sprintf(&line[0], "%s %s %d %d %d %d\n",                   /* yes, format the response */
              r->name, "guard", r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      response(line);                                            /* send it to the enquirer */
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  for (r = initial_rectangle_list_iterator(hold_list, &i);       /* search the hold list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      sprintf(&line[0], "%s %s %d %d %d %d\n",                   /* yes, format the response */
              r->name, "hold", r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      response(line);                                            /* send it to the enquirer */
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  for (r = initial_rectangle_list_iterator(image_list, &i);      /* search the image list */
       continue_rectangle_list_iterator(&i);
       r = next_rectangle_list_iterator(&i)) {

    if (regexec(preg, r->name, 0, NULL, 0) == 0) {               /* does the name match the regular expression? */
      sprintf(&line[0], "%s %s %d %d %d %d\n",                   /* yes, format the response */
              r->name, "image", r->ul_x, r->ul_y, r->lr_x, r->lr_y);
      response(line);                                            /* send it to the enquirer */
      found = TRUE;                                              /* remember that we found at least one */
    }
  }

  regfree(preg);                                                 /* free the compiled regular expression */  

  if (found == TRUE) {                                           /* did we find any names that matched? */
    response("OK\n");                                            /* yes, good */
    return;
  } else {
    response("NO NAMES MATCH\n");                                /* no, bad (maybe?) */
    return;
  }
}


typedef void (*thunk)(void);                                     /* a thunk is a function with no arguments that returns no value */

static char* word[] = { "block",     "guard",     "hold",     "image",     "kill",     "new",     "place",     "show" };     /* command names */
static thunk deed[] = {  block_cmd,   guard_cmd,   hold_cmd,   image_cmd,   kill_cmd,   new_cmd,   place_cmd,   show_cmd  }; /* command functions */

static int num_commands = sizeof(word)/sizeof(word[0]);          /* how many commands there are */

static int EOF_found = FALSE;                                    /* remembers whether EOF has already been seen */

void commander(void) {                                           /* read a command and dispatch it accordingly */
  char cmd_bfr[4096];                                            /* FIXME buffer overflow is possible !!! */
  char* raw_cmd;
  char tok_bfr[4096];
  char* token = &tok_bfr[0];
  int i;

  if (EOF_found == TRUE) {                                       /* have we already seen end-of-file? */
    return;                                                      /* yes, do nothing */
  }

  command = &cmd_bfr[0];                                         /* point to command buffer */
  raw_cmd = enquiry();                                           /* get the raw command */

  if ((int)raw_cmd == EOF) {                                     /* end of file? */
    EOF_found = TRUE;                                            /* yes, remember for next time */
    return;                                                      /* do nothing */
  }

  if (raw_cmd == NULL) {                                         /* no command at all? */
    return;                                                      /* yes, do nothing */
  }

  strcpy(command, raw_cmd);                                      /* preserve the command line */
  command[strlen(command) - 1] = '\0';                           /* chomp off the trailing newline */

  if (command[0] == '\0' ) {                                     /* zero length string? */
    return;                                                      /* yes, do nothing */
  }

  token = next_token();                                          /* got a command line, get its first token, the command word */
  for (i = 0; i < num_commands; i++) {                           /* for each possible command word... */

    if (strcmp(token, word[i]) == 0) {                           /* find the word? */
      (*deed[i])();                                              /* yes, do the deed! */
      response(".\n");
      return;
    }
  }

  response("INVALID COMMAND\n");                                 /* command word not found -- invalid command! */
  response(".\n");
  return;
}