/* * MainApp.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen * Copyright (C) 2008 Stephan Aßmus (MIT Ok) * * Released under the terms of the MIT license. */ #include "MainApp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "EventQueue.h" #include "Playlist.h" #include "Settings.h" #include "SettingsWindow.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "MediaPlayer-Main" static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist"; const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer"; MainApp* gMainApp; MainApp::MainApp() : BApplication(kAppSig), fPlayerCount(0), fSettingsWindow(NULL), fOpenFilePanel(NULL), fSaveFilePanel(NULL), fLastFilePanelFolder(), fAudioWindowFrameSaved(false), fLastSavedAudioWindowCreationTime(0) { fLastFilePanelFolder = Settings::Default()->FilePanelFolder(); if (!BMediaRoster::IsRunning()) { BAlert* alert = new BAlert("start_media_server", B_TRANSLATE("It appears the media server is not running.\n" "Would you like to start it ?"), B_TRANSLATE("Quit"), B_TRANSLATE("Start media server"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); alert->SetShortcut(0, B_ESCAPE); if (alert->Go() == 0) { PostMessage(B_QUIT_REQUESTED); return; } launch_media_server(); } } MainApp::~MainApp() { delete fOpenFilePanel; delete fSaveFilePanel; } bool MainApp::QuitRequested() { // Make sure we store the current playlist, if applicable. for (int32 i = 0; BWindow* window = WindowAt(i); i++) { MainWin* playerWindow = dynamic_cast(window); if (playerWindow == NULL) continue; BAutolock _(playerWindow); BMessage quitMessage; playerWindow->GetQuitMessage(&quitMessage); // Store the playlist if there is one. If the user has multiple // instances playing audio at the this time, the first instance wins. BMessage playlistArchive; if (quitMessage.FindMessage("playlist", &playlistArchive) == B_OK) { _StoreCurrentPlaylist(&playlistArchive); break; } } // Note: This needs to be done here, SettingsWindow::QuitRequested() // returns "false" always. (Standard BApplication quit procedure will // hang otherwise.) if (fSettingsWindow && fSettingsWindow->Lock()) fSettingsWindow->Quit(); fSettingsWindow = NULL; // store the current file panel ref in the global settings Settings::Default()->SetFilePanelFolder(fLastFilePanelFolder); return BApplication::QuitRequested(); } MainWin* MainApp::NewWindow(BMessage* message) { BAutolock _(this); fPlayerCount++; return new(std::nothrow) MainWin(fPlayerCount == 1, message); } int32 MainApp::PlayerCount() const { BAutolock _(const_cast(this)); return fPlayerCount; } // #pragma mark - void MainApp::ReadyToRun() { // make sure we have at least one window open if (fPlayerCount == 0) { MainWin* window = NewWindow(); if (window == NULL) { PostMessage(B_QUIT_REQUESTED); return; } BMessage lastPlaylistArchive; if (_RestoreCurrentPlaylist(&lastPlaylistArchive) == B_OK) { lastPlaylistArchive.what = M_OPEN_PREVIOUS_PLAYLIST; window->PostMessage(&lastPlaylistArchive); } else window->Show(); } // setup the settings window now, we need to have it fSettingsWindow = new SettingsWindow(BRect(150, 150, 450, 520)); fSettingsWindow->Hide(); fSettingsWindow->Show(); _InstallPlaylistMimeType(); } void MainApp::RefsReceived(BMessage* message) { // The user dropped a file (or files) on this app's icon, // or double clicked a file that's handled by this app. // Command line arguments are also redirected to here by // ArgvReceived() but without MIME type check. // If multiple refs are received in short succession we // combine them into a single window/playlist. Tracker // will send multiple messages when opening a multi- // selection for example and we don't want to spawn large // numbers of windows when someone just tries to open an // album. We use half a second time and prolong it for // each new ref received. static bigtime_t sLastRefsReceived = 0; static MainWin* sLastRefsWindow = NULL; if (system_time() - sLastRefsReceived < 500000) { // Find the last opened window for (int32 i = CountWindows() - 1; i >= 0; i--) { MainWin* playerWindow = dynamic_cast(WindowAt(i)); if (playerWindow == NULL) continue; if (playerWindow != sLastRefsWindow) { // The window has changed since the last refs sLastRefsReceived = 0; sLastRefsWindow = NULL; break; } message->AddBool("append to playlist", true); playerWindow->PostMessage(message); sLastRefsReceived = system_time(); return; } } sLastRefsWindow = NewWindow(message); sLastRefsReceived = system_time(); } void MainApp::ArgvReceived(int32 argc, char** argv) { char cwd[B_PATH_NAME_LENGTH]; getcwd(cwd, sizeof(cwd)); for (int i = 1; i < argc; i++) { BUrl url(argv[i]); if (url.IsValid()) { BMessage archivedUrl; url.Archive(&archivedUrl); BMessage msg(M_URL_RECEIVED); if (msg.AddMessage("mediaplayer:url", &archivedUrl) == B_OK) RefsReceived(&msg); continue; } BPath path; if (argv[i][0] != '/') path.SetTo(cwd, argv[i]); else path.SetTo(argv[i]); BEntry entry(path.Path(), true); if (!entry.Exists() || !entry.IsFile()) continue; BMessage message(B_REFS_RECEIVED); entry_ref ref; if (entry.GetRef(&ref) == B_OK && message.AddRef("refs", &ref) == B_OK) RefsReceived(&message); } } void MainApp::MessageReceived(BMessage* message) { switch (message->what) { case M_NEW_PLAYER: { MainWin* window = NewWindow(); if (window != NULL) window->Show(); break; } case M_PLAYER_QUIT: { // store the window settings of this instance MainWin* window = NULL; bool audioOnly = false; BRect windowFrame; bigtime_t creationTime; if (message->FindPointer("instance", (void**)&window) == B_OK && message->FindBool("audio only", &audioOnly) == B_OK && message->FindRect("window frame", &windowFrame) == B_OK && message->FindInt64("creation time", &creationTime) == B_OK) { if (audioOnly && (!fAudioWindowFrameSaved || creationTime < fLastSavedAudioWindowCreationTime)) { fAudioWindowFrameSaved = true; fLastSavedAudioWindowCreationTime = creationTime; Settings::Default()->SetAudioPlayerWindowFrame(windowFrame); } } // Store the playlist if there is one. Since the app is doing // this, it is "atomic". If the user has multiple instances // playing audio at the same time, the last instance which is // quit wins. BMessage playlistArchive; if (message->FindMessage("playlist", &playlistArchive) == B_OK) _StoreCurrentPlaylist(&playlistArchive); // quit if this was the last player window fPlayerCount--; if (fPlayerCount == 0) PostMessage(B_QUIT_REQUESTED); break; } case M_SETTINGS: _ShowSettingsWindow(); break; case M_SHOW_OPEN_PANEL: _ShowOpenFilePanel(message); break; case M_SHOW_SAVE_PANEL: _ShowSaveFilePanel(message); break; case M_OPEN_PANEL_RESULT: _HandleOpenPanelResult(message); break; case M_SAVE_PANEL_RESULT: _HandleSavePanelResult(message); break; case B_CANCEL: { // The user canceled a file panel, but store at least the current // file panel folder. uint32 oldWhat; if (message->FindInt32("old_what", (int32*)&oldWhat) != B_OK) break; if (oldWhat == M_OPEN_PANEL_RESULT && fOpenFilePanel != NULL) fOpenFilePanel->GetPanelDirectory(&fLastFilePanelFolder); else if (oldWhat == M_SAVE_PANEL_RESULT && fSaveFilePanel != NULL) fSaveFilePanel->GetPanelDirectory(&fLastFilePanelFolder); break; } default: BApplication::MessageReceived(message); break; } } // #pragma mark - void MainApp::_BroadcastMessage(const BMessage& _message) { for (int32 i = 0; BWindow* window = WindowAt(i); i++) { BMessage message(_message); window->PostMessage(&message); } } void MainApp::_ShowSettingsWindow() { BAutolock lock(fSettingsWindow); if (!lock.IsLocked()) return; // If the window is already showing, don't jerk the workspaces around, // just pull it to the current one. uint32 workspace = 1UL << (uint32)current_workspace(); uint32 windowWorkspaces = fSettingsWindow->Workspaces(); if ((windowWorkspaces & workspace) == 0) { // window in a different workspace, reopen in current fSettingsWindow->SetWorkspaces(workspace); } if (fSettingsWindow->IsHidden()) fSettingsWindow->Show(); else fSettingsWindow->Activate(); } // #pragma mark - file panels void MainApp::_ShowOpenFilePanel(const BMessage* message) { if (fOpenFilePanel == NULL) { BMessenger target(this); fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target); } _ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message, B_TRANSLATE("Open"), B_TRANSLATE("Open")); } void MainApp::_ShowSaveFilePanel(const BMessage* message) { if (fSaveFilePanel == NULL) { BMessenger target(this); fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target); } _ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message, B_TRANSLATE("Save"), B_TRANSLATE("Save")); } void MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command, const BMessage* message, const char* defaultTitle, const char* defaultLabel) { // printf("_ShowFilePanel()\n"); // message->PrintToStream(); BMessage panelMessage(command); if (message != NULL) { BMessage targetMessage; if (message->FindMessage("message", &targetMessage) == B_OK) panelMessage.AddMessage("message", &targetMessage); BMessenger target; if (message->FindMessenger("target", &target) == B_OK) panelMessage.AddMessenger("target", target); const char* panelTitle; if (message->FindString("title", &panelTitle) != B_OK) panelTitle = defaultTitle; { BString finalPanelTitle = "MediaPlayer: "; finalPanelTitle << panelTitle; BAutolock lock(panel->Window()); panel->Window()->SetTitle(finalPanelTitle.String()); } const char* buttonLabel; if (message->FindString("label", &buttonLabel) != B_OK) buttonLabel = defaultLabel; panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel); } // panelMessage.PrintToStream(); panel->SetMessage(&panelMessage); if (fLastFilePanelFolder != entry_ref()) { panel->SetPanelDirectory(&fLastFilePanelFolder); } panel->Show(); } void MainApp::_HandleOpenPanelResult(const BMessage* message) { _HandleFilePanelResult(fOpenFilePanel, message); } void MainApp::_HandleSavePanelResult(const BMessage* message) { _HandleFilePanelResult(fSaveFilePanel, message); } void MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message) { // printf("_HandleFilePanelResult()\n"); // message->PrintToStream(); panel->GetPanelDirectory(&fLastFilePanelFolder); BMessage targetMessage; if (message->FindMessage("message", &targetMessage) != B_OK) targetMessage.what = message->what; BMessenger target; if (message->FindMessenger("target", &target) != B_OK) { if (targetMessage.what == M_OPEN_PANEL_RESULT || targetMessage.what == M_SAVE_PANEL_RESULT) { // prevent endless message cycle return; } // send result message to ourselves target = BMessenger(this); } // copy the important contents of the message // save panel entry_ref directory; if (message->FindRef("directory", &directory) == B_OK) targetMessage.AddRef("directory", &directory); const char* name; if (message->FindString("name", &name) == B_OK) targetMessage.AddString("name", name); // open panel entry_ref ref; for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) targetMessage.AddRef("refs", &ref); target.SendMessage(&targetMessage); } void MainApp::_StoreCurrentPlaylist(const BMessage* message) const { BPath path; if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK || path.Append(kCurrentPlaylistFilename) != B_OK) { return; } BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); if (file.InitCheck() != B_OK) return; message->Flatten(&file); } status_t MainApp::_RestoreCurrentPlaylist(BMessage* message) const { BPath path; if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK || path.Append(kCurrentPlaylistFilename) != B_OK) { return B_ERROR; } BFile file(path.Path(), B_READ_ONLY); if (file.InitCheck() != B_OK) return B_ERROR; return message->Unflatten(&file); } void MainApp::_InstallPlaylistMimeType() { // install mime type of documents BMimeType mime(kBinaryPlaylistMimeString); status_t ret = mime.InitCheck(); if (ret != B_OK) { fprintf(stderr, "Could not init native document mime type (%s): %s.\n", kBinaryPlaylistMimeString, strerror(ret)); return; } if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) { // mime is already installed, and the user is not // pressing the shift key to force a re-install return; } ret = mime.Install(); if (ret != B_OK && ret != B_FILE_EXISTS) { fprintf(stderr, "Could not install native document mime type (%s): %s.\n", kBinaryPlaylistMimeString, strerror(ret)); return; } // set preferred app ret = mime.SetPreferredApp(kAppSig); if (ret != B_OK) { fprintf(stderr, "Could not set native document preferred app: %s\n", strerror(ret)); } // set descriptions ret = mime.SetShortDescription("MediaPlayer playlist"); if (ret != B_OK) { fprintf(stderr, "Could not set short description of mime type: %s\n", strerror(ret)); } ret = mime.SetLongDescription("MediaPlayer binary playlist file"); if (ret != B_OK) { fprintf(stderr, "Could not set long description of mime type: %s\n", strerror(ret)); } // set extensions BMessage message('extn'); message.AddString("extensions", "playlist"); ret = mime.SetFileExtensions(&message); if (ret != B_OK) { fprintf(stderr, "Could not set extensions of mime type: %s\n", strerror(ret)); } // set sniffer rule char snifferRule[32]; uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes); sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic); ret = mime.SetSnifferRule(snifferRule); if (ret != B_OK) { BString parseError; BMimeType::CheckSnifferRule(snifferRule, &parseError); fprintf(stderr, "Could not set sniffer rule of mime type: %s\n", parseError.String()); } // set playlist icon BResources* resources = AppResources(); // does not need to be freed (belongs to BApplication base) if (resources != NULL) { size_t size; const void* iconData = resources->LoadResource('VICN', "PlaylistIcon", &size); if (iconData != NULL && size > 0) { if (mime.SetIcon(reinterpret_cast(iconData), size) != B_OK) { fprintf(stderr, "Could not set vector icon of mime type.\n"); } } else { fprintf(stderr, "Could not find icon in app resources " "(data: %p, size: %ld).\n", iconData, size); } } else fprintf(stderr, "Could not find app resources.\n"); } // #pragma mark - main int main() { EventQueue::CreateDefault(); srand(system_time()); gMainApp = new MainApp; gMainApp->Run(); delete gMainApp; EventQueue::DeleteDefault(); return 0; }