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

/*
 * Taught to send *real* morse by Lyndon Nerenberg (VE6BBM)
 * <lyndon@orthanc.ca>
 */

#include <sys/time.h>
#include <sys/ioctl.h>

#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <langinfo.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#ifdef __FreeBSD__
/* Always use the speaker, let the open fail if -p is selected */
#define SPEAKER "/dev/speaker"
#endif

#define WHITESPACE " \t\n"
#define DELIMITERS " \t"

#ifdef SPEAKER
#include <dev/speaker/speaker.h>
#endif

struct morsetab {
        const char      inchar;
        const char     *morse;
};

static const struct morsetab mtab[] = {

        /* letters */

        {'a', ".-"},
        {'b', "-..."},
        {'c', "-.-."},
        {'d', "-.."},
        {'e', "."},
        {'f', "..-."},
        {'g', "--."},
        {'h', "...."},
        {'i', ".."},
        {'j', ".---"},
        {'k', "-.-"},
        {'l', ".-.."},
        {'m', "--"},
        {'n', "-."},
        {'o', "---"},
        {'p', ".--."},
        {'q', "--.-"},
        {'r', ".-."},
        {'s', "..."},
        {'t', "-"},
        {'u', "..-"},
        {'v', "...-"},
        {'w', ".--"},
        {'x', "-..-"},
        {'y', "-.--"},
        {'z', "--.."},

        /* digits */

        {'0', "-----"},
        {'1', ".----"},
        {'2', "..---"},
        {'3', "...--"},
        {'4', "....-"},
        {'5', "....."},
        {'6', "-...."},
        {'7', "--..."},
        {'8', "---.."},
        {'9', "----."},

        /* punctuation */

        {',', "--..--"},
        {'.', ".-.-.-"},
        {'"', ".-..-."},
        {'!', "..--."},
        {'?', "..--.."},
        {'/', "-..-."},
        {'-', "-....-"},
        {'=', "-...-"},         /* BT */
        {':', "---..."},
        {';', "-.-.-."},
        {'(', "-.--."},         /* KN */
        {')', "-.--.-"},
        {'$', "...-..-"},
        {'+', ".-.-."},         /* AR */
        {'@', ".--.-."},        /* AC */
        {'_', "..--.-"},
        {'\'', ".----."},

        /* prosigns without already assigned values */

        {'#', ".-..."},         /* AS */
        {'&', "...-.-"},        /* SK */
        {'*', "...-."},         /* VE */
        {'%', "-...-.-"},       /* BK */

        {'\0', ""}
};

/*
 * Code-points for some Latin1 chars in ISO-8859-1 encoding.
 * UTF-8 encoded chars in the comments.
 */
static const struct morsetab iso8859_1tab[] = {
        {'\340', ".--.-"},      /* à */
        {'\341', ".--.-"},      /* á */
        {'\342', ".--.-"},      /* â */
        {'\344', ".-.-"},       /* ä */
        {'\347', "-.-.."},      /* ç */
        {'\350', "..-.."},      /* è */
        {'\351', "..-.."},      /* é */
        {'\352', "-..-."},      /* ê */
        {'\361', "--.--"},      /* ñ */
        {'\366', "---."},       /* ö */
        {'\374', "..--"},       /* ü */

        {'\0', ""}
};

/*
 * Code-points for some Greek chars in ISO-8859-7 encoding.
 * UTF-8 encoded chars in the comments.
 */
static const struct morsetab iso8859_7tab[] = {
        /*
         * This table does not implement:
         * - the special sequences for the seven diphthongs,
         * - the punctuation differences.
         * Implementing these features would introduce too many
         * special-cases in the program's main loop.
         * The diphthong sequences are:
         * alpha iota           .-.-
         * alpha upsilon        ..--
         * epsilon upsilon      ---.
         * eta upsilon          ...-
         * omicron iota         ---..
         * omicron upsilon      ..-
         * upsilon iota         .---
         * The different punctuation symbols are:
         * ;    ..-.-
         * !    --..--
         */
        {'\341', ".-"},         /* α, alpha */
        {'\334', ".-"},         /* ά, alpha with acute */
        {'\342', "-..."},       /* β, beta */
        {'\343', "--."},        /* γ, gamma */
        {'\344', "-.."},        /* δ, delta */
        {'\345', "."},          /* ε, epsilon */
        {'\335', "."},          /* έ, epsilon with acute */
        {'\346', "--.."},       /* ζ, zeta */
        {'\347', "...."},       /* η, eta */
        {'\336', "...."},       /* ή, eta with acute */
        {'\350', "-.-."},       /* θ, theta */
        {'\351', ".."},         /* ι, iota */
        {'\337', ".."},         /* ί, iota with acute */
        {'\372', ".."},         /* ϊ, iota with diaeresis */
        {'\300', ".."},         /* ΐ, iota with acute and diaeresis */
        {'\352', "-.-"},        /* κ, kappa */
        {'\353', ".-.."},       /* λ, lambda */
        {'\354', "--"},         /* μ, mu */
        {'\355', "-."},         /* ν, nu */
        {'\356', "-..-"},       /* ξ, xi */
        {'\357', "---"},        /* ο, omicron */
        {'\374', "---"},        /* ό, omicron with acute */
        {'\360', ".--."},       /* π, pi */
        {'\361', ".-."},        /* ρ, rho */
        {'\363', "..."},        /* σ, sigma */
        {'\362', "..."},        /* ς, final sigma */
        {'\364', "-"},          /* τ, tau */
        {'\365', "-.--"},       /* υ, upsilon */
        {'\375', "-.--"},       /* ύ, upsilon with acute */
        {'\373', "-.--"},       /* ϋ, upsilon and diaeresis */
        {'\340', "-.--"},       /* ΰ, upsilon with acute and diaeresis */
        {'\366', "..-."},       /* φ, phi */
        {'\367', "----"},       /* χ, chi */
        {'\370', "--.-"},       /* ψ, psi */
        {'\371', ".--"},        /* ω, omega */
        {'\376', ".--"},        /* ώ, omega with acute */

        {'\0', ""}
};

/*
 * Code-points for the Cyrillic alphabet in KOI8-R encoding.
 * UTF-8 encoded chars in the comments.
 */
static const struct morsetab koi8rtab[] = {
        {'\301', ".-"},         /* а, a */
        {'\302', "-..."},       /* б, be */
        {'\327', ".--"},        /* в, ve */
        {'\307', "--."},        /* г, ge */
        {'\304', "-.."},        /* д, de */
        {'\305', "."},          /* е, ye */
        {'\243', "."},          /* ё, yo, the same as ye */
        {'\326', "...-"},       /* ж, she */
        {'\332', "--.."},       /* з, ze */
        {'\311', ".."},         /* и, i */
        {'\312', ".---"},       /* й, i kratkoye */
        {'\313', "-.-"},        /* к, ka */
        {'\314', ".-.."},       /* л, el */
        {'\315', "--"},         /* м, em */
        {'\316', "-."},         /* н, en */
        {'\317', "---"},        /* о, o */
        {'\320', ".--."},       /* п, pe */
        {'\322', ".-."},        /* р, er */
        {'\323', "..."},        /* с, es */
        {'\324', "-"},          /* т, te */
        {'\325', "..-"},        /* у, u */
        {'\306', "..-."},       /* ф, ef */
        {'\310', "...."},       /* х, kha */
        {'\303', "-.-."},       /* ц, ce */
        {'\336', "---."},       /* ч, che */
        {'\333', "----"},       /* ш, sha */
        {'\335', "--.-"},       /* щ, shcha */
        {'\331', "-.--"},       /* ы, yi */
        {'\330', "-..-"},       /* ь, myakhkij znak */
        {'\334', "..-.."},      /* э, ae */
        {'\300', "..--"},       /* ю, yu */
        {'\321', ".-.-"},       /* я, ya */

        {'\0', ""}
};

static void     show(const char *), play(const char *), morse(char);
static void     decode (char *), fdecode(FILE *);
static void     ttyout(const char *);
static void     sighandler(int);

static int      pflag, lflag, rflag, sflag, eflag;
static int      wpm = 20;       /* effective words per minute */
static int      cpm;            /* effective words per minute between
                                 * characters */
#define FREQUENCY 600
static int      freq = FREQUENCY;
static char     *device;        /* for tty-controlled generator */

#define DASH_LEN 3
#define CHAR_SPACE 3
#define WORD_SPACE (7 - CHAR_SPACE - 1)
static float    dot_clock;
static float    cdot_clock;
static int      spkr, line;
static struct termios otty, ntty;
static int      olflags;

#ifdef SPEAKER
static tone_t   sound;
#define GETOPTOPTS "c:d:ef:lprsw:"
#define USAGE \
"usage: morse [-elprs] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
#else
#define GETOPTOPTS "c:d:ef:lrsw:"
#define USAGE \
"usage: morse [-elrs] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"

#endif

static const struct morsetab *hightab;

int
main(int argc, char *argv[])
{
        int    ch, lflags;
        char  *p, *codeset;

        while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
                switch ((char) ch) {
                case 'c':
                        cpm = atoi(optarg);
                        break;
                case 'd':
                        device = optarg;
                        break;
                case 'e':
                        eflag = 1;
                        setvbuf(stdout, 0, _IONBF, 0);
                        break;
                case 'f':
                        freq = atoi(optarg);
                        break;
                case 'l':
                        lflag = 1;
                        break;
#ifdef SPEAKER
                case 'p':
                        pflag = 1;
                        break;
#endif
                case 'r':
                        rflag = 1;
                        break;
                case 's':
                        sflag = 1;
                        break;
                case 'w':
                        wpm = atoi(optarg);
                        break;
                case '?':
                default:
                        errx(1, USAGE);
                }
        if ((sflag && lflag) || (sflag && rflag) || (lflag && rflag)) {
                errx(1, "morse: only one of -l, -s, and -r allowed\n");
        }
        if ((pflag || device) && (sflag || lflag)) {
                errx(1, "morse: only one of -p, -d and -l, -s allowed\n");
        }
        if (cpm == 0) {
                cpm = wpm;
        }
        if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (cpm < 1) || (cpm > 60))) {
                errx(1, "morse: insane speed\n");
        }
        if ((pflag || device) && (freq == 0)) {
                freq = FREQUENCY;
        }
#ifdef SPEAKER
        if (pflag) {
                if ((spkr = open(SPEAKER, O_WRONLY, 0)) == -1) {
                        err(1, SPEAKER);
                }
        } else
#endif
        if (device) {
                if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
                        err(1, "open tty line");
                }
                if (tcgetattr(line, &otty) == -1) {
                        err(1, "tcgetattr() failed");
                }
                ntty = otty;
                ntty.c_cflag |= CLOCAL;
                tcsetattr(line, TCSANOW, &ntty);
                lflags = fcntl(line, F_GETFL);
                lflags &= ~O_NONBLOCK;
                fcntl(line, F_SETFL, &lflags);
                ioctl(line, TIOCMGET, &lflags);
                lflags &= ~TIOCM_RTS;
                olflags = lflags;
                ioctl(line, TIOCMSET, &lflags);
                (void)signal(SIGHUP, sighandler);
                (void)signal(SIGINT, sighandler);
                (void)signal(SIGQUIT, sighandler);
                (void)signal(SIGTERM, sighandler);
        }
        if (pflag || device) {
                dot_clock = wpm / 2.4;          /* dots/sec */
                dot_clock = 1 / dot_clock;      /* duration of a dot */
                dot_clock = dot_clock / 2;      /* dot_clock runs at twice */
                                                /* the dot rate */
                dot_clock = dot_clock * 100;    /* scale for ioctl */

                cdot_clock = cpm / 2.4;         /* dots/sec */
                cdot_clock = 1 / cdot_clock;    /* duration of a dot */
                cdot_clock = cdot_clock / 2;    /* dot_clock runs at twice */
                                                /* the dot rate */
                cdot_clock = cdot_clock * 100;  /* scale for ioctl */
        }

        argc -= optind;
        argv += optind;

        if (setlocale(LC_CTYPE, "") != NULL &&
            *(codeset = nl_langinfo(CODESET)) != '\0') {
                if (strcmp(codeset, "KOI8-R") == 0)
                        hightab = koi8rtab;
                else if (strcmp(codeset, "ISO8859-1") == 0 ||
                         strcmp(codeset, "ISO8859-15") == 0)
                        hightab = iso8859_1tab;
                else if (strcmp(codeset, "ISO8859-7") == 0)
                        hightab = iso8859_7tab;
        }

        if (lflag) {
                printf("m");
        }
        if (rflag) {
                if (*argv) {
                        do {
                                p = strtok(*argv, DELIMITERS);
                                if (p == NULL) {
                                        decode(*argv);
                                }
                                else {
                                        while (p) {
                                                decode(p);
                                                p = strtok(NULL, DELIMITERS);
                                        }
                                }
                        } while (*++argv);
                        putchar('\n');
                } else {
                        fdecode(stdin);
                }
        }
        else if (*argv) {
                do {
                        for (p = *argv; *p; ++p) {
                                if (eflag)
                                        putchar(*p);
                                morse(*p);
                        }
                        if (eflag)
                                putchar(' ');
                        morse(' ');
                } while (*++argv);
        } else {
                while ((ch = getchar()) != EOF) {
                        if (eflag)
                                putchar(ch);
                        morse(ch);
                }
        }
        if (device)
                tcsetattr(line, TCSANOW, &otty);
        exit(0);
}

static void
morse(char c)
{
        const struct morsetab *m;

        if (isalpha((unsigned char)c))
                c = tolower((unsigned char)c);
        if ((c == '\r') || (c == '\n'))
                c = ' ';
        if (c == ' ') {
                if (pflag)
                        play(" ");
                else if (device)
                        ttyout(" ");
                else if (lflag)
                        printf("\n");
                else
                        show("");
                return;
        }
        for (m = ((unsigned char)c < 0x80? mtab: hightab);
             m != NULL && m->inchar != '\0';
             m++) {
                if (m->inchar == c) {
                        if (pflag) {
                                play(m->morse);
                        } else if (device) {
                                ttyout(m->morse);
                        } else
                                show(m->morse);
                }
        }
}

static void
show(const char *s)
{
        if (lflag) {
                printf("%s ", s);
        } else if (sflag) {
                printf(" %s\n", s);
        } else {
                for (; *s; ++s)
                        printf(" %s", *s == '.' ? *(s + 1) == '\0' ? "dit" :
                            "di" : "dah");
                printf("\n");
        }
}

static void
play(const char *s)
{
#ifdef SPEAKER
        const char *c;

        for (c = s; *c != '\0'; c++) {
                switch (*c) {
                case '.':
                        sound.frequency = freq;
                        sound.duration = dot_clock;
                        break;
                case '-':
                        sound.frequency = freq;
                        sound.duration = dot_clock * DASH_LEN;
                        break;
                case ' ':
                        sound.frequency = 0;
                        sound.duration = cdot_clock * WORD_SPACE;
                        break;
                default:
                        sound.duration = 0;
                }
                if (sound.duration) {
                        if (ioctl(spkr, SPKRTONE, &sound) == -1) {
                                err(1, "ioctl play");
                        }
                }
                sound.frequency = 0;
                sound.duration = dot_clock;
                if (ioctl(spkr, SPKRTONE, &sound) == -1) {
                        err(1, "ioctl rest");
                }
        }
        sound.frequency = 0;
        sound.duration = cdot_clock * CHAR_SPACE;
        ioctl(spkr, SPKRTONE, &sound);
#endif
}

static void
ttyout(const char *s)
{
        const char *c;
        int duration, on, lflags;

        for (c = s; *c != '\0'; c++) {
                switch (*c) {
                case '.':
                        on = 1;
                        duration = dot_clock;
                        break;
                case '-':
                        on = 1;
                        duration = dot_clock * DASH_LEN;
                        break;
                case ' ':
                        on = 0;
                        duration = cdot_clock * WORD_SPACE;
                        break;
                default:
                        on = 0;
                        duration = 0;
                }
                if (on) {
                        ioctl(line, TIOCMGET, &lflags);
                        lflags |= TIOCM_RTS;
                        ioctl(line, TIOCMSET, &lflags);
                }
                duration *= 10000;
                if (duration)
                        usleep(duration);
                ioctl(line, TIOCMGET, &lflags);
                lflags &= ~TIOCM_RTS;
                ioctl(line, TIOCMSET, &lflags);
                duration = dot_clock * 10000;
                usleep(duration);
        }
        duration = cdot_clock * CHAR_SPACE * 10000;
        usleep(duration);
}

void
fdecode(FILE *stream)
{
        char *n, *p, *s;
        char buf[BUFSIZ];

        s = buf;
        while (fgets(s, BUFSIZ - (s - buf), stream)) {
                p = buf;

                while (*p && isblank(*p)) {
                        p++;
                }
                while (*p && isspace(*p)) {
                        p++;
                        putchar (' ');
                }
                while (*p) {
                        n = strpbrk(p, WHITESPACE);
                        if (n == NULL) {
                                /* The token was interrupted at the end
                                 * of the buffer. Shift it to the begin
                                 * of the buffer.
                                 */
                                for (s = buf; *p; *s++ = *p++)
                                        ;
                        } else {
                                *n = '\0';
                                n++;
                                decode(p);
                                p = n;
                        }
                }
        }
        putchar('\n');
}

void
decode(char *p)
{
        char c;
        const struct morsetab *m;

        c = ' ';
        for (m = mtab; m != NULL && m->inchar != '\0'; m++) {
                if (strcmp(m->morse, p) == 0) {
                        c = m->inchar;
                        break;
                }
        }

        if (c == ' ')
                for (m = hightab; m != NULL && m->inchar != '\0'; m++) {
                        if (strcmp(m->morse, p) == 0) {
                                c = m->inchar;
                                break;
                        }
                }

        putchar(c);
}

static void
sighandler(int signo)
{

        ioctl(line, TIOCMSET, &olflags);
        tcsetattr(line, TCSANOW, &otty);

        signal(signo, SIG_DFL);
        (void)kill(getpid(), signo);
}