xref: /haiku/src/apps/mediaplayer/MainApp.cpp (revision 3c08adef21129761f27ae654a1c5d1705786691a)
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  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  *
20  */
21 
22 
23 #include "MainApp.h"
24 
25 #include <Alert.h>
26 #include <Autolock.h>
27 #include <Catalog.h>
28 #include <Entry.h>
29 #include <FilePanel.h>
30 #include <Locale.h>
31 #include <MediaDefs.h>
32 #include <MediaRoster.h>
33 #include <MimeType.h>
34 #include <Path.h>
35 #include <Resources.h>
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 
41 #include "EventQueue.h"
42 #include "Playlist.h"
43 #include "Settings.h"
44 #include "SettingsWindow.h"
45 
46 
47 #undef B_TRANSLATION_CONTEXT
48 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main"
49 
50 
51 static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist";
52 
53 const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer";
54 
55 MainApp* gMainApp;
56 
57 
58 MainApp::MainApp()
59 	:
60 	BApplication(kAppSig),
61 	fPlayerCount(0),
62 	fSettingsWindow(NULL),
63 
64 	fOpenFilePanel(NULL),
65 	fSaveFilePanel(NULL),
66 	fLastFilePanelFolder(),
67 
68 	fAudioWindowFrameSaved(false),
69 	fLastSavedAudioWindowCreationTime(0)
70 {
71 	fLastFilePanelFolder = Settings::Default()->FilePanelFolder();
72 
73 	if (!BMediaRoster::IsRunning()) {
74 		BAlert* alert = new BAlert("start_media_server",
75 			B_TRANSLATE("It appears the media server is not running.\n"
76 			"Would you like to start it ?"), B_TRANSLATE("Quit"),
77 			B_TRANSLATE("Start media server"), NULL,
78 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
79 		alert->SetShortcut(0, B_ESCAPE);
80 
81 		if (alert->Go() == 0) {
82 			PostMessage(B_QUIT_REQUESTED);
83 			return;
84 		}
85 
86 		launch_media_server();
87 	}
88 }
89 
90 
91 MainApp::~MainApp()
92 {
93 	delete fOpenFilePanel;
94 	delete fSaveFilePanel;
95 }
96 
97 
98 bool
99 MainApp::QuitRequested()
100 {
101 	// Make sure we store the current playlist, if applicable.
102 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
103 		MainWin* playerWindow = dynamic_cast<MainWin*>(window);
104 		if (playerWindow == NULL)
105 			continue;
106 
107 		BAutolock _(playerWindow);
108 
109 		BMessage quitMessage;
110 		playerWindow->GetQuitMessage(&quitMessage);
111 
112 		// Store the playlist if there is one. If the user has multiple
113 		// instances playing audio at the this time, the first instance wins.
114 		BMessage playlistArchive;
115 		if (quitMessage.FindMessage("playlist", &playlistArchive) == B_OK) {
116 			_StoreCurrentPlaylist(&playlistArchive);
117 			break;
118 		}
119 	}
120 
121 	// Note: This needs to be done here, SettingsWindow::QuitRequested()
122 	// returns "false" always. (Standard BApplication quit procedure will
123 	// hang otherwise.)
124 	if (fSettingsWindow && fSettingsWindow->Lock())
125 		fSettingsWindow->Quit();
126 	fSettingsWindow = NULL;
127 
128 	// store the current file panel ref in the global settings
129 	Settings::Default()->SetFilePanelFolder(fLastFilePanelFolder);
130 
131 	return BApplication::QuitRequested();
132 }
133 
134 
135 MainWin*
136 MainApp::NewWindow(BMessage* message)
137 {
138 	BAutolock _(this);
139 	fPlayerCount++;
140 	return new(std::nothrow) MainWin(fPlayerCount == 1, message);
141 }
142 
143 
144 int32
145 MainApp::PlayerCount() const
146 {
147 	BAutolock _(const_cast<MainApp*>(this));
148 	return fPlayerCount;
149 }
150 
151 
152 // #pragma mark -
153 
154 
155 void
156 MainApp::ReadyToRun()
157 {
158 	// make sure we have at least one window open
159 	if (fPlayerCount == 0) {
160 		MainWin* window = NewWindow();
161 		if (window == NULL) {
162 			PostMessage(B_QUIT_REQUESTED);
163 			return;
164 		}
165 		BMessage lastPlaylistArchive;
166 		if (_RestoreCurrentPlaylist(&lastPlaylistArchive) == B_OK) {
167 			lastPlaylistArchive.what = M_OPEN_PREVIOUS_PLAYLIST;
168 			window->PostMessage(&lastPlaylistArchive);
169 		} else
170 			window->Show();
171 	}
172 
173 	// setup the settings window now, we need to have it
174 	fSettingsWindow = new SettingsWindow(BRect(150, 150, 450, 520));
175 	fSettingsWindow->Hide();
176 	fSettingsWindow->Show();
177 
178 	_InstallPlaylistMimeType();
179 }
180 
181 
182 void
183 MainApp::RefsReceived(BMessage* message)
184 {
185 	// The user dropped a file (or files) on this app's icon,
186 	// or double clicked a file that's handled by this app.
187 	// Command line arguments are also redirected to here by
188 	// ArgvReceived() but without MIME type check.
189 
190 	// If multiple refs are received in short succession we
191 	// combine them into a single window/playlist. Tracker
192 	// will send multiple messages when opening a multi-
193 	// selection for example and we don't want to spawn large
194 	// numbers of windows when someone just tries to open an
195 	// album. We use half a second time and prolong it for
196 	// each new ref received.
197 	static bigtime_t sLastRefsReceived = 0;
198 	static MainWin* sLastRefsWindow = NULL;
199 
200 	if (system_time() - sLastRefsReceived < 500000) {
201 		// Find the last opened window
202 		for (int32 i = CountWindows() - 1; i >= 0; i--) {
203 			MainWin* playerWindow = dynamic_cast<MainWin*>(WindowAt(i));
204 			if (playerWindow == NULL)
205 				continue;
206 
207 			if (playerWindow != sLastRefsWindow) {
208 				// The window has changed since the last refs
209 				sLastRefsReceived = 0;
210 				sLastRefsWindow = NULL;
211 				break;
212 			}
213 
214 			message->AddBool("append to playlist", true);
215 			playerWindow->PostMessage(message);
216 			sLastRefsReceived = system_time();
217 			return;
218 		}
219 	}
220 
221 	sLastRefsWindow = NewWindow(message);
222 	sLastRefsReceived = system_time();
223 }
224 
225 
226 void
227 MainApp::ArgvReceived(int32 argc, char** argv)
228 {
229 	char cwd[B_PATH_NAME_LENGTH];
230 	getcwd(cwd, sizeof(cwd));
231 
232 	BMessage message(B_REFS_RECEIVED);
233 
234 	for (int i = 1; i < argc; i++) {
235 		BPath path;
236 		if (argv[i][0] != '/')
237 			path.SetTo(cwd, argv[i]);
238 		else
239 			path.SetTo(argv[i]);
240 		BEntry entry(path.Path(), true);
241 		if (!entry.Exists() || !entry.IsFile())
242 			continue;
243 
244 		entry_ref ref;
245 		if (entry.GetRef(&ref) == B_OK)
246 			message.AddRef("refs", &ref);
247 	}
248 
249 	if (message.HasRef("refs"))
250 		RefsReceived(&message);
251 }
252 
253 
254 void
255 MainApp::MessageReceived(BMessage* message)
256 {
257 	switch (message->what) {
258 		case M_NEW_PLAYER:
259 		{
260 			MainWin* window = NewWindow();
261 			if (window != NULL)
262 				window->Show();
263 			break;
264 		}
265 		case M_PLAYER_QUIT:
266 		{
267 			// store the window settings of this instance
268 			MainWin* window = NULL;
269 			bool audioOnly = false;
270 			BRect windowFrame;
271 			bigtime_t creationTime;
272 			if (message->FindPointer("instance", (void**)&window) == B_OK
273 				&& message->FindBool("audio only", &audioOnly) == B_OK
274 				&& message->FindRect("window frame", &windowFrame) == B_OK
275 				&& message->FindInt64("creation time", &creationTime) == B_OK) {
276 				if (audioOnly && (!fAudioWindowFrameSaved
277 						|| creationTime < fLastSavedAudioWindowCreationTime)) {
278 					fAudioWindowFrameSaved = true;
279 					fLastSavedAudioWindowCreationTime = creationTime;
280 
281 					Settings::Default()->SetAudioPlayerWindowFrame(windowFrame);
282 				}
283 			}
284 
285 			// Store the playlist if there is one. Since the app is doing
286 			// this, it is "atomic". If the user has multiple instances
287 			// playing audio at the same time, the last instance which is
288 			// quit wins.
289 			BMessage playlistArchive;
290 			if (message->FindMessage("playlist", &playlistArchive) == B_OK)
291 				_StoreCurrentPlaylist(&playlistArchive);
292 
293 			// quit if this was the last player window
294 			fPlayerCount--;
295 			if (fPlayerCount == 0)
296 				PostMessage(B_QUIT_REQUESTED);
297 			break;
298 		}
299 
300 		case M_SETTINGS:
301 			_ShowSettingsWindow();
302 			break;
303 
304 		case M_SHOW_OPEN_PANEL:
305 			_ShowOpenFilePanel(message);
306 			break;
307 		case M_SHOW_SAVE_PANEL:
308 			_ShowSaveFilePanel(message);
309 			break;
310 
311 		case M_OPEN_PANEL_RESULT:
312 			_HandleOpenPanelResult(message);
313 			break;
314 		case M_SAVE_PANEL_RESULT:
315 			_HandleSavePanelResult(message);
316 			break;
317 		case B_CANCEL:
318 		{
319 			// The user canceled a file panel, but store at least the current
320 			// file panel folder.
321 			uint32 oldWhat;
322 			if (message->FindInt32("old_what", (int32*)&oldWhat) != B_OK)
323 				break;
324 			if (oldWhat == M_OPEN_PANEL_RESULT && fOpenFilePanel != NULL)
325 				fOpenFilePanel->GetPanelDirectory(&fLastFilePanelFolder);
326 			else if (oldWhat == M_SAVE_PANEL_RESULT && fSaveFilePanel != NULL)
327 				fSaveFilePanel->GetPanelDirectory(&fLastFilePanelFolder);
328 			break;
329 		}
330 
331 		default:
332 			BApplication::MessageReceived(message);
333 			break;
334 	}
335 }
336 
337 
338 // #pragma mark -
339 
340 
341 void
342 MainApp::_BroadcastMessage(const BMessage& _message)
343 {
344 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
345 		BMessage message(_message);
346 		window->PostMessage(&message);
347 	}
348 }
349 
350 
351 void
352 MainApp::_ShowSettingsWindow()
353 {
354 	BAutolock lock(fSettingsWindow);
355 	if (!lock.IsLocked())
356 		return;
357 
358 	// If the window is already showing, don't jerk the workspaces around,
359 	// just pull it to the current one.
360 	uint32 workspace = 1UL << (uint32)current_workspace();
361 	uint32 windowWorkspaces = fSettingsWindow->Workspaces();
362 	if ((windowWorkspaces & workspace) == 0) {
363 		// window in a different workspace, reopen in current
364 		fSettingsWindow->SetWorkspaces(workspace);
365 	}
366 
367 	if (fSettingsWindow->IsHidden())
368 		fSettingsWindow->Show();
369 	else
370 		fSettingsWindow->Activate();
371 }
372 
373 
374 // #pragma mark - file panels
375 
376 
377 void
378 MainApp::_ShowOpenFilePanel(const BMessage* message)
379 {
380 	if (fOpenFilePanel == NULL) {
381 		BMessenger target(this);
382 		fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target);
383 	}
384 
385 	_ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message,
386 		B_TRANSLATE("Open"), B_TRANSLATE("Open"));
387 }
388 
389 
390 void
391 MainApp::_ShowSaveFilePanel(const BMessage* message)
392 {
393 	if (fSaveFilePanel == NULL) {
394 		BMessenger target(this);
395 		fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target);
396 	}
397 
398 	_ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message,
399 		B_TRANSLATE("Save"), B_TRANSLATE("Save"));
400 }
401 
402 
403 void
404 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command,
405 	const BMessage* message, const char* defaultTitle,
406 	const char* defaultLabel)
407 {
408 //	printf("_ShowFilePanel()\n");
409 //	message->PrintToStream();
410 
411 	BMessage panelMessage(command);
412 
413 	if (message != NULL) {
414 		BMessage targetMessage;
415 		if (message->FindMessage("message", &targetMessage) == B_OK)
416 			panelMessage.AddMessage("message", &targetMessage);
417 
418 		BMessenger target;
419 		if (message->FindMessenger("target", &target) == B_OK)
420 			panelMessage.AddMessenger("target", target);
421 
422 		const char* panelTitle;
423 		if (message->FindString("title", &panelTitle) != B_OK)
424 			panelTitle = defaultTitle;
425 		{
426 			BString finalPanelTitle = "MediaPlayer: ";
427 			finalPanelTitle << panelTitle;
428 			BAutolock lock(panel->Window());
429 			panel->Window()->SetTitle(finalPanelTitle.String());
430 		}
431 		const char* buttonLabel;
432 		if (message->FindString("label", &buttonLabel) != B_OK)
433 			buttonLabel = defaultLabel;
434 		panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel);
435 	}
436 
437 //	panelMessage.PrintToStream();
438 	panel->SetMessage(&panelMessage);
439 
440 	if (fLastFilePanelFolder != entry_ref()) {
441 		panel->SetPanelDirectory(&fLastFilePanelFolder);
442 	}
443 
444 	panel->Show();
445 }
446 
447 
448 void
449 MainApp::_HandleOpenPanelResult(const BMessage* message)
450 {
451 	_HandleFilePanelResult(fOpenFilePanel, message);
452 }
453 
454 
455 void
456 MainApp::_HandleSavePanelResult(const BMessage* message)
457 {
458 	_HandleFilePanelResult(fSaveFilePanel, message);
459 }
460 
461 
462 void
463 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message)
464 {
465 //	printf("_HandleFilePanelResult()\n");
466 //	message->PrintToStream();
467 
468 	panel->GetPanelDirectory(&fLastFilePanelFolder);
469 
470 	BMessage targetMessage;
471 	if (message->FindMessage("message", &targetMessage) != B_OK)
472 		targetMessage.what = message->what;
473 
474 	BMessenger target;
475 	if (message->FindMessenger("target", &target) != B_OK) {
476 		if (targetMessage.what == M_OPEN_PANEL_RESULT
477 			|| targetMessage.what == M_SAVE_PANEL_RESULT) {
478 			// prevent endless message cycle
479 			return;
480 		}
481 		// send result message to ourselves
482 		target = BMessenger(this);
483 	}
484 
485 	// copy the important contents of the message
486 	// save panel
487 	entry_ref directory;
488 	if (message->FindRef("directory", &directory) == B_OK)
489 		targetMessage.AddRef("directory", &directory);
490 	const char* name;
491 	if (message->FindString("name", &name) == B_OK)
492 		targetMessage.AddString("name", name);
493 	// open panel
494 	entry_ref ref;
495 	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++)
496 		targetMessage.AddRef("refs", &ref);
497 
498 	target.SendMessage(&targetMessage);
499 }
500 
501 
502 void
503 MainApp::_StoreCurrentPlaylist(const BMessage* message) const
504 {
505 	BPath path;
506 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
507 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
508 		return;
509 	}
510 
511 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
512 	if (file.InitCheck() != B_OK)
513 		return;
514 
515 	message->Flatten(&file);
516 }
517 
518 
519 status_t
520 MainApp::_RestoreCurrentPlaylist(BMessage* message) const
521 {
522 	BPath path;
523 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
524 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
525 		return B_ERROR;
526 	}
527 
528 	BFile file(path.Path(), B_READ_ONLY);
529 	if (file.InitCheck() != B_OK)
530 		return B_ERROR;
531 
532 	return message->Unflatten(&file);
533 }
534 
535 
536 void
537 MainApp::_InstallPlaylistMimeType()
538 {
539 	// install mime type of documents
540 	BMimeType mime(kBinaryPlaylistMimeString);
541 	status_t ret = mime.InitCheck();
542 	if (ret != B_OK) {
543 		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
544 			kBinaryPlaylistMimeString, strerror(ret));
545 		return;
546 	}
547 
548 	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
549 		// mime is already installed, and the user is not
550 		// pressing the shift key to force a re-install
551 		return;
552 	}
553 
554 	ret = mime.Install();
555 	if (ret != B_OK && ret != B_FILE_EXISTS) {
556 		fprintf(stderr, "Could not install native document mime type (%s): %s.\n",
557 			kBinaryPlaylistMimeString, strerror(ret));
558 		return;
559 	}
560 	// set preferred app
561 	ret = mime.SetPreferredApp(kAppSig);
562 	if (ret != B_OK) {
563 		fprintf(stderr, "Could not set native document preferred app: %s\n",
564 			strerror(ret));
565 	}
566 
567 	// set descriptions
568 	ret = mime.SetShortDescription("MediaPlayer playlist");
569 	if (ret != B_OK) {
570 		fprintf(stderr, "Could not set short description of mime type: %s\n",
571 			strerror(ret));
572 	}
573 	ret = mime.SetLongDescription("MediaPlayer binary playlist file");
574 	if (ret != B_OK) {
575 		fprintf(stderr, "Could not set long description of mime type: %s\n",
576 			strerror(ret));
577 	}
578 
579 	// set extensions
580 	BMessage message('extn');
581 	message.AddString("extensions", "playlist");
582 	ret = mime.SetFileExtensions(&message);
583 	if (ret != B_OK) {
584 		fprintf(stderr, "Could not set extensions of mime type: %s\n",
585 			strerror(ret));
586 	}
587 
588 	// set sniffer rule
589 	char snifferRule[32];
590 	uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes);
591 	sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic);
592 	ret = mime.SetSnifferRule(snifferRule);
593 	if (ret != B_OK) {
594 		BString parseError;
595 		BMimeType::CheckSnifferRule(snifferRule, &parseError);
596 		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
597 			parseError.String());
598 	}
599 
600 	// set playlist icon
601 	BResources* resources = AppResources();
602 		// does not need to be freed (belongs to BApplication base)
603 	if (resources != NULL) {
604 		size_t size;
605 		const void* iconData = resources->LoadResource('VICN', "PlaylistIcon",
606 			&size);
607 		if (iconData != NULL && size > 0) {
608 			if (mime.SetIcon(reinterpret_cast<const uint8*>(iconData), size)
609 				!= B_OK) {
610 				fprintf(stderr, "Could not set vector icon of mime type.\n");
611 			}
612 		} else {
613 			fprintf(stderr, "Could not find icon in app resources "
614 				"(data: %p, size: %ld).\n", iconData, size);
615 		}
616 	} else
617 		fprintf(stderr, "Could not find app resources.\n");
618 }
619 
620 
621 // #pragma mark - main
622 
623 
624 int
625 main()
626 {
627 	EventQueue::CreateDefault();
628 
629 	srand(system_time());
630 
631 	gMainApp = new MainApp;
632 	gMainApp->Run();
633 	delete gMainApp;
634 
635 	EventQueue::DeleteDefault();
636 
637 	return 0;
638 }
639