1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1994-1996 S��ren Schmidt
5 * All rights reserved.
6 *
7 * Portions of this software are based in part on the work of
8 * Sascha Wildner <saw@online.de> contributed to The DragonFly Project
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer,
15 *    in this position and unchanged.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. The name of the author may not be used to endorse or promote products
20 *    derived from this software without specific prior written permission
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * $DragonFly: src/usr.sbin/vidcontrol/vidcontrol.c,v 1.10 2005/03/02 06:08:29 joerg Exp $
34 */
35
36#include <ctype.h>
37#include <err.h>
38#include <limits.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43#include <sys/fbio.h>
44#include <sys/consio.h>
45#include <sys/endian.h>
46#include <sys/errno.h>
47#include <sys/param.h>
48#include <sys/types.h>
49#include <sys/stat.h>
50#include <sys/sysctl.h>
51#include "path.h"
52#include "decode.h"
53
54
55#define	DATASIZE(x)	((x).w * (x).h * 256 / 8)
56
57/* Screen dump modes */
58#define DUMP_FMT_RAW	1
59#define DUMP_FMT_TXT	2
60/* Screen dump options */
61#define DUMP_FBF	0
62#define DUMP_ALL	1
63/* Screen dump file format revision */
64#define DUMP_FMT_REV	1
65
66static const char *legal_colors[16] = {
67	"black", "blue", "green", "cyan",
68	"red", "magenta", "brown", "white",
69	"grey", "lightblue", "lightgreen", "lightcyan",
70	"lightred", "lightmagenta", "yellow", "lightwhite"
71};
72
73static struct {
74	int			active_vty;
75	vid_info_t		console_info;
76	unsigned char		screen_map[256];
77	int			video_mode_number;
78	struct video_info	video_mode_info;
79} cur_info;
80
81static int	hex = 0;
82static int	vesa_cols;
83static int	vesa_rows;
84static int	font_height;
85static int	vt4_mode = 0;
86static int	video_mode_changed;
87static struct	video_info new_mode_info;
88
89
90/*
91 * Initialize revert data.
92 *
93 * NOTE: the following parameters are not yet saved/restored:
94 *
95 *   screen saver timeout
96 *   cursor type
97 *   mouse character and mouse show/hide state
98 *   vty switching on/off state
99 *   history buffer size
100 *   history contents
101 *   font maps
102 */
103
104static void
105init(void)
106{
107	if (ioctl(0, VT_GETACTIVE, &cur_info.active_vty) == -1)
108		err(1, "getting active vty");
109
110	cur_info.console_info.size = sizeof(cur_info.console_info);
111	if (ioctl(0, CONS_GETINFO, &cur_info.console_info) == -1)
112		err(1, "getting console information");
113
114	/* vt(4) use unicode, so no screen mapping required. */
115	if (vt4_mode == 0 &&
116	    ioctl(0, GIO_SCRNMAP, &cur_info.screen_map) == -1)
117		err(1, "getting screen map");
118
119	if (ioctl(0, CONS_GET, &cur_info.video_mode_number) == -1)
120		err(1, "getting video mode number");
121
122	cur_info.video_mode_info.vi_mode = cur_info.video_mode_number;
123
124	if (ioctl(0, CONS_MODEINFO, &cur_info.video_mode_info) == -1)
125		err(1, "getting video mode parameters");
126}
127
128
129/*
130 * If something goes wrong along the way we call revert() to go back to the
131 * console state we came from (which is assumed to be working).
132 *
133 * NOTE: please also read the comments of init().
134 */
135
136static void
137revert(void)
138{
139	int save_errno, size[3];
140
141	save_errno = errno;
142
143	ioctl(0, VT_ACTIVATE, cur_info.active_vty);
144
145	ioctl(0, KDSBORDER, cur_info.console_info.mv_ovscan);
146	fprintf(stderr, "\033[=%dH", cur_info.console_info.mv_rev.fore);
147	fprintf(stderr, "\033[=%dI", cur_info.console_info.mv_rev.back);
148
149	if (vt4_mode == 0)
150		ioctl(0, PIO_SCRNMAP, &cur_info.screen_map);
151
152	if (video_mode_changed) {
153		if (cur_info.video_mode_number >= M_VESA_BASE)
154			ioctl(0,
155			    _IO('V', cur_info.video_mode_number - M_VESA_BASE),
156			    NULL);
157		else
158			ioctl(0, _IO('S', cur_info.video_mode_number), NULL);
159		if (cur_info.video_mode_info.vi_flags & V_INFO_GRAPHICS) {
160			size[0] = cur_info.console_info.mv_csz;
161			size[1] = cur_info.console_info.mv_rsz;
162			size[2] = cur_info.console_info.font_size;
163			ioctl(0, KDRASTER, size);
164		}
165	}
166
167	/* Restore some colors last since mode setting forgets some. */
168	fprintf(stderr, "\033[=%dF", cur_info.console_info.mv_norm.fore);
169	fprintf(stderr, "\033[=%dG", cur_info.console_info.mv_norm.back);
170
171	errno = save_errno;
172}
173
174
175/*
176 * Print a short usage string describing all options, then exit.
177 */
178
179static void
180usage(void)
181{
182	if (vt4_mode)
183		fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
184"usage: vidcontrol [-Cx] [-b color] [-c appearance] [-f [[size] file]]",
185"                  [-g geometry] [-h size] [-i active | adapter | mode]",
186"                  [-M char] [-m on | off]",
187"                  [-r foreground background] [-S on | off] [-s number]",
188"                  [-T xterm | cons25] [-t N | off] [mode]",
189"                  [foreground [background]] [show]");
190	else
191		fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
192"usage: vidcontrol [-CdHLPpx] [-b color] [-c appearance] [-E emulator]",
193"                  [-f [[size] file]] [-g geometry] [-h size]",
194"                  [-i active | adapter | mode] [-l screen_map] [-M char]",
195"                  [-m on | off] [-r foreground background] [-S on | off]",
196"                  [-s number] [-T xterm | cons25] [-t N | off] [mode]",
197"                  [foreground [background]] [show]");
198	exit(1);
199}
200
201/* Detect presence of vt(4). */
202static int
203is_vt4(void)
204{
205	char vty_name[4] = "";
206	size_t len = sizeof(vty_name);
207
208	if (sysctlbyname("kern.vty", vty_name, &len, NULL, 0) != 0)
209		return (0);
210	return (strcmp(vty_name, "vt") == 0);
211}
212
213/*
214 * Retrieve the next argument from the command line (for options that require
215 * more than one argument).
216 */
217
218static char *
219nextarg(int ac, char **av, int *indp, int oc, int strict)
220{
221	if (*indp < ac)
222		return(av[(*indp)++]);
223
224	if (strict != 0) {
225		revert();
226		errx(1, "option requires two arguments -- %c", oc);
227	}
228
229	return(NULL);
230}
231
232
233/*
234 * Guess which file to open. Try to open each combination of a specified set
235 * of file name components.
236 */
237
238static FILE *
239openguess(const char *a[], const char *b[], const char *c[], const char *d[], char **name)
240{
241	FILE *f;
242	int i, j, k, l;
243
244	for (i = 0; a[i] != NULL; i++) {
245		for (j = 0; b[j] != NULL; j++) {
246			for (k = 0; c[k] != NULL; k++) {
247				for (l = 0; d[l] != NULL; l++) {
248					asprintf(name, "%s%s%s%s",
249						 a[i], b[j], c[k], d[l]);
250
251					f = fopen(*name, "r");
252
253					if (f != NULL)
254						return (f);
255
256					free(*name);
257				}
258			}
259		}
260	}
261	return (NULL);
262}
263
264
265/*
266 * Load a screenmap from a file and set it.
267 */
268
269static void
270load_scrnmap(const char *filename)
271{
272	FILE *fd;
273	int size;
274	char *name;
275	scrmap_t scrnmap;
276	const char *a[] = {"", SCRNMAP_PATH, NULL};
277	const char *b[] = {filename, NULL};
278	const char *c[] = {"", ".scm", NULL};
279	const char *d[] = {"", NULL};
280
281	fd = openguess(a, b, c, d, &name);
282
283	if (fd == NULL) {
284		revert();
285		errx(1, "screenmap file not found");
286	}
287
288	size = sizeof(scrnmap);
289
290	if (decode(fd, (char *)&scrnmap, size) != size) {
291		rewind(fd);
292
293		if (fread(&scrnmap, 1, size, fd) != (size_t)size) {
294			fclose(fd);
295			revert();
296			errx(1, "bad screenmap file");
297		}
298	}
299
300	if (ioctl(0, PIO_SCRNMAP, &scrnmap) == -1) {
301		revert();
302		err(1, "loading screenmap");
303	}
304
305	fclose(fd);
306}
307
308
309/*
310 * Set the default screenmap.
311 */
312
313static void
314load_default_scrnmap(void)
315{
316	scrmap_t scrnmap;
317	int i;
318
319	for (i=0; i<256; i++)
320		*((char*)&scrnmap + i) = i;
321
322	if (ioctl(0, PIO_SCRNMAP, &scrnmap) == -1) {
323		revert();
324		err(1, "loading default screenmap");
325	}
326}
327
328
329/*
330 * Print the current screenmap to stdout.
331 */
332
333static void
334print_scrnmap(void)
335{
336	unsigned char map[256];
337	size_t i;
338
339	if (ioctl(0, GIO_SCRNMAP, &map) == -1) {
340		revert();
341		err(1, "getting screenmap");
342	}
343	for (i=0; i<sizeof(map); i++) {
344		if (i != 0 && i % 16 == 0)
345			fprintf(stdout, "\n");
346
347		if (hex != 0)
348			fprintf(stdout, " %02x", map[i]);
349		else
350			fprintf(stdout, " %03d", map[i]);
351	}
352	fprintf(stdout, "\n");
353
354}
355
356
357/*
358 * Determine a file's size.
359 */
360
361static int
362fsize(FILE *file)
363{
364	struct stat sb;
365
366	if (fstat(fileno(file), &sb) == 0)
367		return sb.st_size;
368	else
369		return -1;
370}
371
372static vfnt_map_t *
373load_vt4mappingtable(unsigned int nmappings, FILE *f)
374{
375	vfnt_map_t *t;
376	unsigned int i;
377
378	if (nmappings == 0)
379		return (NULL);
380
381	if ((t = calloc(nmappings, sizeof(*t))) == NULL) {
382		warn("calloc");
383		return (NULL);
384	}
385
386	if (fread(t, sizeof *t * nmappings, 1, f) != 1) {
387		warn("read mappings");
388		free(t);
389		return (NULL);
390	}
391
392	for (i = 0; i < nmappings; i++) {
393		t[i].vfm_src = be32toh(t[i].vfm_src);
394		t[i].vfm_dst = be16toh(t[i].vfm_dst);
395		t[i].vfm_len = be16toh(t[i].vfm_len);
396	}
397
398	return (t);
399}
400
401/*
402 * Set the default vt font.
403 */
404
405static void
406load_default_vt4font(void)
407{
408	if (ioctl(0, PIO_VFONT_DEFAULT) == -1) {
409		revert();
410		err(1, "loading default vt font");
411	}
412}
413
414static void
415load_vt4font(FILE *f)
416{
417	struct font_header fh;
418	static vfnt_t vfnt;
419	size_t glyphsize;
420	unsigned int i;
421
422	if (fread(&fh, sizeof fh, 1, f) != 1) {
423		warn("read file_header");
424		return;
425	}
426
427	if (memcmp(fh.fh_magic, "VFNT0002", 8) != 0) {
428		warnx("bad magic in font file\n");
429		return;
430	}
431
432	for (i = 0; i < VFNT_MAPS; i++)
433		vfnt.map_count[i] = be32toh(fh.fh_map_count[i]);
434	vfnt.glyph_count = be32toh(fh.fh_glyph_count);
435	vfnt.width = fh.fh_width;
436	vfnt.height = fh.fh_height;
437
438	glyphsize = howmany(vfnt.width, 8) * vfnt.height * vfnt.glyph_count;
439	if ((vfnt.glyphs = malloc(glyphsize)) == NULL) {
440		warn("malloc");
441		return;
442	}
443
444	if (fread(vfnt.glyphs, glyphsize, 1, f) != 1) {
445		warn("read glyphs");
446		free(vfnt.glyphs);
447		return;
448	}
449
450	for (i = 0; i < VFNT_MAPS; i++)
451		vfnt.map[i] = load_vt4mappingtable(vfnt.map_count[i], f);
452
453	if (ioctl(STDIN_FILENO, PIO_VFONT, &vfnt) == -1)
454		warn("PIO_VFONT");
455
456	for (i = 0; i < VFNT_MAPS; i++)
457		free(vfnt.map[i]);
458	free(vfnt.glyphs);
459}
460
461/*
462 * Load a font from file and set it.
463 */
464
465static void
466load_font(const char *type, const char *filename)
467{
468	FILE	*fd;
469	int	h, i, size, w;
470	unsigned long io = 0;	/* silence stupid gcc(1) in the Wall mode */
471	char	*name, *fontmap, size_sufx[6];
472	const char	*a[] = {"", FONT_PATH, NULL};
473	const char	*vt4a[] = {"", VT_FONT_PATH, NULL};
474	const char	*b[] = {filename, NULL};
475	const char	*c[] = {"", size_sufx, NULL};
476	const char	*d[] = {"", ".fnt", NULL};
477	vid_info_t info;
478
479	struct sizeinfo {
480		int w;
481		int h;
482		unsigned long io;
483	} sizes[] = {{8, 16, PIO_FONT8x16},
484		     {8, 14, PIO_FONT8x14},
485		     {8,  8,  PIO_FONT8x8},
486		     {0,  0,            0}};
487
488	if (vt4_mode) {
489		size_sufx[0] = '\0';
490	} else {
491		info.size = sizeof(info);
492		if (ioctl(0, CONS_GETINFO, &info) == -1) {
493			revert();
494			err(1, "getting console information");
495		}
496
497		snprintf(size_sufx, sizeof(size_sufx), "-8x%d", info.font_size);
498	}
499	fd = openguess((vt4_mode == 0) ? a : vt4a, b, c, d, &name);
500
501	if (fd == NULL) {
502		revert();
503		errx(1, "%s: can't load font file", filename);
504	}
505
506	if (vt4_mode) {
507		load_vt4font(fd);
508		fclose(fd);
509		return;
510	}
511
512	if (type != NULL) {
513		size = 0;
514		if (sscanf(type, "%dx%d", &w, &h) == 2) {
515			for (i = 0; sizes[i].w != 0; i++) {
516				if (sizes[i].w == w && sizes[i].h == h) {
517					size = DATASIZE(sizes[i]);
518					io = sizes[i].io;
519					font_height = sizes[i].h;
520				}
521			}
522		}
523		if (size == 0) {
524			fclose(fd);
525			revert();
526			errx(1, "%s: bad font size specification", type);
527		}
528	} else {
529		/* Apply heuristics */
530
531		int j;
532		int dsize[2];
533
534		size = DATASIZE(sizes[0]);
535		fontmap = (char*) malloc(size);
536		dsize[0] = decode(fd, fontmap, size);
537		dsize[1] = fsize(fd);
538		free(fontmap);
539
540		size = 0;
541		for (j = 0; j < 2; j++) {
542			for (i = 0; sizes[i].w != 0; i++) {
543				if (DATASIZE(sizes[i]) == dsize[j]) {
544					size = dsize[j];
545					io = sizes[i].io;
546					font_height = sizes[i].h;
547					j = 2;	/* XXX */
548					break;
549				}
550			}
551		}
552
553		if (size == 0) {
554			fclose(fd);
555			revert();
556			errx(1, "%s: can't guess font size", filename);
557		}
558
559		rewind(fd);
560	}
561
562	fontmap = (char*) malloc(size);
563
564	if (decode(fd, fontmap, size) != size) {
565		rewind(fd);
566		if (fsize(fd) != size ||
567		    fread(fontmap, 1, size, fd) != (size_t)size) {
568			fclose(fd);
569			free(fontmap);
570			revert();
571			errx(1, "%s: bad font file", filename);
572		}
573	}
574
575	if (ioctl(0, io, fontmap) == -1) {
576		revert();
577		err(1, "loading font");
578	}
579
580	fclose(fd);
581	free(fontmap);
582}
583
584
585/*
586 * Set the timeout for the screensaver.
587 */
588
589static void
590set_screensaver_timeout(char *arg)
591{
592	int nsec;
593
594	if (!strcmp(arg, "off")) {
595		nsec = 0;
596	} else {
597		nsec = atoi(arg);
598
599		if ((*arg == '\0') || (nsec < 1)) {
600			revert();
601			errx(1, "argument must be a positive number");
602		}
603	}
604
605	if (ioctl(0, CONS_BLANKTIME, &nsec) == -1) {
606		revert();
607		err(1, "setting screensaver period");
608	}
609}
610
611static void
612parse_cursor_params(char *param, struct cshape *shape)
613{
614	char *dupparam, *word;
615	int type;
616
617	param = dupparam = strdup(param);
618	type = shape->shape[0];
619	while ((word = strsep(&param, ",")) != NULL) {
620		if (strcmp(word, "normal") == 0)
621			type = 0;
622		else if (strcmp(word, "destructive") == 0)
623			type = CONS_BLINK_CURSOR | CONS_CHAR_CURSOR;
624		else if (strcmp(word, "blink") == 0)
625			type |= CONS_BLINK_CURSOR;
626		else if (strcmp(word, "noblink") == 0)
627			type &= ~CONS_BLINK_CURSOR;
628		else if (strcmp(word, "block") == 0)
629			type &= ~CONS_CHAR_CURSOR;
630		else if (strcmp(word, "noblock") == 0)
631			type |= CONS_CHAR_CURSOR;
632		else if (strcmp(word, "hidden") == 0)
633			type |= CONS_HIDDEN_CURSOR;
634		else if (strcmp(word, "nohidden") == 0)
635			type &= ~CONS_HIDDEN_CURSOR;
636		else if (strncmp(word, "base=", 5) == 0)
637			shape->shape[1] = strtol(word + 5, NULL, 0);
638		else if (strncmp(word, "height=", 7) == 0)
639			shape->shape[2] = strtol(word + 7, NULL, 0);
640		else if (strcmp(word, "charcolors") == 0)
641			type |= CONS_CHARCURSOR_COLORS;
642		else if (strcmp(word, "mousecolors") == 0)
643			type |= CONS_MOUSECURSOR_COLORS;
644		else if (strcmp(word, "default") == 0)
645			type |= CONS_DEFAULT_CURSOR;
646		else if (strcmp(word, "shapeonly") == 0)
647			type |= CONS_SHAPEONLY_CURSOR;
648		else if (strcmp(word, "local") == 0)
649			type |= CONS_LOCAL_CURSOR;
650		else if (strcmp(word, "reset") == 0)
651			type |= CONS_RESET_CURSOR;
652		else if (strcmp(word, "show") == 0)
653			printf("flags %#x, base %d, height %d\n",
654			    type, shape->shape[1], shape->shape[2]);
655		else {
656			revert();
657			errx(1,
658			    "invalid parameters for -c starting at '%s%s%s'",
659			    word, param != NULL ? "," : "",
660			    param != NULL ? param : "");
661		}
662	}
663	free(dupparam);
664	shape->shape[0] = type;
665}
666
667
668/*
669 * Set the cursor's shape/type.
670 */
671
672static void
673set_cursor_type(char *param)
674{
675	struct cshape shape;
676
677	/* Dry run to determine color, default and local flags. */
678	shape.shape[0] = 0;
679	shape.shape[1] = -1;
680	shape.shape[2] = -1;
681	parse_cursor_params(param, &shape);
682
683	/* Get the relevant old setting. */
684	if (ioctl(0, CONS_GETCURSORSHAPE, &shape) != 0) {
685		revert();
686		err(1, "ioctl(CONS_GETCURSORSHAPE)");
687	}
688
689	parse_cursor_params(param, &shape);
690	if (ioctl(0, CONS_SETCURSORSHAPE, &shape) != 0) {
691		revert();
692		err(1, "ioctl(CONS_SETCURSORSHAPE)");
693	}
694}
695
696
697/*
698 * Set the video mode.
699 */
700
701static void
702video_mode(int argc, char **argv, int *mode_index)
703{
704	static struct {
705		const char *name;
706		unsigned long mode;
707		unsigned long mode_num;
708	} modes[] = {
709		{ "80x25",        SW_TEXT_80x25,   M_TEXT_80x25 },
710		{ "80x30",        SW_TEXT_80x30,   M_TEXT_80x30 },
711		{ "80x43",        SW_TEXT_80x43,   M_TEXT_80x43 },
712		{ "80x50",        SW_TEXT_80x50,   M_TEXT_80x50 },
713		{ "80x60",        SW_TEXT_80x60,   M_TEXT_80x60 },
714		{ "132x25",       SW_TEXT_132x25,  M_TEXT_132x25 },
715		{ "132x30",       SW_TEXT_132x30,  M_TEXT_132x30 },
716		{ "132x43",       SW_TEXT_132x43,  M_TEXT_132x43 },
717		{ "132x50",       SW_TEXT_132x50,  M_TEXT_132x50 },
718		{ "132x60",       SW_TEXT_132x60,  M_TEXT_132x60 },
719		{ "VGA_40x25",    SW_VGA_C40x25,   M_VGA_C40x25 },
720		{ "VGA_80x25",    SW_VGA_C80x25,   M_VGA_C80x25 },
721		{ "VGA_80x30",    SW_VGA_C80x30,   M_VGA_C80x30 },
722		{ "VGA_80x50",    SW_VGA_C80x50,   M_VGA_C80x50 },
723		{ "VGA_80x60",    SW_VGA_C80x60,   M_VGA_C80x60 },
724#ifdef SW_VGA_C90x25
725		{ "VGA_90x25",    SW_VGA_C90x25,   M_VGA_C90x25 },
726		{ "VGA_90x30",    SW_VGA_C90x30,   M_VGA_C90x30 },
727		{ "VGA_90x43",    SW_VGA_C90x43,   M_VGA_C90x43 },
728		{ "VGA_90x50",    SW_VGA_C90x50,   M_VGA_C90x50 },
729		{ "VGA_90x60",    SW_VGA_C90x60,   M_VGA_C90x60 },
730#endif
731		{ "VGA_320x200",	SW_VGA_CG320,	M_CG320 },
732		{ "EGA_80x25",		SW_ENH_C80x25,	M_ENH_C80x25 },
733		{ "EGA_80x43",		SW_ENH_C80x43,	M_ENH_C80x43 },
734		{ "VESA_132x25",	SW_VESA_C132x25,M_VESA_C132x25 },
735		{ "VESA_132x43",	SW_VESA_C132x43,M_VESA_C132x43 },
736		{ "VESA_132x50",	SW_VESA_C132x50,M_VESA_C132x50 },
737		{ "VESA_132x60",	SW_VESA_C132x60,M_VESA_C132x60 },
738		{ "VESA_800x600",	SW_VESA_800x600,M_VESA_800x600 },
739		{ NULL, 0, 0 },
740	};
741
742	int new_mode_num = 0;
743	unsigned long mode = 0;
744	int cur_mode;
745	int save_errno;
746	int size[3];
747	int i;
748
749	if (ioctl(0, CONS_GET, &cur_mode) < 0)
750		err(1, "cannot get the current video mode");
751
752	/*
753	 * Parse the video mode argument...
754	 */
755
756	if (*mode_index < argc) {
757		if (!strncmp(argv[*mode_index], "MODE_", 5)) {
758			if (!isdigit(argv[*mode_index][5]))
759				errx(1, "invalid video mode number");
760
761			new_mode_num = atoi(&argv[*mode_index][5]);
762		} else {
763			for (i = 0; modes[i].name != NULL; ++i) {
764				if (!strcmp(argv[*mode_index], modes[i].name)) {
765					mode = modes[i].mode;
766					new_mode_num = modes[i].mode_num;
767					break;
768				}
769			}
770
771			if (modes[i].name == NULL)
772				return;
773			if (ioctl(0, mode, NULL) < 0) {
774				revert();
775				err(1, "cannot set videomode");
776			}
777			video_mode_changed = 1;
778		}
779
780		/*
781		 * Collect enough information about the new video mode...
782		 */
783
784		new_mode_info.vi_mode = new_mode_num;
785
786		if (ioctl(0, CONS_MODEINFO, &new_mode_info) == -1) {
787			revert();
788			err(1, "obtaining new video mode parameters");
789		}
790
791		if (mode == 0) {
792			if (new_mode_num >= M_VESA_BASE)
793				mode = _IO('V', new_mode_num - M_VESA_BASE);
794			else
795				mode = _IO('S', new_mode_num);
796		}
797
798		/*
799		 * Try setting the new mode.
800		 */
801
802		if (ioctl(0, mode, NULL) == -1) {
803			revert();
804			err(1, "setting video mode");
805		}
806		video_mode_changed = 1;
807
808		/*
809		 * For raster modes it's not enough to just set the mode.
810		 * We also need to explicitly set the raster mode.
811		 */
812
813		if (new_mode_info.vi_flags & V_INFO_GRAPHICS) {
814			/* font size */
815
816			if (font_height == 0)
817				font_height = cur_info.console_info.font_size;
818
819			size[2] = font_height;
820
821			/* adjust columns */
822
823			if ((vesa_cols * 8 > new_mode_info.vi_width) ||
824			    (vesa_cols <= 0)) {
825				size[0] = new_mode_info.vi_width / 8;
826			} else {
827				size[0] = vesa_cols;
828			}
829
830			/* adjust rows */
831
832			if ((vesa_rows * font_height > new_mode_info.vi_height) ||
833			    (vesa_rows <= 0)) {
834				size[1] = new_mode_info.vi_height /
835					  font_height;
836			} else {
837				size[1] = vesa_rows;
838			}
839
840			/* set raster mode */
841
842			if (ioctl(0, KDRASTER, size)) {
843				save_errno = errno;
844				if (cur_mode >= M_VESA_BASE)
845					ioctl(0,
846					    _IO('V', cur_mode - M_VESA_BASE),
847					    NULL);
848				else
849					ioctl(0, _IO('S', cur_mode), NULL);
850				revert();
851				errno = save_errno;
852				err(1, "cannot activate raster display");
853			}
854		}
855
856		/* Recover from mode setting forgetting colors. */
857		fprintf(stderr, "\033[=%dF",
858		    cur_info.console_info.mv_norm.fore);
859		fprintf(stderr, "\033[=%dG",
860		    cur_info.console_info.mv_norm.back);
861
862		(*mode_index)++;
863	}
864}
865
866
867/*
868 * Return the number for a specified color name.
869 */
870
871static int
872get_color_number(char *color)
873{
874	int i;
875
876	for (i=0; i<16; i++) {
877		if (!strcmp(color, legal_colors[i]))
878			return i;
879	}
880	return -1;
881}
882
883
884/*
885 * Set normal text and background colors.
886 */
887
888static void
889set_normal_colors(int argc, char **argv, int *_index)
890{
891	int color;
892
893	if (*_index < argc && (color = get_color_number(argv[*_index])) != -1) {
894		(*_index)++;
895		fprintf(stderr, "\033[=%dF", color);
896		if (*_index < argc
897		    && (color = get_color_number(argv[*_index])) != -1) {
898			(*_index)++;
899			fprintf(stderr, "\033[=%dG", color);
900		}
901	}
902}
903
904
905/*
906 * Set reverse text and background colors.
907 */
908
909static void
910set_reverse_colors(int argc, char **argv, int *_index)
911{
912	int color;
913
914	if ((color = get_color_number(argv[*(_index)-1])) != -1) {
915		fprintf(stderr, "\033[=%dH", color);
916		if (*_index < argc
917		    && (color = get_color_number(argv[*_index])) != -1) {
918			(*_index)++;
919			fprintf(stderr, "\033[=%dI", color);
920		}
921	}
922}
923
924
925/*
926 * Switch to virtual terminal #arg.
927 */
928
929static void
930set_console(char *arg)
931{
932	int n;
933
934	if(!arg || strspn(arg,"0123456789") != strlen(arg)) {
935		revert();
936		errx(1, "bad console number");
937	}
938
939	n = atoi(arg);
940
941	if (n < 1 || n > 16) {
942		revert();
943		errx(1, "console number out of range");
944	} else if (ioctl(0, VT_ACTIVATE, n) == -1) {
945		revert();
946		err(1, "switching vty");
947	}
948}
949
950
951/*
952 * Sets the border color.
953 */
954
955static void
956set_border_color(char *arg)
957{
958	int color;
959
960	color = get_color_number(arg);
961	if (color == -1) {
962		revert();
963		errx(1, "invalid color '%s'", arg);
964	}
965	if (ioctl(0, KDSBORDER, color) != 0) {
966		revert();
967		err(1, "ioctl(KD_SBORDER)");
968	}
969}
970
971static void
972set_mouse_char(char *arg)
973{
974	struct mouse_info mouse;
975	long l;
976
977	l = strtol(arg, NULL, 0);
978
979	if ((l < 0) || (l > UCHAR_MAX - 3)) {
980		revert();
981		warnx("argument to -M must be 0 through %d", UCHAR_MAX - 3);
982		return;
983	}
984
985	mouse.operation = MOUSE_MOUSECHAR;
986	mouse.u.mouse_char = (int)l;
987
988	if (ioctl(0, CONS_MOUSECTL, &mouse) == -1) {
989		revert();
990		err(1, "setting mouse character");
991	}
992}
993
994
995/*
996 * Show/hide the mouse.
997 */
998
999static void
1000set_mouse(char *arg)
1001{
1002	struct mouse_info mouse;
1003
1004	if (!strcmp(arg, "on")) {
1005		mouse.operation = MOUSE_SHOW;
1006	} else if (!strcmp(arg, "off")) {
1007		mouse.operation = MOUSE_HIDE;
1008	} else {
1009		revert();
1010		errx(1, "argument to -m must be either on or off");
1011	}
1012
1013	if (ioctl(0, CONS_MOUSECTL, &mouse) == -1) {
1014		revert();
1015		err(1, "%sing the mouse",
1016		     mouse.operation == MOUSE_SHOW ? "show" : "hid");
1017	}
1018}
1019
1020
1021static void
1022set_lockswitch(char *arg)
1023{
1024	int data;
1025
1026	if (!strcmp(arg, "off")) {
1027		data = 0x01;
1028	} else if (!strcmp(arg, "on")) {
1029		data = 0x02;
1030	} else {
1031		revert();
1032		errx(1, "argument to -S must be either on or off");
1033	}
1034
1035	if (ioctl(0, VT_LOCKSWITCH, &data) == -1) {
1036		revert();
1037		err(1, "turning %s vty switching",
1038		     data == 0x01 ? "off" : "on");
1039	}
1040}
1041
1042
1043/*
1044 * Return the adapter name for a specified type.
1045 */
1046
1047static const char
1048*adapter_name(int type)
1049{
1050    static struct {
1051	int type;
1052	const char *name;
1053    } names[] = {
1054	{ KD_MONO,	"MDA" },
1055	{ KD_HERCULES,	"Hercules" },
1056	{ KD_CGA,	"CGA" },
1057	{ KD_EGA,	"EGA" },
1058	{ KD_VGA,	"VGA" },
1059	{ KD_TGA,	"TGA" },
1060	{ -1,		"Unknown" },
1061    };
1062
1063    int i;
1064
1065    for (i = 0; names[i].type != -1; ++i)
1066	if (names[i].type == type)
1067	    break;
1068    return names[i].name;
1069}
1070
1071
1072/*
1073 * Show active VTY, ie current console number.
1074 */
1075
1076static void
1077show_active_info(void)
1078{
1079
1080	printf("%d\n", cur_info.active_vty);
1081}
1082
1083
1084/*
1085 * Show graphics adapter information.
1086 */
1087
1088static void
1089show_adapter_info(void)
1090{
1091	struct video_adapter_info ad;
1092
1093	ad.va_index = 0;
1094
1095	if (ioctl(0, CONS_ADPINFO, &ad) == -1) {
1096		revert();
1097		err(1, "obtaining adapter information");
1098	}
1099
1100	printf("fb%d:\n", ad.va_index);
1101	printf("    %.*s%d, type:%s%s (%d), flags:0x%x\n",
1102	       (int)sizeof(ad.va_name), ad.va_name, ad.va_unit,
1103	       (ad.va_flags & V_ADP_VESA) ? "VESA " : "",
1104	       adapter_name(ad.va_type), ad.va_type, ad.va_flags);
1105	printf("    initial mode:%d, current mode:%d, BIOS mode:%d\n",
1106	       ad.va_initial_mode, ad.va_mode, ad.va_initial_bios_mode);
1107	printf("    frame buffer window:0x%zx, buffer size:0x%zx\n",
1108	       ad.va_window, ad.va_buffer_size);
1109	printf("    window size:0x%zx, origin:0x%x\n",
1110	       ad.va_window_size, ad.va_window_orig);
1111	printf("    display start address (%d, %d), scan line width:%d\n",
1112	       ad.va_disp_start.x, ad.va_disp_start.y, ad.va_line_width);
1113	printf("    reserved:0x%zx\n", ad.va_unused0);
1114}
1115
1116
1117/*
1118 * Show video mode information.
1119 */
1120
1121static void
1122show_mode_info(void)
1123{
1124	char buf[80];
1125	struct video_info info;
1126	int c;
1127	int mm;
1128	int mode;
1129
1130	printf("    mode#     flags   type    size       "
1131	       "font      window      linear buffer\n");
1132	printf("---------------------------------------"
1133	       "---------------------------------------\n");
1134
1135	memset(&info, 0, sizeof(info));
1136	for (mode = 0; mode <= M_VESA_MODE_MAX; ++mode) {
1137		info.vi_mode = mode;
1138		if (ioctl(0, CONS_MODEINFO, &info))
1139			continue;
1140		if (info.vi_mode != mode)
1141			continue;
1142		if (info.vi_width == 0 && info.vi_height == 0 &&
1143		    info.vi_cwidth == 0 && info.vi_cheight == 0)
1144			continue;
1145
1146		printf("%3d (0x%03x)", mode, mode);
1147    		printf(" 0x%08x", info.vi_flags);
1148		if (info.vi_flags & V_INFO_GRAPHICS) {
1149			c = 'G';
1150
1151			if (info.vi_mem_model == V_INFO_MM_PLANAR)
1152				snprintf(buf, sizeof(buf), "%dx%dx%d %d",
1153				    info.vi_width, info.vi_height,
1154				    info.vi_depth, info.vi_planes);
1155			else {
1156				switch (info.vi_mem_model) {
1157				case V_INFO_MM_PACKED:
1158					mm = 'P';
1159					break;
1160				case V_INFO_MM_DIRECT:
1161					mm = 'D';
1162					break;
1163				case V_INFO_MM_CGA:
1164					mm = 'C';
1165					break;
1166				case V_INFO_MM_HGC:
1167					mm = 'H';
1168					break;
1169				case V_INFO_MM_VGAX:
1170					mm = 'V';
1171					break;
1172				default:
1173					mm = ' ';
1174					break;
1175				}
1176				snprintf(buf, sizeof(buf), "%dx%dx%d %c",
1177				    info.vi_width, info.vi_height,
1178				    info.vi_depth, mm);
1179			}
1180		} else {
1181			c = 'T';
1182
1183			snprintf(buf, sizeof(buf), "%dx%d",
1184				 info.vi_width, info.vi_height);
1185		}
1186
1187		printf(" %c %-15s", c, buf);
1188		snprintf(buf, sizeof(buf), "%dx%d",
1189			 info.vi_cwidth, info.vi_cheight);
1190		printf(" %-5s", buf);
1191    		printf(" 0x%05zx %2dk %2dk",
1192		       info.vi_window, (int)info.vi_window_size/1024,
1193		       (int)info.vi_window_gran/1024);
1194    		printf(" 0x%08zx %dk\n",
1195		       info.vi_buffer, (int)info.vi_buffer_size/1024);
1196	}
1197}
1198
1199
1200static void
1201show_info(char *arg)
1202{
1203
1204	if (!strcmp(arg, "active")) {
1205		show_active_info();
1206	} else if (!strcmp(arg, "adapter")) {
1207		show_adapter_info();
1208	} else if (!strcmp(arg, "mode")) {
1209		show_mode_info();
1210	} else {
1211		revert();
1212		errx(1, "argument to -i must be active, adapter, or mode");
1213	}
1214}
1215
1216
1217static void
1218test_frame(void)
1219{
1220	vid_info_t info;
1221	const char *bg, *sep;
1222	int i, fore;
1223
1224	info.size = sizeof(info);
1225	if (ioctl(0, CONS_GETINFO, &info) == -1)
1226		err(1, "getting console information");
1227
1228	fore = 15;
1229	if (info.mv_csz < 80) {
1230		bg = "BG";
1231		sep = " ";
1232	} else {
1233		bg = "BACKGROUND";
1234		sep = "    ";
1235	}
1236
1237	fprintf(stdout, "\033[=0G\n\n");
1238	for (i=0; i<8; i++) {
1239		fprintf(stdout,
1240		    "\033[=%dF\033[=0G%2d \033[=%dF%-7s%s"
1241		    "\033[=%dF\033[=0G%2d \033[=%dF%-12s%s"
1242		    "\033[=%dF%2d \033[=%dG%s\033[=0G%s"
1243		    "\033[=%dF%2d \033[=%dG%s\033[=0G\n",
1244		    fore, i, i, legal_colors[i], sep,
1245		    fore, i + 8, i + 8, legal_colors[i + 8], sep,
1246		    fore, i, i, bg, sep,
1247		    fore, i + 8, i + 8, bg);
1248	}
1249	fprintf(stdout, "\033[=%dF\033[=%dG\033[=%dH\033[=%dI\n",
1250		info.mv_norm.fore, info.mv_norm.back,
1251		info.mv_rev.fore, info.mv_rev.back);
1252}
1253
1254
1255/*
1256 * Snapshot the video memory of that terminal, using the CONS_SCRSHOT
1257 * ioctl, and writes the results to stdout either in the special
1258 * binary format (see manual page for details), or in the plain
1259 * text format.
1260 */
1261
1262static void
1263dump_screen(int mode, int opt)
1264{
1265	scrshot_t shot;
1266	vid_info_t info;
1267
1268	info.size = sizeof(info);
1269	if (ioctl(0, CONS_GETINFO, &info) == -1) {
1270		revert();
1271		err(1, "getting console information");
1272	}
1273
1274	shot.x = shot.y = 0;
1275	shot.xsize = info.mv_csz;
1276	shot.ysize = info.mv_rsz;
1277	if (opt == DUMP_ALL)
1278		shot.ysize += info.mv_hsz;
1279
1280	shot.buf = alloca(shot.xsize * shot.ysize * sizeof(u_int16_t));
1281	if (shot.buf == NULL) {
1282		revert();
1283		errx(1, "failed to allocate memory for dump");
1284	}
1285
1286	if (ioctl(0, CONS_SCRSHOT, &shot) == -1) {
1287		revert();
1288		err(1, "dumping screen");
1289	}
1290
1291	if (mode == DUMP_FMT_RAW) {
1292		printf("SCRSHOT_%c%c%c%c", DUMP_FMT_REV, 2,
1293		       shot.xsize, shot.ysize);
1294
1295		fflush(stdout);
1296
1297		write(STDOUT_FILENO, shot.buf,
1298		      shot.xsize * shot.ysize * sizeof(u_int16_t));
1299	} else {
1300		char *line;
1301		int x, y;
1302		u_int16_t ch;
1303
1304		line = alloca(shot.xsize + 1);
1305
1306		if (line == NULL) {
1307			revert();
1308			errx(1, "failed to allocate memory for line buffer");
1309		}
1310
1311		for (y = 0; y < shot.ysize; y++) {
1312			for (x = 0; x < shot.xsize; x++) {
1313				ch = shot.buf[x + (y * shot.xsize)];
1314				ch &= 0xff;
1315
1316				if (isprint(ch) == 0)
1317					ch = ' ';
1318
1319				line[x] = (char)ch;
1320			}
1321
1322			/* Trim trailing spaces */
1323
1324			do {
1325				line[x--] = '\0';
1326			} while (line[x] == ' ' && x != 0);
1327
1328			puts(line);
1329		}
1330
1331		fflush(stdout);
1332	}
1333}
1334
1335
1336/*
1337 * Set the console history buffer size.
1338 */
1339
1340static void
1341set_history(char *opt)
1342{
1343	int size;
1344
1345	size = atoi(opt);
1346
1347	if ((*opt == '\0') || size < 0) {
1348		revert();
1349		errx(1, "argument must not be less than zero");
1350	}
1351
1352	if (ioctl(0, CONS_HISTORY, &size) == -1) {
1353		revert();
1354		err(1, "setting history buffer size");
1355	}
1356}
1357
1358
1359/*
1360 * Clear the console history buffer.
1361 */
1362
1363static void
1364clear_history(void)
1365{
1366	if (ioctl(0, CONS_CLRHIST) == -1) {
1367		revert();
1368		err(1, "clearing history buffer");
1369	}
1370}
1371
1372static int
1373get_terminal_emulator(int i, struct term_info *tip)
1374{
1375	tip->ti_index = i;
1376	if (ioctl(0, CONS_GETTERM, tip) == 0)
1377		return (1);
1378	strlcpy((char *)tip->ti_name, "unknown", sizeof(tip->ti_name));
1379	strlcpy((char *)tip->ti_desc, "unknown", sizeof(tip->ti_desc));
1380	return (0);
1381}
1382
1383static void
1384get_terminal_emulators(void)
1385{
1386	struct term_info ti;
1387	int i;
1388
1389	for (i = 0; i < 10; i++) {
1390		if (get_terminal_emulator(i, &ti) == 0)
1391			break;
1392		printf("%d: %s (%s)%s\n", i, ti.ti_name, ti.ti_desc,
1393		    i == 0 ? " (active)" : "");
1394	}
1395}
1396
1397static void
1398set_terminal_emulator(const char *name)
1399{
1400	struct term_info old_ti, ti;
1401
1402	get_terminal_emulator(0, &old_ti);
1403	strlcpy((char *)ti.ti_name, name, sizeof(ti.ti_name));
1404	if (ioctl(0, CONS_SETTERM, &ti) != 0)
1405		warn("SETTERM '%s'", name);
1406	get_terminal_emulator(0, &ti);
1407	printf("%s (%s) -> %s (%s)\n", old_ti.ti_name, old_ti.ti_desc,
1408	    ti.ti_name, ti.ti_desc);
1409}
1410
1411static void
1412set_terminal_mode(char *arg)
1413{
1414
1415	if (strcmp(arg, "xterm") == 0)
1416		fprintf(stderr, "\033[=T");
1417	else if (strcmp(arg, "cons25") == 0)
1418		fprintf(stderr, "\033[=1T");
1419}
1420
1421
1422int
1423main(int argc, char **argv)
1424{
1425	char    *font, *type, *termmode;
1426	const char *opts;
1427	int	dumpmod, dumpopt, opt;
1428
1429	vt4_mode = is_vt4();
1430
1431	init();
1432
1433	dumpmod = 0;
1434	dumpopt = DUMP_FBF;
1435	termmode = NULL;
1436	if (vt4_mode)
1437		opts = "b:Cc:fg:h:i:M:m:r:S:s:T:t:x";
1438	else
1439		opts = "b:Cc:deE:fg:h:Hi:l:LM:m:pPr:S:s:T:t:x";
1440
1441	while ((opt = getopt(argc, argv, opts)) != -1)
1442		switch(opt) {
1443		case 'b':
1444			set_border_color(optarg);
1445			break;
1446		case 'C':
1447			clear_history();
1448			break;
1449		case 'c':
1450			set_cursor_type(optarg);
1451			break;
1452		case 'd':
1453			if (vt4_mode)
1454				break;
1455			print_scrnmap();
1456			break;
1457		case 'E':
1458			if (vt4_mode)
1459				break;
1460			set_terminal_emulator(optarg);
1461			break;
1462		case 'e':
1463			if (vt4_mode)
1464				break;
1465			get_terminal_emulators();
1466			break;
1467		case 'f':
1468			optarg = nextarg(argc, argv, &optind, 'f', 0);
1469			if (optarg != NULL) {
1470				font = nextarg(argc, argv, &optind, 'f', 0);
1471
1472				if (font == NULL) {
1473					type = NULL;
1474					font = optarg;
1475				} else
1476					type = optarg;
1477
1478				load_font(type, font);
1479			} else {
1480				if (!vt4_mode)
1481					usage(); /* Switch syscons to ROM? */
1482
1483				load_default_vt4font();
1484			}
1485			break;
1486		case 'g':
1487			if (sscanf(optarg, "%dx%d",
1488			    &vesa_cols, &vesa_rows) != 2) {
1489				revert();
1490				warnx("incorrect geometry: %s", optarg);
1491				usage();
1492			}
1493                	break;
1494		case 'h':
1495			set_history(optarg);
1496			break;
1497		case 'H':
1498			dumpopt = DUMP_ALL;
1499			break;
1500		case 'i':
1501			show_info(optarg);
1502			break;
1503		case 'l':
1504			if (vt4_mode)
1505				break;
1506			load_scrnmap(optarg);
1507			break;
1508		case 'L':
1509			if (vt4_mode)
1510				break;
1511			load_default_scrnmap();
1512			break;
1513		case 'M':
1514			set_mouse_char(optarg);
1515			break;
1516		case 'm':
1517			set_mouse(optarg);
1518			break;
1519		case 'p':
1520			dumpmod = DUMP_FMT_RAW;
1521			break;
1522		case 'P':
1523			dumpmod = DUMP_FMT_TXT;
1524			break;
1525		case 'r':
1526			set_reverse_colors(argc, argv, &optind);
1527			break;
1528		case 'S':
1529			set_lockswitch(optarg);
1530			break;
1531		case 's':
1532			set_console(optarg);
1533			break;
1534		case 'T':
1535			if (strcmp(optarg, "xterm") != 0 &&
1536			    strcmp(optarg, "cons25") != 0)
1537				usage();
1538			termmode = optarg;
1539			break;
1540		case 't':
1541			set_screensaver_timeout(optarg);
1542			break;
1543		case 'x':
1544			hex = 1;
1545			break;
1546		default:
1547			usage();
1548		}
1549
1550	if (dumpmod != 0)
1551		dump_screen(dumpmod, dumpopt);
1552	video_mode(argc, argv, &optind);
1553	set_normal_colors(argc, argv, &optind);
1554
1555	if (optind < argc && !strcmp(argv[optind], "show")) {
1556		test_frame();
1557		optind++;
1558	}
1559
1560	if (termmode != NULL)
1561		set_terminal_mode(termmode);
1562
1563	if ((optind != argc) || (argc == 1))
1564		usage();
1565	return (0);
1566}
1567
1568