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