Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott (at) gmail.com>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
     15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
     16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  */
     18 
     19 #include <sys/types.h>
     20 
     21 #include <stdlib.h>
     22 #include <string.h>
     23 
     24 #include "tmux.h"
     25 
     26 #define SIXEL_WIDTH_LIMIT 10000
     27 #define SIXEL_HEIGHT_LIMIT 10000
     28 
     29 struct sixel_line {
     30 	u_int		 x;
     31 	uint16_t	*data;
     32 };
     33 
     34 struct sixel_image {
     35 	u_int			 x;
     36 	u_int			 y;
     37 	u_int			 xpixel;
     38 	u_int			 ypixel;
     39 
     40 	u_int			 set_ra;
     41 	u_int			 ra_x;
     42 	u_int			 ra_y;
     43 
     44 	u_int			*colours;
     45 	u_int			 ncolours;
     46 	u_int			 used_colours;
     47 	u_int			 p2;
     48 
     49 	u_int			 dx;
     50 	u_int			 dy;
     51 	u_int			 dc;
     52 
     53 	struct sixel_line	*lines;
     54 };
     55 
     56 struct sixel_chunk {
     57 	u_int	 next_x;
     58 	u_int	 next_y;
     59 
     60 	u_int	 count;
     61 	char	 pattern;
     62 	char	 next_pattern;
     63 
     64 	size_t	 len;
     65 	size_t	 used;
     66 	char	*data;
     67 };
     68 
     69 static int
     70 sixel_parse_expand_lines(struct sixel_image *si, u_int y)
     71 {
     72 	if (y <= si->y)
     73 		return (0);
     74 	if (y > SIXEL_HEIGHT_LIMIT)
     75 		return (1);
     76 	si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
     77 	si->y = y;
     78 	return (0);
     79 }
     80 
     81 static int
     82 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
     83 {
     84 	if (x <= sl->x)
     85 		return (0);
     86 	if (x > SIXEL_WIDTH_LIMIT)
     87 		return (1);
     88 	if (x > si->x)
     89 		si->x = x;
     90 	sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
     91 	sl->x = si->x;
     92 	return (0);
     93 }
     94 
     95 static u_int
     96 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
     97 {
     98 	struct sixel_line	*sl;
     99 
    100 	if (y >= si->y)
    101 		return (0);
    102 	sl = &si->lines[y];
    103 	if (x >= sl->x)
    104 		return (0);
    105 	return (sl->data[x]);
    106 }
    107 
    108 static int
    109 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
    110 {
    111 	struct sixel_line	*sl;
    112 
    113 	if (sixel_parse_expand_lines(si, y + 1) != 0)
    114 		return (1);
    115 	sl = &si->lines[y];
    116 	if (sixel_parse_expand_line(si, sl, x + 1) != 0)
    117 		return (1);
    118 	sl->data[x] = c;
    119 	return (0);
    120 }
    121 
    122 static int
    123 sixel_parse_write(struct sixel_image *si, u_int ch)
    124 {
    125 	struct sixel_line	*sl;
    126 	u_int			 i;
    127 
    128 	if (sixel_parse_expand_lines(si, si->dy + 6) != 0)
    129 		return (1);
    130 	sl = &si->lines[si->dy];
    131 
    132 	for (i = 0; i < 6; i++) {
    133 		if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0)
    134 			return (1);
    135 		if (ch & (1 << i))
    136 			sl->data[si->dx] = si->dc;
    137 		sl++;
    138 	}
    139 	return (0);
    140 }
    141 
    142 static const char *
    143 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
    144 {
    145 	const char	*last;
    146 	char		*endptr;
    147 	u_int		 x, y;
    148 
    149 	last = cp;
    150 	while (last != end) {
    151 		if (*last != ';' && (*last < '0' || *last > '9'))
    152 			break;
    153 		last++;
    154 	}
    155 	strtoul(cp, &endptr, 10);
    156 	if (endptr == last || *endptr != ';')
    157 		return (last);
    158 	strtoul(endptr + 1, &endptr, 10);
    159 	if (endptr == last)
    160 		return (last);
    161 	if (*endptr != ';') {
    162 		log_debug("%s: missing ;", __func__);
    163 		return (NULL);
    164 	}
    165 
    166 	x = strtoul(endptr + 1, &endptr, 10);
    167 	if (endptr == last || *endptr != ';') {
    168 		log_debug("%s: missing ;", __func__);
    169 		return (NULL);
    170 	}
    171 	if (x > SIXEL_WIDTH_LIMIT) {
    172 		log_debug("%s: image is too wide", __func__);
    173 		return (NULL);
    174 	}
    175 	y = strtoul(endptr + 1, &endptr, 10);
    176 	if (endptr != last) {
    177 		log_debug("%s: extra ;", __func__);
    178 		return (NULL);
    179 	}
    180 	if (y > SIXEL_HEIGHT_LIMIT) {
    181 		log_debug("%s: image is too tall", __func__);
    182 		return (NULL);
    183 	}
    184 
    185 	si->x = x;
    186 	sixel_parse_expand_lines(si, y);
    187 
    188 	si->set_ra = 1;
    189 	si->ra_x = x;
    190 	si->ra_y = y;
    191 
    192 	return (last);
    193 }
    194 
    195 static const char *
    196 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
    197 {
    198 	const char	*last;
    199 	char		*endptr;
    200 	u_int		 c, type, c1, c2, c3;
    201 
    202 	last = cp;
    203 	while (last != end) {
    204 		if (*last != ';' && (*last < '0' || *last > '9'))
    205 			break;
    206 		last++;
    207 	}
    208 
    209 	c = strtoul(cp, &endptr, 10);
    210 	if (c > SIXEL_COLOUR_REGISTERS) {
    211 		log_debug("%s: too many colours", __func__);
    212 		return (NULL);
    213 	}
    214 	if (si->used_colours <= c)
    215 		si->used_colours = c + 1;
    216 	si->dc = c + 1;
    217 	if (endptr == last || *endptr != ';')
    218 		return (last);
    219 
    220 	type = strtoul(endptr + 1, &endptr, 10);
    221 	if (endptr == last || *endptr != ';') {
    222 		log_debug("%s: missing ;", __func__);
    223 		return (NULL);
    224 	}
    225 	c1 = strtoul(endptr + 1, &endptr, 10);
    226 	if (endptr == last || *endptr != ';') {
    227 		log_debug("%s: missing ;", __func__);
    228 		return (NULL);
    229 	}
    230 	c2 = strtoul(endptr + 1, &endptr, 10);
    231 	if (endptr == last || *endptr != ';') {
    232 		log_debug("%s: missing ;", __func__);
    233 		return (NULL);
    234 	}
    235 	c3 = strtoul(endptr + 1, &endptr, 10);
    236 	if (endptr != last) {
    237 		log_debug("%s: missing ;", __func__);
    238 		return (NULL);
    239 	}
    240 
    241 	if ((type != 1 && type != 2) ||
    242 	    (type == 1 && (c1 > 360 || c2 > 100 || c3 > 100)) ||
    243 	    (type == 2 && (c1 > 100 || c2 > 100 || c3 > 100))) {
    244 		log_debug("%s: invalid color %u;%u;%u;%u", __func__, type,
    245 		    c1, c2, c3);
    246 		return (NULL);
    247 	}
    248 
    249 	if (c + 1 > si->ncolours) {
    250 		si->colours = xrecallocarray(si->colours, si->ncolours, c + 1,
    251 		    sizeof *si->colours);
    252 		si->ncolours = c + 1;
    253 	}
    254 	si->colours[c] = (type << 25) | (c1 << 16) | (c2 << 8) | c3;
    255 	return (last);
    256 }
    257 
    258 static const char *
    259 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
    260 {
    261 	const char	*last;
    262 	char		 tmp[32], ch;
    263 	u_int		 n = 0, i;
    264 	const char	*errstr = NULL;
    265 
    266 	last = cp;
    267 	while (last != end) {
    268 		if (*last < '0' || *last > '9')
    269 			break;
    270 		tmp[n++] = *last++;
    271 		if (n == (sizeof tmp) - 1) {
    272 			log_debug("%s: repeat not terminated", __func__);
    273 			return (NULL);
    274 		}
    275 	}
    276 	if (n == 0 || last == end) {
    277 		log_debug("%s: repeat not terminated", __func__);
    278 		return (NULL);
    279 	}
    280 	tmp[n] = '\0';
    281 
    282 	n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr);
    283 	if (n == 0 || errstr != NULL) {
    284 		log_debug("%s: repeat too wide", __func__);
    285 		return (NULL);
    286 	}
    287 
    288 	ch = (*last++) - 0x3f;
    289 	for (i = 0; i < n; i++) {
    290 		if (sixel_parse_write(si, ch) != 0) {
    291 			log_debug("%s: width limit reached", __func__);
    292 			return (NULL);
    293 		}
    294 		si->dx++;
    295 	}
    296 	return (last);
    297 }
    298 
    299 struct sixel_image *
    300 sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel)
    301 {
    302 	struct sixel_image	*si;
    303 	const char		*cp = buf, *end = buf + len;
    304 	char			 ch;
    305 
    306 	if (len == 0 || len == 1 || *cp++ != 'q') {
    307 		log_debug("%s: empty image", __func__);
    308 		return (NULL);
    309 	}
    310 
    311 	si = xcalloc (1, sizeof *si);
    312 	si->xpixel = xpixel;
    313 	si->ypixel = ypixel;
    314 	si->p2 = p2;
    315 
    316 	while (cp != end) {
    317 		ch = *cp++;
    318 		switch (ch) {
    319 		case '"':
    320 			cp = sixel_parse_attributes(si, cp, end);
    321 			if (cp == NULL)
    322 				goto bad;
    323 			break;
    324 		case '#':
    325 			cp = sixel_parse_colour(si, cp, end);
    326 			if (cp == NULL)
    327 				goto bad;
    328 			break;
    329 		case '!':
    330 			cp = sixel_parse_repeat(si, cp, end);
    331 			if (cp == NULL)
    332 				goto bad;
    333 			break;
    334 		case '-':
    335 			si->dx = 0;
    336 			si->dy += 6;
    337 			break;
    338 		case '$':
    339 			si->dx = 0;
    340 			break;
    341 		default:
    342 			if (ch < 0x20)
    343 				break;
    344 			if (ch < 0x3f || ch > 0x7e)
    345 				goto bad;
    346 			if (sixel_parse_write(si, ch - 0x3f) != 0) {
    347 				log_debug("%s: width limit reached", __func__);
    348 				goto bad;
    349 			}
    350 			si->dx++;
    351 			break;
    352 		}
    353 	}
    354 
    355 	if (si->x == 0 || si->y == 0)
    356 		goto bad;
    357 	return (si);
    358 
    359 bad:
    360 	free(si);
    361 	return (NULL);
    362 }
    363 
    364 void
    365 sixel_free(struct sixel_image *si)
    366 {
    367 	u_int	y;
    368 
    369 	for (y = 0; y < si->y; y++)
    370 		free(si->lines[y].data);
    371 	free(si->lines);
    372 
    373 	free(si->colours);
    374 	free(si);
    375 }
    376 
    377 void
    378 sixel_log(struct sixel_image *si)
    379 {
    380 	struct sixel_line	*sl;
    381 	char			 s[SIXEL_WIDTH_LIMIT + 1];
    382 	u_int			 i, x, y, cx, cy;
    383 
    384 	sixel_size_in_cells(si, &cx, &cy);
    385 	log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
    386 	for (i = 0; i < si->ncolours; i++)
    387 		log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
    388 	for (y = 0; y < si->y; y++) {
    389 		sl = &si->lines[y];
    390 		for (x = 0; x < si->x; x++) {
    391 			if (x >= sl->x)
    392 				s[x] = '_';
    393 			else if (sl->data[x] != 0)
    394 				s[x] = '0' + (sl->data[x] - 1) % 10;
    395 			else
    396 				s[x] = '.';
    397 			}
    398 		s[x] = '\0';
    399 		log_debug("%s: %4u: %s", __func__, y, s);
    400 	}
    401 }
    402 
    403 void
    404 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
    405 {
    406 	if ((si->x % si->xpixel) == 0)
    407 		*x = (si->x / si->xpixel);
    408 	else
    409 		*x = 1 + (si->x / si->xpixel);
    410 	if ((si->y % si->ypixel) == 0)
    411 		*y = (si->y / si->ypixel);
    412 	else
    413 		*y = 1 + (si->y / si->ypixel);
    414 }
    415 
    416 struct sixel_image *
    417 sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
    418     u_int oy, u_int sx, u_int sy, int colours)
    419 {
    420 	struct sixel_image	*new;
    421 	u_int			 cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
    422 	u_int			 x, y, i;
    423 
    424 	/*
    425 	 * We want to get the section of the image at ox,oy in image cells and
    426 	 * map it onto the same size in terminal cells.
    427 	 */
    428 
    429 	sixel_size_in_cells(si, &cx, &cy);
    430 	if (ox >= cx)
    431 		return (NULL);
    432 	if (oy >= cy)
    433 		return (NULL);
    434 	if (ox + sx >= cx)
    435 		sx = cx - ox;
    436 	if (oy + sy >= cy)
    437 		sy = cy - oy;
    438 
    439 	if (xpixel == 0)
    440 		xpixel = si->xpixel;
    441 	if (ypixel == 0)
    442 		ypixel = si->ypixel;
    443 
    444 	pox = ox * si->xpixel;
    445 	poy = oy * si->ypixel;
    446 	psx = sx * si->xpixel;
    447 	psy = sy * si->ypixel;
    448 
    449 	tsx = sx * xpixel;
    450 	tsy = sy * ypixel;
    451 
    452 	new = xcalloc (1, sizeof *si);
    453 	new->xpixel = xpixel;
    454 	new->ypixel = ypixel;
    455 	new->p2 = si->p2;
    456 
    457 	new->set_ra = si->set_ra;
    458 	/* clamp to slice end */
    459 	new->ra_x = si->ra_x < psx ? si->ra_x : psx;
    460 	new->ra_y = si->ra_y < psy ? si->ra_y : psy;
    461 	/* subtract slice origin */
    462 	new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0;
    463 	new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0;
    464 	/* resize */
    465 	new->ra_x = new->ra_x * xpixel / si->xpixel;
    466 	new->ra_y = new->ra_y * ypixel / si->ypixel;
    467 
    468 	new->used_colours = si->used_colours;
    469 	for (y = 0; y < tsy; y++) {
    470 		py = poy + ((double)y * psy / tsy);
    471 		for (x = 0; x < tsx; x++) {
    472 			px = pox + ((double)x * psx / tsx);
    473 			sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py));
    474 		}
    475 	}
    476 
    477 	if (colours && si->ncolours != 0) {
    478 		new->colours = xmalloc(si->ncolours * sizeof *new->colours);
    479 		for (i = 0; i < si->ncolours; i++)
    480 			new->colours[i] = si->colours[i];
    481 		new->ncolours = si->ncolours;
    482 	}
    483 	return (new);
    484 }
    485 
    486 static void
    487 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
    488     size_t slen)
    489 {
    490 	if (*used + slen >= *len + 1) {
    491 		(*len) *= 2;
    492 		*buf = xrealloc(*buf, *len);
    493 	}
    494 	memcpy(*buf + *used, s, slen);
    495 	(*used) += slen;
    496 }
    497 
    498 static void
    499 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
    500 {
    501 	char	tmp[16];
    502 	size_t	tmplen;
    503 
    504 	if (count == 1)
    505 		sixel_print_add(buf, len, used, &ch, 1);
    506 	else if (count == 2) {
    507 		sixel_print_add(buf, len, used, &ch, 1);
    508 		sixel_print_add(buf, len, used, &ch, 1);
    509 	} else if (count == 3) {
    510 		sixel_print_add(buf, len, used, &ch, 1);
    511 		sixel_print_add(buf, len, used, &ch, 1);
    512 		sixel_print_add(buf, len, used, &ch, 1);
    513 	} else if (count != 0) {
    514 		tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
    515 		sixel_print_add(buf, len, used, tmp, tmplen);
    516 	}
    517 }
    518 
    519 static void
    520 sixel_print_compress_colors(struct sixel_image *si, struct sixel_chunk *chunks,
    521     u_int y, u_int *active, u_int *nactive)
    522 {
    523 	u_int			 i, x, c, dx, colors[6];
    524 	struct sixel_chunk	*chunk = NULL;
    525 	struct sixel_line	*sl;
    526 
    527 	for (x = 0; x < si->x; x++) {
    528 		for (i = 0; i < 6; i++) {
    529 			colors[i] = 0;
    530 			if (y + i < si->y) {
    531 				sl = &si->lines[y + i];
    532 				if (x < sl->x && sl->data[x] != 0) {
    533 					colors[i] = sl->data[x];
    534 					c = sl->data[x] - 1;
    535 					chunks[c].next_pattern |= 1 << i;
    536 				}
    537 			}
    538 		}
    539 
    540 		for (i = 0; i < 6; i++) {
    541 			if (colors[i] == 0)
    542 				continue;
    543 
    544 			c = colors[i] - 1;
    545 			chunk = &chunks[c];
    546 			if (chunk->next_x == x + 1)
    547 				continue;
    548 
    549 			if (chunk->next_y < y + 1) {
    550 				chunk->next_y = y + 1;
    551 				active[(*nactive)++] = c;
    552 			}
    553 
    554 			dx = x - chunk->next_x;
    555 			if (chunk->pattern != chunk->next_pattern || dx != 0) {
    556 				sixel_print_repeat(&chunk->data, &chunk->len,
    557 				    &chunk->used, chunk->count,
    558 				    chunk->pattern + 0x3f);
    559 				sixel_print_repeat(&chunk->data, &chunk->len,
    560 				    &chunk->used, dx, '?');
    561 				chunk->pattern = chunk->next_pattern;
    562 				chunk->count = 0;
    563 			}
    564 			chunk->count++;
    565 			chunk->next_pattern = 0;
    566 			chunk->next_x = x + 1;
    567 		}
    568 	}
    569 }
    570 
    571 char *
    572 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
    573 {
    574 	char			*buf, tmp[64];
    575 	size_t			 len, used = 0, tmplen;
    576 	u_int			*colours, ncolours, used_colours, i, c, y;
    577 	u_int			*active, nactive;
    578 	struct sixel_chunk	*chunks, *chunk;
    579 
    580 	if (map != NULL) {
    581 		colours = map->colours;
    582 		ncolours = map->ncolours;
    583 	} else {
    584 		colours = si->colours;
    585 		ncolours = si->ncolours;
    586 	}
    587 
    588 	used_colours = si->used_colours;
    589 	if (used_colours == 0)
    590 		return (NULL);
    591 
    592 	len = 8192;
    593 	buf = xmalloc(len);
    594 
    595 	tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2);
    596 	sixel_print_add(&buf, &len, &used, tmp, tmplen);
    597 
    598 	if (si->set_ra) {
    599 		tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->ra_x,
    600 		    si->ra_y);
    601 		sixel_print_add(&buf, &len, &used, tmp, tmplen);
    602 	}
    603 
    604 	chunks = xcalloc(used_colours, sizeof *chunks);
    605 	active = xcalloc(used_colours, sizeof *active);
    606 
    607 	for (i = 0; i < ncolours; i++) {
    608 		c = colours[i];
    609 		tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
    610 		    i, c >> 25, (c >> 16) & 0x1ff, (c >> 8) & 0xff, c & 0xff);
    611 		sixel_print_add(&buf, &len, &used, tmp, tmplen);
    612 	}
    613 
    614 	for (i = 0; i < used_colours; i++) {
    615 		chunk = &chunks[i];
    616 		chunk->len = 8;
    617 		chunk->data = xmalloc(chunk->len);
    618 	}
    619 
    620 	for (y = 0; y < si->y; y += 6) {
    621 		nactive = 0;
    622 		sixel_print_compress_colors(si, chunks, y, active, &nactive);
    623 
    624 		for (i = 0; i < nactive; i++) {
    625 			c = active[i];
    626 			chunk = &chunks[c];
    627 			tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c);
    628 			sixel_print_add(&buf, &len, &used, tmp, tmplen);
    629 			sixel_print_add(&buf, &len, &used, chunk->data,
    630 			    chunk->used);
    631 			sixel_print_repeat(&buf, &len, &used, chunk->count,
    632 			    chunk->pattern + 0x3f);
    633 			sixel_print_add(&buf, &len, &used, "$", 1);
    634 			chunk->used = chunk->next_x = chunk->count = 0;
    635 		}
    636 
    637 		if (buf[used - 1] == '$')
    638 			used--;
    639 		sixel_print_add(&buf, &len, &used, "-", 1);
    640 	}
    641 	if (buf[used - 1] == '-')
    642 		used--;
    643 
    644 	sixel_print_add(&buf, &len, &used, "\033\\", 2);
    645 
    646 	buf[used] = '\0';
    647 	if (size != NULL)
    648 		*size = used;
    649 
    650 	for (i = 0; i < used_colours; i++)
    651 		free(chunks[i].data);
    652 	free(active);
    653 	free(chunks);
    654 
    655 	return (buf);
    656 }
    657 
    658 struct screen *
    659 sixel_to_screen(struct sixel_image *si)
    660 {
    661 	struct screen		*s;
    662 	struct screen_write_ctx	 ctx;
    663 	struct grid_cell	 gc;
    664 	u_int			 x, y, sx, sy;
    665 
    666 	sixel_size_in_cells(si, &sx, &sy);
    667 
    668 	s = xmalloc(sizeof *s);
    669 	screen_init(s, sx, sy, 0);
    670 
    671 	memcpy(&gc, &grid_default_cell, sizeof gc);
    672 	gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
    673 	utf8_set(&gc.data, '~');
    674 
    675 	screen_write_start(&ctx, s);
    676 	if (sx == 1 || sy == 1) {
    677 		for (y = 0; y < sy; y++) {
    678 			for (x = 0; x < sx; x++)
    679 				grid_view_set_cell(s->grid, x, y, &gc);
    680 		}
    681 	} else {
    682 		screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
    683 		for (y = 1; y < sy - 1; y++) {
    684 			for (x = 1; x < sx - 1; x++)
    685 				grid_view_set_cell(s->grid, x, y, &gc);
    686 		}
    687 	}
    688 	screen_write_stop(&ctx);
    689 	return (s);
    690 }
    691