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