xref: /haiku/src/apps/terminal/TermApp.cpp (revision 4f00613311d0bd6b70fa82ce19931c41f071ea4e)
1 /*
2  * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net>
3  * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
4  *
5  * Distributed unter the terms of the MIT license.
6  */
7 #include <stdio.h>
8 #include <signal.h>
9 #include <sys/wait.h>
10 #include <stdlib.h>
11 #include <malloc.h>
12 
13 #include <storage/Path.h>
14 #include <NodeInfo.h>
15 #include <Roster.h>
16 #include <Alert.h>
17 #include <Screen.h>
18 #include <String.h>
19 #include <TextView.h>
20 
21 #include "TermApp.h"
22 #include "TermWindow.h"
23 #include "TermConst.h"
24 #include "spawn.h"
25 #include "PrefHandler.h"
26 #include "CodeConv.h"
27 
28 // Preference temporary
29 extern PrefHandler *gTermPref;
30 extern char gWindowName[];
31 
32 bool usage_requested = false;
33 bool geometry_requested = false;
34 bool color_requested = false;
35 
36 int argmatch(char **, int, char *, char *, int, char **, int *);
37 void sort_args(int, char **);
38 int text_to_rgb(char *, rgb_color *, char *);
39 
40 
41 const ulong MSG_ACTIVATE_TERM = 'msat';
42 const ulong MSG_TERM_IS_MINIMIZE = 'mtim';
43 
44 
45 TermApp::TermApp(void)
46   :BApplication( TERM_SIGNATURE )
47 {
48 	BList teams;
49 
50 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
51 	fWindowNumber = teams.CountItems();
52 
53 	if(fWindowNumber == 0)
54 	{
55 		be_roster->GetAppList(R5_TERM_SIGNATURE, &teams);
56 		fWindowNumber = teams.CountItems();
57 	}
58 
59 	int i = fWindowNumber / 16;
60 	int j = fWindowNumber % 16;
61 
62 	int k =(j * 16) + (i * 64) + 50;
63 	int l =(j * 16)  + 50;
64 
65 	fTermWindow = NULL;
66 	fAboutPanel = NULL;
67 	fTermFrame.Set(k, l, k + 50, k + 50);
68 
69 	gTermPref = new PrefHandler();
70 }
71 
72 
73 TermApp::~TermApp(void)
74 {
75 	// delete gTermPref;
76 }
77 
78 
79 void
80 TermApp::ReadyToRun(void)
81 {
82 	// Prevent opeing window when option -h is given.
83 	if(usage_requested)
84 		return;
85 
86 	const char *command = NULL;
87 	const char *encoding;
88 	int rows, cols;
89 
90 	encoding = gTermPref->getString(PREF_TEXT_ENCODING);
91 
92 	// Get encoding name (setenv TTYPE in spawn_shell functions)
93 	const etable *p = encoding_table;
94 	while(p->name) {
95 
96 		if(!strcmp(p->name, encoding)) {
97 			encoding = p->shortname;
98 			break;
99 		}
100 		p++;
101 	}
102 
103 	if(CommandLine.Length() > 0)
104 		command = CommandLine.String();
105 	else
106 		command = gTermPref->getString(PREF_SHELL);
107 
108 	rows = gTermPref->getInt32(PREF_ROWS);
109 	cols = gTermPref->getInt32(PREF_COLS);
110 
111 	pfd = spawn_shell(rows, cols, command, encoding);
112 
113 	// failed spawn, print stdout and open alert panel
114 	if(pfd == -1 ) {
115 		(new BAlert("alert", "Terminal couldn't start the shell. Sorry.",
116 					"ok", NULL, NULL,B_WIDTH_FROM_LABEL,
117 					B_INFO_ALERT))->Go(NULL);
118 		PostMessage(B_QUIT_REQUESTED);
119 	}
120 
121 	MakeTermWindow(fTermFrame);
122 }
123 
124 
125 void
126 TermApp::Quit(void)
127 {
128 	if(!usage_requested){
129 
130 		int status;
131 
132 		kill(-sh_pid, SIGHUP);
133 		wait(&status);
134 	}
135 
136 	delete gTermPref;
137 
138 	BApplication::Quit();
139 }
140 
141 
142 void
143 TermApp::AboutRequested()
144 {
145 	BAlert *alert = new BAlert("about", "Terminal\n"
146 		"\twritten by Kazuho Okui and Takashi Murai\n"
147 		"\tupdated by Kian Duffy and others\n\n"
148 		"\tCopyright " B_UTF8_COPYRIGHT "2003-2005, Haiku.\n", "Ok");
149 	BTextView *view = alert->TextView();
150 	BFont font;
151 
152 	view->SetStylable(true);
153 
154 	view->GetFont(&font);
155 	font.SetSize(18);
156 	font.SetFace(B_BOLD_FACE);
157 	view->SetFontAndColor(0, 8, &font);
158 
159 	alert->Go();
160 }
161 
162 
163 void
164 TermApp::MessageReceived(BMessage* msg)
165 {
166 	switch(msg->what)
167 	{
168 		case MENU_NEW_TREM:
169 		{
170 			RunNewTerm();
171 			break;
172 		}
173 		case MENU_SWITCH_TERM:
174 		{
175 			SwitchTerm();
176 			break;
177 		}
178 		case MSG_ACTIVATE_TERM:
179 		{
180 			fTermWindow->TermWinActivate();
181 			break;
182 		}
183 		case MSG_TERM_IS_MINIMIZE:
184 		{
185 			BMessage reply(B_REPLY);
186 			reply.AddBool("result", fTermWindow->IsMinimized());
187 			msg->SendReply(&reply);
188 			break;
189 		}
190 		default:
191 		{
192 			BApplication::MessageReceived(msg);
193 			break;
194 		}
195 	}
196 }
197 
198 struct standard_args
199 {
200 	char *name;
201 	char *longname;
202 	int priority;
203 	int nargs;
204 	const char *prefname;
205 };
206 
207 struct standard_args standard_args[] =
208 {
209 	{ "-h", "--help", 90, 0, NULL },
210 	{ "-p", "--preference", 80, 1, NULL },
211 	{ "-t", "--title", 70, 1, NULL },
212 	{ "-geom", "--geometry", 50, 1, NULL },
213 	{ "-fg", "--text-fore-color", 40, 1, PREF_TEXT_FORE_COLOR },
214 	{ "-bg", "--text-back-color", 35, 1, PREF_TEXT_BACK_COLOR },
215 	{ "-curfg", "--cursor-fore-color", 30, 1, PREF_CURSOR_FORE_COLOR },
216 	{ "-curbg", "--cursor-back-color", 25, 1, PREF_CURSOR_BACK_COLOR },
217 	{ "-selfg", "--select-fore-color", 20, 1, PREF_SELECT_FORE_COLOR },
218 	{ "-selbg", "--select-back-color", 15, 1, PREF_SELECT_BACK_COLOR },
219 	{ "-imfg", "--im-fore-color", 10, 1, PREF_IM_FORE_COLOR },
220 	{ "-imbg", "--im-back-color", 5, 1, PREF_IM_BACK_COLOR },
221 	{ "-imsel", "--im-select-color", 0, 1, PREF_IM_SELECT_COLOR },
222 };
223 
224 void
225 TermApp::ArgvReceived(int32 argc, char **argv)
226 {
227 	int skip_args = 0;
228 	char *value = 0;
229 	rgb_color color;
230 
231 	if(argc < 2)
232 		return;
233 
234 	sort_args(argc, argv);
235 	argc = 0;
236 	while(argv[argc])
237 		argc++;
238 
239 	// Print usage
240 	if(argmatch(argv, argc, "-help", "--help", 3, NULL, &skip_args)) {
241 
242 		Usage(argv[0]);
243 		usage_requested = true;
244 		PostMessage(B_QUIT_REQUESTED);
245 	}
246 
247 	// Load preference file
248 	if(argmatch(argv, argc, "-p", "--preference", 4, &value, &skip_args))
249 		gTermPref->Open(value);
250 
251 	// Set window title
252 	if(argmatch(argv ,argc, "-t", "--title", 3, &value, &skip_args))
253 		strcpy(gWindowName, value);
254 
255     // Set window geometry
256     if(argmatch(argv, argc, "-geom", "--geometry", 4, &value, &skip_args)) {
257 
258 		int width, height, xpos, ypos;
259 
260 		sscanf(value, "%dx%d+%d+%d", &width, &height, &xpos, &ypos);
261 		if(width < 0 || height < 0 || xpos < 0 || ypos < 0
262 			|| width >= 256 || height >= 256 || xpos >= 2048 || ypos >= 2048) {
263 
264 			fprintf(stderr, "%s: invalid geometry format or value.\n", argv[0]);
265 			fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
266 			usage_requested = true;
267 			PostMessage(B_QUIT_REQUESTED);
268 		}
269 		gTermPref->setInt32(PREF_COLS, width);
270 		gTermPref->setInt32(PREF_ROWS, height);
271 
272 		fTermFrame.Set(xpos, ypos, xpos + 50, ypos + 50);
273 		geometry_requested = true;
274     }
275 
276 	// Open '/etc/rgb.txt' file
277 	BFile inFile;
278 	status_t sts;
279 	off_t size = 0;
280 
281 	sts = inFile.SetTo("/etc/rgb.txt", B_READ_ONLY);
282 
283 	if(sts != B_OK) {
284 
285 		fprintf(stderr, "%s: Can't open /etc/rgb.txt file.\n", argv[0]);
286 		usage_requested = true;
287 		PostMessage(B_QUIT_REQUESTED);
288 	}
289 
290 	inFile.GetSize(&size);
291 	char *buffer = new char [size];
292 	inFile.Read(buffer, size);
293 
294 	// Set window, cursor, area and IM color
295 	for(int i = 4; i < 13; i++) {
296 		if(argmatch(argv, argc, standard_args[i].name, standard_args[i].longname,
297 					9, &value, &skip_args)) {
298 
299 			if(text_to_rgb(value, &color, buffer))
300 				gTermPref->setRGB(standard_args[i].prefname, color);
301 			else
302 				fprintf(stderr, "%s: invalid color string -- %s\n", argv[0], value);
303 		}
304 	}
305 
306 	delete buffer;
307 
308 	skip_args++;
309 
310 	if(skip_args < argc) {
311 		// Check invalid options
312 
313 		if(*argv[skip_args] == '-') {
314 			fprintf(stderr, "%s: invalid option `%s'\n", argv[0], argv[skip_args]);
315 			fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
316 			usage_requested = true;
317 			PostMessage(B_QUIT_REQUESTED);
318 		}
319 
320 		CommandLine += argv[skip_args++];
321 		while(skip_args < argc) {
322 			CommandLine += ' ';
323 			CommandLine += argv[skip_args++];
324 		}
325 	}
326 }
327 
328 //void
329 //TermApp::AboutRequested(void)
330 //{
331 //  if(fAboutPanel) {
332   //  fAboutPanel->Activate();
333  // }
334  // else {
335  //   BRect rect(0, 0, 350 - 1, 170 - 1);
336  //   rect.OffsetTo(fTermWindow->Frame().LeftTop());
337  //   rect.OffsetBy(100, 100);
338 
339   //  fAboutPanel = new AboutDlg(rect, this);
340  //   fAboutPanel->Show();
341  // }
342 
343 //}
344 
345 void
346 TermApp::RefsReceived(BMessage *message)
347 {
348 	// Works Only Launced by Double-Click file, or Drags file to App.
349 	if(!(IsLaunching()))
350 		return;
351 
352 	entry_ref ref;
353 	if( message->FindRef("refs", 0, &ref) != B_OK )
354 		return;
355 
356 	BFile file;
357 	if( file.SetTo(&ref, B_READ_WRITE) != B_OK )
358 		return;
359 
360 	BNodeInfo info(&file);
361 	char mimetype[B_MIME_TYPE_LENGTH];
362 	info.GetType(mimetype);
363 
364 	// if App opened by Pref file
365 	if(!strcmp(mimetype, PREFFILE_MIMETYPE)){
366 
367 		BEntry ent(&ref);
368 		BPath path(&ent);
369 		gTermPref->OpenText(path.Path());
370 		return;
371 	}
372 
373 	// if App opened by Shell Script
374 	if(!strcmp(mimetype, "text/x-haiku-shscript")){
375 
376 		// Not implemented.
377 		//    beep();
378 		return;
379 	}
380 }
381 
382 void
383 TermApp::MakeTermWindow(BRect &frame)
384 {
385 	fTermWindow = new TermWindow(frame, fWindowNumber);
386 	fTermWindow->Show();
387 }
388 
389 void
390 TermApp::RunNewTerm(void)
391 {
392 	app_info info;
393 	be_app->GetAppInfo(&info);
394 
395 	// try launching two different ways to work around possible problems
396 	if(be_roster->Launch(&(info.ref))!=B_OK)
397 		be_roster->Launch(TERM_SIGNATURE);
398 }
399 
400 void
401 TermApp::ActivateTermWindow(team_id id)
402 {
403 	BMessenger app(TERM_SIGNATURE, id);
404 
405 	if(app.IsTargetLocal())
406 		fTermWindow->Activate();
407 	else
408 		app.SendMessage(MSG_ACTIVATE_TERM);
409 }
410 
411 void
412 TermApp::SwitchTerm()
413 {
414 	team_id myId = be_app->Team(); // My id
415 	BList teams;
416 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
417 	int32 numTerms = teams.CountItems();
418 
419 	if(numTerms <= 1 )
420 		return; //Can't Switch !!
421 
422 	// Find position of mine in app teams.
423 	int32 i;
424 
425 	for(i = 0; i < numTerms; i++) {
426 		if(myId == reinterpret_cast<team_id>(teams.ItemAt(i)))
427 			break;
428 	}
429 
430 	do{
431 		if(--i < 0)
432 			i = numTerms - 1;
433 	} while(IsMinimize(reinterpret_cast<team_id>(teams.ItemAt(i))));
434 
435 	// Activate switched terminal.
436 	ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i)));
437 }
438 
439 bool
440 TermApp::IsMinimize(team_id id)
441 {
442 	BMessenger app(TERM_SIGNATURE, id);
443 
444 	if(app.IsTargetLocal())
445 		return fTermWindow->IsMinimized();
446 
447 	BMessage reply;
448 	bool hidden;
449 
450 	if(app.SendMessage(MSG_TERM_IS_MINIMIZE, &reply) != B_OK)
451 		return true;
452 
453 	reply.FindBool("result", &hidden);
454 
455 	return hidden;
456 }
457 
458 //#ifndef B_NETPOSITIVE_APP_SIGNATURE
459 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS"
460 //#endif
461 //
462 //void
463 //TermApp::ShowHTML(BMessage *msg)
464 //{
465 //  const char *url;
466 //  msg->FindString("Url", &url);
467 //  BMessage message;
468 //
469 //  message.what = B_NETPOSITIVE_OPEN_URL;
470 //  message.AddString("be:url", url);
471 
472 //  be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message);
473 //  while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE)))
474 //    snooze(10000);
475 //
476 //  // Activate net+
477 //  be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE));
478 //}
479 
480 #define PR(a) fprintf(stderr, a)
481 
482 // Display usage to stderr
483 void
484 TermApp::Usage(char *name)
485 {
486 	fprintf(stderr, "\n");
487 	fprintf(stderr, "Haiku Terminal\n");
488 	fprintf(stderr, "Copyright 2004 Haiku, Inc.\n");
489 	fprintf(stderr, "Parts Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n");
490 	fprintf(stderr, "\n");
491 	fprintf(stderr, "Usage: %s [OPTION] [SHELL]\n", name);
492 
493 	PR("  -p,     --preference         load preference file\n");
494 	PR("  -t,     --title              set window title\n");
495 	PR("  -geom,  --geometry           set window geometry\n");
496 	PR("                               An example of geometry is \"80x25+100+100\"\n");
497 	PR("  -fg,    --text-fore-color    set window foreground color\n");
498 	PR("  -bg,    --text-back-color    set window background color\n");
499 	PR("  -curfg, --cursor-fore-color  set cursor foreground color\n");
500 	PR("  -curbg, --cursor-back-color  set cursor background color\n");
501 	PR("  -selfg, --select-fore-color  set selection area foreground color\n");
502 	PR("  -selbg, --select-back-color  set selection area background color\n");
503 	PR("                               Examples of color are \"#FF00FF\" and \"purple\"\n");
504 	PR("\n");
505 }
506 
507 int
508 text_to_rgb(char *name, rgb_color *color, char *buffer)
509 {
510 	if(name[0] != '#') {
511 
512 	// Convert from /etc/rgb.txt.
513 	BString inStr(buffer);
514 	int32 point, offset = 0;
515 
516 	// Search color name
517 	do {
518 		point = inStr.FindFirst(name, offset);
519 		if(point < 0)
520 			return false;
521 		offset = point + 1;
522 	} while(*(buffer + point -1) != '\t');
523 
524 	char *p = buffer + point;
525 
526 	while(*p != '\n')
527 		p--;
528 	p++;
529 
530 	if(sscanf(p, "%d %d %d", (int *)&color->red, (int *)&color->green,
531 				(int *)&color->blue) == EOF)
532 		return false;
533 		color->alpha = 0;
534 	}
535 	else
536 	if(name[0] == '#') {
537 
538 		// Convert from #RRGGBB format
539 		sscanf(name, "#%2x%2x%2x", (int *)&color->red, (int *)&color->green,
540 				(int *)&color->blue);
541 		color->alpha = 0;
542 	}
543 	else
544 		return false;
545 
546 	return true;
547 }
548 
549 // This routine copy from GNU Emacs.
550 // TODO: This might be a GPL licensing issue here. Investigate.
551 int
552 argmatch(char **argv, int argc, char *sstr, char *lstr,
553 	  int minlen, char **valptr, int *skipptr)
554 {
555 	char *p = 0;
556 	int arglen;
557 	char *arg;
558 
559 	// Don't access argv[argc]; give up in advance
560 	if(argc <= *skipptr + 1)
561 		return 0;
562 
563 	arg = argv[*skipptr+1];
564 	if(arg == NULL)
565 		return 0;
566 
567 	if(strcmp(arg, sstr) == 0)
568 	{
569 		if(valptr != NULL)
570 		{
571 			*valptr = argv[*skipptr+2];
572 			*skipptr += 2;
573 		}
574 		else
575 			*skipptr += 1;
576 		return 1;
577 	}
578 
579 	arglen =(valptr != NULL &&(p = strchr(arg, '=')) != NULL
580 			? p - arg : strlen(arg));
581 
582 	if(lstr == 0 || arglen < minlen || strncmp(arg, lstr, arglen) != 0)
583 		return 0;
584 	else
585 	if(valptr == NULL)
586 	{
587 		*skipptr += 1;
588 		return 1;
589 	}
590 	else
591 	if(p != NULL)
592 	{
593 		*valptr = p+1;
594 		*skipptr += 1;
595 		return 1;
596 	}
597 	else
598 	if(argv[*skipptr+2] != NULL)
599 	{
600 		*valptr = argv[*skipptr+2];
601 		*skipptr += 2;
602 		return 1;
603 	}
604 	else
605 	{
606 		return 0;
607 	}
608 }
609 
610 // This routine copy from GNU Emacs.
611 // TODO: This might be a GPL licensing issue here. Investigate.
612 void
613 sort_args(int argc, char **argv)
614 {
615 	/*
616 		For each element of argv,
617 		the corresponding element of options is:
618 		0 for an option that takes no arguments,
619 		1 for an option that takes one argument, etc.
620 		-1 for an ordinary non-option argument.
621 	*/
622 
623 	char **newargv =(char **) malloc(sizeof(char *) * argc);
624 
625 	int *options =(int *) malloc(sizeof(int) * argc);
626 	int *priority =(int *) malloc(sizeof(int) * argc);
627 	int to = 1;
628 	int incoming_used = 1;
629 	int from;
630 	int i;
631 	//int end_of_options = argc;
632 
633 	// Categorize all the options,
634 	// and figure out which argv elts are option arguments
635 	for(from = 1; from < argc; from++)
636 	{
637 		options[from] = -1;
638 		priority[from] = 0;
639 		if(argv[from][0] == '-')
640 		{
641 			int match, thislen;
642 			char *equals;
643 
644 			// If we have found "--", don't consider any more arguments as options
645 			if(argv[from][1] == '-' && argv[from][2] == 0)
646 			{
647 				// Leave the "--", and everything following it, at the end.
648 				for(; from < argc; from++)
649 				{
650 					priority[from] = -100;
651 					options[from] = -1;
652 				}
653 				break;
654 			}
655 
656 			// Look for a match with a known old-fashioned option.
657 			for(i = 0; i <(int)(sizeof(standard_args) / sizeof(standard_args[0])); i++)
658 				if(!strcmp(argv[from], standard_args[i].name))
659 				{
660 					options[from] = standard_args[i].nargs;
661 					priority[from] = standard_args[i].priority;
662 					if(from + standard_args[i].nargs >= argc)
663 						fprintf(stderr, "Option `%s' requires an argument\n", argv[from]);
664 					from += standard_args[i].nargs;
665 					goto done;
666 				}
667 
668 			/*
669 				Look for a match with a known long option.
670 				MATCH is -1 if no match so far, -2 if two or more matches so far,
671 				>= 0(the table index of the match) if just one match so far.
672 			*/
673 			if(argv[from][1] == '-')
674 			{
675 				match = -1;
676 				thislen = strlen(argv[from]);
677 				equals = strchr(argv[from], '=');
678 				if(equals != 0)
679 					thislen = equals - argv[from];
680 
681 				for(i = 0;i <(int )(sizeof(standard_args) / sizeof(standard_args[0])); i++)
682 					if(standard_args[i].longname
683 						&& !strncmp(argv[from], standard_args[i].longname, thislen))
684 					{
685 						if(match == -1)
686 							match = i;
687 						else
688 							match = -2;
689 					}
690 
691 					// If we found exactly one match, use that
692 					if(match >= 0)
693 					{
694 						options[from] = standard_args[match].nargs;
695 						priority[from] = standard_args[match].priority;
696 
697 						// If --OPTION=VALUE syntax is used,
698 						// this option uses just one argv element
699 						if(equals != 0)
700 							options[from] = 0;
701 						if(from + options[from] >= argc)
702 							fprintf(stderr, "Option `%s' requires an argument\n", argv[from]);
703 						from += options[from];
704 					}
705 			}
706 			done: ;
707 		}
708 	}
709 
710 	// Copy the arguments, in order of decreasing priority, to NEW
711 	newargv[0] = argv[0];
712 	while(incoming_used < argc)
713 	{
714 		int best = -1;
715 		int best_priority = -9999;
716 
717 		// Find the highest priority remaining option.
718 		// If several have equal priority, take the first of them.
719 		for(from = 1; from < argc; from++)
720 		{
721 			if(argv[from] != 0 && priority[from] > best_priority)
722 			{
723 				best_priority = priority[from];
724 				best = from;
725 			}
726 
727 			// Skip option arguments--they are tied to the options.
728 			if(options[from] > 0)
729 				from += options[from];
730 		}
731 
732 		if(best < 0)
733 			abort();
734 
735 		// Copy the highest priority remaining option, with its args, to NEW.
736 		// Unless it is a duplicate of the previous one
737 		if(!(options[best] == 0 && ! strcmp(newargv[to - 1], argv[best])))
738 		{
739 			newargv[to++] = argv[best];
740 			for(i = 0; i < options[best]; i++)
741 				newargv[to++] = argv[best + i + 1];
742 		}
743 
744 		incoming_used += 1 +(options[best] > 0 ? options[best] : 0);
745 
746 		// Clear out this option in ARGV
747 		argv[best] = 0;
748 		for(i = 0; i < options[best]; i++)
749 			argv[best + i + 1] = 0;
750 	}
751 
752 	// If duplicate options were deleted, fill up extra space with null ptrs
753 	while(to < argc)
754 		newargv[to++] = 0;
755 
756 	memcpy(argv, newargv, sizeof(char *) * argc);
757 
758 	free(options);
759 	free(newargv);
760 	free(priority);
761 }
762