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 const char* appName = B_TRANSLATE_APP_NAME("MediaPlayer"); 414 BString message = B_TRANSLATE("%app%\n\nWritten by Marcus Overhagen, " 415 "Stephan Aßmus and Frederik Modéen"); 416 message.ReplaceFirst("%app%", appName); 417 BAlert* alert = new BAlert(appName, message.String(), 418 B_TRANSLATE("Thanks")); 419 alert->SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 420 // Make sure it is on top of any player windows that may have the 421 // floating all window feel. 422 alert->Go(NULL); 423 // asynchronous mode 424 } 425 426 427 // #pragma mark - 428 429 430 void 431 MainApp::_BroadcastMessage(const BMessage& _message) 432 { 433 for (int32 i = 0; BWindow* window = WindowAt(i); i++) { 434 BMessage message(_message); 435 window->PostMessage(&message); 436 } 437 } 438 439 440 void 441 MainApp::_ShowSettingsWindow() 442 { 443 BAutolock lock(fSettingsWindow); 444 if (!lock.IsLocked()) 445 return; 446 447 // If the window is already showing, don't jerk the workspaces around, 448 // just pull it to the current one. 449 uint32 workspace = 1UL << (uint32)current_workspace(); 450 uint32 windowWorkspaces = fSettingsWindow->Workspaces(); 451 if ((windowWorkspaces & workspace) == 0) { 452 // window in a different workspace, reopen in current 453 fSettingsWindow->SetWorkspaces(workspace); 454 } 455 456 if (fSettingsWindow->IsHidden()) 457 fSettingsWindow->Show(); 458 else 459 fSettingsWindow->Activate(); 460 } 461 462 463 // #pragma mark - file panels 464 465 466 void 467 MainApp::_ShowOpenFilePanel(const BMessage* message) 468 { 469 if (fOpenFilePanel == NULL) { 470 BMessenger target(this); 471 fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target); 472 } 473 474 _ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message, 475 B_TRANSLATE("Open"), B_TRANSLATE("Open")); 476 } 477 478 479 void 480 MainApp::_ShowSaveFilePanel(const BMessage* message) 481 { 482 if (fSaveFilePanel == NULL) { 483 BMessenger target(this); 484 fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target); 485 } 486 487 _ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message, 488 B_TRANSLATE("Save"), B_TRANSLATE("Save")); 489 } 490 491 492 void 493 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command, 494 const BMessage* message, const char* defaultTitle, 495 const char* defaultLabel) 496 { 497 // printf("_ShowFilePanel()\n"); 498 // message->PrintToStream(); 499 500 BMessage panelMessage(command); 501 502 if (message != NULL) { 503 BMessage targetMessage; 504 if (message->FindMessage("message", &targetMessage) == B_OK) 505 panelMessage.AddMessage("message", &targetMessage); 506 507 BMessenger target; 508 if (message->FindMessenger("target", &target) == B_OK) 509 panelMessage.AddMessenger("target", target); 510 511 const char* panelTitle; 512 if (message->FindString("title", &panelTitle) != B_OK) 513 panelTitle = defaultTitle; 514 { 515 BString finalPanelTitle = "MediaPlayer: "; 516 finalPanelTitle << panelTitle; 517 BAutolock lock(panel->Window()); 518 panel->Window()->SetTitle(finalPanelTitle.String()); 519 } 520 const char* buttonLabel; 521 if (message->FindString("label", &buttonLabel) != B_OK) 522 buttonLabel = defaultLabel; 523 panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel); 524 } 525 526 // panelMessage.PrintToStream(); 527 panel->SetMessage(&panelMessage); 528 529 if (fLastFilePanelFolder != entry_ref()) { 530 panel->SetPanelDirectory(&fLastFilePanelFolder); 531 } 532 533 panel->Show(); 534 } 535 536 537 void 538 MainApp::_HandleOpenPanelResult(const BMessage* message) 539 { 540 _HandleFilePanelResult(fOpenFilePanel, message); 541 } 542 543 544 void 545 MainApp::_HandleSavePanelResult(const BMessage* message) 546 { 547 _HandleFilePanelResult(fSaveFilePanel, message); 548 } 549 550 551 void 552 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message) 553 { 554 // printf("_HandleFilePanelResult()\n"); 555 // message->PrintToStream(); 556 557 panel->GetPanelDirectory(&fLastFilePanelFolder); 558 559 BMessage targetMessage; 560 if (message->FindMessage("message", &targetMessage) != B_OK) 561 targetMessage.what = message->what; 562 563 BMessenger target; 564 if (message->FindMessenger("target", &target) != B_OK) { 565 if (targetMessage.what == M_OPEN_PANEL_RESULT 566 || targetMessage.what == M_SAVE_PANEL_RESULT) { 567 // prevent endless message cycle 568 return; 569 } 570 // send result message to ourselves 571 target = BMessenger(this); 572 } 573 574 // copy the important contents of the message 575 // save panel 576 entry_ref directory; 577 if (message->FindRef("directory", &directory) == B_OK) 578 targetMessage.AddRef("directory", &directory); 579 const char* name; 580 if (message->FindString("name", &name) == B_OK) 581 targetMessage.AddString("name", name); 582 // open panel 583 entry_ref ref; 584 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) 585 targetMessage.AddRef("refs", &ref); 586 587 target.SendMessage(&targetMessage); 588 } 589 590 591 void 592 MainApp::_StoreCurrentPlaylist(const BMessage* message) const 593 { 594 BPath path; 595 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 596 || path.Append(kCurrentPlaylistFilename) != B_OK) { 597 return; 598 } 599 600 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 601 if (file.InitCheck() != B_OK) 602 return; 603 604 message->Flatten(&file); 605 } 606 607 608 status_t 609 MainApp::_RestoreCurrentPlaylist(BMessage* message) const 610 { 611 BPath path; 612 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 613 || path.Append(kCurrentPlaylistFilename) != B_OK) { 614 return B_ERROR; 615 } 616 617 BFile file(path.Path(), B_READ_ONLY); 618 if (file.InitCheck() != B_OK) 619 return B_ERROR; 620 621 return message->Unflatten(&file); 622 } 623 624 625 void 626 MainApp::_InstallPlaylistMimeType() 627 { 628 // install mime type of documents 629 BMimeType mime(kBinaryPlaylistMimeString); 630 status_t ret = mime.InitCheck(); 631 if (ret != B_OK) { 632 fprintf(stderr, "Could not init native document mime type (%s): %s.\n", 633 kBinaryPlaylistMimeString, strerror(ret)); 634 return; 635 } 636 637 if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) { 638 // mime is already installed, and the user is not 639 // pressing the shift key to force a re-install 640 return; 641 } 642 643 ret = mime.Install(); 644 if (ret != B_OK && ret != B_FILE_EXISTS) { 645 fprintf(stderr, "Could not install native document mime type (%s): %s.\n", 646 kBinaryPlaylistMimeString, strerror(ret)); 647 return; 648 } 649 // set preferred app 650 ret = mime.SetPreferredApp(kAppSig); 651 if (ret != B_OK) { 652 fprintf(stderr, "Could not set native document preferred app: %s\n", 653 strerror(ret)); 654 } 655 656 // set descriptions 657 ret = mime.SetShortDescription("MediaPlayer playlist"); 658 if (ret != B_OK) { 659 fprintf(stderr, "Could not set short description of mime type: %s\n", 660 strerror(ret)); 661 } 662 ret = mime.SetLongDescription("MediaPlayer binary playlist file"); 663 if (ret != B_OK) { 664 fprintf(stderr, "Could not set long description of mime type: %s\n", 665 strerror(ret)); 666 } 667 668 // set extensions 669 BMessage message('extn'); 670 message.AddString("extensions", "playlist"); 671 ret = mime.SetFileExtensions(&message); 672 if (ret != B_OK) { 673 fprintf(stderr, "Could not set extensions of mime type: %s\n", 674 strerror(ret)); 675 } 676 677 // set sniffer rule 678 char snifferRule[32]; 679 uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes); 680 sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic); 681 ret = mime.SetSnifferRule(snifferRule); 682 if (ret != B_OK) { 683 BString parseError; 684 BMimeType::CheckSnifferRule(snifferRule, &parseError); 685 fprintf(stderr, "Could not set sniffer rule of mime type: %s\n", 686 parseError.String()); 687 } 688 689 // set playlist icon 690 BResources* resources = AppResources(); 691 // does not need to be freed (belongs to BApplication base) 692 if (resources != NULL) { 693 size_t size; 694 const void* iconData = resources->LoadResource('VICN', "PlaylistIcon", 695 &size); 696 if (iconData != NULL && size > 0) { 697 if (mime.SetIcon(reinterpret_cast<const uint8*>(iconData), size) 698 != B_OK) { 699 fprintf(stderr, "Could not set vector icon of mime type.\n"); 700 } 701 } else { 702 fprintf(stderr, "Could not find icon in app resources " 703 "(data: %p, size: %ld).\n", iconData, size); 704 } 705 } else 706 fprintf(stderr, "Could not find app resources.\n"); 707 } 708 709 710 // #pragma mark - main 711 712 713 int 714 main() 715 { 716 EventQueue::CreateDefault(); 717 718 srand(system_time()); 719 720 gMainApp = new MainApp; 721 gMainApp->Run(); 722 delete gMainApp; 723 724 EventQueue::DeleteDefault(); 725 726 return 0; 727 } 728