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