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