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