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
36static 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
43static char sccsid[] = "@(#)morse.c	8.1 (Berkeley) 5/31/93";
44#endif
45static 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
69struct morsetab {
70	const char      inchar;
71	const char     *morse;
72};
73
74static 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 */
150static 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 */
169static 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 */
232static 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
269static void	show(const char *), play(const char *), morse(char);
270static void	ttyout(const char *);
271static 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
277static int	pflag, lflag, sflag, eflag;
278static int	wpm = 20;	/* effective words per minute */
279static int	cpm;		/* effective words per minute between
280				 * characters */
281#define FREQUENCY 600
282static int	freq = FREQUENCY;
283static 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)
288static float	dot_clock;
289static float	cdot_clock;
290static int	spkr, line;
291static struct termios otty, ntty;
292static int	olflags;
293
294#ifdef SPEAKER
295static 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
303static const struct morsetab *hightab;
304
305int
306main(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
447static void
448morse(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
481static void
482show(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
496static void
497play(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
538static void
539ttyout(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
580static void
581sighandler(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