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