xref: /freebsd/usr.bin/morse/morse.c (revision 6829dae12bb055451fa467da4589c43bd03b1e64)
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 
35 static 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
40 static char sccsid[] = "@(#)morse.c	8.1 (Berkeley) 5/31/93";
41 #endif
42 static const char rcsid[] =
43  "$FreeBSD$";
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 
72 struct morsetab {
73 	const char      inchar;
74 	const char     *morse;
75 };
76 
77 static 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  */
155 static 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  */
175 static 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  */
238 static 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 
275 static void	show(const char *), play(const char *), morse(char);
276 static void	decode (char *), fdecode(FILE *);
277 static void	ttyout(const char *);
278 static void	sighandler(int);
279 
280 static int	pflag, lflag, rflag, sflag, eflag;
281 static int	wpm = 20;	/* effective words per minute */
282 static int	cpm;		/* effective words per minute between
283 				 * characters */
284 #define FREQUENCY 600
285 static int	freq = FREQUENCY;
286 static 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)
291 static float	dot_clock;
292 static float	cdot_clock;
293 static int	spkr, line;
294 static struct termios otty, ntty;
295 static int	olflags;
296 
297 #ifdef SPEAKER
298 static 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 
309 static const struct morsetab *hightab;
310 
311 int
312 main(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 
470 static void
471 morse(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 
504 static void
505 show(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 
519 static void
520 play(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 
559 static void
560 ttyout(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 
601 void
602 fdecode(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 
638 void
639 decode(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 
663 static void
664 sighandler(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