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