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