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
62struct morsetab {
63	const char      inchar;
64	const char     *morse;
65};
66
67static 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 */
145static 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 */
165static 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 */
228static 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
265static void	show(const char *), play(const char *), morse(char);
266static void	decode (char *), fdecode(FILE *);
267static void	ttyout(const char *);
268static void	sighandler(int);
269
270static int	pflag, lflag, rflag, sflag, eflag;
271static int	wpm = 20;	/* effective words per minute */
272static int	cpm;		/* effective words per minute between
273				 * characters */
274#define FREQUENCY 600
275static int	freq = FREQUENCY;
276static 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)
281static float	dot_clock;
282static float	cdot_clock;
283static int	spkr, line;
284static struct termios otty, ntty;
285static int	olflags;
286
287#ifdef SPEAKER
288static 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
299static const struct morsetab *hightab;
300
301int
302main(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
460static void
461morse(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
494static void
495show(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
509static void
510play(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
549static void
550ttyout(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
591void
592fdecode(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
628void
629decode(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
653static void
654sighandler(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