1 /* 2 * MainApp.cpp - Media Player for the Haiku Operating System 3 * 4 * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de> 5 * Copyright (C) 2008 Stephan Aßmus <superstippi@gmx.de> (MIT Ok) 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * version 2 as published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 * 20 */ 21 22 23 #include "MainApp.h" 24 25 #include <Alert.h> 26 #include <Autolock.h> 27 #include <Catalog.h> 28 #include <Entry.h> 29 #include <FilePanel.h> 30 #include <Locale.h> 31 #include <MediaDefs.h> 32 #include <MediaRoster.h> 33 #include <MimeType.h> 34 #include <Path.h> 35 #include <Resources.h> 36 #include <Roster.h> 37 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <unistd.h> 41 42 #include "EventQueue.h" 43 #include "Playlist.h" 44 #include "Settings.h" 45 #include "SettingsWindow.h" 46 47 48 #undef B_TRANSLATE_CONTEXT 49 #define B_TRANSLATE_CONTEXT "MediaPlayer-Main" 50 51 52 static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist"; 53 54 const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer"; 55 56 MainApp* gMainApp; 57 58 static const char* kMediaServerSig = B_MEDIA_SERVER_SIGNATURE; 59 static const char* kMediaServerAddOnSig = "application/x-vnd.Be.addon-host"; 60 61 62 MainApp::MainApp() 63 : 64 BApplication(kAppSig), 65 fPlayerCount(0), 66 fSettingsWindow(NULL), 67 68 fOpenFilePanel(NULL), 69 fSaveFilePanel(NULL), 70 fLastFilePanelFolder(), 71 72 fMediaServerRunning(false), 73 fMediaAddOnServerRunning(false), 74 75 fAudioWindowFrameSaved(false), 76 fLastSavedAudioWindowCreationTime(0) 77 { 78 mpSettings settings = Settings::CurrentSettings(); 79 fLastFilePanelFolder = settings.filePanelFolder; 80 81 // Now tell the application roster, that we're interested 82 // in getting notifications of apps being launched or quit. 83 // In this way we are going to detect a media_server restart. 84 be_roster->StartWatching(BMessenger(this, this), 85 B_REQUEST_LAUNCHED | B_REQUEST_QUIT); 86 // we will keep track of the status of media_server 87 // and media_addon_server 88 fMediaServerRunning = be_roster->IsRunning(kMediaServerSig); 89 fMediaAddOnServerRunning = be_roster->IsRunning(kMediaServerAddOnSig); 90 91 if (!fMediaServerRunning || !fMediaAddOnServerRunning) { 92 BAlert* alert = new BAlert("start_media_server", 93 B_TRANSLATE("It appears the media server is not running.\n" 94 "Would you like to start it ?"), B_TRANSLATE("Quit"), 95 B_TRANSLATE("Start media server"), NULL, 96 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 97 if (alert->Go() == 0) { 98 PostMessage(B_QUIT_REQUESTED); 99 return; 100 } 101 102 launch_media_server(); 103 104 fMediaServerRunning = be_roster->IsRunning(kMediaServerSig); 105 fMediaAddOnServerRunning = be_roster->IsRunning(kMediaServerAddOnSig); 106 } 107 } 108 109 110 MainApp::~MainApp() 111 { 112 delete fOpenFilePanel; 113 delete fSaveFilePanel; 114 } 115 116 117 bool 118 MainApp::QuitRequested() 119 { 120 // Make sure we store the current playlist, if applicable. 121 for (int32 i = 0; BWindow* window = WindowAt(i); i++) { 122 MainWin* playerWindow = dynamic_cast<MainWin*>(window); 123 if (playerWindow == NULL) 124 continue; 125 126 BAutolock _(playerWindow); 127 128 BMessage quitMessage; 129 playerWindow->GetQuitMessage(&quitMessage); 130 131 // Store the playlist if there is one. If the user has multiple 132 // instances playing audio at the this time, the first instance wins. 133 BMessage playlistArchive; 134 if (quitMessage.FindMessage("playlist", &playlistArchive) == B_OK) { 135 _StoreCurrentPlaylist(&playlistArchive); 136 break; 137 } 138 } 139 140 // Note: This needs to be done here, SettingsWindow::QuitRequested() 141 // returns "false" always. (Standard BApplication quit procedure will 142 // hang otherwise.) 143 if (fSettingsWindow && fSettingsWindow->Lock()) 144 fSettingsWindow->Quit(); 145 fSettingsWindow = NULL; 146 147 // store the current file panel ref in the global settings 148 mpSettings settings = Settings::CurrentSettings(); 149 settings.filePanelFolder = fLastFilePanelFolder; 150 Settings::Default()->SaveSettings(settings); 151 152 return BApplication::QuitRequested(); 153 } 154 155 156 MainWin* 157 MainApp::NewWindow(BMessage* message) 158 { 159 BAutolock _(this); 160 fPlayerCount++; 161 return new(std::nothrow) MainWin(fPlayerCount == 1, message); 162 } 163 164 165 int32 166 MainApp::PlayerCount() const 167 { 168 BAutolock _(const_cast<MainApp*>(this)); 169 return fPlayerCount; 170 } 171 172 173 // #pragma mark - 174 175 176 void 177 MainApp::ReadyToRun() 178 { 179 // make sure we have at least one window open 180 if (fPlayerCount == 0) { 181 MainWin* window = NewWindow(); 182 if (window == NULL) { 183 PostMessage(B_QUIT_REQUESTED); 184 return; 185 } 186 BMessage lastPlaylistArchive; 187 if (_RestoreCurrentPlaylist(&lastPlaylistArchive) == B_OK) { 188 lastPlaylistArchive.what = M_OPEN_PREVIOUS_PLAYLIST; 189 window->PostMessage(&lastPlaylistArchive); 190 } else 191 window->Show(); 192 } 193 194 // setup the settings window now, we need to have it 195 fSettingsWindow = new SettingsWindow(BRect(150, 150, 450, 520)); 196 fSettingsWindow->Hide(); 197 fSettingsWindow->Show(); 198 199 _InstallPlaylistMimeType(); 200 } 201 202 203 void 204 MainApp::RefsReceived(BMessage* message) 205 { 206 // The user dropped a file (or files) on this app's icon, 207 // or double clicked a file that's handled by this app. 208 // Command line arguments are also redirected to here by 209 // ArgvReceived() but without MIME type check. 210 211 // If multiple refs are received in short succession we 212 // combine them into a single window/playlist. Tracker 213 // will send multiple messages when opening a multi- 214 // selection for example and we don't want to spawn large 215 // numbers of windows when someone just tries to open an 216 // album. We use half a second time and prolong it for 217 // each new ref received. 218 static bigtime_t sLastRefsReceived = 0; 219 static MainWin* sLastRefsWindow = NULL; 220 221 if (system_time() - sLastRefsReceived < 500000) { 222 // Find the last opened window 223 for (int32 i = CountWindows() - 1; i >= 0; i--) { 224 MainWin* playerWindow = dynamic_cast<MainWin*>(WindowAt(i)); 225 if (playerWindow == NULL) 226 continue; 227 228 if (playerWindow != sLastRefsWindow) { 229 // The window has changed since the last refs 230 sLastRefsReceived = 0; 231 sLastRefsWindow = NULL; 232 break; 233 } 234 235 message->AddBool("append to playlist", true); 236 playerWindow->PostMessage(message); 237 sLastRefsReceived = system_time(); 238 return; 239 } 240 } 241 242 sLastRefsWindow = NewWindow(message); 243 sLastRefsReceived = system_time(); 244 } 245 246 247 void 248 MainApp::ArgvReceived(int32 argc, char** argv) 249 { 250 char cwd[B_PATH_NAME_LENGTH]; 251 getcwd(cwd, sizeof(cwd)); 252 253 BMessage message(B_REFS_RECEIVED); 254 255 for (int i = 1; i < argc; i++) { 256 BPath path; 257 if (argv[i][0] != '/') 258 path.SetTo(cwd, argv[i]); 259 else 260 path.SetTo(argv[i]); 261 BEntry entry(path.Path(), true); 262 if (!entry.Exists() || !entry.IsFile()) 263 continue; 264 265 entry_ref ref; 266 if (entry.GetRef(&ref) == B_OK) 267 message.AddRef("refs", &ref); 268 } 269 270 if (message.HasRef("refs")) 271 RefsReceived(&message); 272 } 273 274 275 void 276 MainApp::MessageReceived(BMessage* message) 277 { 278 switch (message->what) { 279 case M_NEW_PLAYER: 280 { 281 MainWin* window = NewWindow(); 282 if (window != NULL) 283 window->Show(); 284 break; 285 } 286 case M_PLAYER_QUIT: 287 { 288 // store the window settings of this instance 289 MainWin* window = NULL; 290 bool audioOnly = false; 291 BRect windowFrame; 292 bigtime_t creationTime; 293 if (message->FindPointer("instance", (void**)&window) == B_OK 294 && message->FindBool("audio only", &audioOnly) == B_OK 295 && message->FindRect("window frame", &windowFrame) == B_OK 296 && message->FindInt64("creation time", &creationTime) == B_OK) { 297 if (audioOnly) { 298 if (!fAudioWindowFrameSaved 299 || creationTime < fLastSavedAudioWindowCreationTime) { 300 fAudioWindowFrameSaved = true; 301 fLastSavedAudioWindowCreationTime = creationTime; 302 mpSettings settings 303 = Settings::Default()->CurrentSettings(); 304 settings.audioPlayerWindowFrame = windowFrame; 305 Settings::Default()->SaveSettings(settings); 306 } 307 } 308 } 309 310 // Store the playlist if there is one. Since the app is doing 311 // this, it is "atomic". If the user has multiple instances 312 // playing audio at the same time, the last instance which is 313 // quit wins. 314 BMessage playlistArchive; 315 if (message->FindMessage("playlist", &playlistArchive) == B_OK) 316 _StoreCurrentPlaylist(&playlistArchive); 317 318 // quit if this was the last player window 319 fPlayerCount--; 320 if (fPlayerCount == 0) 321 PostMessage(B_QUIT_REQUESTED); 322 break; 323 } 324 325 case B_SOME_APP_LAUNCHED: 326 case B_SOME_APP_QUIT: 327 { 328 const char* mimeSig; 329 if (message->FindString("be:signature", &mimeSig) < B_OK) 330 break; 331 332 bool isMediaServer = strcmp(mimeSig, kMediaServerSig) == 0; 333 bool isAddonServer = strcmp(mimeSig, kMediaServerAddOnSig) == 0; 334 if (!isMediaServer && !isAddonServer) 335 break; 336 337 bool running = (message->what == B_SOME_APP_LAUNCHED); 338 if (isMediaServer) 339 fMediaServerRunning = running; 340 if (isAddonServer) 341 fMediaAddOnServerRunning = running; 342 343 if (!fMediaServerRunning && !fMediaAddOnServerRunning) { 344 fprintf(stderr, "media server has quit.\n"); 345 // trigger closing of media nodes 346 BMessage broadcast(M_MEDIA_SERVER_QUIT); 347 _BroadcastMessage(broadcast); 348 } else if (fMediaServerRunning && fMediaAddOnServerRunning) { 349 fprintf(stderr, "media server has launched.\n"); 350 // HACK! 351 // quit our now invalid instance of the media roster 352 // so that before new nodes are created, 353 // we get a new roster (it is a normal looper) 354 // TODO: This functionality could become part of 355 // BMediaRoster. It could detect the start/quit of 356 // the servers like it is done here, and either quit 357 // itself, or re-establish the connection, and send some 358 // notification to the app... something along those lines. 359 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 360 if (roster) { 361 roster->Lock(); 362 roster->Quit(); 363 } 364 // give the servers some time to init... 365 snooze(3000000); 366 // trigger re-init of media nodes 367 BMessage broadcast(M_MEDIA_SERVER_STARTED); 368 _BroadcastMessage(broadcast); 369 } 370 break; 371 } 372 case M_SETTINGS: 373 _ShowSettingsWindow(); 374 break; 375 376 case M_SHOW_OPEN_PANEL: 377 _ShowOpenFilePanel(message); 378 break; 379 case M_SHOW_SAVE_PANEL: 380 _ShowSaveFilePanel(message); 381 break; 382 383 case M_OPEN_PANEL_RESULT: 384 _HandleOpenPanelResult(message); 385 break; 386 case M_SAVE_PANEL_RESULT: 387 _HandleSavePanelResult(message); 388 break; 389 case B_CANCEL: 390 { 391 // The user canceled a file panel, but store at least the current 392 // file panel folder. 393 uint32 oldWhat; 394 if (message->FindInt32("old_what", (int32*)&oldWhat) != B_OK) 395 break; 396 if (oldWhat == M_OPEN_PANEL_RESULT && fOpenFilePanel != NULL) 397 fOpenFilePanel->GetPanelDirectory(&fLastFilePanelFolder); 398 else if (oldWhat == M_SAVE_PANEL_RESULT && fSaveFilePanel != NULL) 399 fSaveFilePanel->GetPanelDirectory(&fLastFilePanelFolder); 400 break; 401 } 402 403 default: 404 BApplication::MessageReceived(message); 405 break; 406 } 407 } 408 409 410 void 411 MainApp::AboutRequested() 412 { 413 BAlert* alert = new BAlert("about", B_TRANSLATE( 414 NAME"\n\nWritten by Marcus Overhagen, " 415 "Stephan Aßmus and Frederik Modéen"), 416 B_TRANSLATE("Thanks")); 417 alert->SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 418 // Make sure it is on top of any player windows that may have the 419 // floating all window feel. 420 alert->Go(NULL); 421 // asynchronous mode 422 } 423 424 425 // #pragma mark - 426 427 428 void 429 MainApp::_BroadcastMessage(const BMessage& _message) 430 { 431 for (int32 i = 0; BWindow* window = WindowAt(i); i++) { 432 BMessage message(_message); 433 window->PostMessage(&message); 434 } 435 } 436 437 438 void 439 MainApp::_ShowSettingsWindow() 440 { 441 BAutolock lock(fSettingsWindow); 442 if (!lock.IsLocked()) 443 return; 444 445 // If the window is already showing, don't jerk the workspaces around, 446 // just pull it to the current one. 447 uint32 workspace = 1UL << (uint32)current_workspace(); 448 uint32 windowWorkspaces = fSettingsWindow->Workspaces(); 449 if ((windowWorkspaces & workspace) == 0) { 450 // window in a different workspace, reopen in current 451 fSettingsWindow->SetWorkspaces(workspace); 452 } 453 454 if (fSettingsWindow->IsHidden()) 455 fSettingsWindow->Show(); 456 else 457 fSettingsWindow->Activate(); 458 } 459 460 461 // #pragma mark - file panels 462 463 464 void 465 MainApp::_ShowOpenFilePanel(const BMessage* message) 466 { 467 if (fOpenFilePanel == NULL) { 468 BMessenger target(this); 469 fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target); 470 } 471 472 _ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message, 473 B_TRANSLATE("Open"), B_TRANSLATE("Open")); 474 } 475 476 477 void 478 MainApp::_ShowSaveFilePanel(const BMessage* message) 479 { 480 if (fSaveFilePanel == NULL) { 481 BMessenger target(this); 482 fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target); 483 } 484 485 _ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message, 486 B_TRANSLATE("Save"), B_TRANSLATE("Save")); 487 } 488 489 490 void 491 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command, 492 const BMessage* message, const char* defaultTitle, 493 const char* defaultLabel) 494 { 495 // printf("_ShowFilePanel()\n"); 496 // message->PrintToStream(); 497 498 BMessage panelMessage(command); 499 500 if (message != NULL) { 501 BMessage targetMessage; 502 if (message->FindMessage("message", &targetMessage) == B_OK) 503 panelMessage.AddMessage("message", &targetMessage); 504 505 BMessenger target; 506 if (message->FindMessenger("target", &target) == B_OK) 507 panelMessage.AddMessenger("target", target); 508 509 const char* panelTitle; 510 if (message->FindString("title", &panelTitle) != B_OK) 511 panelTitle = defaultTitle; 512 { 513 BString finalPanelTitle = "MediaPlayer: "; 514 finalPanelTitle << panelTitle; 515 BAutolock lock(panel->Window()); 516 panel->Window()->SetTitle(finalPanelTitle.String()); 517 } 518 const char* buttonLabel; 519 if (message->FindString("label", &buttonLabel) != B_OK) 520 buttonLabel = defaultLabel; 521 panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel); 522 } 523 524 // panelMessage.PrintToStream(); 525 panel->SetMessage(&panelMessage); 526 527 if (fLastFilePanelFolder != entry_ref()) { 528 panel->SetPanelDirectory(&fLastFilePanelFolder); 529 } 530 531 panel->Show(); 532 } 533 534 535 void 536 MainApp::_HandleOpenPanelResult(const BMessage* message) 537 { 538 _HandleFilePanelResult(fOpenFilePanel, message); 539 } 540 541 542 void 543 MainApp::_HandleSavePanelResult(const BMessage* message) 544 { 545 _HandleFilePanelResult(fSaveFilePanel, message); 546 } 547 548 549 void 550 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message) 551 { 552 // printf("_HandleFilePanelResult()\n"); 553 // message->PrintToStream(); 554 555 panel->GetPanelDirectory(&fLastFilePanelFolder); 556 557 BMessage targetMessage; 558 if (message->FindMessage("message", &targetMessage) != B_OK) 559 targetMessage.what = message->what; 560 561 BMessenger target; 562 if (message->FindMessenger("target", &target) != B_OK) { 563 if (targetMessage.what == M_OPEN_PANEL_RESULT 564 || targetMessage.what == M_SAVE_PANEL_RESULT) { 565 // prevent endless message cycle 566 return; 567 } 568 // send result message to ourselves 569 target = BMessenger(this); 570 } 571 572 // copy the important contents of the message 573 // save panel 574 entry_ref directory; 575 if (message->FindRef("directory", &directory) == B_OK) 576 targetMessage.AddRef("directory", &directory); 577 const char* name; 578 if (message->FindString("name", &name) == B_OK) 579 targetMessage.AddString("name", name); 580 // open panel 581 entry_ref ref; 582 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) 583 targetMessage.AddRef("refs", &ref); 584 585 target.SendMessage(&targetMessage); 586 } 587 588 589 void 590 MainApp::_StoreCurrentPlaylist(const BMessage* message) const 591 { 592 BPath path; 593 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 594 || path.Append(kCurrentPlaylistFilename) != B_OK) { 595 return; 596 } 597 598 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 599 if (file.InitCheck() != B_OK) 600 return; 601 602 message->Flatten(&file); 603 } 604 605 606 status_t 607 MainApp::_RestoreCurrentPlaylist(BMessage* message) const 608 { 609 BPath path; 610 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 611 || path.Append(kCurrentPlaylistFilename) != B_OK) { 612 return B_ERROR; 613 } 614 615 BFile file(path.Path(), B_READ_ONLY); 616 if (file.InitCheck() != B_OK) 617 return B_ERROR; 618 619 return message->Unflatten(&file); 620 } 621 622 623 void 624 MainApp::_InstallPlaylistMimeType() 625 { 626 // install mime type of documents 627 BMimeType mime(kBinaryPlaylistMimeString); 628 status_t ret = mime.InitCheck(); 629 if (ret != B_OK) { 630 fprintf(stderr, "Could not init native document mime type (%s): %s.\n", 631 kBinaryPlaylistMimeString, strerror(ret)); 632 return; 633 } 634 635 if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) { 636 // mime is already installed, and the user is not 637 // pressing the shift key to force a re-install 638 return; 639 } 640 641 ret = mime.Install(); 642 if (ret != B_OK && ret != B_FILE_EXISTS) { 643 fprintf(stderr, "Could not install native document mime type (%s): %s.\n", 644 kBinaryPlaylistMimeString, strerror(ret)); 645 return; 646 } 647 // set preferred app 648 ret = mime.SetPreferredApp(kAppSig); 649 if (ret != B_OK) { 650 fprintf(stderr, "Could not set native document preferred app: %s\n", 651 strerror(ret)); 652 } 653 654 // set descriptions 655 ret = mime.SetShortDescription("MediaPlayer playlist"); 656 if (ret != B_OK) { 657 fprintf(stderr, "Could not set short description of mime type: %s\n", 658 strerror(ret)); 659 } 660 ret = mime.SetLongDescription("MediaPlayer binary playlist file"); 661 if (ret != B_OK) { 662 fprintf(stderr, "Could not set long description of mime type: %s\n", 663 strerror(ret)); 664 } 665 666 // set extensions 667 BMessage message('extn'); 668 message.AddString("extensions", "playlist"); 669 ret = mime.SetFileExtensions(&message); 670 if (ret != B_OK) { 671 fprintf(stderr, "Could not set extensions of mime type: %s\n", 672 strerror(ret)); 673 } 674 675 // set sniffer rule 676 char snifferRule[32]; 677 uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes); 678 sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic); 679 ret = mime.SetSnifferRule(snifferRule); 680 if (ret != B_OK) { 681 BString parseError; 682 BMimeType::CheckSnifferRule(snifferRule, &parseError); 683 fprintf(stderr, "Could not set sniffer rule of mime type: %s\n", 684 parseError.String()); 685 } 686 687 // set playlist icon 688 BResources* resources = AppResources(); 689 // does not need to be freed (belongs to BApplication base) 690 if (resources != NULL) { 691 size_t size; 692 const void* iconData = resources->LoadResource('VICN', "PlaylistIcon", 693 &size); 694 if (iconData != NULL && size > 0) { 695 if (mime.SetIcon(reinterpret_cast<const uint8*>(iconData), size) 696 != B_OK) { 697 fprintf(stderr, "Could not set vector icon of mime type.\n"); 698 } 699 } else { 700 fprintf(stderr, "Could not find icon in app resources " 701 "(data: %p, size: %ld).\n", iconData, size); 702 } 703 } else 704 fprintf(stderr, "Could not find app resources.\n"); 705 } 706 707 708 // #pragma mark - main 709 710 711 int 712 main() 713 { 714 EventQueue::CreateDefault(); 715 716 srand(system_time()); 717 718 gMainApp = new MainApp; 719 gMainApp->Run(); 720 delete gMainApp; 721 722 EventQueue::DeleteDefault(); 723 724 return 0; 725 } 726