From b5b8e366f3c5cd89477eb025724f3c44d25c3fae Mon Sep 17 00:00:00 2001
From: Hiroo Ono <hiroo.ono@gmail.com>
Date: Tue, 4 Jul 2023 00:17:09 +0900
Subject: [PATCH] Implement screenshot with vt(4).

---
 sys/conf/files                   |   1 +
 sys/dev/vt/vt.h                  |   4 +
 sys/dev/vt/vt_core.c             |  17 +++
 sys/dev/vt/vt_screenshot.c       |  90 +++++++++++++
 sys/sys/consio.h                 |  15 +++
 usr.sbin/vidcontrol/vidcontrol.c | 223 ++++++++++++++++++++++++++++++-
 6 files changed, 344 insertions(+), 6 deletions(-)
 create mode 100644 sys/dev/vt/vt_screenshot.c

diff --git a/sys/conf/files b/sys/conf/files
index 726c4c7c8b2c..cd4f24098f94 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3447,6 +3447,7 @@ dev/vt/vt_consolectl.c		optional vt
 dev/vt/vt_core.c		optional vt
 dev/vt/vt_cpulogos.c		optional vt splash
 dev/vt/vt_font.c		optional vt
+dev/vt/vt_screenshot.c		optional vt
 dev/vt/vt_sysmouse.c		optional vt
 dev/vte/if_vte.c		optional vte pci
 dev/watchdog/watchdog.c		standard
diff --git a/sys/dev/vt/vt.h b/sys/dev/vt/vt.h
index 649e5839fcbf..7961355fa0ec 100644
--- a/sys/dev/vt/vt.h
+++ b/sys/dev/vt/vt.h
@@ -425,6 +425,10 @@ void vt_mouse_state(int show);
 #define	VT_MOUSE_SHOW 1
 #define	VT_MOUSE_HIDE 0
 
+/* Screenshot. */
+int		vt_screenshot_grph(struct vt_window *vd, vt_scrshot_grph_t *);
+int		vt_screenshot_text(struct vt_window *vd, vt_scrshot_text_t *);
+
 /* Utilities. */
 void	vt_compute_drawable_area(struct vt_window *);
 void	vt_determine_colors(term_char_t c, int cursor,
diff --git a/sys/dev/vt/vt_core.c b/sys/dev/vt/vt_core.c
index b2d3566e59a3..ae0ea24ff66f 100644
--- a/sys/dev/vt/vt_core.c
+++ b/sys/dev/vt/vt_core.c
@@ -2558,6 +2558,7 @@ vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data,
 	case FBIO_GETRGBOFFS:	/* get RGB offsets */
 		if (vd->vd_driver->vd_fb_ioctl)
 			return (vd->vd_driver->vd_fb_ioctl(vd, cmd, data, td));
+		printf("VT(%s) not supported.\r\n", vd->vd_driver->vd_name);
 		break;
 	case CONS_BLANKTIME:
 		/* XXX */
@@ -2598,6 +2599,12 @@ vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data,
 		if (vi->size != sizeof(struct vid_info))
 			return (EINVAL);
 
+		vi->mv_rsz = tm->tm_winsize.ws_row;
+		vi->mv_csz = tm->tm_winsize.ws_col;
+		vtbuf_lock(&vw->vw_buf);
+		vi->mv_hsz = vw->vw_buf.vb_history_size;
+		vtbuf_unlock(&vw->vw_buf);
+
 		if (vw == vd->vd_curwindow) {
 			mtx_lock(&Giant);
 			if ((kbd = vd->vd_keyboard) != NULL)
@@ -2647,6 +2654,16 @@ vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data,
 			return (EINVAL);
 		}
 	}
+	case CONS_VTSCRSHOT_GRPH: {
+		vt_scrshot_grph_t *ptr = (vt_scrshot_grph_t *)data;
+		error = vt_screenshot_grph(vw, ptr);
+		return (error);
+	}
+	case CONS_VTSCRSHOT_TEXT: {
+		vt_scrshot_text_t *ptr = (vt_scrshot_text_t *)data;
+		error = vt_screenshot_text(vw, ptr);
+		return (error);
+	}
 	case PIO_VFONT: {
 		struct vt_font *vf;
 
diff --git a/sys/dev/vt/vt_screenshot.c b/sys/dev/vt/vt_screenshot.c
new file mode 100644
index 000000000000..7d0c552b0fef
--- /dev/null
+++ b/sys/dev/vt/vt_screenshot.c
@@ -0,0 +1,90 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/param.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/fbio.h>
+
+#include <dev/vt/vt.h>
+
+int vt_screenshot_grph(struct vt_window* vw, vt_scrshot_grph_t *ptr)
+{
+	struct fb_info *info;
+	void *from_p;
+
+	/* Check if virtual terminal is locked */
+	if (vw->vw_flags & VWF_VTYLOCK)
+		return (EBUSY);
+
+	info = vw->vw_device->vd_softc;
+	if (ptr->size != info->fb_size)
+		return (EINVAL);
+
+	from_p = (void *)info->fb_vbase;
+	return (copyout(from_p, ptr->buf, info->fb_size));
+}
+
+int vt_screenshot_text(struct vt_window* vw, vt_scrshot_text_t *ptr)
+{
+	int retval = 0;
+	struct vt_buf *vb;
+	int row, hrow;
+	const size_t csize = sizeof(u_int);
+	u_int *outp = ptr->buf;;
+	u_int **rows;
+	void *colp, *bufp;
+
+	if (ptr->x < 0 || ptr->y < 0 || ptr->xsize < 0 || ptr->ysize < 0)
+		return (EINVAL);
+
+	/* Check if virtual terminal is locked */
+	if (vw->vw_flags & VWF_VTYLOCK)
+		return (EBUSY);
+
+	vb = &vw->vw_buf;
+	rows = (u_int **)vb->vb_rows;
+	if ((ptr->xsize + ptr->x) > vb->vb_scr_size.tp_col ||
+			(ptr->ysize + ptr->y) > vb->vb_scr_size.tp_row) {
+		return (EINVAL);
+	}
+	/* Copy current window to output buffer */
+	for (row = ptr->y; row < (ptr->y + ptr->ysize); row++) {
+		/* Translate current view row number to history row */
+		hrow = (vb->vb_roffset + row) % vb->vb_history_size;
+		// colp = (void *)&vb->vb_rows[hrow][ptr->x];
+		colp = (void *)&rows[hrow][ptr->x];
+		bufp = (void *)outp;
+		retval = copyout(colp, bufp, ptr->xsize*csize);
+		if (retval != 0)
+			break;
+		outp += ptr->xsize;
+	}
+	return (retval);
+}
diff --git a/sys/sys/consio.h b/sys/sys/consio.h
index f73f5b474d9e..8ccc1d810e5a 100644
--- a/sys/sys/consio.h
+++ b/sys/sys/consio.h
@@ -293,10 +293,25 @@ struct scrshot {
 	int		ysize;
 	u_int16_t*	buf;
 };
+struct vt_scrshot_text {
+	int		x;
+	int		y;
+	int		xsize;
+	int		ysize;
+	u_int	*buf;
+};
+struct vt_scrshot_grph {
+	size_t	size;
+	void	*buf;
+};
 typedef struct scrshot scrshot_t;
+typedef struct vt_scrshot_text vt_scrshot_text_t;
+typedef struct vt_scrshot_grph vt_scrshot_grph_t;
 
 /* Snapshot the current video buffer */
 #define CONS_SCRSHOT	_IOWR('c', 105, scrshot_t)
+#define CONS_VTSCRSHOT_TEXT	_IOWR('c', 114, vt_scrshot_text_t)
+#define CONS_VTSCRSHOT_GRPH	_IOWR('c', 115, vt_scrshot_grph_t)
 
 /* get/set the current terminal emulator info. */
 #define TI_NAME_LEN	32
diff --git a/usr.sbin/vidcontrol/vidcontrol.c b/usr.sbin/vidcontrol/vidcontrol.c
index 3e0ab102ae8c..c55483b88e22 100644
--- a/usr.sbin/vidcontrol/vidcontrol.c
+++ b/usr.sbin/vidcontrol/vidcontrol.c
@@ -40,10 +40,13 @@ static const char rcsid[] =
 
 #include <ctype.h>
 #include <err.h>
+#include <iconv.h>
 #include <limits.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <strings.h>
 #include <unistd.h>
 #include <sys/fbio.h>
 #include <sys/consio.h>
@@ -53,20 +56,29 @@ static const char rcsid[] =
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/sysctl.h>
+#include <teken/teken.h>
 #include "path.h"
 #include "decode.h"
 
 
 #define	DATASIZE(x)	((x).w * (x).h * 256 / 8)
+#define TCHAR_NONCHAR(c)	((c) & ~0x1fffff)
+/* from sys/terminal.h */
+#define TCHAR_CHARACTER(c)	((c) & 0x1fffff)
+#define TCHAR_FORMAT(c)		(((c) >> 21) & 0x1f)
+#define TCHAR_FGCOLOR(c)	(((c) >> 26) & 0x7)
+#define TCHAR_BGCOLOR(c)	(((c) >> 29) & 0x7)
 
 /* Screen dump modes */
 #define DUMP_FMT_RAW	1
 #define DUMP_FMT_TXT	2
+#define DUMP_FMT_CTXT	3
 /* Screen dump options */
 #define DUMP_FBF	0
 #define DUMP_ALL	1
 /* Screen dump file format revision */
 #define DUMP_FMT_REV	1
+#define DUMP_FMT_REV_VT	2
 
 static const char *legal_colors[16] = {
 	"black", "blue", "green", "cyan",
@@ -188,7 +200,7 @@ usage(void)
 		fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n",
 "usage: vidcontrol [-Cx] [-b color] [-c appearance] [-f [[size] file]]",
 "                  [-g geometry] [-h size] [-i active | adapter | mode]",
-"                  [-M char] [-m on | off]",
+"                  [-M char] [-m on | off] [-p text | term | raw]",
 "                  [-r foreground background] [-S on | off] [-s number]",
 "                  [-T xterm | cons25] [-t N | off] [mode]",
 "                  [foreground [background]] [show]");
@@ -1338,6 +1350,186 @@ dump_screen(int mode, int opt)
 }
 
 
+/*
+ * Snapshot the text buffer or video memory of the termnal.
+ */
+static void
+vt_dump_screen(int mode, int opt)
+{
+	if (mode == DUMP_FMT_RAW) {
+		struct fbtype fb;
+		vt_scrshot_grph_t gshot;
+		u_int32_t xs, ys, depth;
+
+		if (ioctl(0, FBIOGTYPE, &fb) == -1) {
+			revert();
+			err(1, "Dumping in BMP not supported");
+		}
+		if (fb.fb_depth != 32) {
+			revert();
+			errx(0, "Pixel depth %d not supported", fb.fb_depth);
+		}
+		gshot.buf = (void *)malloc(fb.fb_size);
+		gshot.size = fb.fb_size;
+		if (gshot.buf == NULL) {
+			revert();
+			errx(1, "failed to allocate memory for dump");
+		}
+		if (ioctl(0, CONS_VTSCRSHOT_GRPH, &gshot) == -1) {
+			revert();
+			err(1, "getting frame buffer data");
+		}
+		/* output RAW */
+		xs = (u_int32_t)fb.fb_width;
+		ys = (u_int32_t)fb.fb_height;
+		depth = (u_int32_t)fb.fb_depth;
+		printf("SCRSHOT_%c%c", DUMP_FMT_REV_VT, 3);
+		fwrite(&xs, sizeof(u_int32_t), 1, stdout);
+		fwrite(&ys, sizeof(u_int32_t), 1, stdout);
+		fwrite(&depth, sizeof(u_int32_t), 1, stdout);
+		fwrite(gshot.buf, sizeof(u_int32_t), xs*ys, stdout);
+		fflush(stdout);
+	}
+	else {
+		vt_scrshot_text_t tshot;
+		vid_info_t info;
+		int x, y, ret;
+		iconv_t cd;
+		teken_char_t oc, uc, fmt;
+		size_t isize, osize;
+		char obuf[8], escsq[BUFSIZ];
+		char *ibuf_p, *obuf_p;
+		teken_char_t nc_curstate, nc_prevstate;
+		teken_char_t fg, bg;
+		bool need_fini = false;
+
+		info.size = sizeof(info);
+		if (ioctl(0, CONS_GETINFO, &info) == -1) {
+			revert();
+			err(1, "getting console information");
+		}
+		tshot.x = tshot.y = 0;
+		tshot.xsize = info.mv_csz;
+		tshot.ysize = info.mv_rsz;
+		if (opt == DUMP_ALL)
+			tshot.ysize = info.mv_hsz;
+		tshot.buf = (u_int *)alloca(tshot.xsize * tshot.ysize
+							* sizeof(u_int));
+		if (tshot.buf == NULL) {
+			revert();
+			errx(1, "failed to allocate memory for dump");
+		}
+		if (ioctl(0, CONS_VTSCRSHOT_TEXT, &tshot) == -1) {
+			revert();
+			err(1, "dumping screen");
+		}
+
+		cd = iconv_open("UTF-8", "UCS-4LE");
+		if (cd == (iconv_t)-1) {
+			revert();
+			err(1, "iconv_open()");
+		}
+		nc_prevstate = nc_curstate = 0;
+		for (y = 0; y < tshot.ysize; y++) {
+			for (x = 0; x < tshot.xsize; x++) {
+				oc = (teken_char_t)(0xffffffff & tshot.buf[tshot.xsize*y + x]);
+				nc_prevstate = nc_curstate;
+				nc_curstate = TCHAR_NONCHAR(oc);
+				fmt = TCHAR_FORMAT(oc);
+
+				if ((fmt & TF_CJK_RIGHT) == TF_CJK_RIGHT)
+					continue;
+
+				if (mode == DUMP_FMT_CTXT && nc_prevstate != nc_curstate) {
+					need_fini = true;
+					bzero(escsq, BUFSIZ);
+					fg = TCHAR_FGCOLOR(oc);
+					bg = TCHAR_BGCOLOR(oc);
+
+					strncat(escsq, "\e[", BUFSIZ - strlen(escsq) -1);
+					/* reset */
+					strncat(escsq, "0;", BUFSIZ - strlen(escsq) -1);
+					if ((fmt & TF_BOLD) == TF_BOLD)
+						strncat(escsq, "1;", BUFSIZ - strlen(escsq) -1);
+					if ((fmt & TF_UNDERLINE) == TF_UNDERLINE)
+						strncat(escsq, "4;", BUFSIZ - strlen(escsq) -1);
+					if ((fmt & TF_BLINK) == TF_BLINK)
+						strncat(escsq, "5;", BUFSIZ - strlen(escsq) -1);
+					if ((fmt & TF_REVERSE) == TF_REVERSE)
+						strncat(escsq, "7;", BUFSIZ - strlen(escsq) -1);
+
+					if (fg != TC_NCOLORS && (fg & TC_LIGHT) == TC_LIGHT)
+						strncat(escsq, "2;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_BLACK)
+						strncat(escsq, "30;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_RED)
+						strncat(escsq, "31;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_GREEN)
+						strncat(escsq, "32;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_YELLOW)
+						strncat(escsq, "33;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_BLUE)
+						strncat(escsq, "34;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_MAGENTA)
+						strncat(escsq, "35;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_CYAN)
+						strncat(escsq, "36;", BUFSIZ - strlen(escsq) -1);
+					if ((fg & ~TC_LIGHT) == TC_WHITE)
+						strncat(escsq, "37;", BUFSIZ - strlen(escsq) -1);
+
+					if (bg != TC_NCOLORS && (bg & TC_LIGHT) == TC_LIGHT)
+						strncat(escsq, "2;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_BLACK)
+						strncat(escsq, "40;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_RED)
+						strncat(escsq, "41;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_GREEN)
+						strncat(escsq, "42;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_YELLOW)
+						strncat(escsq, "43;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_BLUE)
+						strncat(escsq, "44;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_MAGENTA)
+						strncat(escsq, "45;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_CYAN)
+						strncat(escsq, "46;", BUFSIZ - strlen(escsq) -1);
+					if ((bg & ~TC_LIGHT) == TC_WHITE)
+						strncat(escsq, "47;", BUFSIZ - strlen(escsq) -1);
+
+					/* terminate */
+					escsq[strlen(escsq) - 1] = 'm';
+
+					printf("%s", escsq);
+				}
+
+				uc = TCHAR_CHARACTER(oc);
+				ibuf_p = (char *)&uc;
+				isize = sizeof(uc);
+				bzero(obuf, 8);
+				obuf_p = obuf;
+				osize = sizeof(obuf);
+				iconv(cd, &ibuf_p, &isize, &obuf_p, &osize);
+				if (isize != 0) {
+					iconv_close(cd);
+					revert();
+					err(1, "iconv()");
+				}
+				printf("%s", obuf);
+			}
+			printf("%s", "\r\n");
+		}
+		if (need_fini) {
+			printf("%s", "\e[0m");
+		}
+		ret = iconv_close(cd);
+		if (ret == -1) {
+			revert();
+			err(1, "iconv_close()");
+		}
+	}
+}
+
+
 /*
  * Set the console history buffer size.
  */
@@ -1427,7 +1619,7 @@ set_terminal_mode(char *arg)
 int
 main(int argc, char **argv)
 {
-	char    *font, *type, *termmode;
+	char    *dumparg, *font, *type, *termmode;
 	const char *opts;
 	int	dumpmod, dumpopt, opt;
 
@@ -1439,7 +1631,7 @@ main(int argc, char **argv)
 	dumpopt = DUMP_FBF;
 	termmode = NULL;
 	if (vt4_mode)
-		opts = "b:Cc:fg:h:i:M:m:r:S:s:T:t:x";
+		opts = "b:Cc:fg:h:i:M:m:p:r:S:s:T:t:x";
 	else
 		opts = "b:Cc:deE:fg:h:Hi:l:LM:m:pPr:S:s:T:t:x";
 
@@ -1495,7 +1687,7 @@ main(int argc, char **argv)
 				warnx("incorrect geometry: %s", optarg);
 				usage();
 			}
-                	break;
+			break;
 		case 'h':
 			set_history(optarg);
 			break;
@@ -1522,9 +1714,26 @@ main(int argc, char **argv)
 			set_mouse(optarg);
 			break;
 		case 'p':
-			dumpmod = DUMP_FMT_RAW;
+			if (vt4_mode) {
+				dumparg = optarg;
+				if (strcmp(dumparg, "text") == 0)
+					dumpmod = DUMP_FMT_TXT;
+				else if (strcmp(dumparg, "term") == 0)
+					dumpmod = DUMP_FMT_CTXT;
+				else if (strcmp(dumparg, "raw") == 0)
+					dumpmod = DUMP_FMT_RAW;
+				else {
+					revert();
+					err(1, "invalid argument for -p option.");
+				}
+			}
+			else {
+				dumpmod = DUMP_FMT_RAW;
+			}
 			break;
 		case 'P':
+			if (vt4_mode)
+				break;
 			dumpmod = DUMP_FMT_TXT;
 			break;
 		case 'r':
@@ -1552,8 +1761,10 @@ main(int argc, char **argv)
 			usage();
 		}
 
-	if (dumpmod != 0)
+	if (!vt4_mode && dumpmod != 0)
 		dump_screen(dumpmod, dumpopt);
+	if (vt4_mode && dumpmod != 0)
+		vt_dump_screen(dumpmod, dumpopt);
 	video_mode(argc, argv, &optind);
 	set_normal_colors(argc, argv, &optind);
 
-- 
2.41.0

