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