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
main(int argc,char * argv[])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
morse(char c)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
show(const char * s)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
play(const char * s)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
ttyout(const char * s)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
fdecode(FILE * stream)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
decode(char * p)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
sighandler(int signo)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