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 52static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist"; 53 54const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer"; 55 56MainApp* gMainApp; 57 58static const char* kMediaServerSig = B_MEDIA_SERVER_SIGNATURE; 59static const char* kMediaServerAddOnSig = "application/x-vnd.Be.addon-host"; 60 61 62MainApp::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 111MainApp::~MainApp() 112{ 113 delete fOpenFilePanel; 114 delete fSaveFilePanel; 115} 116 117 118bool 119MainApp::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 155MainWin* 156MainApp::NewWindow(BMessage* message) 157{ 158 BAutolock _(this); 159 fPlayerCount++; 160 return new(std::nothrow) MainWin(fPlayerCount == 1, message); 161} 162 163 164int32 165MainApp::PlayerCount() const 166{ 167 BAutolock _(const_cast<MainApp*>(this)); 168 return fPlayerCount; 169} 170 171 172// #pragma mark - 173 174 175void 176MainApp::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 202void 203MainApp::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 246void 247MainApp::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 274void 275MainApp::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 408void 409MainApp::_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 418void 419MainApp::_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 444void 445MainApp::_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 457void 458MainApp::_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 470void 471MainApp::_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 515void 516MainApp::_HandleOpenPanelResult(const BMessage* message) 517{ 518 _HandleFilePanelResult(fOpenFilePanel, message); 519} 520 521 522void 523MainApp::_HandleSavePanelResult(const BMessage* message) 524{ 525 _HandleFilePanelResult(fSaveFilePanel, message); 526} 527 528 529void 530MainApp::_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 569void 570MainApp::_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 586status_t 587MainApp::_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 603void 604MainApp::_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 691int 692main() 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