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
39static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist";
40
41const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer";
42
43MainApp* gMainApp;
44
45
46MainApp::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
79MainApp::~MainApp()
80{
81	delete fOpenFilePanel;
82	delete fSaveFilePanel;
83}
84
85
86bool
87MainApp::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
123MainWin*
124MainApp::NewWindow(BMessage* message)
125{
126	BAutolock _(this);
127	fPlayerCount++;
128	return new(std::nothrow) MainWin(fPlayerCount == 1, message);
129}
130
131
132int32
133MainApp::PlayerCount() const
134{
135	BAutolock _(const_cast<MainApp*>(this));
136	return fPlayerCount;
137}
138
139
140// #pragma mark -
141
142
143void
144MainApp::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
170void
171MainApp::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
214void
215MainApp::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
250void
251MainApp::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
337void
338MainApp::_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
347void
348MainApp::_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
373void
374MainApp::_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
386void
387MainApp::_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
399void
400MainApp::_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
444void
445MainApp::_HandleOpenPanelResult(const BMessage* message)
446{
447	_HandleFilePanelResult(fOpenFilePanel, message);
448}
449
450
451void
452MainApp::_HandleSavePanelResult(const BMessage* message)
453{
454	_HandleFilePanelResult(fSaveFilePanel, message);
455}
456
457
458void
459MainApp::_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
498void
499MainApp::_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
515status_t
516MainApp::_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
532void
533MainApp::_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
620int
621main()
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