xref: /freebsd/usr.sbin/vidcontrol/vidcontrol.c (revision e4ef2fa23434f985e49db2dff2bbd4c9426bb2a5)
1 /*-
2  * Copyright (c) 1994-1996 S�ren Schmidt
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software withough specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #ifndef lint
30 static const char rcsid[] =
31   "$FreeBSD$";
32 #endif /* not lint */
33 
34 #include <ctype.h>
35 #include <err.h>
36 #include <limits.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <sys/fbio.h>
42 #include <sys/consio.h>
43 #include <sys/errno.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include "path.h"
47 #include "decode.h"
48 
49 #define _VESA_800x600_DFL_COLS 80
50 #define _VESA_800x600_DFL_ROWS 25
51 #define _VESA_800x600_DFL_FNSZ 16
52 
53 char 	legal_colors[16][16] = {
54 	"black", "blue", "green", "cyan",
55 	"red", "magenta", "brown", "white",
56 	"grey", "lightblue", "lightgreen", "lightcyan",
57 	"lightred", "lightmagenta", "yellow", "lightwhite"
58 };
59 int 	hex = 0;
60 int 	number;
61 int	vesa_cols = _VESA_800x600_DFL_COLS;
62 int	vesa_rows = _VESA_800x600_DFL_ROWS;
63 char 	letter;
64 struct 	vid_info info;
65 
66 
67 static void
68 usage()
69 {
70 	fprintf(stderr, "%s\n%s\n%s\n%s\n",
71 "usage: vidcontrol [-r fg bg] [-b color] [-c appearance] [-d] [-l scrmap]",
72 "                  [-i adapter | mode] [-L] [-M char] [-m on|off]",
73 "                  [-f size file] [-s number] [-t N|off] [-x] [-g geometry]",
74 "                  [mode] [fgcol [bgcol]] [show]");
75 	exit(1);
76 }
77 
78 char *
79 nextarg(int ac, char **av, int *indp, int oc, int strict)
80 {
81 	if (*indp < ac)
82 		return(av[(*indp)++]);
83 	if (strict != 0)
84 		errx(1, "option requires two arguments -- %c", oc);
85 	return(NULL);
86 }
87 
88 char *
89 mkfullname(const char *s1, const char *s2, const char *s3)
90 {
91 	static char *buf = NULL;
92 	static int bufl = 0;
93 	int f;
94 
95 	f = strlen(s1) + strlen(s2) + strlen(s3) + 1;
96 	if (f > bufl) {
97 		if (buf)
98 			buf = (char *)realloc(buf, f);
99 		else
100 			buf = (char *)malloc(f);
101 	}
102 	if (!buf) {
103 		bufl = 0;
104 		return(NULL);
105 	}
106 
107 	bufl = f;
108 	strcpy(buf, s1);
109 	strcat(buf, s2);
110 	strcat(buf, s3);
111 	return(buf);
112 }
113 
114 void
115 load_scrnmap(char *filename)
116 {
117 	FILE *fd = 0;
118 	int i, size;
119 	char *name;
120 	scrmap_t scrnmap;
121 	char *prefix[]  = {"", "", SCRNMAP_PATH, SCRNMAP_PATH, NULL};
122 	char *postfix[] = {"", ".scm", "", ".scm"};
123 
124 	for (i=0; prefix[i]; i++) {
125 		name = mkfullname(prefix[i], filename, postfix[i]);
126 		fd = fopen(name, "r");
127 		if (fd)
128 			break;
129 	}
130 	if (fd == NULL) {
131 		warn("screenmap file not found");
132 		return;
133 	}
134 	size = sizeof(scrnmap);
135 	if (decode(fd, (char *)&scrnmap, size) != size) {
136 		rewind(fd);
137 		if (fread(&scrnmap, 1, size, fd) != size) {
138 			warnx("bad screenmap file");
139 			fclose(fd);
140 			return;
141 		}
142 	}
143 	if (ioctl(0, PIO_SCRNMAP, &scrnmap) < 0)
144 		warn("can't load screenmap");
145 	fclose(fd);
146 }
147 
148 void
149 load_default_scrnmap()
150 {
151 	scrmap_t scrnmap;
152 	int i;
153 
154 	for (i=0; i<256; i++)
155 		*((char*)&scrnmap + i) = i;
156 	if (ioctl(0, PIO_SCRNMAP, &scrnmap) < 0)
157 		warn("can't load default screenmap");
158 }
159 
160 void
161 print_scrnmap()
162 {
163 	unsigned char map[256];
164 	int i;
165 
166 	if (ioctl(0, GIO_SCRNMAP, &map) < 0) {
167 		warn("getting screenmap");
168 		return;
169 	}
170 	for (i=0; i<sizeof(map); i++) {
171 		if (i > 0 && i % 16 == 0)
172 			fprintf(stdout, "\n");
173 		if (hex)
174 			fprintf(stdout, " %02x", map[i]);
175 		else
176 			fprintf(stdout, " %03d", map[i]);
177 	}
178 	fprintf(stdout, "\n");
179 
180 }
181 
182 int
183 fsize(FILE *file)
184 {
185 	struct stat sb;
186 
187 	if (fstat(fileno(file), &sb) == 0)
188 		return sb.st_size;
189 	else
190 		return -1;
191 }
192 
193 #define DATASIZE(x) ((x).w * (x).h * 256 / 8)
194 
195 void
196 load_font(char *type, char *filename)
197 {
198 	FILE	*fd = NULL;
199 	int	h, i, size, w;
200 	unsigned long io = 0;	/* silence stupid gcc(1) in the Wall mode */
201 	char	*name, *fontmap;
202 	char	*prefix[]  = {"", "", FONT_PATH, FONT_PATH, NULL};
203 	char	*postfix[] = {"", ".fnt", "", ".fnt"};
204 
205 	struct sizeinfo {
206 		int w;
207 		int h;
208 		unsigned long io;
209 	} sizes[] = {{8, 16, PIO_FONT8x16},
210 		     {8, 14, PIO_FONT8x14},
211 		     {8,  8,  PIO_FONT8x8},
212 		     {0,  0,            0}};
213 
214 	for (i=0; prefix[i]; i++) {
215 		name = mkfullname(prefix[i], filename, postfix[i]);
216 		fd = fopen(name, "r");
217 		if (fd)
218 			break;
219 	}
220 	if (fd == NULL) {
221 		warn("%s: can't load font file", filename);
222 		return;
223 	}
224 	if (type != NULL) {
225 		size = 0;
226 		if (sscanf(type, "%dx%d", &w, &h) == 2)
227 			for (i = 0; sizes[i].w != 0; i++)
228 				if (sizes[i].w == w && sizes[i].h == h) {
229 					size = DATASIZE(sizes[i]);
230 					io = sizes[i].io;
231 				}
232 
233 		if (size == 0) {
234 			warnx("%s: bad font size specification", type);
235 			fclose(fd);
236 			return;
237 		}
238 	} else {
239 		/* Apply heuristics */
240 		int j;
241 		int dsize[2];
242 
243 		size = DATASIZE(sizes[0]);
244 		fontmap = (char*) malloc(size);
245 		dsize[0] = decode(fd, fontmap, size);
246 		dsize[1] = fsize(fd);
247 		free(fontmap);
248 
249 		size = 0;
250 		for (j = 0; j < 2; j++)
251 			for (i = 0; sizes[i].w != 0; i++)
252 				if (DATASIZE(sizes[i]) == dsize[j]) {
253 					size = dsize[j];
254 					io = sizes[i].io;
255 					j = 2;	/* XXX */
256 					break;
257 				}
258 
259 		if (size == 0) {
260 			warnx("%s: can't guess font size", filename);
261 			fclose(fd);
262 			return;
263 		}
264 		rewind(fd);
265 	}
266 
267 	fontmap = (char*) malloc(size);
268 	if (decode(fd, fontmap, size) != size) {
269 		rewind(fd);
270 		if (fsize(fd) != size || fread(fontmap, 1, size, fd) != size) {
271 			warnx("%s: bad font file", filename);
272 			fclose(fd);
273 			free(fontmap);
274 			return;
275 		}
276 	}
277 	if (ioctl(0, io, fontmap) < 0)
278 		warn("can't load font");
279 	fclose(fd);
280 	free(fontmap);
281 }
282 
283 void
284 set_screensaver_timeout(char *arg)
285 {
286 	int nsec;
287 
288 	if (!strcmp(arg, "off"))
289 		nsec = 0;
290 	else {
291 		nsec = atoi(arg);
292 		if ((*arg == '\0') || (nsec < 1)) {
293 			warnx("argument must be a positive number");
294 			return;
295 		}
296 	}
297 	if (ioctl(0, CONS_BLANKTIME, &nsec) == -1)
298 		warn("setting screensaver period");
299 }
300 
301 void
302 set_cursor_type(char *appearence)
303 {
304 	int type;
305 
306 	if (!strcmp(appearence, "normal"))
307 		type = 0;
308 	else if (!strcmp(appearence, "blink"))
309 		type = 1;
310 	else if (!strcmp(appearence, "destructive"))
311 		type = 3;
312 	else {
313 		warnx("argument to -c must be normal, blink or destructive");
314 		return;
315 	}
316 	ioctl(0, CONS_CURSORTYPE, &type);
317 }
318 
319 void
320 video_mode(int argc, char **argv, int *index)
321 {
322 	static struct {
323 		char *name;
324 		unsigned long mode;
325 	} modes[] = {
326 		{ "80x25",		SW_TEXT_80x25 },
327 		{ "80x30",		SW_TEXT_80x30 },
328 		{ "80x43",		SW_TEXT_80x43 },
329 		{ "80x50",		SW_TEXT_80x50 },
330 		{ "80x60",		SW_TEXT_80x60 },
331 		{ "132x25",		SW_TEXT_132x25 },
332 		{ "132x30",		SW_TEXT_132x30 },
333 		{ "132x43",		SW_TEXT_132x43 },
334 		{ "132x50",		SW_TEXT_132x50 },
335 		{ "132x60",		SW_TEXT_132x60 },
336 		{ "VGA_40x25",		SW_VGA_C40x25 },
337 		{ "VGA_80x25",		SW_VGA_C80x25 },
338 		{ "VGA_80x30",		SW_VGA_C80x30 },
339 		{ "VGA_80x50",		SW_VGA_C80x50 },
340 		{ "VGA_80x60",		SW_VGA_C80x60 },
341 #ifdef SW_VGA_C90x25
342 		{ "VGA_90x25",		SW_VGA_C90x25 },
343 		{ "VGA_90x30",		SW_VGA_C90x30 },
344 		{ "VGA_90x43",		SW_VGA_C90x43 },
345 		{ "VGA_90x50",		SW_VGA_C90x50 },
346 		{ "VGA_90x60",		SW_VGA_C90x60 },
347 #endif
348 		{ "VGA_320x200",	SW_VGA_CG320 },
349 		{ "EGA_80x25",		SW_ENH_C80x25 },
350 		{ "EGA_80x43",		SW_ENH_C80x43 },
351 		{ "VESA_132x25",	SW_VESA_C132x25 },
352 		{ "VESA_132x43",	SW_VESA_C132x43 },
353 		{ "VESA_132x50",	SW_VESA_C132x50 },
354 		{ "VESA_132x60",	SW_VESA_C132x60 },
355 		{ "VESA_800x600",	SW_VESA_800x600 },
356 		{ NULL },
357 	};
358 	unsigned long mode = 0;
359 	int cur_mode;
360 	int ioerr;
361 	int size[3];
362 	int i;
363 
364 	if (ioctl(0, CONS_GET, &cur_mode) < 0)
365 		err(1, "cannot get the current video mode");
366 	if (*index < argc) {
367 		for (i = 0; modes[i].name != NULL; ++i) {
368 			if (!strcmp(argv[*index], modes[i].name)) {
369 				mode = modes[i].mode;
370 				break;
371 			}
372 		}
373 		if (modes[i].name == NULL)
374 			return;
375 		if (ioctl(0, mode, NULL) < 0)
376 			warn("cannot set videomode");
377 		if (mode == SW_VESA_800x600) {
378 			/* columns */
379 			if ((vesa_cols * 8 > 800) || (vesa_cols <= 0)) {
380 				warnx("incorrect number of columns: %d",
381 				      vesa_cols);
382 				size[0] = _VESA_800x600_DFL_COLS;
383 			} else {
384 				size[0] = vesa_cols;
385 			}
386 			/* rows */
387 			if ((vesa_rows * _VESA_800x600_DFL_FNSZ > 600) ||
388 			    (vesa_rows <=0)) {
389 				warnx("incorrect number of rows: %d",
390 				      vesa_rows);
391 				size[1] = _VESA_800x600_DFL_ROWS;
392 			} else {
393 				size[1] = vesa_rows;
394 			}
395 			/* font size */
396 			size[2] = _VESA_800x600_DFL_FNSZ;
397 			if (ioctl(0, KDRASTER, size)) {
398 				ioerr = errno;
399 				if (cur_mode >= M_VESA_BASE)
400 					ioctl(0, _IO('V', cur_mode), NULL);
401 				else
402 					ioctl(0, _IO('S', cur_mode), NULL);
403 				warnc(ioerr, "cannot activate raster display");
404 			}
405 		}
406 		(*index)++;
407 	}
408 	return;
409 }
410 
411 int
412 get_color_number(char *color)
413 {
414 	int i;
415 
416 	for (i=0; i<16; i++)
417 		if (!strcmp(color, legal_colors[i]))
418 			return i;
419 	return -1;
420 }
421 
422 void
423 set_normal_colors(int argc, char **argv, int *index)
424 {
425 	int color;
426 
427 	if (*index < argc && (color = get_color_number(argv[*index])) != -1) {
428 		(*index)++;
429 		fprintf(stderr, "[=%dF", color);
430 		if (*index < argc
431 		    && (color = get_color_number(argv[*index])) != -1
432 		    && color < 8) {
433 			(*index)++;
434 			fprintf(stderr, "[=%dG", color);
435 		}
436 	}
437 }
438 
439 void
440 set_reverse_colors(int argc, char **argv, int *index)
441 {
442 	int color;
443 
444 	if ((color = get_color_number(argv[*(index)-1])) != -1) {
445 		fprintf(stderr, "[=%dH", color);
446 		if (*index < argc
447 		    && (color = get_color_number(argv[*index])) != -1
448 		    && color < 8) {
449 			(*index)++;
450 			fprintf(stderr, "[=%dI", color);
451 		}
452 	}
453 }
454 
455 void
456 set_console(char *arg)
457 {
458 	int n;
459 
460 	if( !arg || strspn(arg,"0123456789") != strlen(arg)) {
461 		warnx("bad console number");
462 		return;
463 	}
464 
465 	n = atoi(arg);
466 	if (n < 1 || n > 16) {
467 		warnx("console number out of range");
468 	} else if (ioctl(0, VT_ACTIVATE, (caddr_t) (long) n) == -1)
469 		warn("ioctl(VT_ACTIVATE)");
470 }
471 
472 void
473 set_border_color(char *arg)
474 {
475 	int color;
476 
477 	if ((color = get_color_number(arg)) != -1) {
478 		fprintf(stderr, "[=%dA", color);
479 	}
480 	else
481 		usage();
482 }
483 
484 void
485 set_mouse_char(char *arg)
486 {
487 	struct mouse_info mouse;
488 	long l;
489 
490 	l = strtol(arg, NULL, 0);
491 	if ((l < 0) || (l > UCHAR_MAX - 3)) {
492 		warnx("argument to -M must be 0 through %d", UCHAR_MAX - 3);
493 		return;
494 	}
495 	mouse.operation = MOUSE_MOUSECHAR;
496 	mouse.u.mouse_char = (int)l;
497 	ioctl(0, CONS_MOUSECTL, &mouse);
498 }
499 
500 void
501 set_mouse(char *arg)
502 {
503 	struct mouse_info mouse;
504 
505 	if (!strcmp(arg, "on"))
506 		mouse.operation = MOUSE_SHOW;
507 	else if (!strcmp(arg, "off"))
508 		mouse.operation = MOUSE_HIDE;
509 	else {
510 		warnx("argument to -m must either on or off");
511 		return;
512 	}
513 	ioctl(0, CONS_MOUSECTL, &mouse);
514 }
515 
516 static char
517 *adapter_name(int type)
518 {
519     static struct {
520 	int type;
521 	char *name;
522     } names[] = {
523 	{ KD_MONO,	"MDA" },
524 	{ KD_HERCULES,	"Hercules" },
525 	{ KD_CGA,	"CGA" },
526 	{ KD_EGA,	"EGA" },
527 	{ KD_VGA,	"VGA" },
528 	{ KD_PC98,	"PC-98xx" },
529 	{ KD_TGA,	"TGA" },
530 	{ -1,		"Unknown" },
531     };
532     int i;
533 
534     for (i = 0; names[i].type != -1; ++i)
535 	if (names[i].type == type)
536 	    break;
537     return names[i].name;
538 }
539 
540 void
541 show_adapter_info(void)
542 {
543 	struct video_adapter_info ad;
544 
545 	ad.va_index = 0;
546 	if (ioctl(0, CONS_ADPINFO, &ad)) {
547 		warn("failed to obtain adapter information");
548 		return;
549 	}
550 
551 	printf("fb%d:\n", ad.va_index);
552 	printf("    %.*s%d, type:%s%s (%d), flags:0x%x\n",
553 	       (int)sizeof(ad.va_name), ad.va_name, ad.va_unit,
554 	       (ad.va_flags & V_ADP_VESA) ? "VESA " : "",
555 	       adapter_name(ad.va_type), ad.va_type, ad.va_flags);
556 	printf("    initial mode:%d, current mode:%d, BIOS mode:%d\n",
557 	       ad.va_initial_mode, ad.va_mode, ad.va_initial_bios_mode);
558 	printf("    frame buffer window:0x%x, buffer size:0x%x\n",
559 	       ad.va_window, ad.va_buffer_size);
560 	printf("    window size:0x%x, origin:0x%x\n",
561 	       ad.va_window_size, ad.va_window_orig);
562 	printf("    display start address (%d, %d), scan line width:%d\n",
563 	       ad.va_disp_start.x, ad.va_disp_start.y, ad.va_line_width);
564 	printf("    reserved:0x%x\n", ad.va_unused0);
565 }
566 
567 void
568 show_mode_info(void)
569 {
570 	struct video_info info;
571 	char buf[80];
572 	int mode;
573 	int c;
574 
575 	printf("    mode#     flags   type    size       "
576 	       "font      window      linear buffer\n");
577 	printf("---------------------------------------"
578 	       "---------------------------------------\n");
579 	for (mode = 0; mode < M_VESA_MODE_MAX; ++mode) {
580 		info.vi_mode = mode;
581 		if (ioctl(0, CONS_MODEINFO, &info))
582 			continue;
583 		if (info.vi_mode != mode)
584 			continue;
585 
586 		printf("%3d (0x%03x)", mode, mode);
587     		printf(" 0x%08x", info.vi_flags);
588 		if (info.vi_flags & V_INFO_GRAPHICS) {
589 			c = 'G';
590 			snprintf(buf, sizeof(buf), "%dx%dx%d %d",
591 				 info.vi_width, info.vi_height,
592 				 info.vi_depth, info.vi_planes);
593 		} else {
594 			c = 'T';
595 			snprintf(buf, sizeof(buf), "%dx%d",
596 				 info.vi_width, info.vi_height);
597 		}
598 		printf(" %c %-15s", c, buf);
599 		snprintf(buf, sizeof(buf), "%dx%d",
600 			 info.vi_cwidth, info.vi_cheight);
601 		printf(" %-5s", buf);
602     		printf(" 0x%05x %2dk %2dk",
603 		       info.vi_window, (int)info.vi_window_size/1024,
604 		       (int)info.vi_window_gran/1024);
605     		printf(" 0x%08x %dk\n",
606 		       info.vi_buffer, (int)info.vi_buffer_size/1024);
607 	}
608 }
609 
610 void
611 show_info(char *arg)
612 {
613 	if (!strcmp(arg, "adapter"))
614 		show_adapter_info();
615 	else if (!strcmp(arg, "mode"))
616 		show_mode_info();
617 	else {
618 		warnx("argument to -i must either adapter or mode");
619 		return;
620 	}
621 }
622 
623 void
624 test_frame()
625 {
626 	int i;
627 
628 	fprintf(stdout, "[=0G\n\n");
629 	for (i=0; i<8; i++) {
630 		fprintf(stdout, "[=15F[=0G        %2d [=%dF%-16s"
631 				"[=15F[=0G        %2d [=%dF%-16s        "
632 				"[=15F %2d [=%dGBACKGROUND[=0G\n",
633 			i, i, legal_colors[i], i+8, i+8,
634 			legal_colors[i+8], i, i);
635 	}
636 	fprintf(stdout, "[=%dF[=%dG[=%dH[=%dI\n",
637 		info.mv_norm.fore, info.mv_norm.back,
638 		info.mv_rev.fore, info.mv_rev.back);
639 }
640 
641 int
642 main(int argc, char **argv)
643 {
644 	char	*font, *type;
645 	int	opt;
646 
647 
648 	info.size = sizeof(info);
649 	if (ioctl(0, CONS_GETINFO, &info) < 0)
650 		err(1, "must be on a virtual console");
651 	while((opt = getopt(argc, argv, "b:c:df:g:i:l:LM:m:r:s:t:x")) != -1)
652 		switch(opt) {
653 			case 'b':
654 				set_border_color(optarg);
655 				break;
656 			case 'c':
657 				set_cursor_type(optarg);
658 				break;
659 			case 'd':
660 				print_scrnmap();
661 				break;
662 			case 'f':
663 				type = optarg;
664 				font = nextarg(argc, argv, &optind, 'f', 0);
665 				if (font == NULL) {
666 					type = NULL;
667 					font = optarg;
668 				}
669 				load_font(type, font);
670 				break;
671 			case 'g':
672 				if (sscanf(optarg, "%dx%d", &vesa_cols,
673 					   &vesa_rows) != 2) {
674 					warnx("incorrect geometry: %s", optarg);
675 					usage();
676 				}
677 				break;
678 			case 'i':
679 				show_info(optarg);
680 				break;
681 			case 'l':
682 				load_scrnmap(optarg);
683 				break;
684 			case 'L':
685 				load_default_scrnmap();
686 				break;
687 			case 'M':
688 				set_mouse_char(optarg);
689 				break;
690 			case 'm':
691 				set_mouse(optarg);
692 				break;
693 			case 'r':
694 				set_reverse_colors(argc, argv, &optind);
695 				break;
696 			case 's':
697 				set_console(optarg);
698 				break;
699 			case 't':
700 				set_screensaver_timeout(optarg);
701 				break;
702 			case 'x':
703 				hex = 1;
704 				break;
705 			default:
706 				usage();
707 		}
708 	video_mode(argc, argv, &optind);
709 	set_normal_colors(argc, argv, &optind);
710 	if (optind < argc && !strcmp(argv[optind], "show")) {
711 		test_frame();
712 		optind++;
713 	}
714 	if ((optind != argc) || (argc == 1))
715 		usage();
716 	return 0;
717 }
718 
719