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
MainApp()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
~MainApp()79 MainApp::~MainApp()
80 {
81 delete fOpenFilePanel;
82 delete fSaveFilePanel;
83 }
84
85
86 bool
QuitRequested()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*
NewWindow(BMessage * message)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
PlayerCount() const133 MainApp::PlayerCount() const
134 {
135 BAutolock _(const_cast<MainApp*>(this));
136 return fPlayerCount;
137 }
138
139
140 // #pragma mark -
141
142
143 void
ReadyToRun()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
RefsReceived(BMessage * message)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
ArgvReceived(int32 argc,char ** argv)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
MessageReceived(BMessage * message)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
_BroadcastMessage(const BMessage & _message)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
_ShowSettingsWindow()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
_ShowOpenFilePanel(const BMessage * message)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
_ShowSaveFilePanel(const BMessage * message)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
_ShowFilePanel(BFilePanel * panel,uint32 command,const BMessage * message,const char * defaultTitle,const char * defaultLabel)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
_HandleOpenPanelResult(const BMessage * message)445 MainApp::_HandleOpenPanelResult(const BMessage* message)
446 {
447 _HandleFilePanelResult(fOpenFilePanel, message);
448 }
449
450
451 void
_HandleSavePanelResult(const BMessage * message)452 MainApp::_HandleSavePanelResult(const BMessage* message)
453 {
454 _HandleFilePanelResult(fSaveFilePanel, message);
455 }
456
457
458 void
_HandleFilePanelResult(BFilePanel * panel,const BMessage * message)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
_StoreCurrentPlaylist(const BMessage * message) const499 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
_RestoreCurrentPlaylist(BMessage * message) const516 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
_InstallPlaylistMimeType()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
main()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