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