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