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