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