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