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