xref: /haiku/src/apps/mediaplayer/MainApp.cpp (revision ac078a5b110045de12089052a685a6a080545db1)
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 // #pragma mark -
411 
412 
413 void
414 MainApp::_BroadcastMessage(const BMessage& _message)
415 {
416 	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
417 		BMessage message(_message);
418 		window->PostMessage(&message);
419 	}
420 }
421 
422 
423 void
424 MainApp::_ShowSettingsWindow()
425 {
426 	BAutolock lock(fSettingsWindow);
427 	if (!lock.IsLocked())
428 		return;
429 
430 	// If the window is already showing, don't jerk the workspaces around,
431 	// just pull it to the current one.
432 	uint32 workspace = 1UL << (uint32)current_workspace();
433 	uint32 windowWorkspaces = fSettingsWindow->Workspaces();
434 	if ((windowWorkspaces & workspace) == 0) {
435 		// window in a different workspace, reopen in current
436 		fSettingsWindow->SetWorkspaces(workspace);
437 	}
438 
439 	if (fSettingsWindow->IsHidden())
440 		fSettingsWindow->Show();
441 	else
442 		fSettingsWindow->Activate();
443 }
444 
445 
446 // #pragma mark - file panels
447 
448 
449 void
450 MainApp::_ShowOpenFilePanel(const BMessage* message)
451 {
452 	if (fOpenFilePanel == NULL) {
453 		BMessenger target(this);
454 		fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, &target);
455 	}
456 
457 	_ShowFilePanel(fOpenFilePanel, M_OPEN_PANEL_RESULT, message,
458 		B_TRANSLATE("Open"), B_TRANSLATE("Open"));
459 }
460 
461 
462 void
463 MainApp::_ShowSaveFilePanel(const BMessage* message)
464 {
465 	if (fSaveFilePanel == NULL) {
466 		BMessenger target(this);
467 		fSaveFilePanel = new BFilePanel(B_SAVE_PANEL, &target);
468 	}
469 
470 	_ShowFilePanel(fSaveFilePanel, M_SAVE_PANEL_RESULT, message,
471 		B_TRANSLATE("Save"), B_TRANSLATE("Save"));
472 }
473 
474 
475 void
476 MainApp::_ShowFilePanel(BFilePanel* panel, uint32 command,
477 	const BMessage* message, const char* defaultTitle,
478 	const char* defaultLabel)
479 {
480 //	printf("_ShowFilePanel()\n");
481 //	message->PrintToStream();
482 
483 	BMessage panelMessage(command);
484 
485 	if (message != NULL) {
486 		BMessage targetMessage;
487 		if (message->FindMessage("message", &targetMessage) == B_OK)
488 			panelMessage.AddMessage("message", &targetMessage);
489 
490 		BMessenger target;
491 		if (message->FindMessenger("target", &target) == B_OK)
492 			panelMessage.AddMessenger("target", target);
493 
494 		const char* panelTitle;
495 		if (message->FindString("title", &panelTitle) != B_OK)
496 			panelTitle = defaultTitle;
497 		{
498 			BString finalPanelTitle = "MediaPlayer: ";
499 			finalPanelTitle << panelTitle;
500 			BAutolock lock(panel->Window());
501 			panel->Window()->SetTitle(finalPanelTitle.String());
502 		}
503 		const char* buttonLabel;
504 		if (message->FindString("label", &buttonLabel) != B_OK)
505 			buttonLabel = defaultLabel;
506 		panel->SetButtonLabel(B_DEFAULT_BUTTON, buttonLabel);
507 	}
508 
509 //	panelMessage.PrintToStream();
510 	panel->SetMessage(&panelMessage);
511 
512 	if (fLastFilePanelFolder != entry_ref()) {
513 		panel->SetPanelDirectory(&fLastFilePanelFolder);
514 	}
515 
516 	panel->Show();
517 }
518 
519 
520 void
521 MainApp::_HandleOpenPanelResult(const BMessage* message)
522 {
523 	_HandleFilePanelResult(fOpenFilePanel, message);
524 }
525 
526 
527 void
528 MainApp::_HandleSavePanelResult(const BMessage* message)
529 {
530 	_HandleFilePanelResult(fSaveFilePanel, message);
531 }
532 
533 
534 void
535 MainApp::_HandleFilePanelResult(BFilePanel* panel, const BMessage* message)
536 {
537 //	printf("_HandleFilePanelResult()\n");
538 //	message->PrintToStream();
539 
540 	panel->GetPanelDirectory(&fLastFilePanelFolder);
541 
542 	BMessage targetMessage;
543 	if (message->FindMessage("message", &targetMessage) != B_OK)
544 		targetMessage.what = message->what;
545 
546 	BMessenger target;
547 	if (message->FindMessenger("target", &target) != B_OK) {
548 		if (targetMessage.what == M_OPEN_PANEL_RESULT
549 			|| targetMessage.what == M_SAVE_PANEL_RESULT) {
550 			// prevent endless message cycle
551 			return;
552 		}
553 		// send result message to ourselves
554 		target = BMessenger(this);
555 	}
556 
557 	// copy the important contents of the message
558 	// save panel
559 	entry_ref directory;
560 	if (message->FindRef("directory", &directory) == B_OK)
561 		targetMessage.AddRef("directory", &directory);
562 	const char* name;
563 	if (message->FindString("name", &name) == B_OK)
564 		targetMessage.AddString("name", name);
565 	// open panel
566 	entry_ref ref;
567 	for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++)
568 		targetMessage.AddRef("refs", &ref);
569 
570 	target.SendMessage(&targetMessage);
571 }
572 
573 
574 void
575 MainApp::_StoreCurrentPlaylist(const BMessage* message) const
576 {
577 	BPath path;
578 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
579 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
580 		return;
581 	}
582 
583 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
584 	if (file.InitCheck() != B_OK)
585 		return;
586 
587 	message->Flatten(&file);
588 }
589 
590 
591 status_t
592 MainApp::_RestoreCurrentPlaylist(BMessage* message) const
593 {
594 	BPath path;
595 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
596 		|| path.Append(kCurrentPlaylistFilename) != B_OK) {
597 		return B_ERROR;
598 	}
599 
600 	BFile file(path.Path(), B_READ_ONLY);
601 	if (file.InitCheck() != B_OK)
602 		return B_ERROR;
603 
604 	return message->Unflatten(&file);
605 }
606 
607 
608 void
609 MainApp::_InstallPlaylistMimeType()
610 {
611 	// install mime type of documents
612 	BMimeType mime(kBinaryPlaylistMimeString);
613 	status_t ret = mime.InitCheck();
614 	if (ret != B_OK) {
615 		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
616 			kBinaryPlaylistMimeString, strerror(ret));
617 		return;
618 	}
619 
620 	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
621 		// mime is already installed, and the user is not
622 		// pressing the shift key to force a re-install
623 		return;
624 	}
625 
626 	ret = mime.Install();
627 	if (ret != B_OK && ret != B_FILE_EXISTS) {
628 		fprintf(stderr, "Could not install native document mime type (%s): %s.\n",
629 			kBinaryPlaylistMimeString, strerror(ret));
630 		return;
631 	}
632 	// set preferred app
633 	ret = mime.SetPreferredApp(kAppSig);
634 	if (ret != B_OK) {
635 		fprintf(stderr, "Could not set native document preferred app: %s\n",
636 			strerror(ret));
637 	}
638 
639 	// set descriptions
640 	ret = mime.SetShortDescription("MediaPlayer playlist");
641 	if (ret != B_OK) {
642 		fprintf(stderr, "Could not set short description of mime type: %s\n",
643 			strerror(ret));
644 	}
645 	ret = mime.SetLongDescription("MediaPlayer binary playlist file");
646 	if (ret != B_OK) {
647 		fprintf(stderr, "Could not set long description of mime type: %s\n",
648 			strerror(ret));
649 	}
650 
651 	// set extensions
652 	BMessage message('extn');
653 	message.AddString("extensions", "playlist");
654 	ret = mime.SetFileExtensions(&message);
655 	if (ret != B_OK) {
656 		fprintf(stderr, "Could not set extensions of mime type: %s\n",
657 			strerror(ret));
658 	}
659 
660 	// set sniffer rule
661 	char snifferRule[32];
662 	uint32 bigEndianMagic = B_HOST_TO_BENDIAN_INT32(kPlaylistMagicBytes);
663 	sprintf(snifferRule, "0.9 ('%4s')", (const char*)&bigEndianMagic);
664 	ret = mime.SetSnifferRule(snifferRule);
665 	if (ret != B_OK) {
666 		BString parseError;
667 		BMimeType::CheckSnifferRule(snifferRule, &parseError);
668 		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
669 			parseError.String());
670 	}
671 
672 	// set playlist icon
673 	BResources* resources = AppResources();
674 		// does not need to be freed (belongs to BApplication base)
675 	if (resources != NULL) {
676 		size_t size;
677 		const void* iconData = resources->LoadResource('VICN', "PlaylistIcon",
678 			&size);
679 		if (iconData != NULL && size > 0) {
680 			if (mime.SetIcon(reinterpret_cast<const uint8*>(iconData), size)
681 				!= B_OK) {
682 				fprintf(stderr, "Could not set vector icon of mime type.\n");
683 			}
684 		} else {
685 			fprintf(stderr, "Could not find icon in app resources "
686 				"(data: %p, size: %ld).\n", iconData, size);
687 		}
688 	} else
689 		fprintf(stderr, "Could not find app resources.\n");
690 }
691 
692 
693 // #pragma mark - main
694 
695 
696 int
697 main()
698 {
699 	EventQueue::CreateDefault();
700 
701 	srand(system_time());
702 
703 	gMainApp = new MainApp;
704 	gMainApp->Run();
705 	delete gMainApp;
706 
707 	EventQueue::DeleteDefault();
708 
709 	return 0;
710 }
711