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