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