xref: /openbsd/usr.bin/tmux/layout-custom.c (revision 10e0aa77)
1*10e0aa77Snicm /* $OpenBSD: layout-custom.c,v 1.23 2024/04/15 08:19:55 nicm Exp $ */
2f4611a41Snicm 
3f4611a41Snicm /*
498ca8272Snicm  * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
5f4611a41Snicm  *
6f4611a41Snicm  * Permission to use, copy, modify, and distribute this software for any
7f4611a41Snicm  * purpose with or without fee is hereby granted, provided that the above
8f4611a41Snicm  * copyright notice and this permission notice appear in all copies.
9f4611a41Snicm  *
10f4611a41Snicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11f4611a41Snicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12f4611a41Snicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13f4611a41Snicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14f4611a41Snicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15f4611a41Snicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16f4611a41Snicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17f4611a41Snicm  */
18f4611a41Snicm 
19f4611a41Snicm #include <sys/types.h>
20f4611a41Snicm 
21f4611a41Snicm #include <ctype.h>
22f4611a41Snicm #include <string.h>
23f4611a41Snicm 
24f4611a41Snicm #include "tmux.h"
25f4611a41Snicm 
266ae61da3Snicm static struct layout_cell	*layout_find_bottomright(struct layout_cell *);
276ae61da3Snicm static u_short			 layout_checksum(const char *);
286ae61da3Snicm static int			 layout_append(struct layout_cell *, char *,
296ae61da3Snicm 				     size_t);
306ae61da3Snicm static struct layout_cell	*layout_construct(struct layout_cell *,
316ae61da3Snicm 				     const char **);
326ae61da3Snicm static void			 layout_assign(struct window_pane **,
336ae61da3Snicm 				     struct layout_cell *);
34f4611a41Snicm 
359c4e01faSnicm /* Find the bottom-right cell. */
366ae61da3Snicm static struct layout_cell *
layout_find_bottomright(struct layout_cell * lc)379c4e01faSnicm layout_find_bottomright(struct layout_cell *lc)
389c4e01faSnicm {
399c4e01faSnicm 	if (lc->type == LAYOUT_WINDOWPANE)
409c4e01faSnicm 		return (lc);
419c4e01faSnicm 	lc = TAILQ_LAST(&lc->cells, layout_cells);
429c4e01faSnicm 	return (layout_find_bottomright(lc));
439c4e01faSnicm }
449c4e01faSnicm 
45f4611a41Snicm /* Calculate layout checksum. */
466ae61da3Snicm static u_short
layout_checksum(const char * layout)47f4611a41Snicm layout_checksum(const char *layout)
48f4611a41Snicm {
49f4611a41Snicm 	u_short	csum;
50f4611a41Snicm 
51f4611a41Snicm 	csum = 0;
52f4611a41Snicm 	for (; *layout != '\0'; layout++) {
53f4611a41Snicm 		csum = (csum >> 1) + ((csum & 1) << 15);
54f4611a41Snicm 		csum += *layout;
55f4611a41Snicm 	}
56f4611a41Snicm 	return (csum);
57f4611a41Snicm }
58f4611a41Snicm 
59f4611a41Snicm /* Dump layout as a string. */
60f4611a41Snicm char *
layout_dump(struct layout_cell * root)61b3e07ccdSnicm layout_dump(struct layout_cell *root)
62f4611a41Snicm {
634512d27dSnicm 	char	layout[8192], *out;
64f4611a41Snicm 
65f4611a41Snicm 	*layout = '\0';
66b3e07ccdSnicm 	if (layout_append(root, layout, sizeof layout) != 0)
67f4611a41Snicm 		return (NULL);
68f4611a41Snicm 
696ae61da3Snicm 	xasprintf(&out, "%04hx,%s", layout_checksum(layout), layout);
70f4611a41Snicm 	return (out);
71f4611a41Snicm }
72f4611a41Snicm 
73f4611a41Snicm /* Append information for a single cell. */
746ae61da3Snicm static int
layout_append(struct layout_cell * lc,char * buf,size_t len)75f4611a41Snicm layout_append(struct layout_cell *lc, char *buf, size_t len)
76f4611a41Snicm {
77f4611a41Snicm 	struct layout_cell     *lcchild;
78f4611a41Snicm 	char			tmp[64];
79f4611a41Snicm 	size_t			tmplen;
80f4611a41Snicm 	const char	       *brackets = "][";
81f4611a41Snicm 
82f4611a41Snicm 	if (len == 0)
83f4611a41Snicm 		return (-1);
84f4611a41Snicm 
857050b149Snicm 	if (lc->wp != NULL) {
867050b149Snicm 		tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u,%u",
877050b149Snicm 		    lc->sx, lc->sy, lc->xoff, lc->yoff, lc->wp->id);
887050b149Snicm 	} else {
897050b149Snicm 		tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u",
907050b149Snicm 		    lc->sx, lc->sy, lc->xoff, lc->yoff);
917050b149Snicm 	}
92f4611a41Snicm 	if (tmplen > (sizeof tmp) - 1)
93f4611a41Snicm 		return (-1);
94f4611a41Snicm 	if (strlcat(buf, tmp, len) >= len)
95f4611a41Snicm 		return (-1);
96f4611a41Snicm 
97f4611a41Snicm 	switch (lc->type) {
98f4611a41Snicm 	case LAYOUT_LEFTRIGHT:
99f4611a41Snicm 		brackets = "}{";
100f4611a41Snicm 		/* FALLTHROUGH */
101f4611a41Snicm 	case LAYOUT_TOPBOTTOM:
102f4611a41Snicm 		if (strlcat(buf, &brackets[1], len) >= len)
103f4611a41Snicm 			return (-1);
104f4611a41Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
105f4611a41Snicm 			if (layout_append(lcchild, buf, len) != 0)
106f4611a41Snicm 				return (-1);
107f4611a41Snicm 			if (strlcat(buf, ",", len) >= len)
108f4611a41Snicm 				return (-1);
109f4611a41Snicm 		}
110f4611a41Snicm 		buf[strlen(buf) - 1] = brackets[0];
111f4611a41Snicm 		break;
112f4611a41Snicm 	case LAYOUT_WINDOWPANE:
113f4611a41Snicm 		break;
114f4611a41Snicm 	}
115f4611a41Snicm 
116f4611a41Snicm 	return (0);
117f4611a41Snicm }
118f4611a41Snicm 
119f382be73Snicm /* Check layout sizes fit. */
120f382be73Snicm static int
layout_check(struct layout_cell * lc)121f382be73Snicm layout_check(struct layout_cell *lc)
122f382be73Snicm {
123f382be73Snicm 	struct layout_cell	*lcchild;
124f382be73Snicm 	u_int			 n = 0;
125f382be73Snicm 
126f382be73Snicm 	switch (lc->type) {
127f382be73Snicm 	case LAYOUT_WINDOWPANE:
128f382be73Snicm 		break;
129f382be73Snicm 	case LAYOUT_LEFTRIGHT:
130f382be73Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
131f382be73Snicm 			if (lcchild->sy != lc->sy)
132f382be73Snicm 				return (0);
133f382be73Snicm 			if (!layout_check(lcchild))
134f382be73Snicm 				return (0);
135f382be73Snicm 			n += lcchild->sx + 1;
136f382be73Snicm 		}
137f382be73Snicm 		if (n - 1 != lc->sx)
138f382be73Snicm 			return (0);
139f382be73Snicm 		break;
140f382be73Snicm 	case LAYOUT_TOPBOTTOM:
141f382be73Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
142f382be73Snicm 			if (lcchild->sx != lc->sx)
143f382be73Snicm 				return (0);
144f382be73Snicm 			if (!layout_check(lcchild))
145f382be73Snicm 				return (0);
146f382be73Snicm 			n += lcchild->sy + 1;
147f382be73Snicm 		}
148f382be73Snicm 		if (n - 1 != lc->sy)
149f382be73Snicm 			return (0);
150f382be73Snicm 		break;
151f382be73Snicm 	}
152f382be73Snicm 	return (1);
153f382be73Snicm }
154f382be73Snicm 
155f4611a41Snicm /* Parse a layout string and arrange window as layout. */
156f4611a41Snicm int
layout_parse(struct window * w,const char * layout,char ** cause)1572a71bce1Snicm layout_parse(struct window *w, const char *layout, char **cause)
158f4611a41Snicm {
159f4611a41Snicm 	struct layout_cell	*lc, *lcchild;
160f4611a41Snicm 	struct window_pane	*wp;
161f382be73Snicm 	u_int			 npanes, ncells, sx = 0, sy = 0;
162f4611a41Snicm 	u_short			 csum;
163f4611a41Snicm 
164f4611a41Snicm 	/* Check validity. */
16508d1f2caSnicm 	if (sscanf(layout, "%hx,", &csum) != 1) {
16608d1f2caSnicm 		*cause = xstrdup("invalid layout");
167f4611a41Snicm 		return (-1);
16808d1f2caSnicm 	}
169f4611a41Snicm 	layout += 5;
1702a71bce1Snicm 	if (csum != layout_checksum(layout)) {
1712a71bce1Snicm 		*cause = xstrdup("invalid layout");
172f4611a41Snicm 		return (-1);
1732a71bce1Snicm 	}
174f4611a41Snicm 
175f4611a41Snicm 	/* Build the layout. */
176f4611a41Snicm 	lc = layout_construct(NULL, &layout);
1772a71bce1Snicm 	if (lc == NULL) {
1782a71bce1Snicm 		*cause = xstrdup("invalid layout");
179f4611a41Snicm 		return (-1);
1802a71bce1Snicm 	}
1812a71bce1Snicm 	if (*layout != '\0') {
1822a71bce1Snicm 		*cause = xstrdup("invalid layout");
183f4611a41Snicm 		goto fail;
1842a71bce1Snicm 	}
185f4611a41Snicm 
186f4611a41Snicm 	/* Check this window will fit into the layout. */
187f4611a41Snicm 	for (;;) {
188f4611a41Snicm 		npanes = window_count_panes(w);
189f4611a41Snicm 		ncells = layout_count_cells(lc);
1902a71bce1Snicm 		if (npanes > ncells) {
1912a71bce1Snicm 			xasprintf(cause, "have %u panes but need %u", npanes,
1922a71bce1Snicm 			    ncells);
193f4611a41Snicm 			goto fail;
1942a71bce1Snicm 		}
195f4611a41Snicm 		if (npanes == ncells)
196f4611a41Snicm 			break;
197f4611a41Snicm 
198f4611a41Snicm 		/* Fewer panes than cells - close the bottom right. */
199f4611a41Snicm 		lcchild = layout_find_bottomright(lc);
20007b91187Snicm 		layout_destroy_cell(w, lcchild, &lc);
201f4611a41Snicm 	}
202f4611a41Snicm 
203f382be73Snicm 	/*
204f382be73Snicm 	 * It appears older versions of tmux were able to generate layouts with
205f382be73Snicm 	 * an incorrect top cell size - if it is larger than the top child then
206f382be73Snicm 	 * correct that (if this is still wrong the check code will catch it).
207f382be73Snicm 	 */
208f382be73Snicm 	switch (lc->type) {
209f382be73Snicm 	case LAYOUT_WINDOWPANE:
210f382be73Snicm 		break;
211f382be73Snicm 	case LAYOUT_LEFTRIGHT:
212f382be73Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
213f382be73Snicm 			sy = lcchild->sy + 1;
214f382be73Snicm 			sx += lcchild->sx + 1;
215f382be73Snicm 		}
216f382be73Snicm 		break;
217f382be73Snicm 	case LAYOUT_TOPBOTTOM:
218f382be73Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
219f382be73Snicm 			sx = lcchild->sx + 1;
220f382be73Snicm 			sy += lcchild->sy + 1;
221f382be73Snicm 		}
222f382be73Snicm 		break;
223f382be73Snicm 	}
22438c82affSnicm 	if (lc->type != LAYOUT_WINDOWPANE && (lc->sx != sx || lc->sy != sy)) {
225f382be73Snicm 		log_debug("fix layout %u,%u to %u,%u", lc->sx, lc->sy, sx,sy);
226f382be73Snicm 		layout_print_cell(lc, __func__, 0);
227f382be73Snicm 		lc->sx = sx - 1; lc->sy = sy - 1;
228f382be73Snicm 	}
229f382be73Snicm 
230f382be73Snicm 	/* Check the new layout. */
2312a71bce1Snicm 	if (!layout_check(lc)) {
2322a71bce1Snicm 		*cause = xstrdup("size mismatch after applying layout");
233*10e0aa77Snicm 		goto fail;
2342a71bce1Snicm 	}
235f382be73Snicm 
236b22e26c3Snicm 	/* Resize to the layout size. */
2374a8b0ea5Snicm 	window_resize(w, lc->sx, lc->sy, -1, -1);
238f4611a41Snicm 
239f4611a41Snicm 	/* Destroy the old layout and swap to the new. */
240f4611a41Snicm 	layout_free_cell(w->layout_root);
241f4611a41Snicm 	w->layout_root = lc;
242f4611a41Snicm 
243f4611a41Snicm 	/* Assign the panes into the cells. */
244f4611a41Snicm 	wp = TAILQ_FIRST(&w->panes);
245f4611a41Snicm 	layout_assign(&wp, lc);
246f4611a41Snicm 
247f4611a41Snicm 	/* Update pane offsets and sizes. */
24842fbd26aSnicm 	layout_fix_offsets(w);
249baddd6b2Snicm 	layout_fix_panes(w, NULL);
2508e1abdf8Snicm 	recalculate_sizes();
251f4611a41Snicm 
252f4611a41Snicm 	layout_print_cell(lc, __func__, 0);
253f4611a41Snicm 
2542ae124feSnicm 	notify_window("window-layout-changed", w);
25505babb28Snicm 
256f4611a41Snicm 	return (0);
257f4611a41Snicm 
258f4611a41Snicm fail:
259f4611a41Snicm 	layout_free_cell(lc);
260f4611a41Snicm 	return (-1);
261f4611a41Snicm }
262f4611a41Snicm 
263f4611a41Snicm /* Assign panes into cells. */
2646ae61da3Snicm static void
layout_assign(struct window_pane ** wp,struct layout_cell * lc)265f4611a41Snicm layout_assign(struct window_pane **wp, struct layout_cell *lc)
266f4611a41Snicm {
267f4611a41Snicm 	struct layout_cell	*lcchild;
268f4611a41Snicm 
269f4611a41Snicm 	switch (lc->type) {
270f4611a41Snicm 	case LAYOUT_WINDOWPANE:
271f4611a41Snicm 		layout_make_leaf(lc, *wp);
272f4611a41Snicm 		*wp = TAILQ_NEXT(*wp, entry);
273f4611a41Snicm 		return;
274f4611a41Snicm 	case LAYOUT_LEFTRIGHT:
275f4611a41Snicm 	case LAYOUT_TOPBOTTOM:
276f4611a41Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry)
277f4611a41Snicm 			layout_assign(wp, lcchild);
278f4611a41Snicm 		return;
279f4611a41Snicm 	}
280f4611a41Snicm }
281f4611a41Snicm 
282f4611a41Snicm /* Construct a cell from all or part of a layout tree. */
2836ae61da3Snicm static struct layout_cell *
layout_construct(struct layout_cell * lcparent,const char ** layout)284f4611a41Snicm layout_construct(struct layout_cell *lcparent, const char **layout)
285f4611a41Snicm {
286f4611a41Snicm 	struct layout_cell     *lc, *lcchild;
287f4611a41Snicm 	u_int			sx, sy, xoff, yoff;
288ff23f763Snicm 	const char	       *saved;
289f4611a41Snicm 
290f4611a41Snicm 	if (!isdigit((u_char) **layout))
291f4611a41Snicm 		return (NULL);
292ff23f763Snicm 	if (sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4)
293f4611a41Snicm 		return (NULL);
294f4611a41Snicm 
295f4611a41Snicm 	while (isdigit((u_char) **layout))
296f4611a41Snicm 		(*layout)++;
297f4611a41Snicm 	if (**layout != 'x')
298f4611a41Snicm 		return (NULL);
299f4611a41Snicm 	(*layout)++;
300f4611a41Snicm 	while (isdigit((u_char) **layout))
301f4611a41Snicm 		(*layout)++;
302f4611a41Snicm 	if (**layout != ',')
303f4611a41Snicm 		return (NULL);
304f4611a41Snicm 	(*layout)++;
305f4611a41Snicm 	while (isdigit((u_char) **layout))
306f4611a41Snicm 		(*layout)++;
307f4611a41Snicm 	if (**layout != ',')
308f4611a41Snicm 		return (NULL);
309f4611a41Snicm 	(*layout)++;
310f4611a41Snicm 	while (isdigit((u_char) **layout))
311f4611a41Snicm 		(*layout)++;
3127050b149Snicm 	if (**layout == ',') {
313ff23f763Snicm 		saved = *layout;
3147050b149Snicm 		(*layout)++;
3157050b149Snicm 		while (isdigit((u_char) **layout))
3167050b149Snicm 			(*layout)++;
317ff23f763Snicm 		if (**layout == 'x')
318ff23f763Snicm 			*layout = saved;
3197050b149Snicm 	}
320f4611a41Snicm 
321f4611a41Snicm 	lc = layout_create_cell(lcparent);
322f4611a41Snicm 	lc->sx = sx;
323f4611a41Snicm 	lc->sy = sy;
324f4611a41Snicm 	lc->xoff = xoff;
325f4611a41Snicm 	lc->yoff = yoff;
326f4611a41Snicm 
327f4611a41Snicm 	switch (**layout) {
328f4611a41Snicm 	case ',':
329f4611a41Snicm 	case '}':
330f4611a41Snicm 	case ']':
331f4611a41Snicm 	case '\0':
332f4611a41Snicm 		return (lc);
333f4611a41Snicm 	case '{':
334f4611a41Snicm 		lc->type = LAYOUT_LEFTRIGHT;
335f4611a41Snicm 		break;
336f4611a41Snicm 	case '[':
337f4611a41Snicm 		lc->type = LAYOUT_TOPBOTTOM;
338f4611a41Snicm 		break;
339f4611a41Snicm 	default:
340f4611a41Snicm 		goto fail;
341f4611a41Snicm 	}
342f4611a41Snicm 
343f4611a41Snicm 	do {
344f4611a41Snicm 		(*layout)++;
345f4611a41Snicm 		lcchild = layout_construct(lc, layout);
346f4611a41Snicm 		if (lcchild == NULL)
347f4611a41Snicm 			goto fail;
348f4611a41Snicm 		TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
349f4611a41Snicm 	} while (**layout == ',');
350f4611a41Snicm 
351f4611a41Snicm 	switch (lc->type) {
352f4611a41Snicm 	case LAYOUT_LEFTRIGHT:
353f4611a41Snicm 		if (**layout != '}')
354f4611a41Snicm 			goto fail;
355f4611a41Snicm 		break;
356f4611a41Snicm 	case LAYOUT_TOPBOTTOM:
357f4611a41Snicm 		if (**layout != ']')
358f4611a41Snicm 			goto fail;
359f4611a41Snicm 		break;
360f4611a41Snicm 	default:
361f4611a41Snicm 		goto fail;
362f4611a41Snicm 	}
363f4611a41Snicm 	(*layout)++;
364f4611a41Snicm 
365f4611a41Snicm 	return (lc);
366f4611a41Snicm 
367f4611a41Snicm fail:
368f4611a41Snicm 	layout_free_cell(lc);
369f4611a41Snicm 	return (NULL);
370f4611a41Snicm }
371