xref: /haiku/src/apps/mediaplayer/MainApp.cpp (revision 3065fd47a0564e12b96cddf8d011befffe2294b6)
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