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