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