xref: /openbsd-src/usr.bin/tmux/screen-redraw.c (revision c7e8ea31cd41a963f06f0a8ba93948b06aa6b4a4)
1 /* $OpenBSD: screen-redraw.c,v 1.46 2017/05/01 12:20:55 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, int, u_int);
37 static void	screen_redraw_draw_panes(struct client *, u_int);
38 static void	screen_redraw_draw_status(struct client *, u_int);
39 static void	screen_redraw_draw_number(struct client *, struct window_pane *,
40 		    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;
381 	int	 		 status, pane_status, spos;
382 
383 	/* Suspended clients should not be updated. */
384 	if (c->flags & CLIENT_SUSPENDED)
385 		return;
386 
387 	/* Get status line, er, status. */
388 	spos = options_get_number(oo, "status-position");
389 	if (c->message_string != NULL || c->prompt_string != NULL)
390 		status = 1;
391 	else
392 		status = options_get_number(oo, "status");
393 	top = 0;
394 	if (status && spos == 0)
395 		top = 1;
396 	if (!status)
397 		draw_status = 0;
398 
399 	/* Draw the elements. */
400 	if (draw_borders) {
401 		pane_status = options_get_number(wo, "pane-border-status");
402 		screen_redraw_draw_borders(c, status, pane_status, top);
403 		if (pane_status != CELL_STATUS_OFF)
404 			screen_redraw_draw_pane_status(c, pane_status);
405 	}
406 	if (draw_panes)
407 		screen_redraw_draw_panes(c, top);
408 	if (draw_status)
409 		screen_redraw_draw_status(c, top);
410 	tty_reset(tty);
411 }
412 
413 /* Draw a single pane. */
414 void
415 screen_redraw_pane(struct client *c, struct window_pane *wp)
416 {
417 	u_int	i, yoff;
418 
419 	if (!window_pane_visible(wp))
420 		return;
421 
422 	yoff = wp->yoff;
423 	if (status_at_line(c) == 0)
424 		yoff++;
425 
426 	log_debug("%s: redraw pane %%%u (at %u,%u)", c->name, wp->id,
427 	    wp->xoff, yoff);
428 
429 	for (i = 0; i < wp->sy; i++)
430 		tty_draw_pane(&c->tty, wp, i, wp->xoff, yoff);
431 	tty_reset(&c->tty);
432 }
433 
434 /* Draw the borders. */
435 static void
436 screen_redraw_draw_borders(struct client *c, int status, int pane_status,
437     u_int top)
438 {
439 	struct session		*s = c->session;
440 	struct window		*w = s->curw->window;
441 	struct options		*oo = w->options;
442 	struct tty		*tty = &c->tty;
443 	struct window_pane	*wp;
444 	struct grid_cell	 m_active_gc, active_gc, m_other_gc, other_gc;
445 	struct grid_cell	 msg_gc;
446 	u_int		 	 i, j, type, msgx = 0, msgy = 0;
447 	int			 active, small, flags;
448 	char			 msg[256];
449 	const char		*tmp;
450 	size_t			 msglen = 0;
451 
452 	small = (tty->sy - status + top > w->sy) || (tty->sx > w->sx);
453 	if (small) {
454 		flags = w->flags & (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT);
455 		if (flags == (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT))
456 			tmp = "force-width, force-height";
457 		else if (flags == WINDOW_FORCEWIDTH)
458 			tmp = "force-width";
459 		else if (flags == WINDOW_FORCEHEIGHT)
460 			tmp = "force-height";
461 		else
462 			tmp = "a smaller client";
463 		xsnprintf(msg, sizeof msg, "(size %ux%u from %s)",
464 		    w->sx, w->sy, tmp);
465 		msglen = strlen(msg);
466 
467 		if (tty->sy - 1 - status + top > w->sy && tty->sx >= msglen) {
468 			msgx = tty->sx - msglen;
469 			msgy = tty->sy - 1 - status + top;
470 		} else if (tty->sx - w->sx > msglen) {
471 			msgx = tty->sx - msglen;
472 			msgy = tty->sy - 1 - status + top;
473 		} else
474 			small = 0;
475 	}
476 
477 	style_apply(&other_gc, oo, "pane-border-style");
478 	style_apply(&active_gc, oo, "pane-active-border-style");
479 	active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET;
480 
481 	memcpy(&m_other_gc, &other_gc, sizeof m_other_gc);
482 	m_other_gc.attr ^= GRID_ATTR_REVERSE;
483 	memcpy(&m_active_gc, &active_gc, sizeof m_active_gc);
484 	m_active_gc.attr ^= GRID_ATTR_REVERSE;
485 
486 	for (j = 0; j < tty->sy - status; j++) {
487 		for (i = 0; i < tty->sx; i++) {
488 			type = screen_redraw_check_cell(c, i, j, pane_status,
489 			    &wp);
490 			if (type == CELL_INSIDE)
491 				continue;
492 			if (type == CELL_OUTSIDE && small &&
493 			    i > msgx && j == msgy)
494 				continue;
495 			active = screen_redraw_check_is(i, j, type, pane_status,
496 			    w, w->active, wp);
497 			if (server_is_marked(s, s->curw, marked_pane.wp) &&
498 			    screen_redraw_check_is(i, j, type, pane_status, w,
499 			    marked_pane.wp, wp)) {
500 				if (active)
501 					tty_attributes(tty, &m_active_gc, NULL);
502 				else
503 					tty_attributes(tty, &m_other_gc, NULL);
504 			} else if (active)
505 				tty_attributes(tty, &active_gc, NULL);
506 			else
507 				tty_attributes(tty, &other_gc, NULL);
508 			tty_cursor(tty, i, top + j);
509 			tty_putc(tty, CELL_BORDERS[type]);
510 		}
511 	}
512 
513 	if (small) {
514 		memcpy(&msg_gc, &grid_default_cell, sizeof msg_gc);
515 		tty_attributes(tty, &msg_gc, NULL);
516 		tty_cursor(tty, msgx, msgy);
517 		tty_puts(tty, msg);
518 	}
519 }
520 
521 /* Draw the panes. */
522 static void
523 screen_redraw_draw_panes(struct client *c, u_int top)
524 {
525 	struct window		*w = c->session->curw->window;
526 	struct tty		*tty = &c->tty;
527 	struct window_pane	*wp;
528 	u_int		 	 i;
529 
530 	TAILQ_FOREACH(wp, &w->panes, entry) {
531 		if (!window_pane_visible(wp))
532 			continue;
533 		for (i = 0; i < wp->sy; i++)
534 			tty_draw_pane(tty, wp, i, wp->xoff, top + wp->yoff);
535 		if (c->flags & CLIENT_IDENTIFY)
536 			screen_redraw_draw_number(c, wp, top);
537 	}
538 }
539 
540 /* Draw the status line. */
541 static void
542 screen_redraw_draw_status(struct client *c, u_int top)
543 {
544 	struct tty	*tty = &c->tty;
545 
546 	if (top)
547 		tty_draw_line(tty, NULL, &c->status, 0, 0, 0);
548 	else
549 		tty_draw_line(tty, NULL, &c->status, 0, 0, tty->sy - 1);
550 }
551 
552 /* Draw number on a pane. */
553 static void
554 screen_redraw_draw_number(struct client *c, struct window_pane *wp, u_int top)
555 {
556 	struct tty		*tty = &c->tty;
557 	struct session		*s = c->session;
558 	struct options		*oo = s->options;
559 	struct window		*w = wp->window;
560 	struct grid_cell	 gc;
561 	u_int			 idx, px, py, i, j, xoff, yoff;
562 	int			 colour, active_colour;
563 	char			 buf[16], *ptr;
564 	size_t			 len;
565 
566 	if (window_pane_index(wp, &idx) != 0)
567 		fatalx("index not found");
568 	len = xsnprintf(buf, sizeof buf, "%u", idx);
569 
570 	if (wp->sx < len)
571 		return;
572 	colour = options_get_number(oo, "display-panes-colour");
573 	active_colour = options_get_number(oo, "display-panes-active-colour");
574 
575 	px = wp->sx / 2; py = wp->sy / 2;
576 	xoff = wp->xoff; yoff = wp->yoff;
577 
578 	if (top)
579 		yoff++;
580 
581 	if (wp->sx < len * 6 || wp->sy < 5) {
582 		tty_cursor(tty, xoff + px - len / 2, yoff + py);
583 		goto draw_text;
584 	}
585 
586 	px -= len * 3;
587 	py -= 2;
588 
589 	memcpy(&gc, &grid_default_cell, sizeof gc);
590 	if (w->active == wp)
591 		gc.bg = active_colour;
592 	else
593 		gc.bg = colour;
594 	gc.flags |= GRID_FLAG_NOPALETTE;
595 
596 	tty_attributes(tty, &gc, wp);
597 	for (ptr = buf; *ptr != '\0'; ptr++) {
598 		if (*ptr < '0' || *ptr > '9')
599 			continue;
600 		idx = *ptr - '0';
601 
602 		for (j = 0; j < 5; j++) {
603 			for (i = px; i < px + 5; i++) {
604 				tty_cursor(tty, xoff + i, yoff + py + j);
605 				if (window_clock_table[idx][j][i - px])
606 					tty_putc(tty, ' ');
607 			}
608 		}
609 		px += 6;
610 	}
611 
612 	len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy);
613 	if (wp->sx < len || wp->sy < 6)
614 		return;
615 	tty_cursor(tty, xoff + wp->sx - len, yoff);
616 
617 draw_text:
618 	memcpy(&gc, &grid_default_cell, sizeof gc);
619 	if (w->active == wp)
620 		gc.fg = active_colour;
621 	else
622 		gc.fg = colour;
623 	gc.flags |= GRID_FLAG_NOPALETTE;
624 
625 	tty_attributes(tty, &gc, wp);
626 	tty_puts(tty, buf);
627 
628 	tty_cursor(tty, 0, 0);
629 }
630