1/*-
2 * Copyright (c) 1988, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30/*
31 * Taught to send *real* morse by Lyndon Nerenberg (VE6BBM)
32 * <lyndon@orthanc.ca>
33 */
34
35static const char copyright[] =
36"@(#) Copyright (c) 1988, 1993\n\
37	The Regents of the University of California.  All rights reserved.\n";
38
39#if 0
40static char sccsid[] = "@(#)morse.c	8.1 (Berkeley) 5/31/93";
41#endif
42static const char rcsid[] =
43 "$FreeBSD: stable/11/usr.bin/morse/morse.c 330981 2018-03-15 09:38:18Z eadler $";
44
45#include <sys/time.h>
46#include <sys/ioctl.h>
47
48#include <ctype.h>
49#include <err.h>
50#include <fcntl.h>
51#include <langinfo.h>
52#include <locale.h>
53#include <signal.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <termios.h>
58#include <unistd.h>
59
60#ifdef __FreeBSD__
61/* Always use the speaker, let the open fail if -p is selected */
62#define SPEAKER "/dev/speaker"
63#endif
64
65#define WHITESPACE " \t\n"
66#define DELIMITERS " \t"
67
68#ifdef SPEAKER
69#include <dev/speaker/speaker.h>
70#endif
71
72struct morsetab {
73	const char      inchar;
74	const char     *morse;
75};
76
77static const struct morsetab mtab[] = {
78
79	/* letters */
80
81	{'a', ".-"},
82	{'b', "-..."},
83	{'c', "-.-."},
84	{'d', "-.."},
85	{'e', "."},
86	{'f', "..-."},
87	{'g', "--."},
88	{'h', "...."},
89	{'i', ".."},
90	{'j', ".---"},
91	{'k', "-.-"},
92	{'l', ".-.."},
93	{'m', "--"},
94	{'n', "-."},
95	{'o', "---"},
96	{'p', ".--."},
97	{'q', "--.-"},
98	{'r', ".-."},
99	{'s', "..."},
100	{'t', "-"},
101	{'u', "..-"},
102	{'v', "...-"},
103	{'w', ".--"},
104	{'x', "-..-"},
105	{'y', "-.--"},
106	{'z', "--.."},
107
108	/* digits */
109
110	{'0', "-----"},
111	{'1', ".----"},
112	{'2', "..---"},
113	{'3', "...--"},
114	{'4', "....-"},
115	{'5', "....."},
116	{'6', "-...."},
117	{'7', "--..."},
118	{'8', "---.."},
119	{'9', "----."},
120
121	/* punctuation */
122
123	{',', "--..--"},
124	{'.', ".-.-.-"},
125	{'"', ".-..-."},
126	{'!', "..--."},
127	{'?', "..--.."},
128	{'/', "-..-."},
129	{'-', "-....-"},
130	{'=', "-...-"},		/* BT */
131	{':', "---..."},
132	{';', "-.-.-."},
133	{'(', "-.--."},		/* KN */
134	{')', "-.--.-"},
135	{'$', "...-..-"},
136	{'+', ".-.-."},		/* AR */
137	{'@', ".--.-."},	/* AC */
138	{'_', "..--.-"},
139	{'\'', ".----."},
140
141	/* prosigns without already assigned values */
142
143	{'#', ".-..."},		/* AS */
144	{'&', "...-.-"},	/* SK */
145	{'*', "...-."},		/* VE */
146	{'%', "-...-.-"},	/* BK */
147
148	{'\0', ""}
149};
150
151/*
152 * Code-points for some Latin1 chars in ISO-8859-1 encoding.
153 * UTF-8 encoded chars in the comments.
154 */
155static const struct morsetab iso8859_1tab[] = {
156	{'\340', ".--.-"},	/* �� */
157	{'\341', ".--.-"},	/* �� */
158	{'\342', ".--.-"},	/* �� */
159	{'\344', ".-.-"},	/* �� */
160	{'\347', "-.-.."},	/* �� */
161	{'\350', "..-.."},	/* �� */
162	{'\351', "..-.."},	/* �� */
163	{'\352', "-..-."},	/* �� */
164	{'\361', "--.--"},	/* �� */
165	{'\366', "---."},	/* �� */
166	{'\374', "..--"},	/* �� */
167
168	{'\0', ""}
169};
170
171/*
172 * Code-points for some Greek chars in ISO-8859-7 encoding.
173 * UTF-8 encoded chars in the comments.
174 */
175static const struct morsetab iso8859_7tab[] = {
176	/*
177	 * This table does not implement:
178	 * - the special sequences for the seven diphthongs,
179	 * - the punctuation differences.
180	 * Implementing these features would introduce too many
181	 * special-cases in the program's main loop.
182	 * The diphthong sequences are:
183	 * alpha iota		.-.-
184	 * alpha upsilon	..--
185	 * epsilon upsilon	---.
186	 * eta upsilon		...-
187	 * omicron iota		---..
188	 * omicron upsilon	..-
189	 * upsilon iota		.---
190	 * The different punctuation symbols are:
191	 * ;	..-.-
192	 * !	--..--
193	 */
194	{'\341', ".-"},		/* ��, alpha */
195	{'\334', ".-"},		/* ��, alpha with acute */
196	{'\342', "-..."},	/* ��, beta */
197	{'\343', "--."},	/* ��, gamma */
198	{'\344', "-.."},	/* ��, delta */
199	{'\345', "."},		/* ��, epsilon */
200	{'\335', "."},		/* ��, epsilon with acute */
201	{'\346', "--.."},	/* ��, zeta */
202	{'\347', "...."},	/* ��, eta */
203	{'\336', "...."},	/* ��, eta with acute */
204	{'\350', "-.-."},	/* ��, theta */
205	{'\351', ".."},		/* ��, iota */
206	{'\337', ".."},		/* ��, iota with acute */
207	{'\372', ".."},		/* ��, iota with diaeresis */
208	{'\300', ".."},		/* ��, iota with acute and diaeresis */
209	{'\352', "-.-"},	/* ��, kappa */
210	{'\353', ".-.."},	/* ��, lambda */
211	{'\354', "--"},		/* ��, mu */
212	{'\355', "-."},		/* ��, nu */
213	{'\356', "-..-"},	/* ��, xi */
214	{'\357', "---"},	/* ��, omicron */
215	{'\374', "---"},	/* ��, omicron with acute */
216	{'\360', ".--."},	/* ��, pi */
217	{'\361', ".-."},	/* ��, rho */
218	{'\363', "..."},	/* ��, sigma */
219	{'\362', "..."},	/* ��, final sigma */
220	{'\364', "-"},		/* ��, tau */
221	{'\365', "-.--"},	/* ��, upsilon */
222	{'\375', "-.--"},	/* ��, upsilon with acute */
223	{'\373', "-.--"},	/* ��, upsilon and diaeresis */
224	{'\340', "-.--"},	/* ��, upsilon with acute and diaeresis */
225	{'\366', "..-."},	/* ��, phi */
226	{'\367', "----"},	/* ��, chi */
227	{'\370', "--.-"},	/* ��, psi */
228	{'\371', ".--"},	/* ��, omega */
229	{'\376', ".--"},	/* ��, omega with acute */
230
231	{'\0', ""}
232};
233
234/*
235 * Code-points for the Cyrillic alphabet in KOI8-R encoding.
236 * UTF-8 encoded chars in the comments.
237 */
238static const struct morsetab koi8rtab[] = {
239	{'\301', ".-"},		/* ��, a */
240	{'\302', "-..."},	/* ��, be */
241	{'\327', ".--"},	/* ��, ve */
242	{'\307', "--."},	/* ��, ge */
243	{'\304', "-.."},	/* ��, de */
244	{'\305', "."},		/* ��, ye */
245	{'\243', "."},		/* ��, yo, the same as ye */
246	{'\326', "...-"},	/* ��, she */
247	{'\332', "--.."},	/* ��, ze */
248	{'\311', ".."},		/* ��, i */
249	{'\312', ".---"},	/* ��, i kratkoye */
250	{'\313', "-.-"},	/* ��, ka */
251	{'\314', ".-.."},	/* ��, el */
252	{'\315', "--"},		/* ��, em */
253	{'\316', "-."},		/* ��, en */
254	{'\317', "---"},	/* ��, o */
255	{'\320', ".--."},	/* ��, pe */
256	{'\322', ".-."},	/* ��, er */
257	{'\323', "..."},	/* ��, es */
258	{'\324', "-"},		/* ��, te */
259	{'\325', "..-"},	/* ��, u */
260	{'\306', "..-."},	/* ��, ef */
261	{'\310', "...."},	/* ��, kha */
262	{'\303', "-.-."},	/* ��, ce */
263	{'\336', "---."},	/* ��, che */
264	{'\333', "----"},	/* ��, sha */
265	{'\335', "--.-"},	/* ��, shcha */
266	{'\331', "-.--"},	/* ��, yi */
267	{'\330', "-..-"},	/* ��, myakhkij znak */
268	{'\334', "..-.."},	/* ��, ae */
269	{'\300', "..--"},	/* ��, yu */
270	{'\321', ".-.-"},	/* ��, ya */
271
272	{'\0', ""}
273};
274
275static void	show(const char *), play(const char *), morse(char);
276static void	decode (char *), fdecode(FILE *);
277static void	ttyout(const char *);
278static void	sighandler(int);
279
280static int	pflag, lflag, rflag, sflag, eflag;
281static int	wpm = 20;	/* effective words per minute */
282static int	cpm;		/* effective words per minute between
283				 * characters */
284#define FREQUENCY 600
285static int	freq = FREQUENCY;
286static char	*device;	/* for tty-controlled generator */
287
288#define DASH_LEN 3
289#define CHAR_SPACE 3
290#define WORD_SPACE (7 - CHAR_SPACE - 1)
291static float	dot_clock;
292static float	cdot_clock;
293static int	spkr, line;
294static struct termios otty, ntty;
295static int	olflags;
296
297#ifdef SPEAKER
298static tone_t	sound;
299#define GETOPTOPTS "c:d:ef:lprsw:"
300#define USAGE \
301"usage: morse [-elprs] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
302#else
303#define GETOPTOPTS "c:d:ef:lrsw:"
304#define USAGE \
305"usage: morse [-elrs] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
306
307#endif
308
309static const struct morsetab *hightab;
310
311int
312main(int argc, char *argv[])
313{
314	int    ch, lflags;
315	char  *p, *codeset;
316
317	while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
318		switch ((char) ch) {
319		case 'c':
320			cpm = atoi(optarg);
321			break;
322		case 'd':
323			device = optarg;
324			break;
325		case 'e':
326			eflag = 1;
327			setvbuf(stdout, 0, _IONBF, 0);
328			break;
329		case 'f':
330			freq = atoi(optarg);
331			break;
332		case 'l':
333			lflag = 1;
334			break;
335#ifdef SPEAKER
336		case 'p':
337			pflag = 1;
338			break;
339#endif
340		case 'r':
341			rflag = 1;
342			break;
343		case 's':
344			sflag = 1;
345			break;
346		case 'w':
347			wpm = atoi(optarg);
348			break;
349		case '?':
350		default:
351			errx(1, USAGE);
352		}
353	if ((sflag && lflag) || (sflag && rflag) || (lflag && rflag)) {
354		errx(1, "morse: only one of -l, -s, and -r allowed\n");
355	}
356	if ((pflag || device) && (sflag || lflag)) {
357		errx(1, "morse: only one of -p, -d and -l, -s allowed\n");
358	}
359	if (cpm == 0) {
360		cpm = wpm;
361	}
362	if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (cpm < 1) || (cpm > 60))) {
363		errx(1, "morse: insane speed\n");
364	}
365	if ((pflag || device) && (freq == 0)) {
366		freq = FREQUENCY;
367	}
368#ifdef SPEAKER
369	if (pflag) {
370		if ((spkr = open(SPEAKER, O_WRONLY, 0)) == -1) {
371			err(1, SPEAKER);
372		}
373	} else
374#endif
375	if (device) {
376		if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
377			err(1, "open tty line");
378		}
379		if (tcgetattr(line, &otty) == -1) {
380			err(1, "tcgetattr() failed");
381		}
382		ntty = otty;
383		ntty.c_cflag |= CLOCAL;
384		tcsetattr(line, TCSANOW, &ntty);
385		lflags = fcntl(line, F_GETFL);
386		lflags &= ~O_NONBLOCK;
387		fcntl(line, F_SETFL, &lflags);
388		ioctl(line, TIOCMGET, &lflags);
389		lflags &= ~TIOCM_RTS;
390		olflags = lflags;
391		ioctl(line, TIOCMSET, &lflags);
392		(void)signal(SIGHUP, sighandler);
393		(void)signal(SIGINT, sighandler);
394		(void)signal(SIGQUIT, sighandler);
395		(void)signal(SIGTERM, sighandler);
396	}
397	if (pflag || device) {
398		dot_clock = wpm / 2.4;		/* dots/sec */
399		dot_clock = 1 / dot_clock;	/* duration of a dot */
400		dot_clock = dot_clock / 2;	/* dot_clock runs at twice */
401						/* the dot rate */
402		dot_clock = dot_clock * 100;	/* scale for ioctl */
403
404		cdot_clock = cpm / 2.4;		/* dots/sec */
405		cdot_clock = 1 / cdot_clock;	/* duration of a dot */
406		cdot_clock = cdot_clock / 2;	/* dot_clock runs at twice */
407						/* the dot rate */
408		cdot_clock = cdot_clock * 100;	/* scale for ioctl */
409	}
410
411	argc -= optind;
412	argv += optind;
413
414	if (setlocale(LC_CTYPE, "") != NULL &&
415	    *(codeset = nl_langinfo(CODESET)) != '\0') {
416		if (strcmp(codeset, "KOI8-R") == 0)
417			hightab = koi8rtab;
418		else if (strcmp(codeset, "ISO8859-1") == 0 ||
419			 strcmp(codeset, "ISO8859-15") == 0)
420			hightab = iso8859_1tab;
421		else if (strcmp(codeset, "ISO8859-7") == 0)
422			hightab = iso8859_7tab;
423	}
424
425	if (lflag) {
426		printf("m");
427	}
428	if (rflag) {
429		if (*argv) {
430			do {
431				p = strtok(*argv, DELIMITERS);
432				if (p == NULL) {
433					decode(*argv);
434				}
435				else {
436					while (p) {
437						decode(p);
438						p = strtok(NULL, DELIMITERS);
439					}
440				}
441			} while (*++argv);
442			putchar('\n');
443		} else {
444			fdecode(stdin);
445		}
446	}
447	else if (*argv) {
448		do {
449			for (p = *argv; *p; ++p) {
450				if (eflag)
451					putchar(*p);
452				morse(*p);
453			}
454			if (eflag)
455				putchar(' ');
456			morse(' ');
457		} while (*++argv);
458	} else {
459		while ((ch = getchar()) != EOF) {
460			if (eflag)
461				putchar(ch);
462			morse(ch);
463		}
464	}
465	if (device)
466		tcsetattr(line, TCSANOW, &otty);
467	exit(0);
468}
469
470static void
471morse(char c)
472{
473	const struct morsetab *m;
474
475	if (isalpha((unsigned char)c))
476		c = tolower((unsigned char)c);
477	if ((c == '\r') || (c == '\n'))
478		c = ' ';
479	if (c == ' ') {
480		if (pflag)
481			play(" ");
482		else if (device)
483			ttyout(" ");
484		else if (lflag)
485			printf("\n");
486		else
487			show("");
488		return;
489	}
490	for (m = ((unsigned char)c < 0x80? mtab: hightab);
491	     m != NULL && m->inchar != '\0';
492	     m++) {
493		if (m->inchar == c) {
494			if (pflag) {
495				play(m->morse);
496			} else if (device) {
497				ttyout(m->morse);
498			} else
499				show(m->morse);
500		}
501	}
502}
503
504static void
505show(const char *s)
506{
507	if (lflag) {
508		printf("%s ", s);
509	} else if (sflag) {
510		printf(" %s\n", s);
511	} else {
512		for (; *s; ++s)
513			printf(" %s", *s == '.' ? *(s + 1) == '\0' ? "dit" :
514			    "di" : "dah");
515		printf("\n");
516	}
517}
518
519static void
520play(const char *s)
521{
522#ifdef SPEAKER
523	const char *c;
524
525	for (c = s; *c != '\0'; c++) {
526		switch (*c) {
527		case '.':
528			sound.frequency = freq;
529			sound.duration = dot_clock;
530			break;
531		case '-':
532			sound.frequency = freq;
533			sound.duration = dot_clock * DASH_LEN;
534			break;
535		case ' ':
536			sound.frequency = 0;
537			sound.duration = cdot_clock * WORD_SPACE;
538			break;
539		default:
540			sound.duration = 0;
541		}
542		if (sound.duration) {
543			if (ioctl(spkr, SPKRTONE, &sound) == -1) {
544				err(1, "ioctl play");
545			}
546		}
547		sound.frequency = 0;
548		sound.duration = dot_clock;
549		if (ioctl(spkr, SPKRTONE, &sound) == -1) {
550			err(1, "ioctl rest");
551		}
552	}
553	sound.frequency = 0;
554	sound.duration = cdot_clock * CHAR_SPACE;
555	ioctl(spkr, SPKRTONE, &sound);
556#endif
557}
558
559static void
560ttyout(const char *s)
561{
562	const char *c;
563	int duration, on, lflags;
564
565	for (c = s; *c != '\0'; c++) {
566		switch (*c) {
567		case '.':
568			on = 1;
569			duration = dot_clock;
570			break;
571		case '-':
572			on = 1;
573			duration = dot_clock * DASH_LEN;
574			break;
575		case ' ':
576			on = 0;
577			duration = cdot_clock * WORD_SPACE;
578			break;
579		default:
580			on = 0;
581			duration = 0;
582		}
583		if (on) {
584			ioctl(line, TIOCMGET, &lflags);
585			lflags |= TIOCM_RTS;
586			ioctl(line, TIOCMSET, &lflags);
587		}
588		duration *= 10000;
589		if (duration)
590			usleep(duration);
591		ioctl(line, TIOCMGET, &lflags);
592		lflags &= ~TIOCM_RTS;
593		ioctl(line, TIOCMSET, &lflags);
594		duration = dot_clock * 10000;
595		usleep(duration);
596	}
597	duration = cdot_clock * CHAR_SPACE * 10000;
598	usleep(duration);
599}
600
601void
602fdecode(FILE *stream)
603{
604	char *n, *p, *s;
605	char buf[BUFSIZ];
606
607	s = buf;
608	while (fgets(s, BUFSIZ - (s - buf), stream)) {
609		p = buf;
610
611		while (*p && isblank(*p)) {
612			p++;
613		}
614		while (*p && isspace(*p)) {
615			p++;
616			putchar (' ');
617		}
618		while (*p) {
619			n = strpbrk(p, WHITESPACE);
620			if (n == NULL) {
621				/* The token was interrupted at the end
622				 * of the buffer. Shift it to the begin
623				 * of the buffer.
624				 */
625				for (s = buf; *p; *s++ = *p++)
626					;
627			} else {
628				*n = '\0';
629				n++;
630				decode(p);
631				p = n;
632			}
633		}
634	}
635	putchar('\n');
636}
637
638void
639decode(char *p)
640{
641	char c;
642	const struct morsetab *m;
643
644	c = ' ';
645	for (m = mtab; m != NULL && m->inchar != '\0'; m++) {
646		if (strcmp(m->morse, p) == 0) {
647			c = m->inchar;
648			break;
649		}
650	}
651
652	if (c == ' ')
653		for (m = hightab; m != NULL && m->inchar != '\0'; m++) {
654			if (strcmp(m->morse, p) == 0) {
655				c = m->inchar;
656				break;
657			}
658		}
659
660	putchar(c);
661}
662
663static void
664sighandler(int signo)
665{
666
667	ioctl(line, TIOCMSET, &olflags);
668	tcsetattr(line, TCSANOW, &otty);
669
670	signal(signo, SIG_DFL);
671	(void)kill(getpid(), signo);
672}
673