Logo Search packages:      
Sourcecode: unixcw version File versions  Download package

cwcp.c

/*
 * UnixCW - Unix CW (Morse code) training program
 * Copyright (C) 2001, 2002  Simon Baldwin (simonb@caldera.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 * cwcp - Curses-based Morse practice program, with menuing interface.
 *
 */

/* Include files. */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/param.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <curses.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>

#if defined(HAVE_STRING_H)
#     include <string.h>
#endif /* HAVE_STRING_H */
#if defined(HAVE_STRINGS_H)
#     include <strings.h>
#endif /* HAVE_STRINGS_H */

#if defined(HAVE_GETOPT_H)
#     include <getopt.h>                  /* Linux */
#endif /* HAVE_GETOPT_H */

#if !defined(MAXPATHLEN)
#     define      MAXPATHLEN  _POSIX_PATH_MAX   /* OpenServer */
#endif /* not MAXPATHLEN */

/* Include definitions of CW library functions, and dictionary. */
#include "cwlib.h"
#include "cwcpwords.h"

/* Definitions. */
#define     TITLE       "UNIX Morse Tutor V2.2, (C) 1997-2002 Simon Baldwin"
#define     VERSION           "cwcp version 2.2"
#define     COPYRIGHT   "Copyright (C) 2001, 2002  Simon Baldwin\n"     \
"This program comes with ABSOLUTELY NO WARRANTY; for details\n"         \
"please see the file 'COPYING' supplied with the source code.\n"  \
"This is free software, and you are welcome to redistribute it\n" \
"under certain conditions; again, see 'COPYING' for details.\n"         \
"This program is released under the GNU General Public License."
#define     INTRODUCTION                                          \
"Cwcp is an interactive Morse code tutor program, designed\n"           \
"both for learning Morse code for the first time, and for\n"            \
"experienced Morse users who want, or need, to improve\n"         \
"their receiving speed.\n\n"                                \
"To use the program, select a mode from those listed on\n"        \
"the left, and begin sending by pressing Return or F9.\n\n"       \
"You can vary the speed, tone, volume, and spacing of the\n"            \
"Morse code at any time using the appropriate keys.\n\n"          \
"To stop sending, press F9.  To stop the program, select\n"       \
"Exit from the Mode menu, or use F12 or ^C.\n"

#define     ASC_NUL                 '\0'        /* End of string */
#define     ASC_SPACE         ' '         /* ASCII space char */
#define     ASC_CR                  '\012'            /* ASCII CR char */
#define     ASC_ESC                 '\033'            /* ASCII Esc char */
#define     ASC_CTRLL         '\014'            /* ASCII ^L char */
#define     ASC_CTRLC         '\003'            /* ASCII ^C char */

#define     ASC_OPENBRACKET         '['         /* ASCII [ char */
#define     ASC_CLOSEBRACKET  ']'         /* ASCII ] char */
#define     ASC_OPENBRACE           '{'         /* ASCII { char */
#define     ASC_CLOSEBRACE          '}'         /* ASCII } char */

#define     ASC_FNS                 '/'         /* ASCII filename sep char */

/* Other general macros. */
#define     GENERAL_BUFFER          80          /* General buffer */

/* Alternative F-keys for folks without (some) F-keys. */
#define     CTRL_OFFSET 0100              /* Ctrl keys are 'X' - 0100 */
#define     PSEUDO_KEYF1      ('Q'-CTRL_OFFSET) /* Alternative FKEY1 */
#define     PSEUDO_KEYF2      ('W'-CTRL_OFFSET) /* Alternative FKEY2 */
#define     PSEUDO_KEYF3      ('E'-CTRL_OFFSET) /* Alternative FKEY3 */
#define     PSEUDO_KEYF4      ('R'-CTRL_OFFSET) /* Alternative FKEY4 */
#define     PSEUDO_KEYF5      ('T'-CTRL_OFFSET) /* Alternative FKEY5 */
#define     PSEUDO_KEYF6      ('Y'-CTRL_OFFSET) /* Alternative FKEY6 */
#define     PSEUDO_KEYF7      ('U'-CTRL_OFFSET) /* Alternative FKEY7 */
#define     PSEUDO_KEYF8      ('I'-CTRL_OFFSET) /* Alternative FKEY8 */
#define     PSEUDO_KEYF9      ('A'-CTRL_OFFSET) /* Alternative FKEY9 */
#define     PSEUDO_KEYF10     ('S'-CTRL_OFFSET) /* Alternative FKEY10 */
#define     PSEUDO_KEYF11     ('D'-CTRL_OFFSET) /* Alternative FKEY11 */
#define     PSEUDO_KEYF12     ('F'-CTRL_OFFSET) /* Alternative FKEY12 */
#define     PSEUDO_KEYNPAGE   ('O'-CTRL_OFFSET) /* Alternative PageDown */
#define     PSEUDO_KEYPPAGE   ('P'-CTRL_OFFSET) /* Alternative PageUp */

/* Step values for the UI control of the CW parameters. */
#define     STEP_WPM          1
#define     STEP_HZ                 100
#define     STEP_VOL          5
#define     STEP_GAP          1
#define     STEP_TIME         1
#define     MIN_TIME          1
#define     MAX_TIME          99

/* Some colour definitions. */
static const short      colour_array[] = {
            COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
            COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE };
#define     NUM_COLOURS       ((int) (sizeof (colour_array) \
                              / sizeof (colour_array[0])))
#define     BOX_COLOURS       1           /* Normal colour pair */
#define     DISP_COLOURS            2           /* Blue colour pair */
#define     DISP_FGIND        7           /* White foreground */
#define     DISP_BGIND        4           /* Blue background */
#define     BOX_FGIND         7           /* White foreground */
#define     BOX_BGIND         0           /* Black background */


/*
 * Import the bulk dictionary data into character arrays, so it can be
 * more easily manipulated and arranged.
 */
static const char *cwcp_alphabetic[]      = { CWCP_ALPHABETIC };
static const char *cwcp_numeric[]         = { CWCP_NUMERIC };
static const char *cwcp_alphanumeric[]    = { CWCP_ALPHANUMERIC };
static const char *cwcp_all_characters[]  = { CWCP_ALL_CHARACTERS };
static const char *cwcp_eish5[]           = { CWCP_EISH5 };
static const char *cwcp_tmo0[]            = { CWCP_TMO0 };
static const char *cwcp_auv4[]            = { CWCP_AUV4 };
static const char *cwcp_ndb6[]            = { CWCP_NDB6 };
static const char *cwcp_kxffrp[]          = { CWCP_KXffRP };
static const char *cwcp_flyqc[]           = { CWCP_FLYQC };
static const char *cwcp_wj1gz[]           = { CWCP_WJ1GZ };
static const char *cwcp_x23789[]          = { CWCP_x23789 };
static const char *cwcp_ffffff_1[]  = { CWCP_ffffff_1 };
static const char *cwcp_ffffff_2[]  = { CWCP_ffffff_2 };
static const char *cwcp_words[]           = { CWCP_3_LETTER_WORDS,
                                        CWCP_4_LETTER_WORDS,
                                        CWCP_5_LETTER_WORDS };
static const char *cwcp_cw_words[]  = { CWCP_CW_WORDS };
static const char *cwcp_paris[]           = { CWCP_PARIS };


/*
 * Table of interface operating modes, their descriptions, related
 * dictionaries, and data on how to send for that mode.  The program is
 * always in one of these modes, indicated by current_mode, which is a
 * index into the modes[] array.
 */
typedef     enum {M_NULL,M_DICTIONARY,M_KEYBOARD,M_EXIT}
                        mode_type_t;      /* Enumerated mode type */
typedef     struct {
      const char        *description;     /* Text mode description */
      const mode_type_t type;       /* Mode type, dict or kb */
      const int         groupsize;  /* Size of groups if dict */
      const char        **wordlist; /* Word list if dictionary */
      const int         list_length;      /* Length of word list */
} smode_t;
#define     DICTIONARY(A)           &(A)[0], (sizeof (A) / sizeof ((A)[0]))
static const smode_t    modes[] = {
/*TOP*/     { NULL,                 M_NULL,           0,    NULL, 0 },
      { "Letter Groups     ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_alphabetic) },
      { "Number Groups     ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_numeric) },
      { "Alphanum Groups   ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_alphanumeric) },
      { "All Char Groups   ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_all_characters) },
      { "English Words     ", M_DICTIONARY,     1,
                              DICTIONARY (cwcp_words) },
      { "CW Words          ", M_DICTIONARY,     1,
                              DICTIONARY (cwcp_cw_words) },
      { "PARIS Calibrate   ", M_DICTIONARY,     1,
                              DICTIONARY (cwcp_paris) },
      { "EISH5 Groups      ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_eish5) },
      { "TMO0 Groups       ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_tmo0) },
      { "AUV4 Groups       ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_auv4) },
      { "NDB6 Groups       ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_ndb6) },
      { "KX=-RP Groups     ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_kxffrp) },
      { "FLYQC Groups      ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_flyqc) },
      { "WJ1GZ Groups      ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_wj1gz) },
      { "23789 Groups      ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_x23789) },
      { ",?.;)/ Groups     ", M_DICTIONARY,     5,
                              DICTIONARY (cwcp_ffffff_1) },
      { "\"'$(+:_ Groups    ",M_DICTIONARY,     5,
                              DICTIONARY (cwcp_ffffff_2) },
      { "Keyboard          ", M_KEYBOARD, 0,    NULL, 0 },
      { "Exit(F12)         ", M_EXIT,           0,    NULL, 0 },
/*END*/     { NULL,                 M_NULL,           0,    NULL, 0 } };

static int  current_mode      = 1;        /* Current program mode index */
#define     NUM_MODES         ((sizeof (modes) / sizeof (modes[0])) - 2)

/*
 * Table of keyboard translations to create a handful of prosigs from some
 * otherwise unused characters.
 */
typedef struct {
      const char        character;  /* Prosig character */
      const char        *prosig;    /* Prosig equivalent */
      const int         combination;      /* true if characters sound as
                                       one single item */
} prosig_t;
static const prosig_t   prosigs[] = {
      { '%', "VA", TRUE }, { '!', "BK", TRUE }, { '@', "AR", TRUE },
      { '^', "AA", TRUE }, { '*', "AS", TRUE }, { '&', "ES", FALSE },
      { '#', "KN", TRUE },
      { '\0', NULL, FALSE } };

/* Argument handling macros. */
#define     MAXARGS                 128         /* Args to exec */
#define     MAXOPTSTR         1024        /* Longest _OPTIONS env var */
#define     ARGS_WHITESPACE         " \t"       /* Argument separators */
#define     ENV_OPTIONS       "CWCP_OPTIONS"    /* Env string holding options */

/* Initial values for some variables. */
#define     INITIAL_TIME            15

/* A handful of defines to send '73' on exit */
#define     x73_SPEED         30
#define     x73_FREQUENCY           1000
#define     x73_GAP                 0
#define     x73_STRING        "73 "

/* Warbling tone stuff. */
#define     WARBLE_USECS            20000       /* Use 20mS tones */
#define     WARBLE_TONE1            500         /* 500/1000 Hz */
#define     WARBLE_TONE2            1000

static enum state {STATE_IDLE,STATE_ACTIVE}
            sending_state     = STATE_IDLE;     /* Shows state of sounding */

static int  practice_time     = INITIAL_TIME;   /* Practice timer */
static int  practice_start_timestamp
                        = 0;        /* Time() on practice start */
static bool do_colours  = TRUE;           /* Initially attempt colours */
static bool do_cheerio  = TRUE;           /* Send 73 on exit */

/* Circular buffer of cw characters awaiting send. */
#define     CW_QUEUE          256         /* Allow 256 queued chars */
static unsigned char    cq_queue[ CW_QUEUE ];   /* Buffer */
static int        cq_head = 0;            /* Buffer head index */
static int        cq_tail = 0;            /* Buffer tail index */
static enum{CQ_IDLE,CQ_ACTIVE}
                  cq_dequeue_state = CQ_IDLE;
                                    /* State of dequeuing chars */
static int  dequeue_y = -1, dequeue_x = -1;     /* Dequeue curses coordinates */
static int  enqueue_y = -1, enqueue_x = -1;     /* Enqueue curses coordinates */

/* Colour values as arrays into colour_array. */
static int  disp_fgind  = DISP_FGIND;     /* White foreground */
static int  disp_bgind  = DISP_BGIND;     /* Blue background */
static int  box_fgind   = BOX_FGIND;      /* White foreground */
static int  box_bgind   = BOX_BGIND;      /* Black background */

/* Curses windows. */
static WINDOW     *scr, *base_bkgd;
static WINDOW     *titlebox,  *modebox,  *charbox,  *wpmbox,  *hzbox,  *volbox,
            *gapbox,  *timebox;
static WINDOW     *titledisp, *modedisp, *chardisp, *wpmdisp, *hzdisp, *voldisp,
            *gapdisp, *timedisp;

/* Base name of the program, from argv[0]. */
static char *argv0      = NULL;


/*
 * print_usage()
 *
 * Print out a brief message directing the user to the help function.
 */
static void
print_usage ()
{
      fprintf (stderr,
#if defined(HAVE_GETOPT_LONG)
            "Try '%s --help' for more information.\n",
#else /* not HAVE_GETOPT_LONG */
            "Try '%s -h' for more information.\n",
#endif /* not HAVE_GETOPT_LONG */
            argv0);
      exit (1);
}

/*
 * print_help()
 *
 * Print out a brief page of help information.
 */
static void
print_help ()
{
      int   min_speed, max_speed;         /* Cw library speed limits */
      int   min_frequency, max_frequency; /* Cw library frequncy limits */
      int   min_volume, max_volume;       /* Cw library volume limits */
      int   min_gap, max_gap;       /* Cw library gap limits */
      assert (argv0 != NULL);

      /* Read the cw library limits on its operating parameters. */
      cw_get_speed_limits     (&min_speed, &max_speed);
      cw_get_frequency_limits (&min_frequency, &max_frequency);
      cw_get_volume_limits    (&min_volume, &max_volume);
      cw_get_gap_limits (&min_gap, &max_gap);

      printf (
#if defined(HAVE_GETOPT_LONG)
      "Usage: %s [options...]\n\n\
      -s, --sound=SOURCE      generate sound on SOURCE [default 'soundcard']\n\
                        one of 's[oundcard]', 'c[onsole]', or 'b[oth]'\n\
      -x, --sdevice=SDEVICE   use SDEVICE for soundcard [default %s]\n\
      -y, --mdevice=MDEVICE   use MDEVICE for sound mixer [default %s]\n\
      -d, --cdevice=CDEVICE   use CDEVICE for sound ioctl [default %s]\n\
      -w, --wpm=WPM           set initial words per minute [default %d]\n\
                        valid WPM values are between %d and %d\n\
      -t, --hz,--tone=HZ      set initial tone to HZ [default %d]\n\
                        valid HZ values are between %d and %d\n\
      -v, --volume=PERCENT    set initial volume to PERCENT [default %d]\n\
                        valid PERCENT values are between %d and %d\n\
      -g, --gap=GAP           set extra gap between letters [default %d]\n\
                        valid GAP values are between %d and %d\n\
      -p, --time=TIME         set initial practice time [default %d mins]\n\
                        valid TIME values are between %d and %d\n\
      -c, --colours=CSET      set initial display colours where available\n\
                        [default %d,%d,%d,%d]\n\
      -m, --mono        specify no colours [default colours]\n\
      -q, --no73        specify no cheery '73' on exit [default on]\n\
      -h, --help        print this message\n\
      -V, --version           output version information and exit\n\n",
#else /* not HAVE_GETOPT_LONG */
      "Usage: %s [options...]\n\n\
      -s          generate sound on SOURCE [default 'soundcard']\n\
                  one of 's[oundcard]', 'c[onsole]', or 'b[oth]'\n\
      -x SDEVICE  use SDEVICE for soundcard [default %s]\n\
      -y MDEVICE  use MDEVICE for sound mixer [default %s]\n\
      -d CDEVICE  use CDEVICE for sound ioctl [default %s]\n\
      -w WPM            set initial words per minute [default %d]\n\
                  valid WPM values are between %d and %d\n\
      -t HZ       set initial tone to HZ [default %d]\n\
                  valid HZ values are between %d and %d\n\
      -v PERCENT  set initial volume to PERCENT [default %d]\n\
                  valid PERCENT values are between %d and %d\n\
      -g GAP            set extra gap between letters [default %d]\n\
                  valid GAP values are between %d and %d\n\
      -p TIME           set initial practice time [default %d mins]\n\
                  valid TIME values are between %d and %d\n\
      -c CSET           set initial display colours where available\n\
                  [default %d,%d,%d,%d]\n\
      -m          specify no colours [default colours]\n\
      -q          specify no cheery '73' on exit [default on]\n\
      -h          print this message\n\
      -V          output version information and exit\n\n",
#endif /* not HAVE_GETOPT_LONG */
      argv0,
      cw_get_soundcard_file (),           cw_get_soundmixer_file (),
      cw_get_console_file (),
      cw_get_send_speed (),   min_speed,  max_speed,
      cw_get_frequency (),    min_frequency,    max_frequency,
      cw_get_volume (), min_volume, max_volume,
      cw_get_gap (),          min_gap,    max_gap,
      INITIAL_TIME,           MIN_TIME,   MAX_TIME,
      DISP_FGIND, DISP_BGIND, BOX_FGIND, BOX_BGIND);
}


/*
 * parse_cmdline()
 *
 * Parse the command line options for initial values for the various
 * global and flag definitions. 
 */
static void
parse_cmdline (int argc, char **argv)
{
      int   c;                      /* Option character */
      int   csound = FALSE, ssound = TRUE;      /* Sound source options */
      char  *cdevice = NULL, *sdevice = NULL,
            *mdevice = NULL;        /* Sound generation options */
      int   intarg;                       /* General integer argument */
      int   argind;                       /* Loop index */
      char  env_options[ MAXOPTSTR ];     /* Env options string */
      char  *sptr;                        /* String pointer */
      char  *local_argv[ MAXARGS ];       /* Local argv array */
      int   local_argc;             /* Local argc */
#if defined(HAVE_GETOPT_LONG)
      int   option_index;                 /* Option index */
      static const struct option long_options[] = {   /* Options table */
            { "sound",  1, 0, 's' },
            { "cdevice",      1, 0, 'd' },
            { "sdevice",      1, 0, 'x' },
            { "mdevice",      1, 0, 'y' },
            { "volume", 1, 0, 'v' }, { "tone",  1, 0, 't' },
            { "hz",           1, 0, 't' }, { "wpm",   1, 0, 'w' },
            { "gap",    1, 0, 'g' }, { "time",  1, 0, 'p' },
            { "colours",      1, 0, 'c' }, { "colors",1, 0, 'c' },
            { "mono",   0, 0, 'm' }, { "no73",  0, 0, 'q' },
            { "help",   0, 0, 'h' },
            { "version",      0, 0, 'V' },
            { 0, 0, 0, 0 }};
#endif /* HAVE_GETOPT_LONG */

      /* Set argv0 to be the basename part of the program name. */
      argv0 = argv[0] + strlen (argv[0]);
      while (*argv0 != ASC_FNS && argv0 > argv[0])
            argv0--;
      if (*argv0 == ASC_FNS)
            argv0++;

      /*
       * Build a new view of argc and argv by first prepending
       * the strings from ..._OPTIONS, if defined, then putting the
       * command line args on (so they take precedence).
       */
      local_argc = 0;
      local_argv[ local_argc++ ] = argv[0];
      if (getenv (ENV_OPTIONS) != NULL)
          {
            strcpy (env_options, getenv (ENV_OPTIONS));
            sptr = env_options;
            while (local_argc < MAXARGS - 1)
                {
                  while (strchr (ARGS_WHITESPACE, *sptr) != NULL
                              && *sptr != ASC_NUL)
                        sptr++;
                  if ( *sptr == ASC_NUL )
                        break;
                  else
                      {
                        local_argv[ local_argc++ ] = sptr;
                        while (strchr (ARGS_WHITESPACE, *sptr)
                                    == NULL && *sptr != ASC_NUL)
                              sptr++;
                        if (strchr (ARGS_WHITESPACE, *sptr)
                                    != NULL && *sptr != ASC_NUL)
                            {
                              *sptr = ASC_NUL;
                              sptr++;
                            }
                      }
                }
          }
      for (argind = 1; argind < argc; argind++)
          {
            local_argv[ local_argc++ ] = argv[ argind ];
          }

      /* Process every option. */
      while (TRUE)
          {
#if defined(HAVE_GETOPT_LONG)
            c = getopt_long (local_argc, local_argv,
                              "s:d:x:y:t:v:w:g:p:c:mqhV",
                              long_options, &option_index);
#else /* not HAVE_GETOPT_LONG */
            c = getopt (local_argc, local_argv,
                              "s:d:x:y:t:v:w:g:p:c:mqhV");
#endif /* not HAVE_GETOPT_LONG */
            if (c == -1)
                  break;

            switch (c)
                {
                  case 's':
                        if (!strcmp (optarg, "console")
                                    || !strcmp (optarg, "c"))
                            {
                              csound = TRUE; ssound = FALSE;
                            }
                        else if (!strcmp (optarg, "soundcard")
                                    || !strcmp (optarg, "s"))
                            {
                              csound = FALSE; ssound = TRUE;
                            }
                        else if (!strcmp (optarg, "both")
                                    || !strcmp (optarg, "b"))
                            {
                              csound = TRUE; ssound = TRUE;
                            }
                        else
                            {
                              fprintf (stderr,
                                  "%s: invalid sound source\n",
                                                      argv0);
                              exit (1);
                            }
                        break;

                  case 'd':
                        cdevice = optarg;
                        break;

                  case 'x':
                        sdevice = optarg;
                        break;

                  case 'y':
                        mdevice = optarg;
                        break;

                  case 't':
                        if (sscanf (optarg, "%d", &intarg) != 1
                              || cw_set_frequency (intarg) != 0)
                            {
                              fprintf (stderr,
                                  "%s: invalid tone value\n", argv0);
                              exit (1);
                            }
                        break;

                  case 'v':
                        if (sscanf (optarg, "%d", &intarg) != 1
                              || cw_set_volume (intarg) != 0 )
                            {
                              fprintf (stderr,
                                 "%s: invalid volume value\n", argv0);
                              exit (1);
                            }
                        break;

                  case 'w':
                        if (sscanf (optarg, "%d", &intarg) != 1
                              || cw_set_send_speed (intarg) != 0)
                            {
                              fprintf (stderr,
                                  "%s: invalid wpm value\n", argv0);
                              exit (1);
                            }
                        break;

                  case 'g':
                        if (sscanf (optarg, "%d", &intarg) != 1
                              || cw_set_gap (intarg) != 0)
                            {
                              fprintf (stderr,
                                  "%s: invalid gap value\n", argv0);
                              exit (1);
                            }
                        break;

                  case 'p':
                        if (sscanf (optarg, "%d", &practice_time) != 1
                              || practice_time < MIN_TIME
                              || practice_time > MAX_TIME)
                                  {
                              fprintf (stderr,
                                  "%s: invalid time value\n", argv0);
                              exit (1);
                            }
                        break;

                  case 'c':
                        if (sscanf (optarg, "%d,%d,%d,%d",
                                    &disp_fgind, &disp_bgind,
                                    &box_fgind,  &box_bgind) != 4
                              || disp_fgind < 0
                              || disp_fgind >= NUM_COLOURS
                              || disp_bgind < 0
                              || disp_bgind >= NUM_COLOURS
                              || box_fgind < 0
                              || box_fgind >= NUM_COLOURS
                              || box_bgind < 0
                              || box_bgind >= NUM_COLOURS)
                            {
                              fprintf (stderr,
                                  "%s: invalid colours value\n",
                                                      argv0);
                              exit (1);
                            }
                        break;

                  case 'm':
                        do_colours = FALSE;
                        break;

                  case 'q':
                        do_cheerio = FALSE;
                        break;

                  case 'h':
                        print_help ();
                        exit (0);

                  case 'V':
                        printf ("%s, ", VERSION);
                        printf ("%s\n", COPYRIGHT);
                        exit (0);

                  case '?':
                        print_usage ();

                  default:
                        fprintf (stderr,
                              "%s: getopts returned %c\n",
                                                argv0, c);
                        exit (1);
                }

          }
      if (optind != local_argc)
            print_usage ();

      /* Deal with odd argument combinations. */
      if (!csound && cdevice != NULL)
          {
            fprintf (stderr, "%s: no console sound: -d invalid\n", argv0);
            print_usage ();
          }
      if (!ssound && sdevice != NULL)
          {
            fprintf (stderr, "%s: no soundcard sound: -x invalid\n", argv0);
            print_usage ();
          }
      if (!ssound && mdevice != NULL)
          {
            fprintf (stderr, "%s: no soundcard sound: -y invalid\n", argv0);
            print_usage ();
          }

      /* First set up soundcard sound if required. */
      if (ssound)
          {
            if (sdevice != NULL)
                  cw_set_soundcard_file (sdevice);
            if (cw_soundcard_possible () != 0)
                {
                  fprintf (stderr,
                        "%s: cannot set up soundcard sound\n", argv0);
                  perror (cw_get_soundcard_file ());
                  exit (1);
                }
            if (mdevice != NULL)
                  cw_set_soundmixer_file (mdevice);
          }
      cw_set_soundcard_sound (ssound);

      /* Now set up console sound, again if required. */
      if (csound)
          {
            if (cdevice != NULL)
                  cw_set_console_file (cdevice);
            if (cw_console_possible () != 0)
                {
                  fprintf (stderr,
                        "%s: cannot set up console sound\n", argv0);
                  perror (cw_get_console_file ());
                  exit (1);
                }
          }
      cw_set_console_sound (csound);
}


/*
 * change_state()
 *
 * Change the state of the program, keeping the display updated as to what
 * is happening.
 */
static void cwlib_callback_event ();      /* Forward declaration */
static void dequeue_character ();         /* Forward declaration */
static void
change_state (enum state new_state)
{
      static int        last_mode = -1;   /* Detect changes of mode */

      /* Ignore the call if there is no state change. */
      if (new_state != sending_state)
          {
            /* Now update the display depending on the new state. */
            if (new_state == STATE_ACTIVE)
                {
                  /* Produce a short start warning warble. */
                  cw_flush_tone_queue ();
                  cw_queue_tone (20000,  500);
                  cw_queue_tone (20000, 1000);
                  cw_tone_queue_wait ();

                  /* Update the program state. */
                  sending_state = new_state;

                  mvwaddstr (charbox, 0, 1, "Sending(F9 or Esc to exit)");
                  wnoutrefresh (charbox);

                  /* See if the mode changed since the last start. */
                  if (current_mode != last_mode)
                      {
                        /*
                         * If the mode changed, clear the display
                         * window, and reset the en/dequeue character
                         * coordinates to 0/-1.
                         */
                        werase (chardisp);
                        wmove (chardisp, 0, 0);
                        wrefresh (chardisp);
                        enqueue_y = enqueue_x = 0;
                        dequeue_y = dequeue_x = -1;

                        /*
                         * If we are starting something new, reset the
                         * practice start time variable.
                         */
                        practice_start_timestamp = time (NULL);
                      }
                  last_mode = current_mode;
                }
            else
                {
                  /* Update the program state. */
                  sending_state = new_state;

                  box (charbox, 0, 0);
                  mvwaddstr (charbox, 0, 1, "Start(F9)");
                  wnoutrefresh (charbox);
                  touchwin (chardisp);
                  wnoutrefresh (chardisp);

                  /*
                   * Remove everything in the outgoing character queue,
                   * and force the dequeue to idle manually.  We can't
                   * just let the callbacks do it, since we've set the
                   * program state to idle, meaning that the callbacks
                   * are now ignored.
                   */
                  cq_tail = cq_head;
                  while (cq_dequeue_state == CQ_ACTIVE)
                        dequeue_character ();

                  /*
                   * If we are going idle, cancel all pending tone
                   * output, and then produce a nice little warble.
                   */
                  cw_flush_tone_queue ();
                  cw_queue_tone (WARBLE_USECS, WARBLE_TONE1);
                  cw_queue_tone (WARBLE_USECS, WARBLE_TONE2);
                  cw_queue_tone (WARBLE_USECS, WARBLE_TONE1);
                  cw_queue_tone (WARBLE_USECS, WARBLE_TONE2);
                  cw_tone_queue_wait ();
                }
            doupdate ();
          }
}


/*
 * bye_bye()
 *
 * Sends a cheery little "73" from the system, just prior to exit.
 */
static void
bye_bye ()
{
      /* Ignore the call if we don't want to send '73'. */
      if (do_cheerio)
          {
            /*
             * Set the sending parameters so that we get a speedy exit
             * from the program.  Then send the string.
             */
            cw_set_send_speed (x73_SPEED);
            cw_set_frequency (x73_FREQUENCY);
            cw_set_gap (x73_GAP);
            if (cw_send_string (x73_STRING) != 0)
                {
                  perror ("cw_send_string");
                  exit (1);
                }
            cw_tone_queue_wait ();
          }
}


/*
 * dequeue_character()
 *
 * Called when the cw send buffer is nearly empty.  This routine takes a
 * character, if one is ready, from the send queue and sends it.
 */
static void
dequeue_character ()
{
      unsigned char           c;          /* Character from the queue */
      int               max_y, max_x;     /* Window dimensions */
      const prosig_t          *prosigp;   /* Prosig table pointer */
      const char        *sptr;            /* Prosig equivalent pointer */

      /* If dequeue is idle, ignore the call. */
      if (cq_dequeue_state == CQ_IDLE)
            return;

      /* Undisplay any previous dequeued pointer character. */
      if (dequeue_y != -1 && dequeue_x != -1)
          {
            wmove (chardisp, dequeue_y, dequeue_x);
            waddch (chardisp, winch (chardisp) & ~A_REVERSE);
            getyx (chardisp, dequeue_y, dequeue_x);
            wrefresh (chardisp);
          }

      /* Dequeue a character if we have one available to handle. */
      if (cq_tail != cq_head)
          {
            /* Take the next character off the queue. */
            if (cq_tail + 1 < CW_QUEUE)
                  cq_tail++;
            else
                  cq_tail = 0;
            c = cq_queue[cq_tail];

            /* Display the new character being sent. */           
            if (dequeue_y == -1 && dequeue_x == -1)
                  dequeue_y = dequeue_x = 0;
            wmove (chardisp, dequeue_y, dequeue_x);
            waddch (chardisp, winch (chardisp) | A_REVERSE);
            wrefresh (chardisp);

            /* Try to send the character to the cw send buffer. */
            if (cw_send_character (c) != 0)
               {
                  /* Not sendable as directly; how about a prosig? */
                  for (prosigp = prosigs; prosigp->prosig != NULL
                                    && prosigp->character != c; )
                        prosigp++;
                  if (prosigp->prosig == NULL)
                      {
                        perror ("cw_send_character");
                        exit (1);
                      }
      
                  /*
                   * Found a match, so send it as either partial or
                   * complete characters.
                   */
                  if (prosigp->combination)
                      {
                        for (sptr = prosigp->prosig;
                                    *(sptr + 1) != ASC_NUL; sptr++)
                            {
                              if (cw_send_character_partial (*sptr)
                                                      != 0)
                                  {
                                    perror
                                      ("cw_send_character_partial");
                                    exit (1);
                                  }
                            }
                        if (cw_send_character (*sptr) != 0)
                            {
                              perror ("cw_send_character");
                              exit (1);
                            }
                      }
                  else
                        if (cw_send_string (prosigp->prosig) != 0)
                            {
                              perror ("cw_send_string");
                              exit (1);
                            }
               }
          }
      else
          {
            /*
             * Time to idle; set the state, and reset the dequeue
             * coordinates.  We want the dequeue to remain one position
             * behind the enqueue coordinates.
             */
            cq_dequeue_state = CQ_IDLE;

            /*
             * If by some chance enqueue is at the screen start, then set
             * dequeue back to -1's.
             */
            if (enqueue_x == 0 && enqueue_y == 0)
                  dequeue_y = dequeue_x = -1;
            else
                {
                  /* Set the dequeue to be behind by one. */
                  dequeue_y = enqueue_y;
                  dequeue_x = enqueue_x - 1;

                  /* Adjust for retreats over line beginnings. */
                  getmaxyx (chardisp, max_y, max_x);
                  if (dequeue_x < 0)
                      {
                        dequeue_y--;
                        dequeue_x = max_x - 1;
                      }
                }
          }
}


/*
 * queue_character()
 *
 * Queues a character for sending by the cw sender when ready.
 */
static void
queue_character (unsigned char c)
{
      int         saved_y,saved_x;  /* Saved window coordinate */
      int         max_y, max_x;           /* Window dimensions */
      const prosig_t    *prosigp;         /* Prosig table pointer */
      int         new_cq_head;            /* New value of cq_head */

      /* Reject any characters that we won't be able to send. */
      if (cw_check_character (c) != 0)
         {
            /* Not sendable as a direct character, how about a prosig? */
            for (prosigp = prosigs; prosigp->prosig != NULL
                                    && prosigp->character != c; )
                  prosigp++;
            if (prosigp->prosig == NULL)
                  return;
         }

      /* Calculate the new character queue head index. */
      if (cq_head + 1 < CW_QUEUE)
            new_cq_head = cq_head + 1;
      else
            new_cq_head = 0;

      /*
       * If the new value has hit the current queue tail, the queue is
       * full.  In this case, just ignore the character.
       */
      if (new_cq_head == cq_tail)
            return;

      /* Add the character to the queue.  */
      cq_queue[new_cq_head] = c;

      /* Display the character at the end of the display window. */
      saved_y = enqueue_y;
      saved_x = enqueue_x;
      wmove (chardisp, enqueue_y, enqueue_x);
      waddch (chardisp, toupper (c));
      getyx (chardisp, enqueue_y, enqueue_x);
      wrefresh (chardisp);

      /*
       * As a final indignity, curses forces us to adjust the dequeue
       * coordinates to take account of any scroll that the above produced.
       */
      getmaxyx (chardisp, max_y, max_x);
      if (enqueue_y == saved_y
                  && enqueue_x < saved_x)
            dequeue_y--;

      /*
       * Update the head index, and if the dequeue is currently idle, set
       * it going.
       */
      if (cq_dequeue_state == CQ_IDLE)
          {
            cq_head = new_cq_head;
            cq_dequeue_state = CQ_ACTIVE;
          }
      else
            cq_head = new_cq_head;
}


/*
 * interface_init()
 *
 * Initialize the user interface, boxes and windows.
 */
static void
interface_init ()
{
      char  buffer[ GENERAL_BUFFER ];     /* String buffer - general */
      int   index;                        /* Modes string index */

/* Convenience macros to save a lot of bulky code. */
#define     UI_INIT_BOX(BOX)                                \
      if (do_colours && has_colors ())                      \
          {                                           \
            wbkgdset ((BOX), COLOR_PAIR(BOX_COLOURS)|' ');        \
            werase ((BOX));                                 \
            wattron ((BOX), COLOR_PAIR(BOX_COLOURS));       \
          }                                           \
      else                                            \
            wattron ((BOX), A_REVERSE);                     \
      box ((BOX), 0, 0);

#define     UI_INIT_DISPLAY(DISPLAY)                              \
      if (do_colours && has_colors ())                      \
          {                                           \
            wbkgdset ((DISPLAY), COLOR_PAIR(DISP_COLOURS)|' ');   \
            wattron ((DISPLAY), COLOR_PAIR(DISP_COLOURS));        \
            werase ((DISPLAY));                             \
          }

      /* Start curses initialization */
      scr = initscr ();
      wrefresh (scr);
      if (do_colours && has_colors ())
          {
            start_color ();
            init_pair (BOX_COLOURS,
                  colour_array[box_fgind], colour_array[box_bgind]);
            init_pair (DISP_COLOURS,
                  colour_array[disp_fgind], colour_array[disp_bgind]);
            base_bkgd = newwin (24, 80, 0, 0);
            wbkgdset (base_bkgd, COLOR_PAIR (BOX_COLOURS)|' ');
            werase (base_bkgd);
            wrefresh (base_bkgd);
          }

      /* Create and box in the main windows. */
      titlebox = newwin (3, 60, 0, 20);
      UI_INIT_BOX (titlebox)
      wrefresh (titlebox);

      titledisp = newwin (1, 58, 1, 21);
      UI_INIT_DISPLAY (titledisp)
      mvwaddstr (titledisp, 0, (58 - strlen (TITLE)) / 2, TITLE);
      wrefresh (titledisp);

      modebox = newwin (NUM_MODES + 2, 20, 0, 0);
      UI_INIT_BOX (modebox)
      mvwaddstr (modebox, 0, 1, "Mode(F10v,F11^)");
      wrefresh (modebox);

      modedisp = newwin (NUM_MODES, 18, 1, 1);
      UI_INIT_DISPLAY (modedisp)
      for (index = 1; modes[index].type != M_NULL; index++)
          {
            if (index == current_mode)
                  wattron (modedisp, A_REVERSE);
            else
                  wattroff (modedisp, A_REVERSE);
            mvwaddstr (modedisp, index-1, 0, modes[index].description);
          }
      wrefresh (modedisp);

      charbox = newwin (18, 60, 3, 20);
      UI_INIT_BOX (charbox)
      mvwaddstr (charbox, 0, 1, "Start(F9)");
      wrefresh (charbox);

      chardisp = newwin (16, 58, 4, 21);
      UI_INIT_DISPLAY (chardisp)
      idlok (chardisp, TRUE);
      immedok (chardisp, TRUE);
      scrollok (chardisp, TRUE);
      wrefresh (chardisp);

      /* Add a bit of intro text to the main display. */
      waddstr (chardisp, VERSION);
      waddstr (chardisp, "\n\n");
      waddstr (chardisp, INTRODUCTION);

      /* Create the control feedback boxes. */
      wpmbox = newwin (3, 16, 21, 0);
      UI_INIT_BOX (wpmbox)
      mvwaddstr (wpmbox, 0, 1, "Speed(F1-,F2+)");
      wrefresh (wpmbox);

      wpmdisp = newwin (1, 14, 22, 1);
      UI_INIT_DISPLAY (wpmdisp)
      sprintf (buffer, "%2d WPM", cw_get_send_speed ());
      mvwaddstr (wpmdisp, 0, 4, buffer);
      wrefresh (wpmdisp);

      hzbox = newwin (3, 16, 21, 16);
      UI_INIT_BOX (hzbox)
      mvwaddstr (hzbox, 0, 1, "Tone(F3-,F4+)");
      wrefresh (hzbox);

      hzdisp = newwin (1, 14, 22, 17);
      UI_INIT_DISPLAY (hzdisp)
      sprintf (buffer, "%4d Hz", cw_get_frequency ());
      mvwaddstr (hzdisp, 0, 3, buffer);
      wrefresh (hzdisp);

      volbox = newwin (3, 16, 21, 32);
      UI_INIT_BOX (volbox)
      mvwaddstr (volbox, 0, 1, "Vol(F5-,F6+)");
      wrefresh (volbox);

      voldisp = newwin (1, 14, 22, 33);
      UI_INIT_DISPLAY (voldisp)
      sprintf (buffer, "%3d %%", cw_get_volume ());
      mvwaddstr (voldisp, 0, 4, buffer);
      wrefresh (voldisp);

      gapbox = newwin (3, 16, 21, 48);
      UI_INIT_BOX (gapbox)
      mvwaddstr (gapbox, 0, 1, "Gap(F7-,F8+)");
      wrefresh (gapbox);

      gapdisp = newwin (1, 14, 22, 49);
      UI_INIT_DISPLAY (gapdisp)
      sprintf (buffer, "%2d dot%c",
                  cw_get_gap (), cw_get_gap () != 1 ? 's' : ' ');
      mvwaddstr (gapdisp, 0, 3, buffer);
      wrefresh (gapdisp);

      timebox = newwin (3, 16, 21, 64);
      UI_INIT_BOX (timebox)
      mvwaddstr (timebox, 0, 1, "Time(Dn-,Up+)");
      wrefresh (timebox);

      timedisp = newwin (1, 14, 22, 65);
      UI_INIT_DISPLAY (timedisp)
      sprintf (buffer, " 0/%2d min%c",
                  practice_time, practice_time != 1 ? 's' : ' ');
      mvwaddstr (timedisp, 0, 2, buffer);
      wrefresh (timedisp);

      /* Set up curses input mode. */
      keypad (scr, TRUE);
      noecho (); cbreak ();
      curs_set (0);
      raw (); nodelay (scr, FALSE);
}


/*
 * interface_destroy()
 *
 * Dismantle the user interface, boxes and windows.
 */
static void
interface_destroy ()
{
      /* Clear the screen for neatness. */
      werase (scr);
      wrefresh (scr);

      /* End curses processing - anything else goes as standard. */
      endwin ();
}


/*
 * interface_interpret()
 *
 * Assess a user command, and action it if valid.  If the command turned out
 * to be a valid user interface command, return TRUE, otherwise return FALSE.
 */
static int
interface_interpret (int c)
{
      char  buffer[ GENERAL_BUFFER ];     /* String buffer - general */
      int   previous_mode;                /* Previous mode on mode chg */

      /* Interpret the command passed in */
      switch (c)
          {
            default:
                  return FALSE;

            case ASC_CLOSEBRACKET:
                  disp_bgind++;
                  if (disp_bgind >= NUM_COLOURS)
                        disp_bgind = 0;
                  goto colour_update;

            case ASC_OPENBRACKET:
                  disp_fgind++;
                  if (disp_fgind >= NUM_COLOURS)
                        disp_fgind = 0;
                  goto colour_update;

            case ASC_CLOSEBRACE:
                  box_bgind++;
                  if (box_bgind >= NUM_COLOURS)
                        box_bgind = 0;
                  goto colour_update;

            case ASC_OPENBRACE:
                  box_fgind++;
                  if (box_fgind >= NUM_COLOURS)
                        box_fgind = 0;
                  goto colour_update;

colour_update:
                  if (do_colours && has_colors ())
                      {
                        init_pair (BOX_COLOURS,
                                    colour_array[box_fgind],
                                    colour_array[box_bgind]);
                        init_pair (DISP_COLOURS,
                                    colour_array[disp_fgind],
                                    colour_array[disp_bgind]);
                        wrefresh (curscr);
                      }
                  break;

            case ASC_CTRLL:
                  wrefresh (curscr);
                  break;


            case KEY_F(1):
            case PSEUDO_KEYF1:
            case KEY_LEFT:
                  if (cw_set_send_speed
                              (cw_get_send_speed () - STEP_WPM) == 0)
                        goto speed_update;
                  break;

            case KEY_F(2):
            case PSEUDO_KEYF2:
            case KEY_RIGHT:
                  if (cw_set_send_speed
                              (cw_get_send_speed () + STEP_WPM) == 0)
                        goto speed_update;
                  break;

speed_update:
                  sprintf (buffer, "%2d WPM", cw_get_send_speed ());
                  mvwaddstr (wpmdisp, 0, 4, buffer);
                  wrefresh (wpmdisp);
                  break;


            case KEY_F(3):
            case PSEUDO_KEYF3:
            case KEY_END:
                  if (cw_set_frequency
                              (cw_get_frequency () - STEP_HZ) == 0)
                        goto frequency_update;
                  break;

            case KEY_F(4):
            case PSEUDO_KEYF4:
            case KEY_HOME:
                  if (cw_set_frequency
                              (cw_get_frequency () + STEP_HZ) == 0)
                        goto frequency_update;
                  break;

frequency_update:
                  sprintf (buffer, "%4d Hz", cw_get_frequency ());
                  mvwaddstr (hzdisp, 0, 3, buffer);
                  wrefresh (hzdisp);
                  break;


            case KEY_F(5):
            case PSEUDO_KEYF5:
                  if (cw_set_volume
                              (cw_get_volume () - STEP_VOL) == 0)
                        goto volume_update;
                  break;

            case KEY_F(6):
            case PSEUDO_KEYF6:
                  if (cw_set_volume
                              (cw_get_volume () + STEP_VOL) == 0)
                        goto volume_update;
                  break;

volume_update:
                  sprintf (buffer, "%3d %%", cw_get_volume ());
                  mvwaddstr (voldisp, 0, 4, buffer);
                  wrefresh (voldisp);
                  break;


            case KEY_F(7):
            case PSEUDO_KEYF7:
                  if (cw_set_gap (cw_get_gap () - STEP_GAP) == 0)
                        goto gap_update;
                  break;

            case KEY_F(8):
            case PSEUDO_KEYF8:
                  if (cw_set_gap (cw_get_gap () + STEP_GAP) == 0)
                        goto gap_update;
                  break;

gap_update:
                  sprintf (buffer, "%2d dot%c",
                        cw_get_gap (), cw_get_gap () != 1 ? 's' : ' ');
                  mvwaddstr (gapdisp, 0, 3, buffer);
                  wrefresh (gapdisp);
                  break;


            case KEY_NPAGE:
            case PSEUDO_KEYNPAGE:
                  if (practice_time > MIN_TIME)
                      {
                        practice_time -= STEP_TIME;
                        goto time_update;
                      }
                  break;

            case KEY_PPAGE:
            case PSEUDO_KEYPPAGE:
                  if (practice_time < MAX_TIME)
                      {
                        practice_time += STEP_TIME;
                        goto time_update;
                      }
                  break;

time_update:
                  sprintf (buffer, "%2d min%c",
                        practice_time, practice_time != 1 ? 's' : ' ');
                  mvwaddstr (timedisp, 0, 5, buffer);
                  wrefresh (timedisp);
                  break;


            case KEY_F(11):
            case PSEUDO_KEYF11:
            case KEY_UP:
                  if (sending_state == STATE_ACTIVE)
                        change_state (STATE_IDLE);
                  if (modes[current_mode-1].type != M_NULL)
                      {
                        previous_mode = current_mode;
                        current_mode--;
                        goto mode_update;
                      }
                  break;

            case KEY_F(10):
            case PSEUDO_KEYF10:
            case KEY_DOWN:
                  if (sending_state == STATE_ACTIVE)
                        change_state (STATE_IDLE);
                  if (modes[current_mode+1].type != M_NULL)
                      {
                        previous_mode = current_mode;
                        current_mode++;
                        goto mode_update;
                      }
                  break;

mode_update:
                  wattroff (modedisp, A_REVERSE);
                  mvwaddstr (modedisp, previous_mode-1, 0,
                              modes[previous_mode].description);
                  wattron (modedisp, A_REVERSE);
                  mvwaddstr (modedisp, current_mode-1, 0,
                              modes[current_mode].description);
                  wrefresh (modedisp);
                  break;


            case KEY_F(9):
            case PSEUDO_KEYF9:
                  if (modes[current_mode].type == M_EXIT)
                      {
                        bye_bye ();
                        interface_destroy ();
                        cw_tone_queue_wait ();
                        exit (0);
                      }
                  else
                        if (sending_state == STATE_ACTIVE)
                              change_state (STATE_IDLE);
                        else
                            {
                              change_state (STATE_ACTIVE);
                              cw_block_callback (TRUE);
                              cwlib_callback_event ();
                              cw_block_callback (FALSE);
                            }
                  break;

            case ASC_CR:
                  if (modes[current_mode].type == M_EXIT)
                      {
                        bye_bye ();
                        interface_destroy ();
                        cw_tone_queue_wait ();
                        exit (0);
                      }
                  else
                        if (sending_state == STATE_IDLE)
                            {
                              change_state (STATE_ACTIVE);
                              cw_block_callback (TRUE);
                              cwlib_callback_event ();
                              cw_block_callback (FALSE);
                            }
                  break;

            case ASC_ESC:
                  if (sending_state == STATE_ACTIVE)
                        change_state (STATE_IDLE);
                  break;

            case KEY_F(12):
            case PSEUDO_KEYF12:
                  sending_state = STATE_IDLE;
                  cq_tail = cq_head;
                  cw_flush_tone_queue ();
                  bye_bye ();
                  interface_destroy ();
                  cw_tone_queue_wait ();
                  exit (0);

            case ASC_CTRLC:
                  sending_state = STATE_IDLE;
                  cq_tail = cq_head;
                  cw_flush_tone_queue ();
                  interface_destroy ();
                  cw_tone_queue_wait ();
                  exit (0);
          }

      /* The command was a recognized interface key. */
      return TRUE;
}


/*
 * cwlib_callback_event()
 *
 * Handles the callback from cwlib indicating that its tone queue is
 * nearly empty.  This routine is always called either from the SIGALRM
 * handler from with cwlib, or with SIGALRM blocked.
 */
static void
cwlib_callback_event ()
{
      char        buffer[ GENERAL_BUFFER ];
                                    /* String buffer - general */
      const char  *word;                  /* Random word to buffer */
      const char  *sptr;                  /* Pointer through word */
      int         random;                 /* Random word index */
      int         index;                  /* Groupsize index count */
      int         minutes;          /* Minutes of practice */

      /*
       * If the current state is not an active one, then we can totally
       * ignore the call.
       */
      if (sending_state != STATE_ACTIVE)
            return;

      /* What to do now depends on the program mode. */
      switch (modes[current_mode].type)
          {
            case M_DICTIONARY:
                  /* 
                   * Check the time we've been practicing.  Update the
                   * count of minutes we've been at this, and if it's
                   * reached the set practice time, we can stop.
                   */
                  minutes = (time (NULL) - practice_start_timestamp) / 60;
                  sprintf (buffer, "%2d", minutes);
                  mvwaddstr (timedisp, 0, 2, buffer);
                  wrefresh (timedisp);

                  /* Check the time, and stop if over practice time. */
                  if (minutes >= practice_time)
                      {
                        change_state (STATE_IDLE);
                        return;
                      }

                  /* See if the character queue needs a top up. */
                  if (cq_head == cq_tail)
                      {
                        /* Queue a before-group space. */
                        queue_character (ASC_SPACE);

                        /* Grab and buffer groupsize random words. */
                        for (index = 0; index <
                                    modes[current_mode].groupsize;
                                                      index++)
                            {
                              random = rand () % modes[current_mode]
                                          .list_length;
                              word = modes[current_mode]
                                          .wordlist[random];

                              /* Queue each character in the word. */
                              for ( sptr = word; *sptr != ASC_NUL;
                                                      sptr++)
                                    queue_character (*sptr);
                            }

                      }

                  /* Now dequeue the next character into the cw sender. */
                  dequeue_character ();
                  break;

            case M_KEYBOARD:
                  /*
                   * If we are interested in keyboard data just dequeue
                   * whatever is in the character queue.
                   */
                  dequeue_character ();
                  break;

            default:
                  break;
          }
}


/*
 * interface_event()
 *
 * Handle an 'interface' event, in this case simply a character from the
 * keyboard via curses.
 */
static void
interface_event (unsigned int c)
{
      /* See if this character is a valid user interface command. */
      if (interface_interpret (c))
            return;

      /*
       * If the character is standard 8-bit ASCII, and the current
       * sending mode is from the keyboard, then make an effort to
       * queue the character for sending.
       */
      if (c <= UCHAR_MAX)
          {
            if (sending_state == STATE_ACTIVE
                        && modes[current_mode].type == M_KEYBOARD)
                {
                  /*
                   * If the queue is idle, give it a nudge after
                   * queueing the character.  Otherwise, just queue
                   * the character.
                   */
                  cw_block_callback (TRUE);
                  if (cq_dequeue_state == CQ_IDLE)
                      {
                        queue_character ((unsigned char)c);
                        cwlib_callback_event ();
                      }
                  else
                        queue_character ((unsigned char)c);
                  cw_block_callback (FALSE);
                  return;
                }
          }

      /* The 'event' is nothing at all of interest; drop it. */
}


/*
 * signal_handler()
 *
 * Signal handler for signals, to clear up on kill.
 */
static void
signal_handler (int signal_number)
{
      /* Attempt to wrestle the screen back from curses. */
      interface_destroy ();

      /* Show the signal caught, and exit. */
      fprintf (stderr, "\nCaught signal %d, exiting...\n", signal_number);
      exit (1);
}


/*
 * main
 *
 * Parse the command line, initialize a few things, then enter the main
 * program event loop, from which there is no return.
 */
int
main (int argc, char **argv)
{
      int               error;            /* Error return status */
      struct timeval          timeofday;  /* Current time, for srand */
      struct sigaction  action;           /* Sigaction structure */

      /* Parse the command line parameters and arguments. */
      parse_cmdline (argc, argv);

      /*
       * Seed the random number generator, using the usecs field of
       * the current timeofday as a source.  This gives us a reasonable
       * seed value.
       */
      if (gettimeofday (&timeofday, NULL) == -1)
          {
            perror ("timeofday");
            exit (1);
          }
      srand (timeofday.tv_usec);

      /* Install the handler for signals. */
      action.sa_handler = signal_handler;
      action.sa_flags         = 0;
      sigemptyset (&action.sa_mask);
      error  =  sigaction (SIGHUP,  &action, NULL);
            + sigaction (SIGINT,  &action, NULL);
            + sigaction (SIGQUIT, &action, NULL);
            + sigaction (SIGPIPE, &action, NULL);
            + sigaction (SIGTERM, &action, NULL);
      if (error != 0)
          {
            perror ("sigaction");
            exit (1);
          }

      /*
       * Set up our callback routine as being the one that cwlib calls
       * when it has run out of buffered tones to send.
       */
      cw_tone_queue_low_callback (cwlib_callback_event, 0);

      /*
       * Initialize the curses user interface, then catch and action
       * every keypress we see.
       */
      interface_init ();
      while (TRUE)
            interface_event (getch ());

      /* Not reached. */
}

Generated by  Doxygen 1.6.0   Back to index