xref: /haiku/src/apps/mediaplayer/MainApp.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
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 <Entry.h>
28 #include <FilePanel.h>
29 #include <MediaDefs.h>
30 #include <MediaRoster.h>
31 #include <MimeType.h>
32 #include <Path.h>
33 #include <Resources.h>
34 #include <Roster.h>
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 
40 #include "EventQueue.h"
41 #include "Playlist.h"
42 #include "Settings.h"
43 #include "SettingsWindow.h"
44 
45 
46 MainApp* gMainApp;
47 const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer";
48 
49 static const char* kMediaServerSig = B_MEDIA_SERVER_SIGNATURE;
50 static const char* kMediaServerAddOnSig = "application/x-vnd.Be.addon-host";
51 
52 MainApp::MainApp()
53 	:
54 	BApplication(kAppSig),
55 	fPlayerCount(0),
56 	fSettingsWindow(NULL),
57 
58 	fOpenFilePanel(NULL),
59 	fSaveFilePanel(NULL),
60 	fLastFilePanelFolder(),
61 
62 	fMediaServerRunning(false),
63 	fMediaAddOnServerRunning(false),
64 
65 	fAudioWindowFrameSaved(false),
66 	fLastSavedAudioWindowCreationTime(0)
67 {
68 	mpSettings settings = Settings::CurrentSettings();
69 	fLastFilePanelFolder = settings.filePanelFolder;
70 
71 	// Now tell the application roster, that we're interested
72 	// in getting notifications of apps being launched or quit.
73 	// In this way we are going to detect a media_server restart.
74 	be_roster->StartWatching(BMessenger(this, this),
75 		B_REQUEST_LAUNCHED | B_REQUEST_QUIT);
76 	// we will keep track of the status of media_server
77 	// and media_addon_server
78 	fMediaServerRunning = be_roster->IsRunning(kMediaServerSig);
79 	fMediaAddOnServerRunning = be_roster->IsRunning(kMediaServerAddOnSig);
80 
81 	if (!fMediaServerRunning || !fMediaAddOnServerRunning) {
82 		BAlert* alert = new BAlert("start_media_server",
83 			"It appears the media server is not running.\n"
84 			"Would you like to start it ?", "Quit", "Start media server", NULL,
85 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
86 		if (alert->Go() == 0) {
87 			PostMessage(B_QUIT_REQUESTED);
88 			return;
89 		}
90 
91 		launch_media_server();
92 
93 		fMediaServerRunning = be_roster->IsRunning(kMediaServerSig);
94 		fMediaAddOnServerRunning = be_roster->IsRunning(kMediaServerAddOnSig);
95 	}
96 }
97 
98 
99 MainApp::~MainApp()
100 {
101 	delete fOpenFilePanel;
102 	delete fSaveFilePanel;
103 }
104 
105 
106 bool
107 MainApp::QuitRequested()
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 	mpSettings settings = Settings::CurrentSettings();
118 	settings.filePanelFolder = fLastFilePanelFolder;
119 	Settings::Default()->SaveSettings(settings);
120 
121 	return BApplication::QuitRequested();
122 }
123 
124 
125 MainWin*
126 MainApp::NewWindow(BMessage* message)
127 {
128 	BAutolock _(this);
129 	fPlayerCount++;
130 	return new(std::nothrow) MainWin(fPlayerCount == 1, message);
131 }
132 
133 
134 int32
135 MainApp::PlayerCount() const
136 {
137 	BAutolock _(const_cast<MainApp*>(this));
138 	return fPlayerCount;
139 }
140 
141 
142 // #pragma mark -
143 
144 
145 void
146 MainApp::ReadyToRun()
147 {
148 	// make sure we have at least one window open
149 	if (fPlayerCount == 0) {
150 		MainWin* window = NewWindow();
151 		if (window == NULL) {
152 			PostMessage(B_QUIT_REQUESTED);
153 			return;
154 		}
155 		BMessage lastPlaylistArchive;
156 		if (_RestoreCurrentPlaylist(&lastPlaylistArchive) == B_OK) {
157 			lastPlaylistArchive.what = M_OPEN_PREVIOUS_PLAYLIST;
158 			window->PostMessage(&lastPlaylistArchive);
159 		} else
160 			window->Show();
161 	}
162 
163 	// setup the settings window now, we need to have it
164 	fSettingsWindow = new SettingsWindow(BRect(150, 150, 450, 520));
165 	fSettingsWindow->Hide();
166 	fSettingsWindow->Show();
167 
168 	_InstallPlaylistMimeType();
169 }
170 
171 
172 void
173 MainApp::RefsReceived(BMessage* message)
174 {
175 	// The user dropped a file (or files) on this app's icon,
176 	// or double clicked a file that's handled by this app.
177 	// Command line arguments are also redirected to here by
178 	// ArgvReceived() but without MIME type check.
179 	// For each file we create a new window and send it a
180 	// B_REFS_RECEIVED message with a single file.
181 	NewWindow(message);
182 }
183 
184 
185 void
186 MainApp::ArgvReceived(int32 argc, char** argv)
187 {
188 	char cwd[B_PATH_NAME_LENGTH];
189 	getcwd(cwd, sizeof(cwd));
190 
191 	BMessage message(B_REFS_RECEIVED);
192 
193 	for (int i = 1; i < argc; i++) {
194 		printf("MainApp::ArgvReceived %s\n", argv[i]);
195 		BPath path;
196 		if (argv[i][0] != '/')
197 			path.SetTo(cwd, argv[i]);
198 		else
199 			path.SetTo(argv[i]);
200 		BEntry entry(path.Path(), true);
201 		if (!entry.Exists() || !entry.IsFile())
202 			continue;
203 
204 		entry_ref ref;
205 		if (entry.GetRef(&ref) == B_OK)
206 			message.AddRef("refs", &ref);
207 	}
208 
209 	if (message.HasRef("refs")) {
210 		printf("MainApp::ArgvReceived calling RefsReceived\n");
211 		RefsReceived(&message);
212 	}
213 }
214 
215 
216 void
217 MainApp::MessageReceived(BMessage* message)
218 {
219 	switch (message->what) {
220 		case M_PLAYER_QUIT:
221 		{
222 			// store the window settings of this instance
223 			MainWin* window = NULL;
224 			bool audioOnly = false;
225 			BRect windowFrame;
226 			bigtime_t creationTime;
227 			if (message->FindPointer("instance", (void**)&window) == B_OK
228 				&& message->FindBool("audio only", &audioOnly) == B_OK
229 				&& message->FindRect("window frame", &windowFrame) == B_OK
230 				&& message->FindInt64("creation time", &creationTime) == B_OK) {
231 				if (audioOnly) {
232 					if (!fAudioWindowFrameSaved
233 						|| creationTime < fLastSavedAudioWindowCreationTime) {
234 						fAudioWindowFrameSaved = true;
235 						fLastSavedAudioWindowCreationTime = creationTime;
236 						mpSettings settings
237 							= Settings::Default()->CurrentSettings();
238 						settings.audioPlayerWindowFrame = windowFrame;
239 						Settings::Default()->SaveSettings(settings);
240 					}
241 				}
242 			}
243 
244 			// Store the playlist if there is one. Since the app is doing
245 			// this, it is "atomic". If the user has multiple instances
246 			// playing audio at the same time, the last instance which is
247 			// quit wins.
248 			BMessage playlistArchive;
249 			if (message->FindMessage("playlist", &playlistArchive) == B_OK)
250 				_StoreCurrentPlaylist(&playlistArchive);
251 
252 			// quit if this was the last player window
253 			fPlayerCount--;
254 			if (fPlayerCount == 0)
255 				PostMessage(B_QUIT_REQUESTED);
256 			break;
257 		}
258 
259 		case B_SOME_APP_LAUNCHED:
260 		case B_SOME_APP_QUIT:
261 		{
262 			const char* mimeSig;
263 			if (message->FindString("be:signature", &mimeSig) < B_OK)
264 				break;
265 
266 			bool isMediaServer = strcmp(mimeSig, kMediaServerSig) == 0;
267 			bool isAddonServer = strcmp(mimeSig, kMediaServerAddOnSig) == 0;
268 			if (!isMediaServer && !isAddonServer)
269 				break;
270 
271 			bool running = (message->what == B_SOME_APP_LAUNCHED);
272 			if (isMediaServer)
273 				fMediaServerRunning = running;
274 			if (isAddonServer)
275 				fMediaAddOnServerRunning = running;
276 
277 			if (!fMediaServerRunning && !fMediaAddOnServerRunning) {
278 				fprintf(stderr, "media server has quit.\n");
279 				// trigger closing of media nodes
280 				BMessage broadcast(M_MEDIA_SERVER_QUIT);
281 				_BroadcastMessage(broadcast);
282 			} else if (fMediaServerRunning && fMediaAddOnServerRunning) {
283 				fprintf(stderr, "media server has launched.\n");
284 				// HACK!
285 				// quit our now invalid instance of the media roster
286 				// so that before new nodes are created,
287 				// we get a new roster (it is a normal looper)
288 				// TODO: This functionality could become part of
289 				// BMediaRoster. It could detect the start/quit of
290 				// the servers like it is done here, and either quit
291 				// itself, or re-establish the connection, and send some
292 				// notification to the app... something along those lines.
293 				BMediaRoster* roster = BMediaRoster::CurrentRoster();
294 				if (roster) {
295 					roster->Lock();
296 					roster->Quit();
297 				}
298 				// give the servers some time to init...
299 				snooze(3000000);
300 				// trigger re-init of media nodes
301 				BMessage broadcast(M_MEDIA_SERVER_STARTED);
302 				_BroadcastMessage(broadcast);
303 			}
304 			break;
305 		}
306 		case M_SETTINGS:
307 			_ShowSettingsWindow();
308 			break;
309 
310 		case M_SHOW_OPEN_PANEL:
311 			_ShowOpenFilePanel(message);
312 			break;
313 		case M_SHOW_SAVE_PANEL:
314 			_ShowSaveFilePanel(message);
315 			break;
316 
317 		case M_OPEN_PANEL_RESULT:
318 			_HandleOpenPanelResult(message);
319 			break;
320 		case M_SAVE_PANEL_RESULT:
321 			_HandleSavePanelResult(message);
322 			break;
323 		case B_CANCEL:
324 		{
325 			// The user canceled a file panel, but store at least the current
326 			// file panel folder.
327 			uint32 oldWhat;
328 			if (message->FindInt32("old_what", (int32*)&oldWhat) != B_OK)
329 				break;
330 			if (oldWhat == M_OPEN_PANEL_RESULT && fOpenFilePanel != NULL)
331 				fOpenFilePanel->GetPanelDirectory(&fLastFilePanelFolder);
332 			else if (oldWhat == M_SAVE_PANEL_RESULT && fSaveFilePanel != NULL)
333 				fSaveFilePanel->GetPanelDirectory(&fLastFilePanelFolder);
334 			break;
335 		}
336 
337 		default:
338 			BApplication::MessageReceived(message);
339 			break;
340 	}
341 }
342 
343 
344 void
345 MainApp::AboutRequested()
346 {
347 	BAlert* alert = new BAlert("about", NAME"\n\nWritten by Marcus Overhagen "
348 		", Stephan Aßmus and Frederik Modéen", "Thanks");
349 	alert->SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
350 		// Make sure it is on top of any player windows that may have the
351 		// floating all window feel.
352 	alert->Go(NULL);
353 		// asynchronous mode
354 }
355 
356 
357 // #pragma mark -
358 
359 
360 void
361 MainApp::_BroadcastMessage(const BMessage& _message)
362 {
363 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
364 		BMessage message(_message);
365 		window->PostMessage(&message);
366 	}
367 }
368 
369 
370 void
371 MainApp::_ShowSettingsWindow()
372 {
373 	BAutolock lock(fSettingsWindow);
374 	if (!lock.IsLocked())
375 		return;
376 
377 	// If the window is already showing, don't jerk the workspaces around,
378 	// just pull it to the current one.
379 	uint32 workspace = 1UL << (uint32)current_workspace();
380 	uint32 windowWorkspaces = fSettingsWindow->Workspaces();
381 	if ((windowWorkspaces & workspace) == 0) {
382 		// window in a different workspace, reopen in current
383 		fSettingsWindow->SetWorkspaces(workspace);
384 	}
385 
386 	if (fSettingsWindow->IsHidden())
387 		fSettingsWindow->Show();
388 	else
389 		fSettingsWindow->Activate();
390 }
391 
392 
393 // #pragma mark - file panels
394 
395 
396 void
397 MainApp::_ShowOpenFilePanel(const BMessage* message)
398 {
399 	if (fOpenFilePanel == NULL) {
400 		BMessenger target(this);
401 		fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target);
402 	}
403 
404 	_ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message,
405 		"Open", "Open");
406 }
407 
408 
409 void
410 MainApp::_ShowSaveFilePanel(const BMessage* message)
411 {
412 	if (fSaveFilePanel == NULL) {
413 		BMessenger target(this);
414 		fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target);
415 	}
416 
417 	_ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message,
418 		"Save", "Save");
419 }
420 
421 
422 void
423 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command,
424 	const BMessage* message, const char* defaultTitle,
425 	const char* defaultLabel)
426 {
427 //	printf("_ShowFilePanel()\n");
428 //	message->PrintToStream();
429 
430 	BMessage panelMessage(command);
431 
432 	if (message != NULL) {
433 		BMessage targetMessage;
434 		if (message->FindMessage("message", &targetMessage) == B_OK)
435 			panelMessage.AddMessage("message", &targetMessage);
436 
437 		BMessenger target;
438 		if (message->FindMessenger("target", &target) == B_OK)
439 			panelMessage.AddMessenger("target", target);
440 
441 		const char* panelTitle;
442 		if (message->FindString("title", &panelTitle) != B_OK)
443 			panelTitle = defaultTitle;
444 		{
445 			BString finalPanelTitle = "MediaPlayer: ";
446 			finalPanelTitle << panelTitle;
447 			BAutolock lock(panel->Window());
448 			panel->Window()->SetTitle(finalPanelTitle.String());
449 		}
450 		const char* buttonLabel;
451 		if (message->FindString("label", &buttonLabel) != B_OK)
452 			buttonLabel = defaultLabel;
453 		panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel);
454 	}
455 
456 //	panelMessage.PrintToStream();
457 	panel->SetMessage(&panelMessage);
458 
459 	if (fLastFilePanelFolder != entry_ref()) {
460 		panel->SetPanelDirectory(&fLastFilePanelFolder);
461 	}
462 
463 	panel->Show();
464 }
465 
466 
467 void
468 MainApp::_HandleOpenPanelResult(const BMessage* message)
469 {
470 	_HandleFilePanelResult(fOpenFilePanel, message);
471 }
472 
473 
474 void
475 MainApp::_HandleSavePanelResult(const BMessage* message)
476 {
477 	_HandleFilePanelResult(fSaveFilePanel, message);
478 }
479 
480 
481 void
482 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message)
483 {
484 //	printf("_HandleFilePanelResult()\n");
485 //	message->PrintToStream();
486 
487 	panel->GetPanelDirectory(&fLastFilePanelFolder);
488 
489 	BMessage targetMessage;
490 	if (message->FindMessage("message", &targetMessage) != B_OK)
491 		targetMessage.what = message->what;
492 
493 	BMessenger target;
494 	if (message->FindMessenger("target", &target) != B_OK) {
495 		if (targetMessage.what == M_OPEN_PANEL_RESULT
496 			|| targetMessage.what == M_SAVE_PANEL_RESULT) {
497 			// prevent endless message cycle
498 			return;
499 		}
500 		// send result message to ourselves
501 		target = BMessenger(this);
502 	}
503 
504 	// copy the important contents of the message
505 	// save panel
506 	entry_ref directory;
507 	if (message->FindRef("directory", &directory) == B_OK)
508 		targetMessage.AddRef("directory", &directory);
509 	const char* name;
510 	if (message->FindString("name", &name) == B_OK)
511 		targetMessage.AddString("name", name);
512 	// open panel
513 	entry_ref ref;
514 	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++)
515 		targetMessage.AddRef("refs", &ref);
516 
517 	target.SendMessage(&targetMessage);
518 }
519 
520 
521 static const char* kCurrentPlaylistFilename = "MediaPlayer Current Playlist";
522 
523 
524 void
525 MainApp::_StoreCurrentPlaylist(const BMessage* message) const
526 {
527 	BPath path;
528 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
529 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
530 		return;
531 	}
532 
533 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
534 	if (file.InitCheck() != B_OK)
535 		return;
536 
537 	message->Flatten(&file);
538 }
539 
540 
541 status_t
542 MainApp::_RestoreCurrentPlaylist(BMessage* message) const
543 {
544 	BPath path;
545 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
546 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
547 		return B_ERROR;
548 	}
549 
550 	BFile file(path.Path(), B_READ_ONLY);
551 	if (file.InitCheck() != B_OK)
552 		return B_ERROR;
553 
554 	return message->Unflatten(&file);
555 }
556 
557 void
558 MainApp::_InstallPlaylistMimeType()
559 {
560 	// install mime type of documents
561 	BMimeType mime(kBinaryPlaylistMimeString);
562 	status_t ret = mime.InitCheck();
563 	if (ret != B_OK) {
564 		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
565 			kBinaryPlaylistMimeString, strerror(ret));
566 		return;
567 	}
568 
569 	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
570 		// mime is already installed, and the user is not
571 		// pressing the shift key to force a re-install
572 		return;
573 	}
574 
575 	ret = mime.Install();
576 	if (ret != B_OK && ret != B_FILE_EXISTS) {
577 		fprintf(stderr, "Could not install native document mime type (%s): %s.\n",
578 			kBinaryPlaylistMimeString, strerror(ret));
579 		return;
580 	}
581 	// set preferred app
582 	ret = mime.SetPreferredApp(kAppSig);
583 	if (ret != B_OK) {
584 		fprintf(stderr, "Could not set native document preferred app: %s\n",
585 			strerror(ret));
586 	}
587 
588 	// set descriptions
589 	ret = mime.SetShortDescription("MediaPlayer playlist");
590 	if (ret != B_OK) {
591 		fprintf(stderr, "Could not set short description of mime type: %s\n",
592 			strerror(ret));
593 	}
594 	ret = mime.SetLongDescription("MediaPlayer binary playlist file");
595 	if (ret != B_OK) {
596 		fprintf(stderr, "Could not set long description of mime type: %s\n",
597 			strerror(ret));
598 	}
599 
600 	// set extensions
601 	BMessage message('extn');
602 	message.AddString("extensions", "playlist");
603 	ret = mime.SetFileExtensions(&message);
604 	if (ret != B_OK) {
605 		fprintf(stderr, "Could not set extensions of mime type: %s\n",
606 			strerror(ret));
607 	}
608 
609 	// set sniffer rule
610 	char snifferRule[32];
611 	uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes);
612 	sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic);
613 	ret = mime.SetSnifferRule(snifferRule);
614 	if (ret != B_OK) {
615 		BString parseError;
616 		BMimeType::CheckSnifferRule(snifferRule, &parseError);
617 		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
618 			parseError.String());
619 	}
620 
621 	// set playlist icon
622 	BResources* resources = AppResources();
623 		// does not need to be freed (belongs to BApplication base)
624 	if (resources != NULL) {
625 		size_t size;
626 		const void* iconData = resources->LoadResource('VICN', "PlaylistIcon",
627 			&size);
628 		if (iconData != NULL && size > 0) {
629 			if (mime.SetIcon(reinterpret_cast<const uint8*>(iconData), size)
630 				!= B_OK) {
631 				fprintf(stderr, "Could not set vector icon of mime type.\n");
632 			}
633 		} else {
634 			fprintf(stderr, "Could not find icon in app resources "
635 				"(data: %p, size: %ld).\n", iconData, size);
636 		}
637 	} else
638 		fprintf(stderr, "Could not find app resources.\n");
639 }
640 
641 
642 // #pragma mark - main
643 
644 
645 int
646 main()
647 {
648 	EventQueue::CreateDefault();
649 
650 	srand(system_time());
651 
652 	gMainApp = new MainApp;
653 	gMainApp->Run();
654 	delete gMainApp;
655 
656 	EventQueue::DeleteDefault();
657 
658 	return 0;
659 }
660