xref: /freebsd/usr.bin/morse/morse.c (revision 5b31cc94b10d4bb7109c6b27940a0fc76a44a331)
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 
40 #include <sys/time.h>
41 #include <sys/ioctl.h>
42 
43 #include <ctype.h>
44 #include <err.h>
45 #include <fcntl.h>
46 #include <langinfo.h>
47 #include <locale.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <termios.h>
53 #include <unistd.h>
54 
55 #ifdef __FreeBSD__
56 /* Always use the speaker, let the open fail if -p is selected */
57 #define SPEAKER "/dev/speaker"
58 #endif
59 
60 #define WHITESPACE " \t\n"
61 #define DELIMITERS " \t"
62 
63 #ifdef SPEAKER
64 #include <dev/speaker/speaker.h>
65 #endif
66 
67 struct morsetab {
68 	const char      inchar;
69 	const char     *morse;
70 };
71 
72 static const struct morsetab mtab[] = {
73 
74 	/* letters */
75 
76 	{'a', ".-"},
77 	{'b', "-..."},
78 	{'c', "-.-."},
79 	{'d', "-.."},
80 	{'e', "."},
81 	{'f', "..-."},
82 	{'g', "--."},
83 	{'h', "...."},
84 	{'i', ".."},
85 	{'j', ".---"},
86 	{'k', "-.-"},
87 	{'l', ".-.."},
88 	{'m', "--"},
89 	{'n', "-."},
90 	{'o', "---"},
91 	{'p', ".--."},
92 	{'q', "--.-"},
93 	{'r', ".-."},
94 	{'s', "..."},
95 	{'t', "-"},
96 	{'u', "..-"},
97 	{'v', "...-"},
98 	{'w', ".--"},
99 	{'x', "-..-"},
100 	{'y', "-.--"},
101 	{'z', "--.."},
102 
103 	/* digits */
104 
105 	{'0', "-----"},
106 	{'1', ".----"},
107 	{'2', "..---"},
108 	{'3', "...--"},
109 	{'4', "....-"},
110 	{'5', "....."},
111 	{'6', "-...."},
112 	{'7', "--..."},
113 	{'8', "---.."},
114 	{'9', "----."},
115 
116 	/* punctuation */
117 
118 	{',', "--..--"},
119 	{'.', ".-.-.-"},
120 	{'"', ".-..-."},
121 	{'!', "..--."},
122 	{'?', "..--.."},
123 	{'/', "-..-."},
124 	{'-', "-....-"},
125 	{'=', "-...-"},		/* BT */
126 	{':', "---..."},
127 	{';', "-.-.-."},
128 	{'(', "-.--."},		/* KN */
129 	{')', "-.--.-"},
130 	{'$', "...-..-"},
131 	{'+', ".-.-."},		/* AR */
132 	{'@', ".--.-."},	/* AC */
133 	{'_', "..--.-"},
134 	{'\'', ".----."},
135 
136 	/* prosigns without already assigned values */
137 
138 	{'#', ".-..."},		/* AS */
139 	{'&', "...-.-"},	/* SK */
140 	{'*', "...-."},		/* VE */
141 	{'%', "-...-.-"},	/* BK */
142 
143 	{'\0', ""}
144 };
145 
146 /*
147  * Code-points for some Latin1 chars in ISO-8859-1 encoding.
148  * UTF-8 encoded chars in the comments.
149  */
150 static const struct morsetab iso8859_1tab[] = {
151 	{'\340', ".--.-"},	/* à */
152 	{'\341', ".--.-"},	/* á */
153 	{'\342', ".--.-"},	/* â */
154 	{'\344', ".-.-"},	/* ä */
155 	{'\347', "-.-.."},	/* ç */
156 	{'\350', "..-.."},	/* è */
157 	{'\351', "..-.."},	/* é */
158 	{'\352', "-..-."},	/* ê */
159 	{'\361', "--.--"},	/* ñ */
160 	{'\366', "---."},	/* ö */
161 	{'\374', "..--"},	/* ü */
162 
163 	{'\0', ""}
164 };
165 
166 /*
167  * Code-points for some Greek chars in ISO-8859-7 encoding.
168  * UTF-8 encoded chars in the comments.
169  */
170 static const struct morsetab iso8859_7tab[] = {
171 	/*
172 	 * This table does not implement:
173 	 * - the special sequences for the seven diphthongs,
174 	 * - the punctuation differences.
175 	 * Implementing these features would introduce too many
176 	 * special-cases in the program's main loop.
177 	 * The diphthong sequences are:
178 	 * alpha iota		.-.-
179 	 * alpha upsilon	..--
180 	 * epsilon upsilon	---.
181 	 * eta upsilon		...-
182 	 * omicron iota		---..
183 	 * omicron upsilon	..-
184 	 * upsilon iota		.---
185 	 * The different punctuation symbols are:
186 	 * ;	..-.-
187 	 * !	--..--
188 	 */
189 	{'\341', ".-"},		/* α, alpha */
190 	{'\334', ".-"},		/* ά, alpha with acute */
191 	{'\342', "-..."},	/* β, beta */
192 	{'\343', "--."},	/* γ, gamma */
193 	{'\344', "-.."},	/* δ, delta */
194 	{'\345', "."},		/* ε, epsilon */
195 	{'\335', "."},		/* έ, epsilon with acute */
196 	{'\346', "--.."},	/* ζ, zeta */
197 	{'\347', "...."},	/* η, eta */
198 	{'\336', "...."},	/* ή, eta with acute */
199 	{'\350', "-.-."},	/* θ, theta */
200 	{'\351', ".."},		/* ι, iota */
201 	{'\337', ".."},		/* ί, iota with acute */
202 	{'\372', ".."},		/* ϊ, iota with diaeresis */
203 	{'\300', ".."},		/* ΐ, iota with acute and diaeresis */
204 	{'\352', "-.-"},	/* κ, kappa */
205 	{'\353', ".-.."},	/* λ, lambda */
206 	{'\354', "--"},		/* μ, mu */
207 	{'\355', "-."},		/* ν, nu */
208 	{'\356', "-..-"},	/* ξ, xi */
209 	{'\357', "---"},	/* ο, omicron */
210 	{'\374', "---"},	/* ό, omicron with acute */
211 	{'\360', ".--."},	/* π, pi */
212 	{'\361', ".-."},	/* ρ, rho */
213 	{'\363', "..."},	/* σ, sigma */
214 	{'\362', "..."},	/* ς, final sigma */
215 	{'\364', "-"},		/* τ, tau */
216 	{'\365', "-.--"},	/* υ, upsilon */
217 	{'\375', "-.--"},	/* ύ, upsilon with acute */
218 	{'\373', "-.--"},	/* ϋ, upsilon and diaeresis */
219 	{'\340', "-.--"},	/* ΰ, upsilon with acute and diaeresis */
220 	{'\366', "..-."},	/* φ, phi */
221 	{'\367', "----"},	/* χ, chi */
222 	{'\370', "--.-"},	/* ψ, psi */
223 	{'\371', ".--"},	/* ω, omega */
224 	{'\376', ".--"},	/* ώ, omega with acute */
225 
226 	{'\0', ""}
227 };
228 
229 /*
230  * Code-points for the Cyrillic alphabet in KOI8-R encoding.
231  * UTF-8 encoded chars in the comments.
232  */
233 static const struct morsetab koi8rtab[] = {
234 	{'\301', ".-"},		/* а, a */
235 	{'\302', "-..."},	/* б, be */
236 	{'\327', ".--"},	/* в, ve */
237 	{'\307', "--."},	/* г, ge */
238 	{'\304', "-.."},	/* д, de */
239 	{'\305', "."},		/* е, ye */
240 	{'\243', "."},		/* ё, yo, the same as ye */
241 	{'\326', "...-"},	/* ж, she */
242 	{'\332', "--.."},	/* з, ze */
243 	{'\311', ".."},		/* и, i */
244 	{'\312', ".---"},	/* й, i kratkoye */
245 	{'\313', "-.-"},	/* к, ka */
246 	{'\314', ".-.."},	/* л, el */
247 	{'\315', "--"},		/* м, em */
248 	{'\316', "-."},		/* н, en */
249 	{'\317', "---"},	/* о, o */
250 	{'\320', ".--."},	/* п, pe */
251 	{'\322', ".-."},	/* р, er */
252 	{'\323', "..."},	/* с, es */
253 	{'\324', "-"},		/* т, te */
254 	{'\325', "..-"},	/* у, u */
255 	{'\306', "..-."},	/* ф, ef */
256 	{'\310', "...."},	/* х, kha */
257 	{'\303', "-.-."},	/* ц, ce */
258 	{'\336', "---."},	/* ч, che */
259 	{'\333', "----"},	/* ш, sha */
260 	{'\335', "--.-"},	/* щ, shcha */
261 	{'\331', "-.--"},	/* ы, yi */
262 	{'\330', "-..-"},	/* ь, myakhkij znak */
263 	{'\334', "..-.."},	/* э, ae */
264 	{'\300', "..--"},	/* ю, yu */
265 	{'\321', ".-.-"},	/* я, ya */
266 
267 	{'\0', ""}
268 };
269 
270 static void	show(const char *), play(const char *), morse(char);
271 static void	decode (char *), fdecode(FILE *);
272 static void	ttyout(const char *);
273 static void	sighandler(int);
274 
275 static int	pflag, lflag, rflag, sflag, eflag;
276 static int	wpm = 20;	/* effective words per minute */
277 static int	cpm;		/* effective words per minute between
278 				 * characters */
279 #define FREQUENCY 600
280 static int	freq = FREQUENCY;
281 static char	*device;	/* for tty-controlled generator */
282 
283 #define DASH_LEN 3
284 #define CHAR_SPACE 3
285 #define WORD_SPACE (7 - CHAR_SPACE - 1)
286 static float	dot_clock;
287 static float	cdot_clock;
288 static int	spkr, line;
289 static struct termios otty, ntty;
290 static int	olflags;
291 
292 #ifdef SPEAKER
293 static tone_t	sound;
294 #define GETOPTOPTS "c:d:ef:lprsw:"
295 #define USAGE \
296 "usage: morse [-elprs] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
297 #else
298 #define GETOPTOPTS "c:d:ef:lrsw:"
299 #define USAGE \
300 "usage: morse [-elrs] [-d device] [-w speed] [-c speed] [-f frequency] [string ...]\n"
301 
302 #endif
303 
304 static const struct morsetab *hightab;
305 
306 int
307 main(int argc, char *argv[])
308 {
309 	int    ch, lflags;
310 	char  *p, *codeset;
311 
312 	while ((ch = getopt(argc, argv, GETOPTOPTS)) != -1)
313 		switch ((char) ch) {
314 		case 'c':
315 			cpm = atoi(optarg);
316 			break;
317 		case 'd':
318 			device = optarg;
319 			break;
320 		case 'e':
321 			eflag = 1;
322 			setvbuf(stdout, 0, _IONBF, 0);
323 			break;
324 		case 'f':
325 			freq = atoi(optarg);
326 			break;
327 		case 'l':
328 			lflag = 1;
329 			break;
330 #ifdef SPEAKER
331 		case 'p':
332 			pflag = 1;
333 			break;
334 #endif
335 		case 'r':
336 			rflag = 1;
337 			break;
338 		case 's':
339 			sflag = 1;
340 			break;
341 		case 'w':
342 			wpm = atoi(optarg);
343 			break;
344 		case '?':
345 		default:
346 			errx(1, USAGE);
347 		}
348 	if ((sflag && lflag) || (sflag && rflag) || (lflag && rflag)) {
349 		errx(1, "morse: only one of -l, -s, and -r allowed\n");
350 	}
351 	if ((pflag || device) && (sflag || lflag)) {
352 		errx(1, "morse: only one of -p, -d and -l, -s allowed\n");
353 	}
354 	if (cpm == 0) {
355 		cpm = wpm;
356 	}
357 	if ((pflag || device) && ((wpm < 1) || (wpm > 60) || (cpm < 1) || (cpm > 60))) {
358 		errx(1, "morse: insane speed\n");
359 	}
360 	if ((pflag || device) && (freq == 0)) {
361 		freq = FREQUENCY;
362 	}
363 #ifdef SPEAKER
364 	if (pflag) {
365 		if ((spkr = open(SPEAKER, O_WRONLY, 0)) == -1) {
366 			err(1, SPEAKER);
367 		}
368 	} else
369 #endif
370 	if (device) {
371 		if ((line = open(device, O_WRONLY | O_NONBLOCK)) == -1) {
372 			err(1, "open tty line");
373 		}
374 		if (tcgetattr(line, &otty) == -1) {
375 			err(1, "tcgetattr() failed");
376 		}
377 		ntty = otty;
378 		ntty.c_cflag |= CLOCAL;
379 		tcsetattr(line, TCSANOW, &ntty);
380 		lflags = fcntl(line, F_GETFL);
381 		lflags &= ~O_NONBLOCK;
382 		fcntl(line, F_SETFL, &lflags);
383 		ioctl(line, TIOCMGET, &lflags);
384 		lflags &= ~TIOCM_RTS;
385 		olflags = lflags;
386 		ioctl(line, TIOCMSET, &lflags);
387 		(void)signal(SIGHUP, sighandler);
388 		(void)signal(SIGINT, sighandler);
389 		(void)signal(SIGQUIT, sighandler);
390 		(void)signal(SIGTERM, sighandler);
391 	}
392 	if (pflag || device) {
393 		dot_clock = wpm / 2.4;		/* dots/sec */
394 		dot_clock = 1 / dot_clock;	/* duration of a dot */
395 		dot_clock = dot_clock / 2;	/* dot_clock runs at twice */
396 						/* the dot rate */
397 		dot_clock = dot_clock * 100;	/* scale for ioctl */
398 
399 		cdot_clock = cpm / 2.4;		/* dots/sec */
400 		cdot_clock = 1 / cdot_clock;	/* duration of a dot */
401 		cdot_clock = cdot_clock / 2;	/* dot_clock runs at twice */
402 						/* the dot rate */
403 		cdot_clock = cdot_clock * 100;	/* scale for ioctl */
404 	}
405 
406 	argc -= optind;
407 	argv += optind;
408 
409 	if (setlocale(LC_CTYPE, "") != NULL &&
410 	    *(codeset = nl_langinfo(CODESET)) != '\0') {
411 		if (strcmp(codeset, "KOI8-R") == 0)
412 			hightab = koi8rtab;
413 		else if (strcmp(codeset, "ISO8859-1") == 0 ||
414 			 strcmp(codeset, "ISO8859-15") == 0)
415 			hightab = iso8859_1tab;
416 		else if (strcmp(codeset, "ISO8859-7") == 0)
417 			hightab = iso8859_7tab;
418 	}
419 
420 	if (lflag) {
421 		printf("m");
422 	}
423 	if (rflag) {
424 		if (*argv) {
425 			do {
426 				p = strtok(*argv, DELIMITERS);
427 				if (p == NULL) {
428 					decode(*argv);
429 				}
430 				else {
431 					while (p) {
432 						decode(p);
433 						p = strtok(NULL, DELIMITERS);
434 					}
435 				}
436 			} while (*++argv);
437 			putchar('\n');
438 		} else {
439 			fdecode(stdin);
440 		}
441 	}
442 	else if (*argv) {
443 		do {
444 			for (p = *argv; *p; ++p) {
445 				if (eflag)
446 					putchar(*p);
447 				morse(*p);
448 			}
449 			if (eflag)
450 				putchar(' ');
451 			morse(' ');
452 		} while (*++argv);
453 	} else {
454 		while ((ch = getchar()) != EOF) {
455 			if (eflag)
456 				putchar(ch);
457 			morse(ch);
458 		}
459 	}
460 	if (device)
461 		tcsetattr(line, TCSANOW, &otty);
462 	exit(0);
463 }
464 
465 static void
466 morse(char c)
467 {
468 	const struct morsetab *m;
469 
470 	if (isalpha((unsigned char)c))
471 		c = tolower((unsigned char)c);
472 	if ((c == '\r') || (c == '\n'))
473 		c = ' ';
474 	if (c == ' ') {
475 		if (pflag)
476 			play(" ");
477 		else if (device)
478 			ttyout(" ");
479 		else if (lflag)
480 			printf("\n");
481 		else
482 			show("");
483 		return;
484 	}
485 	for (m = ((unsigned char)c < 0x80? mtab: hightab);
486 	     m != NULL && m->inchar != '\0';
487 	     m++) {
488 		if (m->inchar == c) {
489 			if (pflag) {
490 				play(m->morse);
491 			} else if (device) {
492 				ttyout(m->morse);
493 			} else
494 				show(m->morse);
495 		}
496 	}
497 }
498 
499 static void
500 show(const char *s)
501 {
502 	if (lflag) {
503 		printf("%s ", s);
504 	} else if (sflag) {
505 		printf(" %s\n", s);
506 	} else {
507 		for (; *s; ++s)
508 			printf(" %s", *s == '.' ? *(s + 1) == '\0' ? "dit" :
509 			    "di" : "dah");
510 		printf("\n");
511 	}
512 }
513 
514 static void
515 play(const char *s)
516 {
517 #ifdef SPEAKER
518 	const char *c;
519 
520 	for (c = s; *c != '\0'; c++) {
521 		switch (*c) {
522 		case '.':
523 			sound.frequency = freq;
524 			sound.duration = dot_clock;
525 			break;
526 		case '-':
527 			sound.frequency = freq;
528 			sound.duration = dot_clock * DASH_LEN;
529 			break;
530 		case ' ':
531 			sound.frequency = 0;
532 			sound.duration = cdot_clock * WORD_SPACE;
533 			break;
534 		default:
535 			sound.duration = 0;
536 		}
537 		if (sound.duration) {
538 			if (ioctl(spkr, SPKRTONE, &sound) == -1) {
539 				err(1, "ioctl play");
540 			}
541 		}
542 		sound.frequency = 0;
543 		sound.duration = dot_clock;
544 		if (ioctl(spkr, SPKRTONE, &sound) == -1) {
545 			err(1, "ioctl rest");
546 		}
547 	}
548 	sound.frequency = 0;
549 	sound.duration = cdot_clock * CHAR_SPACE;
550 	ioctl(spkr, SPKRTONE, &sound);
551 #endif
552 }
553 
554 static void
555 ttyout(const char *s)
556 {
557 	const char *c;
558 	int duration, on, lflags;
559 
560 	for (c = s; *c != '\0'; c++) {
561 		switch (*c) {
562 		case '.':
563 			on = 1;
564 			duration = dot_clock;
565 			break;
566 		case '-':
567 			on = 1;
568 			duration = dot_clock * DASH_LEN;
569 			break;
570 		case ' ':
571 			on = 0;
572 			duration = cdot_clock * WORD_SPACE;
573 			break;
574 		default:
575 			on = 0;
576 			duration = 0;
577 		}
578 		if (on) {
579 			ioctl(line, TIOCMGET, &lflags);
580 			lflags |= TIOCM_RTS;
581 			ioctl(line, TIOCMSET, &lflags);
582 		}
583 		duration *= 10000;
584 		if (duration)
585 			usleep(duration);
586 		ioctl(line, TIOCMGET, &lflags);
587 		lflags &= ~TIOCM_RTS;
588 		ioctl(line, TIOCMSET, &lflags);
589 		duration = dot_clock * 10000;
590 		usleep(duration);
591 	}
592 	duration = cdot_clock * CHAR_SPACE * 10000;
593 	usleep(duration);
594 }
595 
596 void
597 fdecode(FILE *stream)
598 {
599 	char *n, *p, *s;
600 	char buf[BUFSIZ];
601 
602 	s = buf;
603 	while (fgets(s, BUFSIZ - (s - buf), stream)) {
604 		p = buf;
605 
606 		while (*p && isblank(*p)) {
607 			p++;
608 		}
609 		while (*p && isspace(*p)) {
610 			p++;
611 			putchar (' ');
612 		}
613 		while (*p) {
614 			n = strpbrk(p, WHITESPACE);
615 			if (n == NULL) {
616 				/* The token was interrupted at the end
617 				 * of the buffer. Shift it to the begin
618 				 * of the buffer.
619 				 */
620 				for (s = buf; *p; *s++ = *p++)
621 					;
622 			} else {
623 				*n = '\0';
624 				n++;
625 				decode(p);
626 				p = n;
627 			}
628 		}
629 	}
630 	putchar('\n');
631 }
632 
633 void
634 decode(char *p)
635 {
636 	char c;
637 	const struct morsetab *m;
638 
639 	c = ' ';
640 	for (m = mtab; m != NULL && m->inchar != '\0'; m++) {
641 		if (strcmp(m->morse, p) == 0) {
642 			c = m->inchar;
643 			break;
644 		}
645 	}
646 
647 	if (c == ' ')
648 		for (m = hightab; m != NULL && m->inchar != '\0'; m++) {
649 			if (strcmp(m->morse, p) == 0) {
650 				c = m->inchar;
651 				break;
652 			}
653 		}
654 
655 	putchar(c);
656 }
657 
658 static void
659 sighandler(int signo)
660 {
661 
662 	ioctl(line, TIOCMSET, &olflags);
663 	tcsetattr(line, TCSANOW, &otty);
664 
665 	signal(signo, SIG_DFL);
666 	(void)kill(getpid(), signo);
667 }
668