vfs: check userland buffers before reading them.
[haiku.git] / src / apps / mediaplayer / MainApp.cpp
blobc454d8b99662140894739313f0a9f8abc4e4a3ad
1 /*
2 * MainApp.cpp - Media Player for the Haiku Operating System
4 * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
5 * Copyright (C) 2008 Stephan Aßmus <superstippi@gmx.de> (MIT Ok)
7 * Released under the terms of the MIT license.
8 */
11 #include "MainApp.h"
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>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
29 #include "EventQueue.h"
30 #include "Playlist.h"
31 #include "Settings.h"
32 #include "SettingsWindow.h"
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main"
39 static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist";
41 const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer";
43 MainApp* gMainApp;
46 MainApp::MainApp()
48 BApplication(kAppSig),
49 fPlayerCount(0),
50 fSettingsWindow(NULL),
52 fOpenFilePanel(NULL),
53 fSaveFilePanel(NULL),
54 fLastFilePanelFolder(),
56 fAudioWindowFrameSaved(false),
57 fLastSavedAudioWindowCreationTime(0)
59 fLastFilePanelFolder = Settings::Default()->FilePanelFolder();
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);
69 if (alert->Go() == 0) {
70 PostMessage(B_QUIT_REQUESTED);
71 return;
74 launch_media_server();
79 MainApp::~MainApp()
81 delete fOpenFilePanel;
82 delete fSaveFilePanel;
86 bool
87 MainApp::QuitRequested()
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;
95 BAutolock _(playerWindow);
97 BMessage quitMessage;
98 playerWindow->GetQuitMessage(&quitMessage);
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;
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;
116 // store the current file panel ref in the global settings
117 Settings::Default()->SetFilePanelFolder(fLastFilePanelFolder);
119 return BApplication::QuitRequested();
123 MainWin*
124 MainApp::NewWindow(BMessage* message)
126 BAutolock _(this);
127 fPlayerCount++;
128 return new(std::nothrow) MainWin(fPlayerCount == 1, message);
132 int32
133 MainApp::PlayerCount() const
135 BAutolock _(const_cast<MainApp*>(this));
136 return fPlayerCount;
140 // #pragma mark -
143 void
144 MainApp::ReadyToRun()
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;
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();
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();
166 _InstallPlaylistMimeType();
170 void
171 MainApp::RefsReceived(BMessage* message)
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.
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;
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;
195 if (playerWindow != sLastRefsWindow) {
196 // The window has changed since the last refs
197 sLastRefsReceived = 0;
198 sLastRefsWindow = NULL;
199 break;
202 message->AddBool("append to playlist", true);
203 playerWindow->PostMessage(message);
204 sLastRefsReceived = system_time();
205 return;
209 sLastRefsWindow = NewWindow(message);
210 sLastRefsReceived = system_time();
214 void
215 MainApp::ArgvReceived(int32 argc, char** argv)
217 char cwd[B_PATH_NAME_LENGTH];
218 getcwd(cwd, sizeof(cwd));
220 for (int i = 1; i < argc; i++) {
221 BUrl url(argv[i]);
222 if (url.IsValid()) {
223 BMessage archivedUrl;
224 url.Archive(&archivedUrl);
226 BMessage msg(M_URL_RECEIVED);
227 if (msg.AddMessage("mediaplayer:url", &archivedUrl) == B_OK)
228 RefsReceived(&msg);
230 continue;
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;
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);
250 void
251 MainApp::MessageReceived(BMessage* message)
253 switch (message->what) {
254 case M_NEW_PLAYER:
256 MainWin* window = NewWindow();
257 if (window != NULL)
258 window->Show();
259 break;
261 case M_PLAYER_QUIT:
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;
277 Settings::Default()->SetAudioPlayerWindowFrame(windowFrame);
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);
289 // quit if this was the last player window
290 fPlayerCount--;
291 if (fPlayerCount == 0)
292 PostMessage(B_QUIT_REQUESTED);
293 break;
296 case M_SETTINGS:
297 _ShowSettingsWindow();
298 break;
300 case M_SHOW_OPEN_PANEL:
301 _ShowOpenFilePanel(message);
302 break;
303 case M_SHOW_SAVE_PANEL:
304 _ShowSaveFilePanel(message);
305 break;
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:
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;
327 default:
328 BApplication::MessageReceived(message);
329 break;
334 // #pragma mark -
337 void
338 MainApp::_BroadcastMessage(const BMessage& _message)
340 for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
341 BMessage message(_message);
342 window->PostMessage(&message);
347 void
348 MainApp::_ShowSettingsWindow()
350 BAutolock lock(fSettingsWindow);
351 if (!lock.IsLocked())
352 return;
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);
363 if (fSettingsWindow->IsHidden())
364 fSettingsWindow->Show();
365 else
366 fSettingsWindow->Activate();
370 // #pragma mark - file panels
373 void
374 MainApp::_ShowOpenFilePanel(const BMessage* message)
376 if (fOpenFilePanel == NULL) {
377 BMessenger target(this);
378 fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target);
381 _ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message,
382 B_TRANSLATE("Open"), B_TRANSLATE("Open"));
386 void
387 MainApp::_ShowSaveFilePanel(const BMessage* message)
389 if (fSaveFilePanel == NULL) {
390 BMessenger target(this);
391 fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target);
394 _ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message,
395 B_TRANSLATE("Save"), B_TRANSLATE("Save"));
399 void
400 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command,
401 const BMessage* message, const char* defaultTitle,
402 const char* defaultLabel)
404 // printf("_ShowFilePanel()\n");
405 // message->PrintToStream();
407 BMessage panelMessage(command);
409 if (message != NULL) {
410 BMessage targetMessage;
411 if (message->FindMessage("message", &targetMessage) == B_OK)
412 panelMessage.AddMessage("message", &targetMessage);
414 BMessenger target;
415 if (message->FindMessenger("target", &target) == B_OK)
416 panelMessage.AddMessenger("target", target);
418 const char* panelTitle;
419 if (message->FindString("title", &panelTitle) != B_OK)
420 panelTitle = defaultTitle;
422 BString finalPanelTitle = "MediaPlayer: ";
423 finalPanelTitle << panelTitle;
424 BAutolock lock(panel->Window());
425 panel->Window()->SetTitle(finalPanelTitle.String());
427 const char* buttonLabel;
428 if (message->FindString("label", &buttonLabel) != B_OK)
429 buttonLabel = defaultLabel;
430 panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel);
433 // panelMessage.PrintToStream();
434 panel->SetMessage(&panelMessage);
436 if (fLastFilePanelFolder != entry_ref()) {
437 panel->SetPanelDirectory(&fLastFilePanelFolder);
440 panel->Show();
444 void
445 MainApp::_HandleOpenPanelResult(const BMessage* message)
447 _HandleFilePanelResult(fOpenFilePanel, message);
451 void
452 MainApp::_HandleSavePanelResult(const BMessage* message)
454 _HandleFilePanelResult(fSaveFilePanel, message);
458 void
459 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message)
461 // printf("_HandleFilePanelResult()\n");
462 // message->PrintToStream();
464 panel->GetPanelDirectory(&fLastFilePanelFolder);
466 BMessage targetMessage;
467 if (message->FindMessage("message", &targetMessage) != B_OK)
468 targetMessage.what = message->what;
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;
477 // send result message to ourselves
478 target = BMessenger(this);
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);
494 target.SendMessage(&targetMessage);
498 void
499 MainApp::_StoreCurrentPlaylist(const BMessage* message) const
501 BPath path;
502 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
503 || path.Append(kCurrentPlaylistFilename) != B_OK) {
504 return;
507 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
508 if (file.InitCheck() != B_OK)
509 return;
511 message->Flatten(&file);
515 status_t
516 MainApp::_RestoreCurrentPlaylist(BMessage* message) const
518 BPath path;
519 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
520 || path.Append(kCurrentPlaylistFilename) != B_OK) {
521 return B_ERROR;
524 BFile file(path.Path(), B_READ_ONLY);
525 if (file.InitCheck() != B_OK)
526 return B_ERROR;
528 return message->Unflatten(&file);
532 void
533 MainApp::_InstallPlaylistMimeType()
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;
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;
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;
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));
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));
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));
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));
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());
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");
608 } else {
609 fprintf(stderr, "Could not find icon in app resources "
610 "(data: %p, size: %ld).\n", iconData, size);
612 } else
613 fprintf(stderr, "Could not find app resources.\n");
617 // #pragma mark - main
621 main()
623 EventQueue::CreateDefault();
625 srand(system_time());
627 gMainApp = new MainApp;
628 gMainApp->Run();
629 delete gMainApp;
631 EventQueue::DeleteDefault();
633 return 0;