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