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