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