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