xref: /haiku/src/kits/app/Application.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
1 /*
2  * Copyright 2001-2008, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Erik Jaesler (erik@cgsoftware.com)
7  * 		Jerome Duval
8  *		Axel Dörfler, axeld@pinc-software.de
9  */
10 
11 
12 #include <Application.h>
13 
14 #include <new>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19 
20 #include <Alert.h>
21 #include <AppFileInfo.h>
22 #include <Cursor.h>
23 #include <Debug.h>
24 #include <Entry.h>
25 #include <File.h>
26 #include <Locker.h>
27 #include <MessageRunner.h>
28 #include <Path.h>
29 #include <PropertyInfo.h>
30 #include <RegistrarDefs.h>
31 #include <Resources.h>
32 #include <Roster.h>
33 #include <Window.h>
34 
35 #include <AppMisc.h>
36 #include <AppServerLink.h>
37 #include <AutoLocker.h>
38 #include <DraggerPrivate.h>
39 #include <LooperList.h>
40 #include <MenuWindow.h>
41 #include <PortLink.h>
42 #include <RosterPrivate.h>
43 #include <ServerMemoryAllocator.h>
44 #include <ServerProtocol.h>
45 
46 using namespace BPrivate;
47 
48 
49 BApplication *be_app = NULL;
50 BMessenger be_app_messenger;
51 
52 BResources *BApplication::sAppResources = NULL;
53 BLocker BApplication::sAppResourcesLock("_app_resources_lock");
54 
55 
56 enum {
57 	kWindowByIndex,
58 	kWindowByName,
59 	kLooperByIndex,
60 	kLooperByID,
61 	kLooperByName,
62 	kApplication
63 };
64 
65 static property_info sPropertyInfo[] = {
66 	{
67 		"Window",
68 		{},
69 		{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
70 		NULL, kWindowByIndex,
71 		{},
72 		{},
73 		{}
74 	},
75 	{
76 		"Window",
77 		{},
78 		{B_NAME_SPECIFIER},
79 		NULL, kWindowByName,
80 		{},
81 		{},
82 		{}
83 	},
84 	{
85 		"Looper",
86 		{},
87 		{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
88 		NULL, kLooperByIndex,
89 		{},
90 		{},
91 		{}
92 	},
93 	{
94 		"Looper",
95 		{},
96 		{B_ID_SPECIFIER},
97 		NULL, kLooperByID,
98 		{},
99 		{},
100 		{}
101 	},
102 	{
103 		"Looper",
104 		{},
105 		{B_NAME_SPECIFIER},
106 		NULL, kLooperByName,
107 		{},
108 		{},
109 		{}
110 	},
111 	{
112 		"Name",
113 		{B_GET_PROPERTY},
114 		{B_DIRECT_SPECIFIER},
115 		NULL, kApplication,
116 		{B_STRING_TYPE},
117 		{},
118 		{}
119 	},
120 	{
121 		"Window",
122 		{B_COUNT_PROPERTIES},
123 		{B_DIRECT_SPECIFIER},
124 		NULL, kApplication,
125 		{B_INT32_TYPE},
126 		{},
127 		{}
128 	},
129 	{
130 		"Loopers",
131 		{B_GET_PROPERTY},
132 		{B_DIRECT_SPECIFIER},
133 		NULL, kApplication,
134 		{B_MESSENGER_TYPE},
135 		{},
136 		{}
137 	},
138 	{
139 		"Windows",
140 		{B_GET_PROPERTY},
141 		{B_DIRECT_SPECIFIER},
142 		NULL, kApplication,
143 		{B_MESSENGER_TYPE},
144 		{},
145 		{}
146 	},
147 	{
148 		"Looper",
149 		{B_COUNT_PROPERTIES},
150 		{B_DIRECT_SPECIFIER},
151 		NULL, kApplication,
152 		{B_INT32_TYPE},
153 		{},
154 		{}
155 	},
156 	{}
157 };
158 
159 // argc/argv
160 extern const int __libc_argc;
161 extern const char * const *__libc_argv;
162 
163 
164 // debugging
165 //#define DBG(x) x
166 #define DBG(x)
167 #define OUT	printf
168 
169 
170 // prototypes of helper functions
171 static const char* looper_name_for(const char *signature);
172 static status_t check_app_signature(const char *signature);
173 static void fill_argv_message(BMessage &message);
174 
175 
176 BApplication::BApplication(const char *signature)
177 	: BLooper(looper_name_for(signature))
178 {
179 	_InitData(signature, true, NULL);
180 }
181 
182 
183 BApplication::BApplication(const char *signature, status_t *_error)
184 	: BLooper(looper_name_for(signature))
185 {
186 	_InitData(signature, true, _error);
187 }
188 
189 
190 BApplication::BApplication(const char *signature, bool initGUI,
191 		status_t *_error)
192 	: BLooper(looper_name_for(signature))
193 {
194 	_InitData(signature, initGUI, _error);
195 }
196 
197 
198 BApplication::BApplication(BMessage *data)
199 	// Note: BeOS calls the private BLooper(int32, port_id, const char *)
200 	// constructor here, test if it's needed
201 	: BLooper(looper_name_for(NULL))
202 {
203 	const char *signature = NULL;
204 	data->FindString("mime_sig", &signature);
205 
206 	_InitData(signature, true, NULL);
207 
208 	bigtime_t pulseRate;
209 	if (data->FindInt64("_pulse", &pulseRate) == B_OK)
210 		SetPulseRate(pulseRate);
211 }
212 
213 
214 BApplication::BApplication(uint32 signature)
215 {
216 }
217 
218 
219 BApplication::BApplication(const BApplication &rhs)
220 {
221 }
222 
223 
224 BApplication::~BApplication()
225 {
226 	Lock();
227 
228 	// tell all loopers(usually windows) to quit. Also, wait for them.
229 	_QuitAllWindows(true);
230 
231 	// unregister from the roster
232 	BRoster::Private().RemoveApp(Team());
233 
234 #ifndef RUN_WITHOUT_APP_SERVER
235 	// tell app_server we're quitting...
236 	if (be_app) {
237 		// be_app can be NULL here if the application fails to initialize
238 		// correctly. For example, if it's already running and it's set to
239 		// exclusive launch.
240 		BPrivate::AppServerLink link;
241 		link.StartMessage(B_QUIT_REQUESTED);
242 		link.Flush();
243 	}
244 	delete_port(fServerLink->SenderPort());
245 	delete_port(fServerLink->ReceiverPort());
246 	delete fServerLink;
247 #endif	// RUN_WITHOUT_APP_SERVER
248 
249 	delete fServerAllocator;
250 
251 	// uninitialize be_app, the be_app_messenger is invalidated automatically
252 	be_app = NULL;
253 }
254 
255 
256 BApplication &
257 BApplication::operator=(const BApplication &rhs)
258 {
259 	return *this;
260 }
261 
262 
263 void
264 BApplication::_InitData(const char *signature, bool initGUI, status_t *_error)
265 {
266 	DBG(OUT("BApplication::InitData(`%s', %p)\n", signature, _error));
267 	// check whether there exists already an application
268 	if (be_app)
269 		debugger("2 BApplication objects were created. Only one is allowed.");
270 
271 	fServerLink = new BPrivate::PortLink(-1, -1);
272 	fServerAllocator = NULL;
273 	fInitialWorkspace = 0;
274 	//fDraggedMessage = NULL;
275 	fReadyToRunCalled = false;
276 
277 	// initially, there is no pulse
278 	fPulseRunner = NULL;
279 	fPulseRate = 0;
280 
281 	// check signature
282 	fInitError = check_app_signature(signature);
283 	fAppName = signature;
284 
285 #ifndef RUN_WITHOUT_REGISTRAR
286 	bool isRegistrar = signature
287 		&& !strcasecmp(signature, kRegistrarSignature);
288 	// get team and thread
289 	team_id team = Team();
290 	thread_id thread = BPrivate::main_thread_for(team);
291 #endif
292 
293 	// get app executable ref
294 	entry_ref ref;
295 	if (fInitError == B_OK) {
296 		fInitError = BPrivate::get_app_ref(&ref);
297 		if (fInitError != B_OK) {
298 			DBG(OUT("BApplication::InitData(): Failed to get app ref: %s\n",
299 				strerror(fInitError)));
300 		}
301 	}
302 
303 	// get the BAppFileInfo and extract the information we need
304 	uint32 appFlags = B_REG_DEFAULT_APP_FLAGS;
305 	if (fInitError == B_OK) {
306 		BAppFileInfo fileInfo;
307 		BFile file(&ref, B_READ_ONLY);
308 		fInitError = fileInfo.SetTo(&file);
309 		if (fInitError == B_OK) {
310 			fileInfo.GetAppFlags(&appFlags);
311 			char appFileSignature[B_MIME_TYPE_LENGTH];
312 			// compare the file signature and the supplied signature
313 			if (fileInfo.GetSignature(appFileSignature) == B_OK
314 				&& strcasecmp(appFileSignature, signature) != 0) {
315 				printf("Signature in rsrc doesn't match constructor arg. (%s, %s)\n",
316 					signature, appFileSignature);
317 			}
318 		} else {
319 			DBG(OUT("BApplication::InitData(): Failed to get info from: "
320 				"BAppFileInfo: %s\n", strerror(fInitError)));
321 		}
322 	}
323 
324 #ifndef RUN_WITHOUT_REGISTRAR
325 	// check whether be_roster is valid
326 	if (fInitError == B_OK && !isRegistrar
327 		&& !BRoster::Private().IsMessengerValid(false)) {
328 		printf("FATAL: be_roster is not valid. Is the registrar running?\n");
329 		fInitError = B_NO_INIT;
330 	}
331 
332 	// check whether or not we are pre-registered
333 	bool preRegistered = false;
334 	app_info appInfo;
335 	if (fInitError == B_OK && !isRegistrar) {
336 		if (BRoster::Private().IsAppRegistered(&ref, team, 0, &preRegistered,
337 				&appInfo) != B_OK) {
338 			preRegistered = false;
339 		}
340 	}
341 	if (preRegistered) {
342 		// we are pre-registered => the app info has been filled in
343 		// Check whether we need to replace the looper port with a port
344 		// created by the roster.
345 		if (appInfo.port >= 0 && appInfo.port != fMsgPort) {
346 			delete_port(fMsgPort);
347 			fMsgPort = appInfo.port;
348 		} else
349 			appInfo.port = fMsgPort;
350 		// check the signature and correct it, if necessary, also the case
351 		if (strcmp(appInfo.signature, fAppName))
352 			BRoster::Private().SetSignature(team, fAppName);
353 		// complete the registration
354 		fInitError = BRoster::Private().CompleteRegistration(team, thread,
355 						appInfo.port);
356 	} else if (fInitError == B_OK) {
357 		// not pre-registered -- try to register the application
358 		team_id otherTeam = -1;
359 		// the registrar must not register
360 		if (!isRegistrar) {
361 			fInitError = BRoster::Private().AddApplication(signature, &ref,
362 				appFlags, team, thread, fMsgPort, true, NULL, &otherTeam);
363 			if (fInitError != B_OK) {
364 				DBG(OUT("BApplication::InitData(): Failed to add app: %s\n",
365 					strerror(fInitError)));
366 			}
367 		}
368 		if (fInitError == B_ALREADY_RUNNING) {
369 			// An instance is already running and we asked for
370 			// single/exclusive launch. Send our argv to the running app.
371 			// Do that only, if the app is NOT B_ARGV_ONLY.
372 			if (otherTeam >= 0) {
373 				BMessenger otherApp(NULL, otherTeam);
374 				app_info otherAppInfo;
375 				if (__libc_argc > 1
376 					&& be_roster->GetRunningAppInfo(otherTeam, &otherAppInfo) == B_OK
377 					&& !(otherAppInfo.flags & B_ARGV_ONLY)) {
378 					// create an B_ARGV_RECEIVED message
379 					BMessage argvMessage(B_ARGV_RECEIVED);
380 					fill_argv_message(argvMessage);
381 
382 					// replace the first argv string with the path of the
383 					// other application
384 					BPath path;
385 					if (path.SetTo(&otherAppInfo.ref) == B_OK)
386 						argvMessage.ReplaceString("argv", 0, path.Path());
387 
388 					// send the message
389 					otherApp.SendMessage(&argvMessage);
390 				} else
391 					otherApp.SendMessage(B_SILENT_RELAUNCH);
392 			}
393 		} else if (fInitError == B_OK) {
394 			// the registrations was successful
395 			// Create a B_ARGV_RECEIVED message and send it to ourselves.
396 			// Do that even, if we are B_ARGV_ONLY.
397 			// TODO: When BLooper::AddMessage() is done, use that instead of
398 			// PostMessage().
399 
400 			DBG(OUT("info: BApplication sucessfully registered.\n"));
401 
402 			if (__libc_argc > 1) {
403 				BMessage argvMessage(B_ARGV_RECEIVED);
404 				fill_argv_message(argvMessage);
405 				PostMessage(&argvMessage, this);
406 			}
407 			// send a B_READY_TO_RUN message as well
408 			PostMessage(B_READY_TO_RUN, this);
409 		} else if (fInitError > B_ERRORS_END) {
410 			// Registrar internal errors shouldn't fall into the user's hands.
411 			fInitError = B_ERROR;
412 		}
413 	}
414 #else
415 	// We need to have ReadyToRun called even when we're not using the registrar
416 	PostMessage(B_READY_TO_RUN, this);
417 #endif	// ifndef RUN_WITHOUT_REGISTRAR
418 
419 	if (fInitError == B_OK) {
420 		// TODO: Not completely sure about the order, but this should be close.
421 
422 		// init be_app and be_app_messenger
423 		be_app = this;
424 		be_app_messenger = BMessenger(NULL, this);
425 
426 		// set the BHandler's name
427 		SetName(ref.name);
428 
429 		// create meta MIME
430 		BPath path;
431 		if (path.SetTo(&ref) == B_OK)
432 			create_app_meta_mime(path.Path(), false, true, false);
433 
434 #ifndef RUN_WITHOUT_APP_SERVER
435 		// app server connection and IK initialization
436 		if (initGUI)
437 			fInitError = _InitGUIContext();
438 #endif	// RUN_WITHOUT_APP_SERVER
439 	}
440 
441 	// Return the error or exit, if there was an error and no error variable
442 	// has been supplied.
443 	if (_error) {
444 		*_error = fInitError;
445 	} else if (fInitError != B_OK) {
446 		DBG(OUT("BApplication::InitData() failed: %s\n", strerror(fInitError)));
447 		exit(0);
448 	}
449 DBG(OUT("BApplication::InitData() done\n"));
450 }
451 
452 
453 BArchivable *
454 BApplication::Instantiate(BMessage *data)
455 {
456 	if (validate_instantiation(data, "BApplication"))
457 		return new BApplication(data);
458 
459 	return NULL;
460 }
461 
462 
463 status_t
464 BApplication::Archive(BMessage *data, bool deep) const
465 {
466 	status_t status = BLooper::Archive(data, deep);
467 	if (status < B_OK)
468 		return status;
469 
470 	app_info info;
471 	status = GetAppInfo(&info);
472 	if (status < B_OK)
473 		return status;
474 
475 	status = data->AddString("mime_sig", info.signature);
476 	if (status < B_OK)
477 		return status;
478 
479 	return data->AddInt64("_pulse", fPulseRate);
480 }
481 
482 
483 status_t
484 BApplication::InitCheck() const
485 {
486 	return fInitError;
487 }
488 
489 
490 thread_id
491 BApplication::Run()
492 {
493 	if (fInitError != B_OK)
494 		return fInitError;
495 
496 	AssertLocked();
497 
498 	if (fRunCalled)
499 		debugger("BApplication::Run was already called. Can only be called once.");
500 
501 	fThread = find_thread(NULL);
502 	fRunCalled = true;
503 
504 	task_looper();
505 
506 	delete fPulseRunner;
507 	return fThread;
508 }
509 
510 
511 void
512 BApplication::Quit()
513 {
514 	bool unlock = false;
515 	if (!IsLocked()) {
516 		const char *name = Name();
517 		if (!name)
518 			name = "no-name";
519 		printf("ERROR - you must Lock the application object before calling "
520 			   "Quit(), team=%ld, looper=%s\n", Team(), name);
521 		unlock = true;
522 		if (!Lock())
523 			return;
524 	}
525 	// Delete the object, if not running only.
526 	if (!fRunCalled) {
527 		delete this;
528 	} else if (find_thread(NULL) != fThread) {
529 // ToDo: why shouldn't we set fTerminating to true directly in this case?
530 		// We are not the looper thread.
531 		// We push a _QUIT_ into the queue.
532 		// TODO: When BLooper::AddMessage() is done, use that instead of
533 		// PostMessage()??? This would overtake messages that are still at
534 		// the port.
535 		// NOTE: We must not unlock here -- otherwise we had to re-lock, which
536 		// may not work. This is bad, since, if the port is full, it
537 		// won't get emptier, as the looper thread needs to lock the object
538 		// before dispatching messages.
539 		while (PostMessage(_QUIT_, this) == B_WOULD_BLOCK)
540 			snooze(10000);
541 	} else {
542 		// We are the looper thread.
543 		// Just set fTerminating to true which makes us fall through the
544 		// message dispatching loop and return from Run().
545 		fTerminating = true;
546 	}
547 	// If we had to lock the object, unlock now.
548 	if (unlock)
549 		Unlock();
550 }
551 
552 
553 bool
554 BApplication::QuitRequested()
555 {
556 	return _QuitAllWindows(false);
557 }
558 
559 
560 void
561 BApplication::Pulse()
562 {
563 	// supposed to be implemented by subclasses
564 }
565 
566 
567 void
568 BApplication::ReadyToRun()
569 {
570 	// supposed to be implemented by subclasses
571 }
572 
573 
574 void
575 BApplication::MessageReceived(BMessage *message)
576 {
577 	switch (message->what) {
578 		case B_COUNT_PROPERTIES:
579 		case B_GET_PROPERTY:
580 		case B_SET_PROPERTY:
581 		{
582 			int32 index;
583 			BMessage specifier;
584 			int32 what;
585 			const char *property = NULL;
586 			if (message->GetCurrentSpecifier(&index, &specifier, &what, &property) < B_OK
587 				|| !ScriptReceived(message, index, &specifier, what, property))
588 				BLooper::MessageReceived(message);
589 			break;
590 		}
591 
592 		case B_SILENT_RELAUNCH:
593 			// Sent to a B_SINGLE_LAUNCH application when it's launched again
594 			// (see _InitData())
595 			be_roster->ActivateApp(Team());
596 			break;
597 
598 		default:
599 			BLooper::MessageReceived(message);
600 			break;
601 	}
602 }
603 
604 
605 void
606 BApplication::ArgvReceived(int32 argc, char **argv)
607 {
608 	// supposed to be implemented by subclasses
609 }
610 
611 
612 void
613 BApplication::AppActivated(bool active)
614 {
615 	// supposed to be implemented by subclasses
616 }
617 
618 
619 void
620 BApplication::RefsReceived(BMessage *message)
621 {
622 	// supposed to be implemented by subclasses
623 }
624 
625 
626 void
627 BApplication::AboutRequested()
628 {
629 	thread_info info;
630 	if (get_thread_info(Thread(), &info) == B_OK) {
631 		BAlert *alert = new BAlert("_about_", info.name, "OK");
632 		alert->Go(NULL);
633 	}
634 }
635 
636 
637 BHandler *
638 BApplication::ResolveSpecifier(BMessage *message, int32 index,
639 	BMessage *specifier, int32 what, const char *property)
640 {
641 	BPropertyInfo propInfo(sPropertyInfo);
642 	status_t err = B_OK;
643 	uint32 data;
644 
645 	if (propInfo.FindMatch(message, 0, specifier, what, property, &data) >= 0) {
646 		switch (data) {
647 			case kWindowByIndex:
648 			{
649 				int32 index;
650 				err = specifier->FindInt32("index", &index);
651 				if (err != B_OK)
652 					break;
653 
654 				if (what == B_REVERSE_INDEX_SPECIFIER)
655 					index = CountWindows() - index;
656 
657 				BWindow *window = WindowAt(index);
658 				if (window != NULL) {
659 					message->PopSpecifier();
660 					BMessenger(window).SendMessage(message);
661 				} else
662 					err = B_BAD_INDEX;
663 				break;
664 			}
665 
666 			case kWindowByName:
667 			{
668 				const char *name;
669 				err = specifier->FindString("name", &name);
670 				if (err != B_OK)
671 					break;
672 
673 				for (int32 i = 0;; i++) {
674 					BWindow *window = WindowAt(i);
675 					if (window == NULL) {
676 						err = B_NAME_NOT_FOUND;
677 						break;
678 					}
679 					if (window->Title() != NULL && !strcmp(window->Title(), name)) {
680 						message->PopSpecifier();
681 						BMessenger(window).SendMessage(message);
682 						break;
683 					}
684 				}
685 				break;
686 			}
687 
688 			case kLooperByIndex:
689 			{
690 				int32 index;
691 				err = specifier->FindInt32("index", &index);
692 				if (err != B_OK)
693 					break;
694 
695 				if (what == B_REVERSE_INDEX_SPECIFIER)
696 					index = CountLoopers() - index;
697 
698 				BLooper *looper = LooperAt(index);
699 				if (looper != NULL) {
700 					message->PopSpecifier();
701 					BMessenger(looper).SendMessage(message);
702 				} else
703 					err = B_BAD_INDEX;
704 				break;
705 			}
706 
707 			case kLooperByID:
708 				// TODO: implement getting looper by ID!
709 				break;
710 
711 			case kLooperByName:
712 			{
713 				const char *name;
714 				err = specifier->FindString("name", &name);
715 				if (err != B_OK)
716 					break;
717 
718 				for (int32 i = 0;; i++) {
719 					BLooper *looper = LooperAt(i);
720 					if (looper == NULL) {
721 						err = B_NAME_NOT_FOUND;
722 						break;
723 					}
724 					if (looper->Name() != NULL && !strcmp(looper->Name(), name)) {
725 						message->PopSpecifier();
726 						BMessenger(looper).SendMessage(message);
727 						break;
728 					}
729 				}
730 				break;
731 			}
732 
733 			case kApplication:
734 				return this;
735 		}
736 	} else {
737 		return BLooper::ResolveSpecifier(message, index, specifier, what,
738 			property);
739 	}
740 
741 	if (err != B_OK) {
742 		BMessage reply(B_MESSAGE_NOT_UNDERSTOOD);
743 		reply.AddInt32("error", err);
744 		reply.AddString("message", strerror(err));
745 		message->SendReply(&reply);
746 	}
747 
748 	return NULL;
749 
750 }
751 
752 
753 void
754 BApplication::ShowCursor()
755 {
756 	BPrivate::AppServerLink link;
757 	link.StartMessage(AS_SHOW_CURSOR);
758 	link.Flush();
759 }
760 
761 
762 void
763 BApplication::HideCursor()
764 {
765 	BPrivate::AppServerLink link;
766 	link.StartMessage(AS_HIDE_CURSOR);
767 	link.Flush();
768 }
769 
770 
771 void
772 BApplication::ObscureCursor()
773 {
774 	BPrivate::AppServerLink link;
775 	link.StartMessage(AS_OBSCURE_CURSOR);
776 	link.Flush();
777 }
778 
779 
780 bool
781 BApplication::IsCursorHidden() const
782 {
783 	BPrivate::AppServerLink link;
784 	int32 status = B_ERROR;
785 	link.StartMessage(AS_QUERY_CURSOR_HIDDEN);
786 	link.FlushWithReply(status);
787 
788 	return status == B_OK;
789 }
790 
791 
792 void
793 BApplication::SetCursor(const void *cursorData)
794 {
795 	BCursor cursor(cursorData);
796 	SetCursor(&cursor, true);
797 		// forces the cursor to be sync'ed
798 }
799 
800 
801 void
802 BApplication::SetCursor(const BCursor *cursor, bool sync)
803 {
804 	BPrivate::AppServerLink link;
805 	link.StartMessage(AS_SET_CURSOR);
806 	link.Attach<bool>(sync);
807 	link.Attach<int32>(cursor->fServerToken);
808 
809 	if (sync) {
810 		int32 code;
811 		link.FlushWithReply(code);
812 	} else
813 		link.Flush();
814 }
815 
816 
817 int32
818 BApplication::CountWindows() const
819 {
820 	return _CountWindows(false);
821 		// we're ignoring menu windows
822 }
823 
824 
825 BWindow *
826 BApplication::WindowAt(int32 index) const
827 {
828 	return _WindowAt(index, false);
829 		// we're ignoring menu windows
830 }
831 
832 
833 int32
834 BApplication::CountLoopers() const
835 {
836 	AutoLocker<BLooperList> ListLock(gLooperList);
837 	if (ListLock.IsLocked())
838 		return gLooperList.CountLoopers();
839 
840 	// Some bad, non-specific thing has happened
841 	return B_ERROR;
842 }
843 
844 
845 BLooper *
846 BApplication::LooperAt(int32 index) const
847 {
848 	BLooper *looper = NULL;
849 	AutoLocker<BLooperList> listLock(gLooperList);
850 	if (listLock.IsLocked())
851 		looper = gLooperList.LooperAt(index);
852 
853 	return looper;
854 }
855 
856 
857 bool
858 BApplication::IsLaunching() const
859 {
860 	return !fReadyToRunCalled;
861 }
862 
863 
864 status_t
865 BApplication::GetAppInfo(app_info *info) const
866 {
867 	if (be_app == NULL || be_roster == NULL)
868 		return B_NO_INIT;
869 	return be_roster->GetRunningAppInfo(be_app->Team(), info);
870 }
871 
872 
873 BResources *
874 BApplication::AppResources()
875 {
876 	AutoLocker<BLocker> lock(sAppResourcesLock);
877 
878 	// BApplication caches its resources, so check
879 	// if it already happened.
880 	if (sAppResources != NULL)
881 		return sAppResources;
882 
883 	entry_ref ref;
884 	bool found = false;
885 
886 	// App is already running. Get its entry ref with
887 	// GetAppInfo()
888 	app_info appInfo;
889 	if (be_app && be_app->GetAppInfo(&appInfo) == B_OK) {
890 		ref = appInfo.ref;
891 		found = true;
892 	} else {
893 		// Run() hasn't been called yet
894 		found = BPrivate::get_app_ref(&ref) == B_OK;
895 	}
896 
897 	if (!found)
898 		return NULL;
899 
900 	BFile file(&ref, B_READ_ONLY);
901 	if (file.InitCheck() == B_OK) {
902 		sAppResources = new (std::nothrow) BResources(&file, false);
903 		if (sAppResources != NULL
904 			&& sAppResources->InitCheck() != B_OK) {
905 			delete sAppResources;
906 			sAppResources = NULL;
907 		}
908 	}
909 
910 	return sAppResources;
911 }
912 
913 
914 void
915 BApplication::DispatchMessage(BMessage *message, BHandler *handler)
916 {
917 	if (handler != this) {
918 		// it's not ours to dispatch
919 		BLooper::DispatchMessage(message, handler);
920 		return;
921 	}
922 
923 	switch (message->what) {
924 		case B_ARGV_RECEIVED:
925 			_ArgvReceived(message);
926 			break;
927 
928 		case B_REFS_RECEIVED:
929 		{
930 			// this adds the refs that are part of this message to the recent
931 			// lists, but only folders and documents are handled here
932 			entry_ref ref;
933 			int32 i = 0;
934 			while (message->FindRef("refs", i++, &ref) == B_OK) {
935 				BEntry entry(&ref, true);
936 				if (entry.InitCheck() != B_OK)
937 					continue;
938 
939 				if (entry.IsDirectory())
940 					BRoster().AddToRecentFolders(&ref);
941 				else {
942 					// filter out applications, we only want to have documents
943 					// in the recent files list
944 					BNode node(&entry);
945 					BNodeInfo info(&node);
946 
947 					char mimeType[B_MIME_TYPE_LENGTH];
948 					if (info.GetType(mimeType) != B_OK
949 						|| strcasecmp(mimeType, B_APP_MIME_TYPE))
950 						BRoster().AddToRecentDocuments(&ref);
951 				}
952 			}
953 
954 			RefsReceived(message);
955 			break;
956 		}
957 
958 		case B_READY_TO_RUN:
959 			if (!fReadyToRunCalled) {
960 				ReadyToRun();
961 				fReadyToRunCalled = true;
962 			}
963 			break;
964 
965 		case B_ABOUT_REQUESTED:
966 			AboutRequested();
967 			break;
968 
969 		case B_PULSE:
970 			Pulse();
971 			break;
972 
973 		case B_APP_ACTIVATED:
974 		{
975 			bool active;
976 			if (message->FindBool("active", &active) == B_OK)
977 				AppActivated(active);
978 			break;
979 		}
980 
981 		case _SHOW_DRAG_HANDLES_:
982 		{
983 			bool show;
984 			if (message->FindBool("show", &show) != B_OK)
985 				break;
986 
987 			BDragger::Private::UpdateShowAllDraggers(show);
988 			break;
989 		}
990 
991 		// TODO: Handle these as well
992 		case _DISPOSE_DRAG_:
993 		case _PING_:
994 			puts("not yet handled message:");
995 			DBG(message->PrintToStream());
996 			break;
997 
998 		default:
999 			BLooper::DispatchMessage(message, handler);
1000 			break;
1001 	}
1002 }
1003 
1004 
1005 void
1006 BApplication::SetPulseRate(bigtime_t rate)
1007 {
1008 	if (rate < 0)
1009 		rate = 0;
1010 
1011 	// BeBook states that we have only 100,000 microseconds granularity
1012 	rate -= rate % 100000;
1013 
1014 	if (!Lock())
1015 		return;
1016 
1017 	if (rate != 0) {
1018 		// reset existing pulse runner, or create new one
1019 		if (fPulseRunner == NULL) {
1020 			BMessage pulse(B_PULSE);
1021 			fPulseRunner = new BMessageRunner(be_app_messenger, &pulse, rate);
1022 		} else
1023 			fPulseRunner->SetInterval(rate);
1024 	} else {
1025 		// turn off pulse messages
1026 		delete fPulseRunner;
1027 		fPulseRunner = NULL;
1028 	}
1029 
1030 	fPulseRate = rate;
1031 	Unlock();
1032 }
1033 
1034 
1035 status_t
1036 BApplication::GetSupportedSuites(BMessage *data)
1037 {
1038 	if (!data)
1039 		return B_BAD_VALUE;
1040 
1041 	status_t status = data->AddString("suites", "suite/vnd.Be-application");
1042 	if (status == B_OK) {
1043 		BPropertyInfo propertyInfo(sPropertyInfo);
1044 		status = data->AddFlat("messages", &propertyInfo);
1045 		if (status == B_OK)
1046 			status = BLooper::GetSupportedSuites(data);
1047 	}
1048 
1049 	return status;
1050 }
1051 
1052 
1053 status_t
1054 BApplication::Perform(perform_code d, void *arg)
1055 {
1056 	return BLooper::Perform(d, arg);
1057 }
1058 
1059 
1060 void BApplication::_ReservedApplication1() {}
1061 void BApplication::_ReservedApplication2() {}
1062 void BApplication::_ReservedApplication3() {}
1063 void BApplication::_ReservedApplication4() {}
1064 void BApplication::_ReservedApplication5() {}
1065 void BApplication::_ReservedApplication6() {}
1066 void BApplication::_ReservedApplication7() {}
1067 void BApplication::_ReservedApplication8() {}
1068 
1069 
1070 bool
1071 BApplication::ScriptReceived(BMessage *message, int32 index,
1072 	BMessage *specifier, int32 what, const char *property)
1073 {
1074 	BMessage reply(B_REPLY);
1075 	status_t err = B_BAD_SCRIPT_SYNTAX;
1076 
1077 	switch (message->what) {
1078 		case B_GET_PROPERTY:
1079 			if (strcmp("Loopers", property) == 0) {
1080 				int32 count = CountLoopers();
1081 				err = B_OK;
1082 				for (int32 i=0; err == B_OK && i<count; i++) {
1083 					BMessenger messenger(LooperAt(i));
1084 					err = reply.AddMessenger("result", messenger);
1085 				}
1086 			} else if (strcmp("Windows", property) == 0) {
1087 				int32 count = CountWindows();
1088 				err = B_OK;
1089 				for (int32 i=0; err == B_OK && i<count; i++) {
1090 					BMessenger messenger(WindowAt(i));
1091 					err = reply.AddMessenger("result", messenger);
1092 				}
1093 			} else if (strcmp("Window", property) == 0) {
1094 				switch (what) {
1095 					case B_INDEX_SPECIFIER:
1096 					case B_REVERSE_INDEX_SPECIFIER:
1097 					{
1098 						int32 index = -1;
1099 						err = specifier->FindInt32("index", &index);
1100 						if (err != B_OK)
1101 							break;
1102 						if (what == B_REVERSE_INDEX_SPECIFIER)
1103 							index = CountWindows() - index;
1104 						err = B_BAD_INDEX;
1105 						BWindow *win = WindowAt(index);
1106 						if (!win)
1107 							break;
1108 						BMessenger messenger(win);
1109 						err = reply.AddMessenger("result", messenger);
1110 						break;
1111 					}
1112 					case B_NAME_SPECIFIER:
1113 					{
1114 						const char *name;
1115 						err = specifier->FindString("name", &name);
1116 						if (err != B_OK)
1117 							break;
1118 						err = B_NAME_NOT_FOUND;
1119 						for (int32 i = 0; i < CountWindows(); i++) {
1120 							BWindow* window = WindowAt(i);
1121 							if (window && window->Name() != NULL
1122 								&& !strcmp(window->Name(), name)) {
1123 								BMessenger messenger(window);
1124 								err = reply.AddMessenger("result", messenger);
1125 								break;
1126 							}
1127 						}
1128 						break;
1129 					}
1130 				}
1131 			} else if (strcmp("Looper", property) == 0) {
1132 				switch (what) {
1133 					case B_INDEX_SPECIFIER:
1134 					case B_REVERSE_INDEX_SPECIFIER:
1135 					{
1136 						int32 index = -1;
1137 						err = specifier->FindInt32("index", &index);
1138 						if (err != B_OK)
1139 							break;
1140 						if (what == B_REVERSE_INDEX_SPECIFIER)
1141 							index = CountLoopers() - index;
1142 						err = B_BAD_INDEX;
1143 						BLooper *looper = LooperAt(index);
1144 						if (!looper)
1145 							break;
1146 						BMessenger messenger(looper);
1147 						err = reply.AddMessenger("result", messenger);
1148 						break;
1149 					}
1150 					case B_NAME_SPECIFIER:
1151 					{
1152 						const char *name;
1153 						err = specifier->FindString("name", &name);
1154 						if (err != B_OK)
1155 							break;
1156 						err = B_NAME_NOT_FOUND;
1157 						for (int32 i = 0; i < CountLoopers(); i++) {
1158 							BLooper *looper = LooperAt(i);
1159 							if (looper && looper->Name()
1160 								&& !strcmp(looper->Name(), name)) {
1161 								BMessenger messenger(looper);
1162 								err = reply.AddMessenger("result", messenger);
1163 								break;
1164 							}
1165 						}
1166 						break;
1167 					}
1168 					case B_ID_SPECIFIER:
1169 					{
1170 						// TODO
1171 						debug_printf("Looper's ID specifier used but not implemented.\n");
1172 						break;
1173 					}
1174 				}
1175 			} else if (strcmp("Name", property) == 0) {
1176 				err = reply.AddString("result", Name());
1177 			}
1178 			break;
1179 		case B_COUNT_PROPERTIES:
1180 			if (strcmp("Looper", property) == 0) {
1181 				err = reply.AddInt32("result", CountLoopers());
1182 			} else if (strcmp("Window", property) == 0) {
1183 				err = reply.AddInt32("result", CountWindows());
1184 			}
1185 			break;
1186 	}
1187 	if (err == B_BAD_SCRIPT_SYNTAX)
1188 		return false;
1189 
1190 	if (err < B_OK) {
1191 		reply.what = B_MESSAGE_NOT_UNDERSTOOD;
1192 		reply.AddString("message", strerror(err));
1193 	}
1194 	reply.AddInt32("error", err);
1195 	message->SendReply(&reply);
1196 	return true;
1197 }
1198 
1199 
1200 void
1201 BApplication::BeginRectTracking(BRect rect, bool trackWhole)
1202 {
1203 	BPrivate::AppServerLink link;
1204 	link.StartMessage(AS_BEGIN_RECT_TRACKING);
1205 	link.Attach<BRect>(rect);
1206 	link.Attach<int32>(trackWhole);
1207 	link.Flush();
1208 }
1209 
1210 
1211 void
1212 BApplication::EndRectTracking()
1213 {
1214 	BPrivate::AppServerLink link;
1215 	link.StartMessage(AS_END_RECT_TRACKING);
1216 	link.Flush();
1217 }
1218 
1219 
1220 status_t
1221 BApplication::_SetupServerAllocator()
1222 {
1223 	fServerAllocator = new (std::nothrow) BPrivate::ServerMemoryAllocator();
1224 	if (fServerAllocator == NULL)
1225 		return B_NO_MEMORY;
1226 
1227 	return fServerAllocator->InitCheck();
1228 }
1229 
1230 
1231 status_t
1232 BApplication::_InitGUIContext()
1233 {
1234 	// An app_server connection is necessary for a lot of stuff, so get that first.
1235 	status_t error = _ConnectToServer();
1236 	if (error != B_OK)
1237 		return error;
1238 
1239 	// Initialize the IK after we have set be_app because of a construction
1240 	// of a AppServerLink (which depends on be_app) nested inside the call
1241 	// to get_menu_info.
1242 	error = _init_interface_kit_();
1243 	if (error != B_OK)
1244 		return error;
1245 
1246 	// create global system cursors
1247 	B_CURSOR_SYSTEM_DEFAULT = new BCursor(B_HAND_CURSOR);
1248 	B_CURSOR_I_BEAM = new BCursor(B_I_BEAM_CURSOR);
1249 
1250 	// TODO: would be nice to get the workspace at launch time from the registrar
1251 	fInitialWorkspace = current_workspace();
1252 
1253 	return B_OK;
1254 }
1255 
1256 
1257 status_t
1258 BApplication::_ConnectToServer()
1259 {
1260 	port_id serverPort = find_port(SERVER_PORT_NAME);
1261 	if (serverPort < B_OK)
1262 		return serverPort;
1263 
1264 	// Create the port so that the app_server knows where to send messages
1265 	port_id clientPort = create_port(100, "a<app_server");
1266 	if (clientPort < B_OK)
1267 		return clientPort;
1268 
1269 	// We can't use AppServerLink because be_app == NULL
1270 	fServerLink->SetTo(serverPort, clientPort);
1271 
1272 	fServerLink->StartMessage(AS_GET_DESKTOP);
1273 	fServerLink->Attach<port_id>(clientPort);
1274 	fServerLink->Attach<int32>(getuid());
1275 
1276 	int32 code;
1277 	if (fServerLink->FlushWithReply(code) != B_OK || code != B_OK) {
1278 		fServerLink->SetSenderPort(-1);
1279 		return B_ERROR;
1280 	}
1281 
1282 	// we talk to the desktop to create our application
1283 	fServerLink->Read<port_id>(&serverPort);
1284 	fServerLink->SetSenderPort(serverPort);
1285 
1286 	// AS_CREATE_APP:
1287 	//
1288 	// Attach data:
1289 	// 1) port_id - receiver port of a regular app
1290 	// 2) port_id - looper port for this BApplication
1291 	// 3) team_id - team identification field
1292 	// 4) int32 - handler ID token of the app
1293 	// 5) char * - signature of the regular app
1294 
1295 	fServerLink->StartMessage(AS_CREATE_APP);
1296 	fServerLink->Attach<port_id>(clientPort);
1297 	fServerLink->Attach<port_id>(_get_looper_port_(this));
1298 	fServerLink->Attach<team_id>(Team());
1299 	fServerLink->Attach<int32>(_get_object_token_(this));
1300 	fServerLink->AttachString(fAppName);
1301 
1302 	area_id sharedReadOnlyArea;
1303 
1304 	if (fServerLink->FlushWithReply(code) == B_OK
1305 		&& code == B_OK) {
1306 		// We don't need to contact the main app_server anymore
1307 		// directly; we now talk to our server alter ego only.
1308 		fServerLink->Read<port_id>(&serverPort);
1309 		fServerLink->Read<area_id>(&sharedReadOnlyArea);
1310 	} else {
1311 		fServerLink->SetSenderPort(-1);
1312 		debugger("BApplication: couldn't obtain new app_server comm port");
1313 		return B_ERROR;
1314 	}
1315 
1316 	fServerLink->SetSenderPort(serverPort);
1317 
1318 	status_t status = _SetupServerAllocator();
1319 	if (status != B_OK)
1320 		return status;
1321 
1322 	area_id area;
1323 	uint8* base;
1324 	status = fServerAllocator->AddArea(sharedReadOnlyArea, area, base, true);
1325 	if (status < B_OK)
1326 		return status;
1327 
1328 	fServerReadOnlyMemory = base;
1329 	return B_OK;
1330 }
1331 
1332 
1333 #if 0
1334 void
1335 BApplication::send_drag(BMessage *message, int32 vs_token, BPoint offset,
1336 	BRect dragRect, BHandler *replyTo)
1337 {
1338 	// TODO: implement
1339 }
1340 
1341 
1342 void
1343 BApplication::send_drag(BMessage *message, int32 vs_token, BPoint offset,
1344 	int32 bitmapToken, drawing_mode dragMode, BHandler *replyTo)
1345 {
1346 	// TODO: implement
1347 }
1348 
1349 
1350 void
1351 BApplication::write_drag(_BSession_ *session, BMessage *message)
1352 {
1353 	// TODO: implement
1354 }
1355 #endif
1356 
1357 bool
1358 BApplication::_WindowQuitLoop(bool quitFilePanels, bool force)
1359 {
1360 	int32 index = 0;
1361 	while (true) {
1362 		 BWindow *window = WindowAt(index);
1363 		 if (window == NULL)
1364 		 	break;
1365 
1366 		// NOTE: the window pointer might be stale, in case the looper
1367 		// was already quit by quitting an earlier looper... but fortunately,
1368 		// we can still call Lock() on the invalid pointer, and it
1369 		// will return false...
1370 		if (!window->Lock())
1371 			continue;
1372 
1373 		// don't quit file panels if we haven't been asked for it
1374 		if (!quitFilePanels && window->IsFilePanel()) {
1375 			window->Unlock();
1376 			index++;
1377 			continue;
1378 		}
1379 
1380 		if (!force && !window->QuitRequested()
1381 			&& !(quitFilePanels && window->IsFilePanel())) {
1382 			// the window does not want to quit, so we don't either
1383 			window->Unlock();
1384 			return false;
1385 		}
1386 
1387 		// Re-lock, just to make sure that the user hasn't done nasty
1388 		// things in QuitRequested(). Quit() unlocks fully, thus
1389 		// double-locking is harmless.
1390 		if (window->Lock())
1391 			window->Quit();
1392 
1393 		index = 0;
1394 			// we need to continue at the start of the list again - it
1395 			// might have changed
1396 	}
1397 	return true;
1398 }
1399 
1400 
1401 bool
1402 BApplication::_QuitAllWindows(bool force)
1403 {
1404 	AssertLocked();
1405 
1406 	// We need to unlock here because BWindow::QuitRequested() must be
1407 	// allowed to lock the application - which would cause a deadlock
1408 	Unlock();
1409 
1410 	bool quit = _WindowQuitLoop(false, force);
1411 	if (quit)
1412 		quit = _WindowQuitLoop(true, force);
1413 
1414 	Lock();
1415 
1416 	return quit;
1417 }
1418 
1419 
1420 void
1421 BApplication::_ArgvReceived(BMessage *message)
1422 {
1423 	ASSERT(message != NULL);
1424 
1425 	// build the argv vector
1426 	status_t error = B_OK;
1427 	int32 argc = 0;
1428 	char **argv = NULL;
1429 	if (message->FindInt32("argc", &argc) == B_OK && argc > 0) {
1430 		// allocate a NULL terminated array
1431 		argv = new(std::nothrow) char*[argc + 1];
1432 		if (argv == NULL)
1433 			return;
1434 
1435 		// copy the arguments
1436 		for (int32 i = 0; error == B_OK && i < argc; i++) {
1437 			const char *arg = NULL;
1438 			error = message->FindString("argv", i, &arg);
1439 			if (error == B_OK && arg) {
1440 				argv[i] = strdup(arg);
1441 				if (argv[i] == NULL)
1442 					error = B_NO_MEMORY;
1443 			} else
1444 				argc = i;
1445 		}
1446 
1447 		argv[argc] = NULL;
1448 	}
1449 
1450 	// call the hook
1451 	if (error == B_OK && argc > 0)
1452 		ArgvReceived(argc, argv);
1453 
1454 	if (error != B_OK) {
1455 		printf("Error parsing B_ARGV_RECEIVED message. Message:\n");
1456 		message->PrintToStream();
1457 	}
1458 
1459 	// cleanup
1460 	if (argv) {
1461 		for (int32 i = 0; i < argc; i++)
1462 			free(argv[i]);
1463 		delete[] argv;
1464 	}
1465 }
1466 
1467 
1468 uint32
1469 BApplication::InitialWorkspace()
1470 {
1471 	return fInitialWorkspace;
1472 }
1473 
1474 
1475 int32
1476 BApplication::_CountWindows(bool includeMenus) const
1477 {
1478 	uint32 count = 0;
1479 	for (int32 i = 0; i < gLooperList.CountLoopers(); i++) {
1480 		BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
1481 		if (window != NULL && !window->fOffscreen && (includeMenus
1482 				|| dynamic_cast<BMenuWindow *>(window) == NULL)) {
1483 			count++;
1484 		}
1485 	}
1486 
1487 	return count;
1488 }
1489 
1490 
1491 BWindow *
1492 BApplication::_WindowAt(uint32 index, bool includeMenus) const
1493 {
1494 	AutoLocker<BLooperList> listLock(gLooperList);
1495 	if (!listLock.IsLocked())
1496 		return NULL;
1497 
1498 	uint32 count = gLooperList.CountLoopers();
1499 	for (uint32 i = 0; i < count && index < count; i++) {
1500 		BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
1501 		if (window == NULL || (window != NULL && window->fOffscreen)
1502 			|| (!includeMenus && dynamic_cast<BMenuWindow *>(window) != NULL)) {
1503 			index++;
1504 			continue;
1505 		}
1506 
1507 		if (i == index)
1508 			return window;
1509 	}
1510 
1511 	return NULL;
1512 }
1513 
1514 
1515 //	#pragma mark -
1516 
1517 
1518 /*!
1519 	\brief Checks whether the supplied string is a valid application signature.
1520 
1521 	An error message is printed, if the string is no valid app signature.
1522 
1523 	\param signature The string to be checked.
1524 	\return
1525 	- \c B_OK: \a signature is a valid app signature.
1526 	- \c B_BAD_VALUE: \a signature is \c NULL or no valid app signature.
1527 */
1528 static status_t
1529 check_app_signature(const char *signature)
1530 {
1531 	bool isValid = false;
1532 	BMimeType type(signature);
1533 	if (type.IsValid() && !type.IsSupertypeOnly()
1534 		&& BMimeType("application").Contains(&type)) {
1535 		isValid = true;
1536 	}
1537 	if (!isValid) {
1538 		printf("bad signature (%s), must begin with \"application/\" and "
1539 			   "can't conflict with existing registered mime types inside "
1540 			   "the \"application\" media type.\n", signature);
1541 	}
1542 	return (isValid ? B_OK : B_BAD_VALUE);
1543 }
1544 
1545 
1546 /*!
1547 	\brief Returns the looper name for a given signature.
1548 
1549 	Normally this is "AppLooperPort", but in case of the registrar a
1550 	special name.
1551 
1552 	\return The looper name.
1553 */
1554 static const char *
1555 looper_name_for(const char *signature)
1556 {
1557 	if (signature && !strcasecmp(signature, kRegistrarSignature))
1558 		return BPrivate::get_roster_port_name();
1559 	return "AppLooperPort";
1560 }
1561 
1562 
1563 /*!
1564 	\brief Fills the passed BMessage with B_ARGV_RECEIVED infos.
1565 */
1566 static void
1567 fill_argv_message(BMessage &message)
1568 {
1569    	message.what = B_ARGV_RECEIVED;
1570 
1571 	int32 argc = __libc_argc;
1572 	const char * const *argv = __libc_argv;
1573 
1574 	// add argc
1575 	message.AddInt32("argc", argc);
1576 
1577 	// add argv
1578 	for (int32 i = 0; i < argc; i++) {
1579 		if (argv[i] != NULL)
1580 			message.AddString("argv", argv[i]);
1581 	}
1582 
1583 	// add current working directory
1584 	char cwd[B_PATH_NAME_LENGTH];
1585 	if (getcwd(cwd, B_PATH_NAME_LENGTH))
1586 		message.AddString("cwd", cwd);
1587 }
1588 
1589