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