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