1 /* 2 * Copyright 2001-2009, Haiku, Inc. All rights reserved. 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 <errno.h> 13 #include <signal.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <unistd.h> 17 18 #include <Alert.h> 19 #include <Catalog.h> 20 #include <Clipboard.h> 21 #include <Catalog.h> 22 #include <FindDirectory.h> 23 #include <InterfaceDefs.h> 24 #include <Locale.h> 25 #include <NodeInfo.h> 26 #include <Path.h> 27 #include <Roster.h> 28 #include <Screen.h> 29 #include <String.h> 30 31 32 #include "Arguments.h" 33 #include "Globals.h" 34 #include "PrefHandler.h" 35 #include "TermConst.h" 36 #include "TermView.h" 37 #include "TermWindow.h" 38 39 40 static bool sUsageRequested = false; 41 //static bool sGeometryRequested = false; 42 43 const ulong MSG_ACTIVATE_TERM = 'msat'; 44 const ulong MSG_TERM_WINDOW_INFO = 'mtwi'; 45 46 47 int 48 main() 49 { 50 TermApp app; 51 app.Run(); 52 53 return 0; 54 } 55 56 #undef B_TRANSLATE_CONTEXT 57 #define B_TRANSLATE_CONTEXT "Terminal TermApp" 58 59 TermApp::TermApp() 60 : BApplication(TERM_SIGNATURE), 61 fStartFullscreen(false), 62 fWindowNumber(-1), 63 fTermWindow(NULL), 64 fArgs(NULL) 65 { 66 67 fArgs = new Arguments(0, NULL); 68 69 fWindowTitle = B_TRANSLATE("Terminal"); 70 _RegisterTerminal(); 71 72 if (fWindowNumber > 0) 73 fWindowTitle << " " << fWindowNumber; 74 75 if (_LoadWindowPosition(&fTermFrame, &fTermWorkspaces) != B_OK) { 76 int i = fWindowNumber / 16; 77 int j = fWindowNumber % 16; 78 int k = (j * 16) + (i * 64) + 50; 79 int l = (j * 16) + 50; 80 81 fTermFrame.Set(k, l, k + 50, k + 50); 82 } 83 } 84 85 86 TermApp::~TermApp() 87 { 88 delete fArgs; 89 } 90 91 92 void 93 TermApp::ReadyToRun() 94 { 95 // Prevent opeing window when option -h is given. 96 if (sUsageRequested) 97 return; 98 99 // Install a SIGCHLD signal handler, so that we will be notified, when 100 // a shell exits. 101 struct sigaction action; 102 #ifdef __HAIKU__ 103 action.sa_handler = (sighandler_t)_SigChildHandler; 104 #else 105 action.sa_handler = (__signal_func_ptr)_SigChildHandler; 106 #endif 107 sigemptyset(&action.sa_mask); 108 #ifdef SA_NODEFER 109 action.sa_flags = SA_NODEFER; 110 #endif 111 action.sa_userdata = this; 112 if (sigaction(SIGCHLD, &action, NULL) < 0) { 113 fprintf(stderr, B_TRANSLATE("sigaction() failed: %s\n"), 114 strerror(errno)); 115 // continue anyway 116 } 117 118 // init the mouse copy'n'paste clipboard 119 gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true); 120 121 status_t status = _MakeTermWindow(fTermFrame, fTermWorkspaces); 122 123 // failed spawn, print stdout and open alert panel 124 // TODO: This alert does never show up. 125 if (status < B_OK) { 126 (new BAlert("alert", 127 B_TRANSLATE("Terminal couldn't start the shell. Sorry."), 128 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_FROM_LABEL, 129 B_INFO_ALERT))->Go(NULL); 130 PostMessage(B_QUIT_REQUESTED); 131 return; 132 } 133 134 // using BScreen::Frame isn't enough 135 if (fStartFullscreen) 136 BMessenger(fTermWindow).SendMessage(FULLSCREEN); 137 } 138 139 140 void 141 TermApp::Quit() 142 { 143 if (!sUsageRequested) 144 _UnregisterTerminal(); 145 146 BApplication::Quit(); 147 } 148 149 150 void 151 TermApp::AboutRequested() 152 { 153 TermView::AboutRequested(); 154 } 155 156 157 void 158 TermApp::MessageReceived(BMessage* msg) 159 { 160 switch (msg->what) { 161 case MENU_SWITCH_TERM: 162 _SwitchTerm(); 163 break; 164 165 case MSG_ACTIVATE_TERM: 166 fTermWindow->Activate(); 167 break; 168 169 case MSG_TERM_WINDOW_INFO: 170 { 171 BMessage reply(B_REPLY); 172 reply.AddBool("minimized", fTermWindow->IsMinimized()); 173 reply.AddInt32("workspaces", fTermWindow->Workspaces()); 174 msg->SendReply(&reply); 175 break; 176 } 177 178 case MSG_SAVE_WINDOW_POSITION: 179 _SaveWindowPosition(msg); 180 break; 181 182 case MSG_CHECK_CHILDREN: 183 _HandleChildCleanup(); 184 break; 185 186 default: 187 BApplication::MessageReceived(msg); 188 break; 189 } 190 } 191 192 193 void 194 TermApp::ArgvReceived(int32 argc, char **argv) 195 { 196 fArgs->Parse(argc, argv); 197 198 if (fArgs->UsageRequested()) { 199 _Usage(argv[0]); 200 sUsageRequested = true; 201 PostMessage(B_QUIT_REQUESTED); 202 return; 203 } 204 205 if (fArgs->Title() != NULL) 206 fWindowTitle = fArgs->Title(); 207 208 fStartFullscreen = fArgs->FullScreen(); 209 } 210 211 212 void 213 TermApp::RefsReceived(BMessage* message) 214 { 215 // Works Only Launced by Double-Click file, or Drags file to App. 216 if (!IsLaunching()) 217 return; 218 219 entry_ref ref; 220 if (message->FindRef("refs", 0, &ref) != B_OK) 221 return; 222 223 BFile file; 224 if (file.SetTo(&ref, B_READ_WRITE) != B_OK) 225 return; 226 227 BNodeInfo info(&file); 228 char mimetype[B_MIME_TYPE_LENGTH]; 229 info.GetType(mimetype); 230 231 // if App opened by Pref file 232 if (!strcmp(mimetype, PREFFILE_MIMETYPE)) { 233 234 BEntry ent(&ref); 235 BPath path(&ent); 236 PrefHandler::Default()->OpenText(path.Path()); 237 return; 238 } 239 240 // if App opened by Shell Script 241 if (!strcmp(mimetype, "text/x-haiku-shscript")){ 242 // Not implemented. 243 // beep(); 244 return; 245 } 246 } 247 248 249 status_t 250 TermApp::_GetWindowPositionFile(BFile* file, uint32 openMode) 251 { 252 BPath path; 253 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 254 if (status != B_OK) 255 return status; 256 257 status = path.Append("Terminal_windows"); 258 if (status != B_OK) 259 return status; 260 261 return file->SetTo(path.Path(), openMode); 262 } 263 264 265 status_t 266 TermApp::_LoadWindowPosition(BRect* frame, uint32* workspaces) 267 { 268 status_t status = B_ERROR; 269 BMessage position = BMessage(); 270 271 BFile file; 272 status = _GetWindowPositionFile(&file, B_READ_ONLY); 273 if (status != B_OK) 274 return status; 275 276 status = position.Unflatten(&file); 277 278 file.Unset(); 279 280 if (status != B_OK) 281 return status; 282 283 status = position.FindRect("rect", fWindowNumber - 1, frame); 284 if (status != B_OK) 285 return status; 286 287 int32 _workspaces; 288 status = position.FindInt32("workspaces", fWindowNumber - 1, &_workspaces); 289 if (status != B_OK) 290 return status; 291 if (modifiers() & B_OPTION_KEY) 292 *workspaces = _workspaces; 293 294 printf("loading settings ok\n"); 295 return B_OK; 296 } 297 298 299 status_t 300 TermApp::_SaveWindowPosition(BMessage* position) 301 { 302 BFile file; 303 BMessage originalSettings; 304 305 // We append ourself to the existing settings file 306 // So we have to read it, insert our BMessage, and rewrite it. 307 308 status_t status = _GetWindowPositionFile(&file, B_READ_ONLY); 309 if (status == B_OK) { 310 originalSettings.Unflatten(&file); 311 // No error checking on that : it fails if the settings 312 // file is missing, but we can create it. 313 314 file.Unset(); 315 } 316 317 // Append the new settings 318 BRect rect; 319 position->FindRect("rect", &rect); 320 if (originalSettings.ReplaceRect("rect", fWindowNumber - 1, rect) != B_OK) 321 originalSettings.AddRect("rect", rect); 322 323 int32 workspaces; 324 position->FindInt32("workspaces", &workspaces); 325 if (originalSettings.ReplaceInt32("workspaces", fWindowNumber - 1, workspaces) 326 != B_OK) 327 originalSettings.AddInt32("workspaces", workspaces); 328 329 // Resave the whole thing 330 status = _GetWindowPositionFile (&file, 331 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 332 if (status != B_OK) 333 return status; 334 335 return originalSettings.Flatten(&file); 336 } 337 338 339 status_t 340 TermApp::_MakeTermWindow(BRect &frame, uint32 workspaces) 341 { 342 try { 343 fTermWindow = new TermWindow(frame, fWindowTitle.String(), workspaces, 344 fArgs); 345 } catch (int error) { 346 return (status_t)error; 347 } catch (...) { 348 return B_ERROR; 349 } 350 351 fTermWindow->Show(); 352 353 return B_OK; 354 } 355 356 357 void 358 TermApp::_ActivateTermWindow(team_id id) 359 { 360 BMessenger app(TERM_SIGNATURE, id); 361 if (app.IsTargetLocal()) 362 fTermWindow->Activate(); 363 else 364 app.SendMessage(MSG_ACTIVATE_TERM); 365 } 366 367 368 void 369 TermApp::_SwitchTerm() 370 { 371 team_id myId = be_app->Team(); 372 BList teams; 373 be_roster->GetAppList(TERM_SIGNATURE, &teams); 374 375 int32 numTerms = teams.CountItems(); 376 if (numTerms <= 1) 377 return; 378 379 // Find our position in the app teams. 380 int32 i; 381 382 for (i = 0; i < numTerms; i++) { 383 if (myId == reinterpret_cast<team_id>(teams.ItemAt(i))) 384 break; 385 } 386 387 do { 388 if (--i < 0) 389 i = numTerms - 1; 390 } while (!_IsSwitchTarget(reinterpret_cast<team_id>(teams.ItemAt(i)))); 391 392 // Activate switched terminal. 393 _ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i))); 394 } 395 396 397 bool 398 TermApp::_IsSwitchTarget(team_id id) 399 { 400 uint32 currentWorkspace = 1L << current_workspace(); 401 402 BMessenger app(TERM_SIGNATURE, id); 403 if (app.IsTargetLocal()) { 404 return !fTermWindow->IsMinimized() 405 && (fTermWindow->Workspaces() & currentWorkspace) != 0; 406 } 407 408 BMessage reply; 409 if (app.SendMessage(MSG_TERM_WINDOW_INFO, &reply) != B_OK) 410 return false; 411 412 bool minimized; 413 int32 workspaces; 414 if (reply.FindBool("minimized", &minimized) != B_OK 415 || reply.FindInt32("workspaces", &workspaces) != B_OK) 416 return false; 417 418 return !minimized && (workspaces & currentWorkspace) != 0; 419 } 420 421 422 /*! Checks if all teams that have an ID-to-team mapping in the message 423 are still running. 424 The IDs for teams that are gone will be made available again, and 425 their mapping is removed from the message. 426 */ 427 void 428 TermApp::_SanitizeIDs(BMessage* data, uint8* windows, ssize_t length) 429 { 430 BList teams; 431 be_roster->GetAppList(TERM_SIGNATURE, &teams); 432 433 for (int32 i = 0; i < length; i++) { 434 if (!windows[i]) 435 continue; 436 437 BString id("id-"); 438 id << i + 1; 439 440 team_id team; 441 if (data->FindInt32(id.String(), &team) != B_OK) 442 continue; 443 444 if (!teams.HasItem((void*)team)) { 445 windows[i] = false; 446 data->RemoveName(id.String()); 447 } 448 } 449 } 450 451 452 /*! 453 Removes the current fWindowNumber (ID) from the supplied array, or 454 finds a free ID in it, and sets fWindowNumber accordingly. 455 */ 456 bool 457 TermApp::_UpdateIDs(bool set, uint8* windows, ssize_t maxLength, 458 ssize_t* _length) 459 { 460 ssize_t length = *_length; 461 462 if (set) { 463 int32 i; 464 for (i = 0; i < length; i++) { 465 if (!windows[i]) { 466 windows[i] = true; 467 fWindowNumber = i + 1; 468 break; 469 } 470 } 471 472 if (i == length) { 473 if (length >= maxLength) 474 return false; 475 476 windows[length] = true; 477 length++; 478 fWindowNumber = length; 479 } 480 } else { 481 // update information and write it back 482 windows[fWindowNumber - 1] = false; 483 } 484 485 *_length = length; 486 return true; 487 } 488 489 490 void 491 TermApp::_UpdateRegistration(bool set) 492 { 493 if (set) 494 fWindowNumber = -1; 495 else if (fWindowNumber < 0) 496 return; 497 498 #ifdef __HAIKU__ 499 // use BClipboard - it supports atomic access in Haiku 500 BClipboard clipboard(TERM_SIGNATURE); 501 502 while (true) { 503 if (!clipboard.Lock()) 504 return; 505 506 BMessage* data = clipboard.Data(); 507 508 const uint8* windowsData; 509 uint8 windows[512]; 510 ssize_t length; 511 if (data->FindData("ids", B_RAW_TYPE, 512 (const void**)&windowsData, &length) != B_OK) 513 length = 0; 514 515 if (length > (ssize_t)sizeof(windows)) 516 length = sizeof(windows); 517 if (length > 0) 518 memcpy(windows, windowsData, length); 519 520 _SanitizeIDs(data, windows, length); 521 522 status_t status = B_OK; 523 if (_UpdateIDs(set, windows, sizeof(windows), &length)) { 524 // add/remove our ID-to-team mapping 525 BString id("id-"); 526 id << fWindowNumber; 527 528 if (set) 529 data->AddInt32(id.String(), Team()); 530 else 531 data->RemoveName(id.String()); 532 533 data->RemoveName("ids"); 534 //if (data->ReplaceData("ids", B_RAW_TYPE, windows, length) != B_OK) 535 data->AddData("ids", B_RAW_TYPE, windows, length); 536 537 status = clipboard.Commit(true); 538 } 539 540 clipboard.Unlock(); 541 542 if (status == B_OK) 543 break; 544 } 545 #else // !__HAIKU__ 546 // use a file to store the IDs - unfortunately, locking 547 // doesn't work on BeOS either here 548 int fd = open("/tmp/terminal_ids", O_RDWR | O_CREAT); 549 if (fd < 0) 550 return; 551 552 struct flock lock; 553 lock.l_type = F_WRLCK; 554 lock.l_whence = SEEK_CUR; 555 lock.l_start = 0; 556 lock.l_len = -1; 557 fcntl(fd, F_SETLKW, &lock); 558 559 uint8 windows[512]; 560 ssize_t length = read_pos(fd, 0, windows, sizeof(windows)); 561 if (length < 0) { 562 close(fd); 563 return; 564 } 565 566 if (length > (ssize_t)sizeof(windows)) 567 length = sizeof(windows); 568 569 if (_UpdateIDs(set, windows, sizeof(windows), &length)) 570 write_pos(fd, 0, windows, length); 571 572 close(fd); 573 #endif // !__HAIKU__ 574 } 575 576 577 void 578 TermApp::_UnregisterTerminal() 579 { 580 _UpdateRegistration(false); 581 } 582 583 584 void 585 TermApp::_RegisterTerminal() 586 { 587 _UpdateRegistration(true); 588 } 589 590 591 //#ifndef B_NETPOSITIVE_APP_SIGNATURE 592 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS" 593 //#endif 594 // 595 //void 596 //TermApp::ShowHTML(BMessage *msg) 597 //{ 598 // const char *url; 599 // msg->FindString("Url", &url); 600 // BMessage message; 601 // 602 // message.what = B_NETPOSITIVE_OPEN_URL; 603 // message.AddString("be:url", url); 604 605 // be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message); 606 // while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE))) 607 // snooze(10000); 608 // 609 // // Activate net+ 610 // be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE)); 611 //} 612 613 614 void 615 TermApp::_HandleChildCleanup() 616 { 617 } 618 619 620 /*static*/ void 621 TermApp::_SigChildHandler(int signal, void* data) 622 { 623 // Spawing a thread that does the actual signal handling is pretty much 624 // the only safe thing to do in a multi-threaded application. The 625 // interrupted thread might have been anywhere, e.g. in a critical section, 626 // holding locks. If we do anything that does require locking at any point 627 // (e.g. memory allocation, messaging), we risk a dead-lock or data 628 // structure corruption. Spawing a thread is safe though, since its only 629 // a system call. 630 thread_id thread = spawn_thread(_ChildCleanupThread, "child cleanup", 631 B_NORMAL_PRIORITY, ((TermApp*)data)->fTermWindow); 632 if (thread >= 0) 633 resume_thread(thread); 634 } 635 636 637 /*static*/ status_t 638 TermApp::_ChildCleanupThread(void* data) 639 { 640 // Just drop the windowa message and let it do the actual work. This 641 // saves us additional synchronization measures. 642 return ((TermWindow*)data)->PostMessage(MSG_CHECK_CHILDREN); 643 } 644 645 646 647 void 648 TermApp::_Usage(char *name) 649 { 650 fprintf(stderr, B_TRANSLATE("Haiku Terminal\n" 651 "Copyright 2001-2009 Haiku, Inc.\n" 652 "Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n" 653 "\n" 654 "Usage: %s [OPTION] [SHELL]\n"), name); 655 656 fprintf(stderr, 657 B_TRANSLATE(" -h, --help print this help\n" 658 //" -p, --preference load preference file\n" 659 " -t, --title set window title\n" 660 " -f, --fullscreen start fullscreen\n") 661 //" -geom, --geometry set window geometry\n" 662 //" An example of geometry is \"80x25+100+100\"\n" 663 ); 664 } 665 666