1 /* 2 * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net> 3 * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. 4 * 5 * Distributed unter the terms of the MIT license. 6 */ 7 #include <stdio.h> 8 #include <signal.h> 9 #include <sys/wait.h> 10 #include <stdlib.h> 11 #include <malloc.h> 12 13 #include <storage/Path.h> 14 #include <NodeInfo.h> 15 #include <Roster.h> 16 #include <Alert.h> 17 #include <Screen.h> 18 #include <String.h> 19 #include <TextView.h> 20 21 #include "TermApp.h" 22 #include "TermWindow.h" 23 #include "TermConst.h" 24 #include "spawn.h" 25 #include "PrefHandler.h" 26 #include "CodeConv.h" 27 28 // Preference temporary 29 extern PrefHandler *gTermPref; 30 extern char gWindowName[]; 31 32 bool usage_requested = false; 33 bool geometry_requested = false; 34 bool color_requested = false; 35 36 int argmatch(char **, int, char *, char *, int, char **, int *); 37 void sort_args(int, char **); 38 int text_to_rgb(char *, rgb_color *, char *); 39 40 41 const ulong MSG_ACTIVATE_TERM = 'msat'; 42 const ulong MSG_TERM_IS_MINIMIZE = 'mtim'; 43 44 45 TermApp::TermApp(void) 46 :BApplication( TERM_SIGNATURE ) 47 { 48 BList teams; 49 50 be_roster->GetAppList(TERM_SIGNATURE, &teams); 51 fWindowNumber = teams.CountItems(); 52 53 if(fWindowNumber == 0) 54 { 55 be_roster->GetAppList(R5_TERM_SIGNATURE, &teams); 56 fWindowNumber = teams.CountItems(); 57 } 58 59 int i = fWindowNumber / 16; 60 int j = fWindowNumber % 16; 61 62 int k =(j * 16) + (i * 64) + 50; 63 int l =(j * 16) + 50; 64 65 fTermWindow = NULL; 66 fAboutPanel = NULL; 67 fTermFrame.Set(k, l, k + 50, k + 50); 68 69 gTermPref = new PrefHandler(); 70 } 71 72 73 TermApp::~TermApp(void) 74 { 75 // delete gTermPref; 76 } 77 78 79 void 80 TermApp::ReadyToRun(void) 81 { 82 // Prevent opeing window when option -h is given. 83 if(usage_requested) 84 return; 85 86 const char *command = NULL; 87 const char *encoding; 88 int rows, cols; 89 90 encoding = gTermPref->getString(PREF_TEXT_ENCODING); 91 92 // Get encoding name (setenv TTYPE in spawn_shell functions) 93 const etable *p = encoding_table; 94 while(p->name) { 95 96 if(!strcmp(p->name, encoding)) { 97 encoding = p->shortname; 98 break; 99 } 100 p++; 101 } 102 103 if(CommandLine.Length() > 0) 104 command = CommandLine.String(); 105 else 106 command = gTermPref->getString(PREF_SHELL); 107 108 rows = gTermPref->getInt32(PREF_ROWS); 109 cols = gTermPref->getInt32(PREF_COLS); 110 111 pfd = spawn_shell(rows, cols, command, encoding); 112 113 // failed spawn, print stdout and open alert panel 114 if(pfd == -1 ) { 115 (new BAlert("alert", "Terminal couldn't start the shell. Sorry.", 116 "ok", NULL, NULL,B_WIDTH_FROM_LABEL, 117 B_INFO_ALERT))->Go(NULL); 118 PostMessage(B_QUIT_REQUESTED); 119 } 120 121 MakeTermWindow(fTermFrame); 122 } 123 124 125 void 126 TermApp::Quit(void) 127 { 128 if(!usage_requested){ 129 130 int status; 131 132 kill(-sh_pid, SIGHUP); 133 wait(&status); 134 } 135 136 delete gTermPref; 137 138 BApplication::Quit(); 139 } 140 141 142 void 143 TermApp::AboutRequested() 144 { 145 BAlert *alert = new BAlert("about", "Terminal\n" 146 "\twritten by Kazuho Okui and Takashi Murai\n" 147 "\tupdated by Kian Duffy and others\n\n" 148 "\tCopyright " B_UTF8_COPYRIGHT "2003-2005, Haiku.\n", "Ok"); 149 BTextView *view = alert->TextView(); 150 BFont font; 151 152 view->SetStylable(true); 153 154 view->GetFont(&font); 155 font.SetSize(18); 156 font.SetFace(B_BOLD_FACE); 157 view->SetFontAndColor(0, 8, &font); 158 159 alert->Go(); 160 } 161 162 163 void 164 TermApp::MessageReceived(BMessage* msg) 165 { 166 switch(msg->what) 167 { 168 case MENU_NEW_TREM: 169 { 170 RunNewTerm(); 171 break; 172 } 173 case MENU_SWITCH_TERM: 174 { 175 SwitchTerm(); 176 break; 177 } 178 case MSG_ACTIVATE_TERM: 179 { 180 fTermWindow->TermWinActivate(); 181 break; 182 } 183 case MSG_TERM_IS_MINIMIZE: 184 { 185 BMessage reply(B_REPLY); 186 reply.AddBool("result", fTermWindow->IsMinimized()); 187 msg->SendReply(&reply); 188 break; 189 } 190 default: 191 { 192 BApplication::MessageReceived(msg); 193 break; 194 } 195 } 196 } 197 198 struct standard_args 199 { 200 char *name; 201 char *longname; 202 int priority; 203 int nargs; 204 const char *prefname; 205 }; 206 207 struct standard_args standard_args[] = 208 { 209 { "-h", "--help", 90, 0, NULL }, 210 { "-p", "--preference", 80, 1, NULL }, 211 { "-t", "--title", 70, 1, NULL }, 212 { "-geom", "--geometry", 50, 1, NULL }, 213 { "-fg", "--text-fore-color", 40, 1, PREF_TEXT_FORE_COLOR }, 214 { "-bg", "--text-back-color", 35, 1, PREF_TEXT_BACK_COLOR }, 215 { "-curfg", "--cursor-fore-color", 30, 1, PREF_CURSOR_FORE_COLOR }, 216 { "-curbg", "--cursor-back-color", 25, 1, PREF_CURSOR_BACK_COLOR }, 217 { "-selfg", "--select-fore-color", 20, 1, PREF_SELECT_FORE_COLOR }, 218 { "-selbg", "--select-back-color", 15, 1, PREF_SELECT_BACK_COLOR }, 219 { "-imfg", "--im-fore-color", 10, 1, PREF_IM_FORE_COLOR }, 220 { "-imbg", "--im-back-color", 5, 1, PREF_IM_BACK_COLOR }, 221 { "-imsel", "--im-select-color", 0, 1, PREF_IM_SELECT_COLOR }, 222 }; 223 224 void 225 TermApp::ArgvReceived(int32 argc, char **argv) 226 { 227 int skip_args = 0; 228 char *value = 0; 229 rgb_color color; 230 231 if(argc < 2) 232 return; 233 234 sort_args(argc, argv); 235 argc = 0; 236 while(argv[argc]) 237 argc++; 238 239 // Print usage 240 if(argmatch(argv, argc, "-help", "--help", 3, NULL, &skip_args)) { 241 242 Usage(argv[0]); 243 usage_requested = true; 244 PostMessage(B_QUIT_REQUESTED); 245 } 246 247 // Load preference file 248 if(argmatch(argv, argc, "-p", "--preference", 4, &value, &skip_args)) 249 gTermPref->Open(value); 250 251 // Set window title 252 if(argmatch(argv ,argc, "-t", "--title", 3, &value, &skip_args)) 253 strcpy(gWindowName, value); 254 255 // Set window geometry 256 if(argmatch(argv, argc, "-geom", "--geometry", 4, &value, &skip_args)) { 257 258 int width, height, xpos, ypos; 259 260 sscanf(value, "%dx%d+%d+%d", &width, &height, &xpos, &ypos); 261 if(width < 0 || height < 0 || xpos < 0 || ypos < 0 262 || width >= 256 || height >= 256 || xpos >= 2048 || ypos >= 2048) { 263 264 fprintf(stderr, "%s: invalid geometry format or value.\n", argv[0]); 265 fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); 266 usage_requested = true; 267 PostMessage(B_QUIT_REQUESTED); 268 } 269 gTermPref->setInt32(PREF_COLS, width); 270 gTermPref->setInt32(PREF_ROWS, height); 271 272 fTermFrame.Set(xpos, ypos, xpos + 50, ypos + 50); 273 geometry_requested = true; 274 } 275 276 // Open '/etc/rgb.txt' file 277 BFile inFile; 278 status_t sts; 279 off_t size = 0; 280 281 sts = inFile.SetTo("/etc/rgb.txt", B_READ_ONLY); 282 283 if(sts != B_OK) { 284 285 fprintf(stderr, "%s: Can't open /etc/rgb.txt file.\n", argv[0]); 286 usage_requested = true; 287 PostMessage(B_QUIT_REQUESTED); 288 } 289 290 inFile.GetSize(&size); 291 char *buffer = new char [size]; 292 inFile.Read(buffer, size); 293 294 // Set window, cursor, area and IM color 295 for(int i = 4; i < 13; i++) { 296 if(argmatch(argv, argc, standard_args[i].name, standard_args[i].longname, 297 9, &value, &skip_args)) { 298 299 if(text_to_rgb(value, &color, buffer)) 300 gTermPref->setRGB(standard_args[i].prefname, color); 301 else 302 fprintf(stderr, "%s: invalid color string -- %s\n", argv[0], value); 303 } 304 } 305 306 delete buffer; 307 308 skip_args++; 309 310 if(skip_args < argc) { 311 // Check invalid options 312 313 if(*argv[skip_args] == '-') { 314 fprintf(stderr, "%s: invalid option `%s'\n", argv[0], argv[skip_args]); 315 fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); 316 usage_requested = true; 317 PostMessage(B_QUIT_REQUESTED); 318 } 319 320 CommandLine += argv[skip_args++]; 321 while(skip_args < argc) { 322 CommandLine += ' '; 323 CommandLine += argv[skip_args++]; 324 } 325 } 326 } 327 328 //void 329 //TermApp::AboutRequested(void) 330 //{ 331 // if(fAboutPanel) { 332 // fAboutPanel->Activate(); 333 // } 334 // else { 335 // BRect rect(0, 0, 350 - 1, 170 - 1); 336 // rect.OffsetTo(fTermWindow->Frame().LeftTop()); 337 // rect.OffsetBy(100, 100); 338 339 // fAboutPanel = new AboutDlg(rect, this); 340 // fAboutPanel->Show(); 341 // } 342 343 //} 344 345 void 346 TermApp::RefsReceived(BMessage *message) 347 { 348 // Works Only Launced by Double-Click file, or Drags file to App. 349 if(!(IsLaunching())) 350 return; 351 352 entry_ref ref; 353 if( message->FindRef("refs", 0, &ref) != B_OK ) 354 return; 355 356 BFile file; 357 if( file.SetTo(&ref, B_READ_WRITE) != B_OK ) 358 return; 359 360 BNodeInfo info(&file); 361 char mimetype[B_MIME_TYPE_LENGTH]; 362 info.GetType(mimetype); 363 364 // if App opened by Pref file 365 if(!strcmp(mimetype, PREFFILE_MIMETYPE)){ 366 367 BEntry ent(&ref); 368 BPath path(&ent); 369 gTermPref->OpenText(path.Path()); 370 return; 371 } 372 373 // if App opened by Shell Script 374 if(!strcmp(mimetype, "text/x-haiku-shscript")){ 375 376 // Not implemented. 377 // beep(); 378 return; 379 } 380 } 381 382 void 383 TermApp::MakeTermWindow(BRect &frame) 384 { 385 fTermWindow = new TermWindow(frame, fWindowNumber); 386 fTermWindow->Show(); 387 } 388 389 void 390 TermApp::RunNewTerm(void) 391 { 392 app_info info; 393 be_app->GetAppInfo(&info); 394 395 // try launching two different ways to work around possible problems 396 if(be_roster->Launch(&(info.ref))!=B_OK) 397 be_roster->Launch(TERM_SIGNATURE); 398 } 399 400 void 401 TermApp::ActivateTermWindow(team_id id) 402 { 403 BMessenger app(TERM_SIGNATURE, id); 404 405 if(app.IsTargetLocal()) 406 fTermWindow->Activate(); 407 else 408 app.SendMessage(MSG_ACTIVATE_TERM); 409 } 410 411 void 412 TermApp::SwitchTerm() 413 { 414 team_id myId = be_app->Team(); // My id 415 BList teams; 416 be_roster->GetAppList(TERM_SIGNATURE, &teams); 417 int32 numTerms = teams.CountItems(); 418 419 if(numTerms <= 1 ) 420 return; //Can't Switch !! 421 422 // Find position of mine in app teams. 423 int32 i; 424 425 for(i = 0; i < numTerms; i++) { 426 if(myId == reinterpret_cast<team_id>(teams.ItemAt(i))) 427 break; 428 } 429 430 do{ 431 if(--i < 0) 432 i = numTerms - 1; 433 } while(IsMinimize(reinterpret_cast<team_id>(teams.ItemAt(i)))); 434 435 // Activate switched terminal. 436 ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i))); 437 } 438 439 bool 440 TermApp::IsMinimize(team_id id) 441 { 442 BMessenger app(TERM_SIGNATURE, id); 443 444 if(app.IsTargetLocal()) 445 return fTermWindow->IsMinimized(); 446 447 BMessage reply; 448 bool hidden; 449 450 if(app.SendMessage(MSG_TERM_IS_MINIMIZE, &reply) != B_OK) 451 return true; 452 453 reply.FindBool("result", &hidden); 454 455 return hidden; 456 } 457 458 //#ifndef B_NETPOSITIVE_APP_SIGNATURE 459 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS" 460 //#endif 461 // 462 //void 463 //TermApp::ShowHTML(BMessage *msg) 464 //{ 465 // const char *url; 466 // msg->FindString("Url", &url); 467 // BMessage message; 468 // 469 // message.what = B_NETPOSITIVE_OPEN_URL; 470 // message.AddString("be:url", url); 471 472 // be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message); 473 // while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE))) 474 // snooze(10000); 475 // 476 // // Activate net+ 477 // be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE)); 478 //} 479 480 #define PR(a) fprintf(stderr, a) 481 482 // Display usage to stderr 483 void 484 TermApp::Usage(char *name) 485 { 486 fprintf(stderr, "\n"); 487 fprintf(stderr, "Haiku Terminal\n"); 488 fprintf(stderr, "Copyright 2004 Haiku, Inc.\n"); 489 fprintf(stderr, "Parts Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n"); 490 fprintf(stderr, "\n"); 491 fprintf(stderr, "Usage: %s [OPTION] [SHELL]\n", name); 492 493 PR(" -p, --preference load preference file\n"); 494 PR(" -t, --title set window title\n"); 495 PR(" -geom, --geometry set window geometry\n"); 496 PR(" An example of geometry is \"80x25+100+100\"\n"); 497 PR(" -fg, --text-fore-color set window foreground color\n"); 498 PR(" -bg, --text-back-color set window background color\n"); 499 PR(" -curfg, --cursor-fore-color set cursor foreground color\n"); 500 PR(" -curbg, --cursor-back-color set cursor background color\n"); 501 PR(" -selfg, --select-fore-color set selection area foreground color\n"); 502 PR(" -selbg, --select-back-color set selection area background color\n"); 503 PR(" Examples of color are \"#FF00FF\" and \"purple\"\n"); 504 PR("\n"); 505 } 506 507 int 508 text_to_rgb(char *name, rgb_color *color, char *buffer) 509 { 510 if(name[0] != '#') { 511 512 // Convert from /etc/rgb.txt. 513 BString inStr(buffer); 514 int32 point, offset = 0; 515 516 // Search color name 517 do { 518 point = inStr.FindFirst(name, offset); 519 if(point < 0) 520 return false; 521 offset = point + 1; 522 } while(*(buffer + point -1) != '\t'); 523 524 char *p = buffer + point; 525 526 while(*p != '\n') 527 p--; 528 p++; 529 530 if(sscanf(p, "%d %d %d", (int *)&color->red, (int *)&color->green, 531 (int *)&color->blue) == EOF) 532 return false; 533 color->alpha = 0; 534 } 535 else 536 if(name[0] == '#') { 537 538 // Convert from #RRGGBB format 539 sscanf(name, "#%2x%2x%2x", (int *)&color->red, (int *)&color->green, 540 (int *)&color->blue); 541 color->alpha = 0; 542 } 543 else 544 return false; 545 546 return true; 547 } 548 549 // This routine copy from GNU Emacs. 550 // TODO: This might be a GPL licensing issue here. Investigate. 551 int 552 argmatch(char **argv, int argc, char *sstr, char *lstr, 553 int minlen, char **valptr, int *skipptr) 554 { 555 char *p = 0; 556 int arglen; 557 char *arg; 558 559 // Don't access argv[argc]; give up in advance 560 if(argc <= *skipptr + 1) 561 return 0; 562 563 arg = argv[*skipptr+1]; 564 if(arg == NULL) 565 return 0; 566 567 if(strcmp(arg, sstr) == 0) 568 { 569 if(valptr != NULL) 570 { 571 *valptr = argv[*skipptr+2]; 572 *skipptr += 2; 573 } 574 else 575 *skipptr += 1; 576 return 1; 577 } 578 579 arglen =(valptr != NULL &&(p = strchr(arg, '=')) != NULL 580 ? p - arg : strlen(arg)); 581 582 if(lstr == 0 || arglen < minlen || strncmp(arg, lstr, arglen) != 0) 583 return 0; 584 else 585 if(valptr == NULL) 586 { 587 *skipptr += 1; 588 return 1; 589 } 590 else 591 if(p != NULL) 592 { 593 *valptr = p+1; 594 *skipptr += 1; 595 return 1; 596 } 597 else 598 if(argv[*skipptr+2] != NULL) 599 { 600 *valptr = argv[*skipptr+2]; 601 *skipptr += 2; 602 return 1; 603 } 604 else 605 { 606 return 0; 607 } 608 } 609 610 // This routine copy from GNU Emacs. 611 // TODO: This might be a GPL licensing issue here. Investigate. 612 void 613 sort_args(int argc, char **argv) 614 { 615 /* 616 For each element of argv, 617 the corresponding element of options is: 618 0 for an option that takes no arguments, 619 1 for an option that takes one argument, etc. 620 -1 for an ordinary non-option argument. 621 */ 622 623 char **newargv =(char **) malloc(sizeof(char *) * argc); 624 625 int *options =(int *) malloc(sizeof(int) * argc); 626 int *priority =(int *) malloc(sizeof(int) * argc); 627 int to = 1; 628 int incoming_used = 1; 629 int from; 630 int i; 631 //int end_of_options = argc; 632 633 // Categorize all the options, 634 // and figure out which argv elts are option arguments 635 for(from = 1; from < argc; from++) 636 { 637 options[from] = -1; 638 priority[from] = 0; 639 if(argv[from][0] == '-') 640 { 641 int match, thislen; 642 char *equals; 643 644 // If we have found "--", don't consider any more arguments as options 645 if(argv[from][1] == '-' && argv[from][2] == 0) 646 { 647 // Leave the "--", and everything following it, at the end. 648 for(; from < argc; from++) 649 { 650 priority[from] = -100; 651 options[from] = -1; 652 } 653 break; 654 } 655 656 // Look for a match with a known old-fashioned option. 657 for(i = 0; i <(int)(sizeof(standard_args) / sizeof(standard_args[0])); i++) 658 if(!strcmp(argv[from], standard_args[i].name)) 659 { 660 options[from] = standard_args[i].nargs; 661 priority[from] = standard_args[i].priority; 662 if(from + standard_args[i].nargs >= argc) 663 fprintf(stderr, "Option `%s' requires an argument\n", argv[from]); 664 from += standard_args[i].nargs; 665 goto done; 666 } 667 668 /* 669 Look for a match with a known long option. 670 MATCH is -1 if no match so far, -2 if two or more matches so far, 671 >= 0(the table index of the match) if just one match so far. 672 */ 673 if(argv[from][1] == '-') 674 { 675 match = -1; 676 thislen = strlen(argv[from]); 677 equals = strchr(argv[from], '='); 678 if(equals != 0) 679 thislen = equals - argv[from]; 680 681 for(i = 0;i <(int )(sizeof(standard_args) / sizeof(standard_args[0])); i++) 682 if(standard_args[i].longname 683 && !strncmp(argv[from], standard_args[i].longname, thislen)) 684 { 685 if(match == -1) 686 match = i; 687 else 688 match = -2; 689 } 690 691 // If we found exactly one match, use that 692 if(match >= 0) 693 { 694 options[from] = standard_args[match].nargs; 695 priority[from] = standard_args[match].priority; 696 697 // If --OPTION=VALUE syntax is used, 698 // this option uses just one argv element 699 if(equals != 0) 700 options[from] = 0; 701 if(from + options[from] >= argc) 702 fprintf(stderr, "Option `%s' requires an argument\n", argv[from]); 703 from += options[from]; 704 } 705 } 706 done: ; 707 } 708 } 709 710 // Copy the arguments, in order of decreasing priority, to NEW 711 newargv[0] = argv[0]; 712 while(incoming_used < argc) 713 { 714 int best = -1; 715 int best_priority = -9999; 716 717 // Find the highest priority remaining option. 718 // If several have equal priority, take the first of them. 719 for(from = 1; from < argc; from++) 720 { 721 if(argv[from] != 0 && priority[from] > best_priority) 722 { 723 best_priority = priority[from]; 724 best = from; 725 } 726 727 // Skip option arguments--they are tied to the options. 728 if(options[from] > 0) 729 from += options[from]; 730 } 731 732 if(best < 0) 733 abort(); 734 735 // Copy the highest priority remaining option, with its args, to NEW. 736 // Unless it is a duplicate of the previous one 737 if(!(options[best] == 0 && ! strcmp(newargv[to - 1], argv[best]))) 738 { 739 newargv[to++] = argv[best]; 740 for(i = 0; i < options[best]; i++) 741 newargv[to++] = argv[best + i + 1]; 742 } 743 744 incoming_used += 1 +(options[best] > 0 ? options[best] : 0); 745 746 // Clear out this option in ARGV 747 argv[best] = 0; 748 for(i = 0; i < options[best]; i++) 749 argv[best + i + 1] = 0; 750 } 751 752 // If duplicate options were deleted, fill up extra space with null ptrs 753 while(to < argc) 754 newargv[to++] = 0; 755 756 memcpy(argv, newargv, sizeof(char *) * argc); 757 758 free(options); 759 free(newargv); 760 free(priority); 761 } 762