xref: /openbsd-src/usr.bin/tmux/screen-redraw.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
1 /* $OpenBSD: screen-redraw.c,v 1.54 2018/08/19 20:13:07 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@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 struct screen_redraw_ctx {
27 	struct client	*c;
28 
29 	u_int		 lines;
30 	int		 top;
31 
32 	int		 pane_status;
33 
34 	u_int		 sx;
35 	u_int		 sy;
36 };
37 
38 static void	screen_redraw_draw_borders(struct screen_redraw_ctx *);
39 static void	screen_redraw_draw_panes(struct screen_redraw_ctx *);
40 static void	screen_redraw_draw_status(struct screen_redraw_ctx *);
41 static void	screen_redraw_draw_number(struct screen_redraw_ctx *,
42 		    struct window_pane *);
43 
44 #define CELL_INSIDE 0
45 #define CELL_LEFTRIGHT 1
46 #define CELL_TOPBOTTOM 2
47 #define CELL_TOPLEFT 3
48 #define CELL_TOPRIGHT 4
49 #define CELL_BOTTOMLEFT 5
50 #define CELL_BOTTOMRIGHT 6
51 #define CELL_TOPJOIN 7
52 #define CELL_BOTTOMJOIN 8
53 #define CELL_LEFTJOIN 9
54 #define CELL_RIGHTJOIN 10
55 #define CELL_JOIN 11
56 #define CELL_OUTSIDE 12
57 
58 #define CELL_BORDERS " xqlkmjwvtun~"
59 
60 #define CELL_STATUS_OFF 0
61 #define CELL_STATUS_TOP 1
62 #define CELL_STATUS_BOTTOM 2
63 
64 /* Check if cell is on the border of a particular pane. */
65 static int
66 screen_redraw_cell_border1(struct window_pane *wp, u_int px, u_int py)
67 {
68 	/* Inside pane. */
69 	if (px >= wp->xoff && px < wp->xoff + wp->sx &&
70 	    py >= wp->yoff && py < wp->yoff + wp->sy)
71 		return (0);
72 
73 	/* Left/right borders. */
74 	if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= wp->yoff + wp->sy) {
75 		if (wp->xoff != 0 && px == wp->xoff - 1)
76 			return (1);
77 		if (px == wp->xoff + wp->sx)
78 			return (2);
79 	}
80 
81 	/* Top/bottom borders. */
82 	if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= wp->xoff + wp->sx) {
83 		if (wp->yoff != 0 && py == wp->yoff - 1)
84 			return (3);
85 		if (py == wp->yoff + wp->sy)
86 			return (4);
87 	}
88 
89 	/* Outside pane. */
90 	return (-1);
91 }
92 
93 /* Check if a cell is on the pane border. */
94 static int
95 screen_redraw_cell_border(struct client *c, u_int px, u_int py)
96 {
97 	struct window		*w = c->session->curw->window;
98 	struct window_pane	*wp;
99 	int			 retval;
100 
101 	/* Check all the panes. */
102 	TAILQ_FOREACH(wp, &w->panes, entry) {
103 		if (!window_pane_visible(wp))
104 			continue;
105 		if ((retval = screen_redraw_cell_border1(wp, px, py)) != -1)
106 			return (!!retval);
107 	}
108 
109 	return (0);
110 }
111 
112 /* Check if cell inside a pane. */
113 static int
114 screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status,
115     struct window_pane **wpp)
116 {
117 	struct window		*w = c->session->curw->window;
118 	struct window_pane	*wp;
119 	int			 borders;
120 	u_int			 right, line;
121 
122 	*wpp = NULL;
123 
124 	if (px > w->sx || py > w->sy)
125 		return (CELL_OUTSIDE);
126 
127 	if (pane_status != CELL_STATUS_OFF) {
128 		TAILQ_FOREACH(wp, &w->panes, entry) {
129 			if (!window_pane_visible(wp))
130 				continue;
131 
132 			if (pane_status == CELL_STATUS_TOP)
133 				line = wp->yoff - 1;
134 			else
135 				line = wp->yoff + wp->sy;
136 			right = wp->xoff + 2 + wp->status_size - 1;
137 
138 			if (py == line && px >= wp->xoff + 2 && px <= right)
139 				return (CELL_INSIDE);
140 		}
141 	}
142 
143 	TAILQ_FOREACH(wp, &w->panes, entry) {
144 		if (!window_pane_visible(wp))
145 			continue;
146 		*wpp = wp;
147 
148 		/* If outside the pane and its border, skip it. */
149 		if ((wp->xoff != 0 && px < wp->xoff - 1) ||
150 		    px > wp->xoff + wp->sx ||
151 		    (wp->yoff != 0 && py < wp->yoff - 1) ||
152 		    py > wp->yoff + wp->sy)
153 			continue;
154 
155 		/* If definitely inside, return so. */
156 		if (!screen_redraw_cell_border(c, px, py))
157 			return (CELL_INSIDE);
158 
159 		/*
160 		 * Construct a bitmask of whether the cells to the left (bit
161 		 * 4), right, top, and bottom (bit 1) of this cell are borders.
162 		 */
163 		borders = 0;
164 		if (px == 0 || screen_redraw_cell_border(c, px - 1, py))
165 			borders |= 8;
166 		if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py))
167 			borders |= 4;
168 		if (pane_status == CELL_STATUS_TOP) {
169 			if (py != 0 && screen_redraw_cell_border(c, px, py - 1))
170 				borders |= 2;
171 		} else {
172 			if (py == 0 || screen_redraw_cell_border(c, px, py - 1))
173 				borders |= 2;
174 		}
175 		if (py <= w->sy && screen_redraw_cell_border(c, px, py + 1))
176 			borders |= 1;
177 
178 		/*
179 		 * Figure out what kind of border this cell is. Only one bit
180 		 * set doesn't make sense (can't have a border cell with no
181 		 * others connected).
182 		 */
183 		switch (borders) {
184 		case 15:	/* 1111, left right top bottom */
185 			return (CELL_JOIN);
186 		case 14:	/* 1110, left right top */
187 			return (CELL_BOTTOMJOIN);
188 		case 13:	/* 1101, left right bottom */
189 			return (CELL_TOPJOIN);
190 		case 12:	/* 1100, left right */
191 			return (CELL_TOPBOTTOM);
192 		case 11:	/* 1011, left top bottom */
193 			return (CELL_RIGHTJOIN);
194 		case 10:	/* 1010, left top */
195 			return (CELL_BOTTOMRIGHT);
196 		case 9:		/* 1001, left bottom */
197 			return (CELL_TOPRIGHT);
198 		case 7:		/* 0111, right top bottom */
199 			return (CELL_LEFTJOIN);
200 		case 6:		/* 0110, right top */
201 			return (CELL_BOTTOMLEFT);
202 		case 5:		/* 0101, right bottom */
203 			return (CELL_TOPLEFT);
204 		case 3:		/* 0011, top bottom */
205 			return (CELL_LEFTRIGHT);
206 		}
207 	}
208 
209 	return (CELL_OUTSIDE);
210 }
211 
212 /* Check if the border of a particular pane. */
213 static int
214 screen_redraw_check_is(u_int px, u_int py, int type, int pane_status,
215     struct window *w, struct window_pane *wantwp, struct window_pane *wp)
216 {
217 	int	border;
218 
219 	/* Is this off the active pane border? */
220 	border = screen_redraw_cell_border1(wantwp, px, py);
221 	if (border == 0 || border == -1)
222 		return (0);
223 	if (pane_status == CELL_STATUS_TOP && border == 4)
224 		return (0);
225 	if (pane_status == CELL_STATUS_BOTTOM && border == 3)
226 		return (0);
227 
228 	/* If there are more than two panes, that's enough. */
229 	if (window_count_panes(w) != 2)
230 		return (1);
231 
232 	/* Else if the cell is not a border cell, forget it. */
233 	if (wp == NULL || (type == CELL_OUTSIDE || type == CELL_INSIDE))
234 		return (1);
235 
236 	/* With status lines mark the entire line. */
237 	if (pane_status != CELL_STATUS_OFF)
238 		return (1);
239 
240 	/* Check if the pane covers the whole width. */
241 	if (wp->xoff == 0 && wp->sx == w->sx) {
242 		/* This can either be the top pane or the bottom pane. */
243 		if (wp->yoff == 0) { /* top pane */
244 			if (wp == wantwp)
245 				return (px <= wp->sx / 2);
246 			return (px > wp->sx / 2);
247 		}
248 		return (0);
249 	}
250 
251 	/* Check if the pane covers the whole height. */
252 	if (wp->yoff == 0 && wp->sy == w->sy) {
253 		/* This can either be the left pane or the right pane. */
254 		if (wp->xoff == 0) { /* left pane */
255 			if (wp == wantwp)
256 				return (py <= wp->sy / 2);
257 			return (py > wp->sy / 2);
258 		}
259 		return (0);
260 	}
261 
262 	return (1);
263 }
264 
265 /* Update pane status. */
266 static int
267 screen_redraw_make_pane_status(struct client *c, struct window *w,
268     struct window_pane *wp)
269 {
270 	struct grid_cell	 gc;
271 	const char		*fmt;
272 	struct format_tree	*ft;
273 	char			*out;
274 	size_t			 outlen;
275 	struct screen_write_ctx	 ctx;
276 	struct screen		 old;
277 
278 	if (wp == w->active)
279 		style_apply(&gc, w->options, "pane-active-border-style");
280 	else
281 		style_apply(&gc, w->options, "pane-border-style");
282 
283 	fmt = options_get_string(w->options, "pane-border-format");
284 
285 	ft = format_create(c, NULL, FORMAT_PANE|wp->id, 0);
286 	format_defaults(ft, c, NULL, NULL, wp);
287 
288 	memcpy(&old, &wp->status_screen, sizeof old);
289 	screen_init(&wp->status_screen, wp->sx, 1, 0);
290 	wp->status_screen.mode = 0;
291 
292 	out = format_expand(ft, fmt);
293 	outlen = screen_write_cstrlen("%s", out);
294 	if (outlen > wp->sx - 4)
295 		outlen = wp->sx - 4;
296 	screen_resize(&wp->status_screen, outlen, 1, 0);
297 
298 	screen_write_start(&ctx, NULL, &wp->status_screen);
299 	screen_write_cursormove(&ctx, 0, 0);
300 	screen_write_clearline(&ctx, 8);
301 	screen_write_cnputs(&ctx, outlen, &gc, "%s", out);
302 	screen_write_stop(&ctx);
303 
304 	free(out);
305 	format_free(ft);
306 
307 	wp->status_size = outlen;
308 
309 	if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
310 		screen_free(&old);
311 		return (0);
312 	}
313 	screen_free(&old);
314 	return (1);
315 }
316 
317 /* Draw pane status. */
318 static void
319 screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx)
320 {
321 	struct client		*c = ctx->c;
322 	struct window		*w = c->session->curw->window;
323 	struct options		*oo = c->session->options;
324 	struct tty		*tty = &c->tty;
325 	struct window_pane	*wp;
326 	int			 spos;
327 	u_int			 yoff;
328 
329 	spos = options_get_number(oo, "status-position");
330 	TAILQ_FOREACH(wp, &w->panes, entry) {
331 		if (!window_pane_visible(wp))
332 			continue;
333 		if (ctx->pane_status == CELL_STATUS_TOP)
334 			yoff = wp->yoff - 1;
335 		else
336 			yoff = wp->yoff + wp->sy;
337 		if (spos == 0)
338 			yoff += 1;
339 
340 		tty_draw_line(tty, NULL, &wp->status_screen, 0, wp->xoff + 2,
341 		    yoff);
342 	}
343 	tty_cursor(tty, 0, 0);
344 }
345 
346 /* Update status line and change flags if unchanged. */
347 static int
348 screen_redraw_update(struct client *c, int flags)
349 {
350 	struct window		*w = c->session->curw->window;
351 	struct window_pane	*wp;
352 	struct options		*wo = w->options;
353 	int			 redraw;
354 
355 	if (c->message_string != NULL)
356 		redraw = status_message_redraw(c);
357 	else if (c->prompt_string != NULL)
358 		redraw = status_prompt_redraw(c);
359 	else
360 		redraw = status_redraw(c);
361 	if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS))
362 		flags &= ~CLIENT_REDRAWSTATUS;
363 
364 	if (options_get_number(wo, "pane-border-status") != CELL_STATUS_OFF) {
365 		redraw = 0;
366 		TAILQ_FOREACH(wp, &w->panes, entry) {
367 			if (screen_redraw_make_pane_status(c, w, wp))
368 				redraw = 1;
369 		}
370 		if (redraw)
371 			flags |= CLIENT_REDRAWBORDERS;
372 	}
373 	return (flags);
374 }
375 
376 /* Set up redraw context. */
377 static void
378 screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx)
379 {
380 	struct session	*s = c->session;
381 	struct options	*oo = s->options;
382 	struct window	*w = s->curw->window;
383 	struct options	*wo = w->options;
384 
385 	memset(ctx, 0, sizeof *ctx);
386 	ctx->c = c;
387 
388 	ctx->lines = tty_status_lines(c);
389 	if (ctx->lines != 0 && options_get_number(oo, "status-position") == 0)
390 		ctx->top = 1;
391 	ctx->pane_status = options_get_number(wo, "pane-border-status");
392 
393 	ctx->sx = c->tty.sx;
394 	ctx->sy = c->tty.sy - ctx->lines;
395 }
396 
397 /* Redraw entire screen. */
398 void
399 screen_redraw_screen(struct client *c)
400 {
401 	struct screen_redraw_ctx	ctx;
402 	int				flags;
403 
404 	if (c->flags & CLIENT_SUSPENDED)
405 		return;
406 
407 	flags = screen_redraw_update(c, c->flags);
408 	screen_redraw_set_context(c, &ctx);
409 
410 	if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) {
411 		if (ctx.pane_status != CELL_STATUS_OFF)
412 			screen_redraw_draw_pane_status(&ctx);
413 		screen_redraw_draw_borders(&ctx);
414 	}
415 	if (flags & CLIENT_REDRAWWINDOW)
416 		screen_redraw_draw_panes(&ctx);
417 	if (ctx.lines != 0 &&
418 	    (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS)))
419 		screen_redraw_draw_status(&ctx);
420 	tty_reset(&c->tty);
421 }
422 
423 /* Draw a single pane. */
424 void
425 screen_redraw_pane(struct client *c, struct window_pane *wp)
426 {
427 	u_int	i, yoff;
428 
429 	if (!window_pane_visible(wp))
430 		return;
431 
432 	yoff = wp->yoff;
433 	if (status_at_line(c) == 0)
434 		yoff += status_line_size(c->session);
435 
436 	log_debug("%s: redraw pane %%%u (at %u,%u)", c->name, wp->id,
437 	    wp->xoff, yoff);
438 
439 	for (i = 0; i < wp->sy; i++)
440 		tty_draw_pane(&c->tty, wp, i, wp->xoff, yoff);
441 	tty_reset(&c->tty);
442 }
443 
444 /* Draw a border cell. */
445 static void
446 screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int x, u_int y,
447     int small, u_int msgx, u_int msgy, struct grid_cell *m_active_gc,
448     struct grid_cell *active_gc, struct grid_cell *m_other_gc,
449     struct grid_cell *other_gc)
450 {
451 	struct client		*c = ctx->c;
452 	struct session		*s = c->session;
453 	struct window		*w = s->curw->window;
454 	struct tty		*tty = &c->tty;
455 	struct window_pane	*wp;
456 	struct window_pane	*active = w->active;
457 	struct window_pane	*marked = marked_pane.wp;
458 	u_int			 type;
459 	int			 flag, pane_status = ctx->pane_status;
460 
461 	type = screen_redraw_check_cell(c, x, y, pane_status, &wp);
462 	if (type == CELL_INSIDE)
463 		return;
464 	if (type == CELL_OUTSIDE && small && x > msgx && y == msgy)
465 		return;
466 	flag = screen_redraw_check_is(x, y, type, pane_status, w, active, wp);
467 
468 	if (server_is_marked(s, s->curw, marked_pane.wp) &&
469 	    screen_redraw_check_is(x, y, type, pane_status, w, marked, wp)) {
470 		if (flag)
471 			tty_attributes(tty, m_active_gc, NULL);
472 		else
473 			tty_attributes(tty, m_other_gc, NULL);
474 	} else if (flag)
475 		tty_attributes(tty, active_gc, NULL);
476 	else
477 		tty_attributes(tty, other_gc, NULL);
478 	if (ctx->top)
479 		tty_cursor(tty, x, ctx->lines + y);
480 	else
481 		tty_cursor(tty, x, y);
482 	tty_putc(tty, CELL_BORDERS[type]);
483 }
484 
485 /* Draw the borders. */
486 static void
487 screen_redraw_draw_borders(struct screen_redraw_ctx *ctx)
488 {
489 	struct client		*c = ctx->c;
490 	struct session		*s = c->session;
491 	struct window		*w = s->curw->window;
492 	struct options		*oo = w->options;
493 	struct tty		*tty = &c->tty;
494 	struct grid_cell	 m_active_gc, active_gc, m_other_gc, other_gc;
495 	struct grid_cell	 msg_gc;
496 	u_int			 i, j, msgx = 0, msgy = 0;
497 	int			 small, flags;
498 	char			 msg[256];
499 	const char		*tmp;
500 	size_t			 msglen = 0;
501 
502 	small = (ctx->sy + ctx->top > w->sy) || (ctx->sx > w->sx);
503 	if (small) {
504 		flags = w->flags & (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT);
505 		if (flags == (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT))
506 			tmp = "force-width, force-height";
507 		else if (flags == WINDOW_FORCEWIDTH)
508 			tmp = "force-width";
509 		else if (flags == WINDOW_FORCEHEIGHT)
510 			tmp = "force-height";
511 		else if (c->flags & CLIENT_STATUSOFF)
512 			tmp = "status line";
513 		else
514 			tmp = "a smaller client";
515 		xsnprintf(msg, sizeof msg, "(size %ux%u from %s)",
516 		    w->sx, w->sy, tmp);
517 		msglen = strlen(msg);
518 
519 		if (ctx->sy - 1 + ctx->top > w->sy && ctx->sx >= msglen) {
520 			msgx = ctx->sx - msglen;
521 			msgy = ctx->sy - 1 + ctx->top;
522 		} else if (ctx->sx - w->sx > msglen) {
523 			msgx = ctx->sx - msglen;
524 			msgy = ctx->sy - 1 + ctx->top;
525 		} else
526 			small = 0;
527 	}
528 
529 	style_apply(&other_gc, oo, "pane-border-style");
530 	style_apply(&active_gc, oo, "pane-active-border-style");
531 	active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET;
532 
533 	memcpy(&m_other_gc, &other_gc, sizeof m_other_gc);
534 	m_other_gc.attr ^= GRID_ATTR_REVERSE;
535 	memcpy(&m_active_gc, &active_gc, sizeof m_active_gc);
536 	m_active_gc.attr ^= GRID_ATTR_REVERSE;
537 
538 	for (j = 0; j < ctx->sy; j++) {
539 		for (i = 0; i < ctx->sx; i++) {
540 			screen_redraw_draw_borders_cell(ctx, i, j, small,
541 			    msgx, msgy, &m_active_gc, &active_gc, &m_other_gc,
542 			    &other_gc);
543 		}
544 	}
545 
546 	if (small) {
547 		memcpy(&msg_gc, &grid_default_cell, sizeof msg_gc);
548 		tty_attributes(tty, &msg_gc, NULL);
549 		tty_cursor(tty, msgx, msgy);
550 		tty_puts(tty, msg);
551 	}
552 }
553 
554 /* Draw the panes. */
555 static void
556 screen_redraw_draw_panes(struct screen_redraw_ctx *ctx)
557 {
558 	struct client		*c = ctx->c;
559 	struct window		*w = c->session->curw->window;
560 	struct tty		*tty = &c->tty;
561 	struct window_pane	*wp;
562 	u_int			 i, y;
563 
564 	if (ctx->top)
565 		y = ctx->lines;
566 	else
567 		y = 0;
568 	TAILQ_FOREACH(wp, &w->panes, entry) {
569 		if (!window_pane_visible(wp))
570 			continue;
571 		for (i = 0; i < wp->sy; i++)
572 			tty_draw_pane(tty, wp, i, wp->xoff, y + wp->yoff);
573 		if (c->flags & CLIENT_IDENTIFY)
574 			screen_redraw_draw_number(ctx, wp);
575 	}
576 }
577 
578 /* Draw the status line. */
579 static void
580 screen_redraw_draw_status(struct screen_redraw_ctx *ctx)
581 {
582 	struct client	*c = ctx->c;
583 	struct tty	*tty = &c->tty;
584 	u_int		 i, y;
585 
586 	if (ctx->top)
587 		y = 0;
588 	else
589 		y = ctx->sy;
590 	for (i = 0; i < ctx->lines; i++)
591 		tty_draw_line(tty, NULL, &c->status.status, i, 0, y);
592 }
593 
594 /* Draw number on a pane. */
595 static void
596 screen_redraw_draw_number(struct screen_redraw_ctx *ctx, struct window_pane *wp)
597 {
598 	struct client		*c = ctx->c;
599 	struct tty		*tty = &c->tty;
600 	struct session		*s = c->session;
601 	struct options		*oo = s->options;
602 	struct window		*w = wp->window;
603 	struct grid_cell	 gc;
604 	u_int			 idx, px, py, i, j, xoff, yoff;
605 	int			 colour, active_colour;
606 	char			 buf[16], *ptr;
607 	size_t			 len;
608 
609 	if (window_pane_index(wp, &idx) != 0)
610 		fatalx("index not found");
611 	len = xsnprintf(buf, sizeof buf, "%u", idx);
612 
613 	if (wp->sx < len)
614 		return;
615 	colour = options_get_number(oo, "display-panes-colour");
616 	active_colour = options_get_number(oo, "display-panes-active-colour");
617 
618 	px = wp->sx / 2; py = wp->sy / 2;
619 	xoff = wp->xoff; yoff = wp->yoff;
620 
621 	if (ctx->top)
622 		yoff += ctx->lines;
623 
624 	if (wp->sx < len * 6 || wp->sy < 5) {
625 		tty_cursor(tty, xoff + px - len / 2, yoff + py);
626 		goto draw_text;
627 	}
628 
629 	px -= len * 3;
630 	py -= 2;
631 
632 	memcpy(&gc, &grid_default_cell, sizeof gc);
633 	if (w->active == wp)
634 		gc.bg = active_colour;
635 	else
636 		gc.bg = colour;
637 	gc.flags |= GRID_FLAG_NOPALETTE;
638 
639 	tty_attributes(tty, &gc, wp);
640 	for (ptr = buf; *ptr != '\0'; ptr++) {
641 		if (*ptr < '0' || *ptr > '9')
642 			continue;
643 		idx = *ptr - '0';
644 
645 		for (j = 0; j < 5; j++) {
646 			for (i = px; i < px + 5; i++) {
647 				tty_cursor(tty, xoff + i, yoff + py + j);
648 				if (window_clock_table[idx][j][i - px])
649 					tty_putc(tty, ' ');
650 			}
651 		}
652 		px += 6;
653 	}
654 
655 	len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy);
656 	if (wp->sx < len || wp->sy < 6)
657 		return;
658 	tty_cursor(tty, xoff + wp->sx - len, yoff);
659 
660 draw_text:
661 	memcpy(&gc, &grid_default_cell, sizeof gc);
662 	if (w->active == wp)
663 		gc.fg = active_colour;
664 	else
665 		gc.fg = colour;
666 	gc.flags |= GRID_FLAG_NOPALETTE;
667 
668 	tty_attributes(tty, &gc, wp);
669 	tty_puts(tty, buf);
670 
671 	tty_cursor(tty, 0, 0);
672 }
673