1 /* 2 * Copyright 2001-2008, 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_WINDOW_INFO = 'mtwi'; 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-2008, 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_WINDOW_INFO: 163 { 164 BMessage reply(B_REPLY); 165 reply.AddBool("minimized", fTermWindow->IsMinimized()); 166 reply.AddInt32("workspaces", fTermWindow->Workspaces()); 167 msg->SendReply(&reply); 168 break; 169 } 170 171 case MSG_CHECK_CHILDREN: 172 _HandleChildCleanup(); 173 break; 174 175 default: 176 BApplication::MessageReceived(msg); 177 break; 178 } 179 } 180 181 182 void 183 TermApp::ArgvReceived(int32 argc, char **argv) 184 { 185 fArgs->Parse(argc, argv); 186 187 if (fArgs->UsageRequested()) { 188 _Usage(argv[0]); 189 sUsageRequested = true; 190 PostMessage(B_QUIT_REQUESTED); 191 return; 192 } 193 194 if (fArgs->Title() != NULL) 195 fWindowTitle = fArgs->Title(); 196 197 fStartFullscreen = fArgs->FullScreen(); 198 } 199 200 201 void 202 TermApp::RefsReceived(BMessage* message) 203 { 204 // Works Only Launced by Double-Click file, or Drags file to App. 205 if (!IsLaunching()) 206 return; 207 208 entry_ref ref; 209 if (message->FindRef("refs", 0, &ref) != B_OK) 210 return; 211 212 BFile file; 213 if (file.SetTo(&ref, B_READ_WRITE) != B_OK) 214 return; 215 216 BNodeInfo info(&file); 217 char mimetype[B_MIME_TYPE_LENGTH]; 218 info.GetType(mimetype); 219 220 // if App opened by Pref file 221 if (!strcmp(mimetype, PREFFILE_MIMETYPE)) { 222 223 BEntry ent(&ref); 224 BPath path(&ent); 225 PrefHandler::Default()->OpenText(path.Path()); 226 return; 227 } 228 229 // if App opened by Shell Script 230 if (!strcmp(mimetype, "text/x-haiku-shscript")){ 231 // Not implemented. 232 // beep(); 233 return; 234 } 235 } 236 237 238 status_t 239 TermApp::_MakeTermWindow(BRect &frame) 240 { 241 try { 242 fTermWindow = new TermWindow(frame, fWindowTitle.String(), fArgs); 243 } catch (int error) { 244 return (status_t)error; 245 } catch (...) { 246 return B_ERROR; 247 } 248 249 fTermWindow->Show(); 250 251 return B_OK; 252 } 253 254 255 void 256 TermApp::_ActivateTermWindow(team_id id) 257 { 258 BMessenger app(TERM_SIGNATURE, id); 259 if (app.IsTargetLocal()) 260 fTermWindow->Activate(); 261 else 262 app.SendMessage(MSG_ACTIVATE_TERM); 263 } 264 265 266 void 267 TermApp::_SwitchTerm() 268 { 269 team_id myId = be_app->Team(); 270 BList teams; 271 be_roster->GetAppList(TERM_SIGNATURE, &teams); 272 273 int32 numTerms = teams.CountItems(); 274 if (numTerms <= 1) 275 return; 276 277 // Find our position in the app teams. 278 int32 i; 279 280 for (i = 0; i < numTerms; i++) { 281 if (myId == reinterpret_cast<team_id>(teams.ItemAt(i))) 282 break; 283 } 284 285 do { 286 if (--i < 0) 287 i = numTerms - 1; 288 } while (!_IsSwitchTarget(reinterpret_cast<team_id>(teams.ItemAt(i)))); 289 290 // Activate switched terminal. 291 _ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i))); 292 } 293 294 295 bool 296 TermApp::_IsSwitchTarget(team_id id) 297 { 298 uint32 currentWorkspace = 1L << current_workspace(); 299 300 BMessenger app(TERM_SIGNATURE, id); 301 if (app.IsTargetLocal()) { 302 return !fTermWindow->IsMinimized() 303 && (fTermWindow->Workspaces() & currentWorkspace) != 0; 304 } 305 306 BMessage reply; 307 if (app.SendMessage(MSG_TERM_WINDOW_INFO, &reply) != B_OK) 308 return false; 309 310 bool minimized; 311 int32 workspaces; 312 if (reply.FindBool("minimized", &minimized) != B_OK 313 || reply.FindInt32("workspaces", &workspaces) != B_OK) 314 return false; 315 316 return !minimized && (workspaces & currentWorkspace) != 0; 317 } 318 319 320 /*! Checks if all teams that have an ID-to-team mapping in the message 321 are still running. 322 The IDs for teams that are gone will be made available again, and 323 their mapping is removed from the message. 324 */ 325 void 326 TermApp::_SanitizeIDs(BMessage* data, uint8* windows, ssize_t length) 327 { 328 BList teams; 329 be_roster->GetAppList(TERM_SIGNATURE, &teams); 330 331 for (int32 i = 0; i < length; i++) { 332 if (!windows[i]) 333 continue; 334 335 BString id("id-"); 336 id << i; 337 338 team_id team; 339 if (data->FindInt32(id.String(), &team) != B_OK) 340 continue; 341 342 if (!teams.HasItem((void*)team)) { 343 windows[i] = false; 344 data->RemoveName(id.String()); 345 } 346 } 347 } 348 349 350 /*! 351 Removes the current fWindowNumber (ID) from the supplied array, or 352 finds a free ID in it, and sets fWindowNumber accordingly. 353 */ 354 bool 355 TermApp::_UpdateIDs(bool set, uint8* windows, ssize_t maxLength, 356 ssize_t* _length) 357 { 358 ssize_t length = *_length; 359 360 if (set) { 361 int32 i; 362 for (i = 0; i < length; i++) { 363 if (!windows[i]) { 364 windows[i] = true; 365 fWindowNumber = i + 1; 366 break; 367 } 368 } 369 370 if (i == length) { 371 if (length >= maxLength) 372 return false; 373 374 windows[length] = true; 375 length++; 376 fWindowNumber = length; 377 } 378 } else { 379 // update information and write it back 380 windows[fWindowNumber - 1] = false; 381 } 382 383 *_length = length; 384 return true; 385 } 386 387 388 void 389 TermApp::_UpdateRegistration(bool set) 390 { 391 if (set) 392 fWindowNumber = -1; 393 else if (fWindowNumber < 0) 394 return; 395 396 #ifdef __HAIKU__ 397 // use BClipboard - it supports atomic access in Haiku 398 BClipboard clipboard(TERM_SIGNATURE); 399 400 while (true) { 401 if (!clipboard.Lock()) 402 return; 403 404 BMessage* data = clipboard.Data(); 405 406 const uint8* windowsData; 407 uint8 windows[512]; 408 ssize_t length; 409 if (data->FindData("ids", B_RAW_TYPE, 410 (const void**)&windowsData, &length) != B_OK) 411 length = 0; 412 413 if (length > (ssize_t)sizeof(windows)) 414 length = sizeof(windows); 415 if (length > 0) 416 memcpy(windows, windowsData, length); 417 418 _SanitizeIDs(data, windows, length); 419 420 status_t status = B_OK; 421 if (_UpdateIDs(set, windows, sizeof(windows), &length)) { 422 // add/remove our ID-to-team mapping 423 BString id("id-"); 424 id << fWindowNumber; 425 426 if (set) 427 data->AddInt32(id.String(), Team()); 428 else 429 data->RemoveName(id.String()); 430 431 data->RemoveName("ids"); 432 //if (data->ReplaceData("ids", B_RAW_TYPE, windows, length) != B_OK) 433 data->AddData("ids", B_RAW_TYPE, windows, length); 434 435 status = clipboard.Commit(true); 436 } 437 438 clipboard.Unlock(); 439 440 if (status == B_OK) 441 break; 442 } 443 #else // !__HAIKU__ 444 // use a file to store the IDs - unfortunately, locking 445 // doesn't work on BeOS either here 446 int fd = open("/tmp/terminal_ids", O_RDWR | O_CREAT); 447 if (fd < 0) 448 return; 449 450 struct flock lock; 451 lock.l_type = F_WRLCK; 452 lock.l_whence = SEEK_CUR; 453 lock.l_start = 0; 454 lock.l_len = -1; 455 fcntl(fd, F_SETLKW, &lock); 456 457 uint8 windows[512]; 458 ssize_t length = read_pos(fd, 0, windows, sizeof(windows)); 459 if (length < 0) { 460 close(fd); 461 return; 462 } 463 464 if (length > (ssize_t)sizeof(windows)) 465 length = sizeof(windows); 466 467 if (_UpdateIDs(set, windows, sizeof(windows), &length)) 468 write_pos(fd, 0, windows, length); 469 470 close(fd); 471 #endif // !__HAIKU__ 472 } 473 474 475 void 476 TermApp::_UnregisterTerminal() 477 { 478 _UpdateRegistration(false); 479 } 480 481 482 void 483 TermApp::_RegisterTerminal() 484 { 485 _UpdateRegistration(true); 486 } 487 488 489 //#ifndef B_NETPOSITIVE_APP_SIGNATURE 490 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS" 491 //#endif 492 // 493 //void 494 //TermApp::ShowHTML(BMessage *msg) 495 //{ 496 // const char *url; 497 // msg->FindString("Url", &url); 498 // BMessage message; 499 // 500 // message.what = B_NETPOSITIVE_OPEN_URL; 501 // message.AddString("be:url", url); 502 503 // be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message); 504 // while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE))) 505 // snooze(10000); 506 // 507 // // Activate net+ 508 // be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE)); 509 //} 510 511 512 void 513 TermApp::_HandleChildCleanup() 514 { 515 } 516 517 518 /*static*/ void 519 TermApp::_SigChildHandler(int signal, void* data) 520 { 521 // Spawing a thread that does the actual signal handling is pretty much 522 // the only safe thing to do in a multi-threaded application. The 523 // interrupted thread might have been anywhere, e.g. in a critical section, 524 // holding locks. If we do anything that does require locking at any point 525 // (e.g. memory allocation, messaging), we risk a dead-lock or data 526 // structure corruption. Spawing a thread is safe though, since its only 527 // a system call. 528 thread_id thread = spawn_thread(_ChildCleanupThread, "child cleanup", 529 B_NORMAL_PRIORITY, ((TermApp*)data)->fTermWindow); 530 if (thread >= 0) 531 resume_thread(thread); 532 } 533 534 535 /*static*/ status_t 536 TermApp::_ChildCleanupThread(void* data) 537 { 538 // Just drop the windowa message and let it do the actual work. This 539 // saves us additional synchronization measures. 540 return ((TermWindow*)data)->PostMessage(MSG_CHECK_CHILDREN); 541 } 542 543 544 545 void 546 TermApp::_Usage(char *name) 547 { 548 fprintf(stderr, "Haiku Terminal\n" 549 "Copyright 2001-2007 Haiku, Inc.\n" 550 "Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n" 551 "\n" 552 "Usage: %s [OPTION] [SHELL]\n", name); 553 554 fprintf(stderr, 555 " -h, --help print this help\n" 556 //" -p, --preference load preference file\n" 557 " -t, --title set window title\n" 558 " -f, --fullscreen start fullscreen\n" 559 //" -geom, --geometry set window geometry\n" 560 //" An example of geometry is \"80x25+100+100\"\n" 561 ); 562 } 563 564