1 /* 2 * xmcd - Motif(R) CD Audio Player/Ripper 3 * 4 * Copyright (C) 1993-2004 Ti Kan 5 * E-mail: xmcd@amb.org 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20 * 21 */ 22 #ifndef lint 23 static char *_main_c_ident_ = "@(#)main.c 7.36 04/03/17"; 24 #endif 25 26 #include "common_d/appenv.h" 27 #include "common_d/version.h" 28 #include "common_d/util.h" 29 #include "xmcd_d/xmcd.h" 30 #include "xmcd_d/resource.h" 31 #include "xmcd_d/widget.h" 32 #include "xmcd_d/callback.h" 33 #include "xmcd_d/cdfunc.h" 34 #include "xmcd_d/command.h" 35 #include "libdi_d/libdi.h" 36 #include "cdda_d/cdda.h" 37 38 39 #define STARTUP_CMD_DELAY 1500 /* Delay interval before running 40 * startup command 41 */ 42 43 /* Global data */ 44 char *progname, /* The path name we are invoked with */ 45 *cdisplay; /* Command-line specified display */ 46 appdata_t app_data; /* Options data */ 47 widgets_t widgets; /* Holder of all widgets */ 48 pixmaps_t pixmaps; /* Holder of all pixmaps */ 49 FILE *errfp; /* Error message stream */ 50 51 /* Data global to this module only */ 52 STATIC curstat_t status; /* Current CD player status */ 53 STATIC XtAppContext app_context; /* Application context */ 54 STATIC Colormap localcmap; /* local colormap */ 55 STATIC char *cmd; /* Command string */ 56 57 58 /*********************** 59 * public routines * 60 ***********************/ 61 62 /* 63 * curstat_addr 64 * Return the address of the curstat_t structure. 65 * 66 * Args: 67 * Nothing. 68 * 69 * Return: 70 * Nothing. 71 */ 72 curstat_t * 73 curstat_addr(void) 74 { 75 return (&status); 76 } 77 78 79 /* 80 * event_loop 81 * Used to handle X events while waiting on I/O. 82 * 83 * Args: 84 * flag - Currently unused 85 * 86 * Return: 87 * Nothing. 88 */ 89 /* ARGSUSED */ 90 void 91 event_loop(int flag) 92 { 93 while (XtAppPending(app_context)) 94 XtAppProcessEvent(app_context, XtIMAll); 95 } 96 97 98 /* 99 * shutdown_gui 100 * Perform window system shutdown cleanup. 101 * 102 * Args: 103 * None. 104 * 105 * Return: 106 * Nothing. 107 */ 108 void 109 shutdown_gui(void) 110 { 111 if (app_data.instcmap) 112 XFreeColormap(XtDisplay(widgets.toplevel), localcmap); 113 } 114 115 116 /*********************** 117 * internal routines * 118 ***********************/ 119 120 121 /* 122 * usage 123 * Display command line usage syntax 124 * 125 * Args: 126 * arg - The command line arg string that is invalid 127 * 128 * Return: 129 * Nothing. 130 */ 131 STATIC void 132 usage(char *arg) 133 { 134 (void) fprintf(errfp, 135 "%s %s.%s.%s Motif(R) CD player/ripper\n%s\n%s\n\n", 136 PROGNAME, VERSION_MAJ, VERSION_MIN, VERSION_TEENY, 137 COPYRIGHT, XMCD_URL 138 ); 139 140 if (strcmp(arg, "-help") != 0) 141 (void) fprintf(errfp, "%s: %s\n\n", app_data.str_badopts, arg); 142 143 (void) fprintf(errfp, "Usage: %s %s %s %s\n %s %s %s", 144 PROGNAME, 145 "[-dev device]", 146 "[-instcmap]", 147 "[-remote]", 148 "[-rmthost hostname]", 149 "[-debug level#]", 150 "[-help]" 151 ); 152 153 #ifdef _SOLARIS 154 /* Solaris volume manager auto-start support */ 155 (void) fprintf(errfp, "\n [-c device] [-X] [-o]"); 156 #endif 157 158 (void) fprintf(errfp, "\n [command [arg ...]]\n\n"); 159 160 /* Display command usage */ 161 cmd_usage(); 162 163 (void) fprintf(errfp, 164 "\nStandard Xt Intrinsics toolkit options are also supported.\n"); 165 } 166 167 168 /* 169 * chk_resource_ver 170 * Look for XMcd.version in the user's private X resource files 171 * and check if there is a mismatch to this version of xmcd. 172 * Display error message if mismatch found. 173 * 174 * Args: 175 * dpy - The X display. 176 * 177 * Return: 178 * FALSE - Mismatch found, or other error. 179 * TRUE - No mismatch found. 180 */ 181 STATIC bool_t 182 chk_resource_ver(Display *dpy) 183 { 184 #ifdef __VMS 185 return TRUE; 186 #else 187 int pfd[2], 188 ret; 189 unsigned int ver, 190 vmaj, 191 vmin; 192 pid_t cpid; 193 waitret_t wstat; 194 bool_t chkok = FALSE; 195 FILE *fp; 196 char *cp, 197 *path, 198 buf[STR_BUF_SZ * 4]; 199 200 if (PIPE(pfd) < 0) { 201 perror("chk_resource_ver: pipe failed"); 202 return FALSE; 203 } 204 205 if ((cp = getenv("XUSERFILESEARCHPATH")) == NULL) 206 return TRUE; /* Cannot check, just hope it's ok */ 207 208 path = XtResolvePathname(dpy, NULL, "XMcd", NULL, cp, NULL, 0, NULL); 209 if (path == NULL || path[0] == '\0') 210 return TRUE; /* Cannot check, just hope it's ok */ 211 212 switch (cpid = FORK()) { 213 case -1: 214 /* Fork failed */ 215 perror("chk_resource_ver: fork failed"); 216 return FALSE; 217 case 0: 218 /* Child */ 219 (void) util_signal(SIGPIPE, SIG_IGN); 220 (void) close(pfd[0]); 221 break; 222 default: 223 /* Parent */ 224 (void) close(pfd[1]); 225 226 (void) read(pfd[0], &ver, sizeof(ver)); 227 (void) close(pfd[0]); 228 229 ret = util_waitchild(cpid, 5, NULL, 0, FALSE, &wstat); 230 231 if (ret < 0) 232 chkok = FALSE; 233 else if (WIFEXITED(wstat)) 234 chkok = (bool_t) (WEXITSTATUS(wstat) == 0); 235 else if (WTERMSIG(wstat)) 236 chkok = FALSE; 237 238 if (!chkok) 239 return TRUE; /* Cannot check, just hope it's ok */ 240 241 vmaj = (ver & 0xff00) >> 8; 242 vmin = (ver & 0x00ff); 243 244 if (vmaj != atoi(VERSION_MAJ) || vmin != atoi(VERSION_MIN)) { 245 (void) fprintf(errfp, "%s Fatal Error:\n" 246 "The XMcd.version (%u.%u) in the %s file\n" 247 "is not compatible with this version of " 248 "%s (%s.%s).\n", 249 PROGNAME, vmaj, vmin, path, 250 PROGNAME, VERSION_MAJ, VERSION_MIN 251 ); 252 return FALSE; 253 } 254 255 return TRUE; 256 } 257 258 /* Set defaults */ 259 vmaj = atoi(VERSION_MAJ); 260 vmin = atoi(VERSION_MIN); 261 262 /* Force uid and gid to original setting */ 263 if (!util_set_ougid()) 264 _exit(errno); 265 266 /* Read resource file and look for XMcd.version */ 267 if ((fp = fopen(path, "r")) == NULL) 268 _exit(0); 269 270 while (fgets(buf, sizeof(buf), fp) != NULL) { 271 if (buf[0] == '!' || buf[0] == '#' || 272 buf[0] == '\n' || buf[0] == '\0') 273 continue; 274 275 if (sscanf(buf, "XMcd.version: %u.%u", &vmaj, &vmin) == 2) 276 break; /* Found it */ 277 } 278 279 (void) fclose(fp); 280 281 ver = ((vmaj << 8) | vmin); 282 (void) write(pfd[1], &ver, sizeof(ver)); 283 (void) close(pfd[1]); 284 285 _exit(0); 286 /*NOTREACHED*/ 287 #endif /* __VMS */ 288 } 289 290 291 /* 292 * x_error 293 * X error handler - Used when user interface debugging is enabled 294 * 295 * Args: 296 * dpy - The X display 297 * ev - The X event 298 * 299 * Return: 300 * Does not return. 301 */ 302 STATIC int 303 x_error(Display *dpy, XErrorEvent *ev) 304 { 305 char str[ERR_BUF_SZ], 306 msg[ERR_BUF_SZ], 307 num[32]; 308 char *mtyp = "XlibMessage"; 309 310 (void) fprintf(errfp, "x_error: X error on \"%s\"\n", 311 DisplayString(dpy) 312 ); 313 XGetErrorText(dpy, ev->error_code, str, sizeof(str)); 314 XGetErrorDatabaseText( 315 dpy, mtyp, "XError", "X Error", msg, sizeof(msg) 316 ); 317 (void) fprintf(errfp, "%s: %s\n ", msg, str); 318 XGetErrorDatabaseText( 319 dpy, mtyp, "MajorCode", "Request Major code %d", 320 msg, sizeof(msg) 321 ); 322 (void) fprintf(errfp, msg, ev->request_code); 323 if (ev->request_code < 128) { 324 (void) sprintf(num, "%d", ev->request_code); 325 XGetErrorDatabaseText( 326 dpy, "XRequest", num, "", str, sizeof(str) 327 ); 328 (void) fprintf(errfp, " (%s)\n", str); 329 } 330 else { 331 XGetErrorDatabaseText( 332 dpy, mtyp, "MinorCode", "Request Minor code %d", 333 msg, sizeof(msg) 334 ); 335 (void) fputs(" ", errfp); 336 (void) fprintf(errfp, msg, ev->minor_code); 337 (void) fputs("\n", errfp); 338 } 339 if (ev->error_code >= 128) { 340 (void) fprintf(errfp, " Error code 0x%x\n", ev->error_code); 341 } 342 else { 343 switch (ev->error_code) { 344 case BadValue: 345 XGetErrorDatabaseText( 346 dpy, mtyp, "Value", "Value 0x%x", 347 msg, sizeof(msg) 348 ); 349 (void) fputs(" ", errfp); 350 (void) fprintf(errfp, msg, ev->resourceid); 351 (void) fputs("\n", errfp); 352 break; 353 case BadAtom: 354 XGetErrorDatabaseText( 355 dpy, mtyp, "AtomID", "AtomID 0x%x", 356 msg, sizeof(msg) 357 ); 358 (void) fputs(" ", errfp); 359 (void) fprintf(errfp, msg, ev->resourceid); 360 (void) fputs("\n", errfp); 361 break; 362 case BadWindow: 363 case BadPixmap: 364 case BadCursor: 365 case BadFont: 366 case BadDrawable: 367 case BadColor: 368 case BadGC: 369 case BadIDChoice: 370 XGetErrorDatabaseText( 371 dpy, mtyp, "ResourceID", "ResourceID 0x%x", 372 msg, sizeof(msg) 373 ); 374 (void) fputs(" ", errfp); 375 (void) fprintf(errfp, msg, ev->resourceid); 376 (void) fputs("\n", errfp); 377 378 break; 379 default: 380 (void) fprintf(errfp, " Error code: 0x%x\n", 381 ev->error_code 382 ); 383 break; 384 } 385 } 386 XGetErrorDatabaseText( 387 dpy, mtyp, "ErrorSerial", "Error Serial #%d", 388 msg, sizeof(msg) 389 ); 390 (void) fputs(" ", errfp); 391 (void) fprintf(errfp, msg, ev->serial); 392 (void) fputs("\n", errfp); 393 XGetErrorDatabaseText( 394 dpy, mtyp, "CurrentSerial", "Current Serial #%d", 395 msg, sizeof(msg) 396 ); 397 (void) fputs("\n", errfp); 398 399 if (ev->error_code == BadImplementation) 400 return 0; 401 402 /* Cause a core dump if possible, and exit immediately */ 403 abort(); 404 return 0; /* Just to silence some picky compilers */ 405 } 406 407 408 /* 409 * x_ioerror 410 * X IO error handler - Used when user interface debugging is enabled 411 * 412 * Args: 413 * dpy - The X display 414 * ev - The X event 415 * 416 * Return: 417 * Does not return. 418 */ 419 STATIC int 420 x_ioerror(Display *dpy) 421 { 422 if (errno == EPIPE) { 423 (void) fprintf(errfp, 424 "x_ioerror: X connection to \"%s\" broken:\n" 425 "explicit kill or server shutdown.\n", 426 DisplayString(dpy) 427 ); 428 } 429 else { 430 (void) fprintf(errfp, 431 "x_ioerror: fatal IO error (errno %d) on \"%s\"\n", 432 errno, DisplayString(dpy) 433 ); 434 } 435 436 /* Cause a core dump if possible, and exit immediately */ 437 abort(); 438 return 0; /* Just to silence some picky compilers */ 439 } 440 441 442 /* 443 * main 444 * The main function 445 */ 446 int 447 main(int argc, char **argv) 448 { 449 int i; 450 Display *display; 451 #ifdef HAS_EUID 452 uid_t euid, 453 ruid; 454 gid_t egid, 455 rgid; 456 #endif 457 458 /* Program startup multi-threading initializations */ 459 cdda_preinit(); 460 461 /* Error message stream */ 462 errfp = stderr; 463 464 /* Initialize variables */ 465 progname = argv[0]; 466 cmd = NULL; 467 localcmap = (Colormap) 0; 468 (void) memset(&status, 0, sizeof(curstat_t)); 469 470 /* Handle some signals */ 471 if (util_signal(SIGINT, onsig) == SIG_IGN) 472 (void) util_signal(SIGINT, SIG_IGN); 473 if (util_signal(SIGHUP, onsig) == SIG_IGN) 474 (void) util_signal(SIGHUP, SIG_IGN); 475 if (util_signal(SIGTERM, onsig) == SIG_IGN) 476 (void) util_signal(SIGTERM, SIG_IGN); 477 if (util_signal(SIGQUIT, onsig) == SIG_IGN) 478 (void) util_signal(SIGQUIT, SIG_IGN); 479 480 /* Set SIGCHLD handler to default */ 481 #ifndef __VMS_VER 482 (void) util_signal(SIGCHLD, SIG_DFL); 483 #else 484 #if (__VMS_VER >= 70000000) && (__DECC_VER > 50230003) 485 /* OpenVMS v7.0 and DECCV5.2 have these defined */ 486 (void) util_signal(SIGCHLD, SIG_DFL); 487 #endif 488 #endif 489 490 /* Ignore SIGALRMs until we need them */ 491 (void) util_signal(SIGALRM, SIG_IGN); 492 493 /* Hack: Aside from stdin, stdout, stderr, there shouldn't 494 * be any other files open, so force-close them. This is 495 * necessary in case xmcd was inheriting any open file 496 * descriptors from a parent process which is for the 497 * CD-ROM device, and violating exclusive-open requirements 498 * on some platforms. 499 */ 500 for (i = 3; i < 10; i++) 501 (void) close(i); 502 503 #if (XtSpecificationRelease >= 5) 504 /* Set locale */ 505 XtSetLanguageProc(NULL, NULL, NULL); 506 #endif 507 508 /* Snoop command line for -display before XtVaAppInitialize 509 * consumes it Also, handle -help here before we attempt 510 * to open the X display. 511 */ 512 for (i = 0; i < argc; i++) { 513 if (strcmp(argv[i], "-display") == 0) 514 cdisplay = argv[++i]; 515 else if (strcmp(argv[i], "-help") == 0) { 516 usage(argv[i]); 517 exit(1); 518 } 519 } 520 521 /* Initialize libutil */ 522 util_init(); 523 524 #ifdef HAS_EUID 525 /* Get real IDs */ 526 ruid = getuid(); 527 rgid = getgid(); 528 529 /* Save effective IDs */ 530 euid = geteuid(); 531 egid = getegid(); 532 533 /* Give up root until we have a connection to the X server, so 534 * that X authentication will work correctly. 535 */ 536 (void) util_seteuid(ruid); 537 (void) util_setegid(rgid); 538 #endif 539 540 /* Initialize X toolkit */ 541 widgets.toplevel = XtVaAppInitialize( 542 &app_context, 543 "XMcd", 544 options, XtNumber(options), 545 &argc, argv, 546 NULL, 547 XmNmappedWhenManaged, False, 548 NULL 549 ); 550 551 display = XtDisplay(widgets.toplevel); 552 553 /* Check for old xmcd X resources */ 554 if (!chk_resource_ver(display)) 555 exit(1); 556 557 /* Get application options */ 558 XtVaGetApplicationResources( 559 widgets.toplevel, 560 (XtPointer) &app_data, 561 resources, 562 XtNumber(resources), 563 NULL 564 ); 565 566 /* Build command */ 567 if (argc > 1) { 568 for (i = 1; i < argc; i++) { 569 if (argv[i][0] == '-') { 570 usage(argv[i]); 571 exit(1); 572 } 573 574 if (i == 1) { 575 cmd = (char *) MEM_ALLOC( 576 "cmd", 577 strlen(argv[i]) + 1 578 ); 579 if (cmd == NULL) { 580 CD_FATAL(app_data.str_nomemory); 581 exit(1); 582 } 583 cmd[0] = '\0'; 584 } 585 else { 586 cmd = (char *) MEM_REALLOC( 587 "cmd", 588 cmd, 589 strlen(cmd) + strlen(argv[i]) + 2 590 ); 591 if (cmd == NULL) { 592 CD_FATAL(app_data.str_nomemory); 593 exit(1); 594 } 595 (void) strcat(cmd, " "); 596 } 597 (void) strcat(cmd, argv[i]); 598 } 599 } 600 601 /* Set X error handlers if user interface debugging is enabled */ 602 if ((app_data.debug & DBG_UI) != 0) { 603 (void) XSetErrorHandler(x_error); 604 (void) XSetIOErrorHandler(x_ioerror); 605 } 606 607 /* Remote control specified - handle that here */ 608 if (app_data.remotemode) { 609 cmd_init(&status, display, TRUE); 610 exit(cmd_sendrmt(display, cmd)); 611 } 612 613 /* Install colormap if specified */ 614 if (app_data.instcmap) { 615 localcmap = XCopyColormapAndFree( 616 display, 617 DefaultColormap(display, DefaultScreen(display)) 618 ); 619 XtVaSetValues(widgets.toplevel, XmNcolormap, localcmap, NULL); 620 } 621 622 /* Create all widgets */ 623 create_widgets(&widgets); 624 625 /* Configure resources before realizing widgets */ 626 pre_realize_config(&widgets); 627 628 /* Display widgets */ 629 XtRealizeWidget(widgets.toplevel); 630 631 /* Configure resources after realizing widgets */ 632 post_realize_config(&widgets, &pixmaps); 633 634 /* Register callback routines */ 635 register_callbacks(&widgets, &status); 636 637 #ifdef HAS_EUID 638 /* Ok, back to root */ 639 (void) util_seteuid(euid); 640 (void) util_setegid(egid); 641 #endif 642 643 /* Initialize various subsystems */ 644 cd_init(&status); 645 646 /* Start various subsystems */ 647 cd_start(&status); 648 649 /* Make main window appear */ 650 XtMapWidget(widgets.toplevel); 651 652 /* Start remote control */ 653 cmd_init(&status, display, FALSE); 654 655 if (cmd != NULL) { 656 /* Change to watch cursor */ 657 cd_busycurs(TRUE, CURS_ALL); 658 659 /* Schedule to perform start-up command */ 660 (void) cd_timeout( 661 STARTUP_CMD_DELAY, 662 cmd_startup, 663 (byte_t *) cmd 664 ); 665 } 666 667 /* Main event processing loop */ 668 XtAppMainLoop(app_context); 669 670 shutdown_gui(); 671 exit(0); 672 673 /*NOTREACHED*/ 674 } 675 676