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