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