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