xref: /openbsd-src/usr.bin/tmux/resize.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /* $OpenBSD: resize.c,v 1.38 2020/01/28 13:23:24 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 void
26 resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
27 {
28 	int	zoomed;
29 
30 	/* Check size limits. */
31 	if (sx < WINDOW_MINIMUM)
32 		sx = WINDOW_MINIMUM;
33 	if (sx > WINDOW_MAXIMUM)
34 		sx = WINDOW_MAXIMUM;
35 	if (sy < WINDOW_MINIMUM)
36 		sy = WINDOW_MINIMUM;
37 	if (sy > WINDOW_MAXIMUM)
38 		sy = WINDOW_MAXIMUM;
39 
40 	/* If the window is zoomed, unzoom. */
41 	zoomed = w->flags & WINDOW_ZOOMED;
42 	if (zoomed)
43 		window_unzoom(w);
44 
45 	/* Resize the layout first. */
46 	layout_resize(w, sx, sy);
47 
48 	/* Resize the window, it can be no smaller than the layout. */
49 	if (sx < w->layout_root->sx)
50 		sx = w->layout_root->sx;
51 	if (sy < w->layout_root->sy)
52 		sy = w->layout_root->sy;
53 	window_resize(w, sx, sy, xpixel, ypixel);
54 	log_debug("%s: @%u resized to %u,%u; layout %u,%u", __func__, w->id,
55 	    sx, sy, w->layout_root->sx, w->layout_root->sy);
56 
57 	/* Restore the window zoom state. */
58 	if (zoomed)
59 		window_zoom(w->active);
60 
61 	tty_update_window_offset(w);
62 	server_redraw_window(w);
63 	notify_window("window-layout-changed", w);
64 }
65 
66 static int
67 ignore_client_size(struct client *c)
68 {
69 	struct client	*loop;
70 
71 	if (c->session == NULL)
72 		return (1);
73 	if (c->flags & CLIENT_NOSIZEFLAGS)
74 		return (1);
75 	if (c->flags & CLIENT_READONLY) {
76 		/*
77 		 * Ignore readonly clients if there are any attached clients
78 		 * that aren't readonly.
79 		 */
80 		TAILQ_FOREACH (loop, &clients, entry) {
81 			if (loop->session == NULL)
82 				continue;
83 			if (loop->flags & CLIENT_NOSIZEFLAGS)
84 				continue;
85 			if (~loop->flags & CLIENT_READONLY)
86 				return (1);
87 		}
88 	}
89 	if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED))
90 		return (1);
91 	return (0);
92 }
93 
94 void
95 default_window_size(struct client *c, struct session *s, struct window *w,
96     u_int *sx, u_int *sy, u_int *xpixel, u_int *ypixel, int type)
97 {
98 	struct client	*loop;
99 	u_int		 cx, cy, n;
100 	const char	*value;
101 
102 	if (type == -1)
103 		type = options_get_number(global_w_options, "window-size");
104 	switch (type) {
105 	case WINDOW_SIZE_LARGEST:
106 		*sx = *sy = 0;
107 		*xpixel = *ypixel = 0;
108 		TAILQ_FOREACH(loop, &clients, entry) {
109 			if (ignore_client_size(loop))
110 				continue;
111 			if (w != NULL && !session_has(loop->session, w))
112 				continue;
113 			if (w == NULL && loop->session != s)
114 				continue;
115 
116 			cx = loop->tty.sx;
117 			cy = loop->tty.sy - status_line_size(loop);
118 
119 			if (cx > *sx)
120 				*sx = cx;
121 			if (cy > *sy)
122 				*sy = cy;
123 
124 			if (loop->tty.xpixel > *xpixel &&
125 			    loop->tty.ypixel > *ypixel) {
126 				*xpixel = loop->tty.xpixel;
127 				*ypixel = loop->tty.ypixel;
128 			}
129 		}
130 		if (*sx == 0 || *sy == 0)
131 			goto manual;
132 		break;
133 	case WINDOW_SIZE_SMALLEST:
134 		*sx = *sy = UINT_MAX;
135 		*xpixel = *ypixel = 0;
136 		TAILQ_FOREACH(loop, &clients, entry) {
137 			if (ignore_client_size(loop))
138 				continue;
139 			if (w != NULL && !session_has(loop->session, w))
140 				continue;
141 			if (w == NULL && loop->session != s)
142 				continue;
143 
144 			cx = loop->tty.sx;
145 			cy = loop->tty.sy - status_line_size(loop);
146 
147 			if (cx < *sx)
148 				*sx = cx;
149 			if (cy < *sy)
150 				*sy = cy;
151 
152 			if (loop->tty.xpixel > *xpixel &&
153 			    loop->tty.ypixel > *ypixel) {
154 				*xpixel = loop->tty.xpixel;
155 				*ypixel = loop->tty.ypixel;
156 			}
157 		}
158 		if (*sx == UINT_MAX || *sy == UINT_MAX)
159 			goto manual;
160 		break;
161 	case WINDOW_SIZE_LATEST:
162 		if (c != NULL && !ignore_client_size(c)) {
163 			*sx = c->tty.sx;
164 			*sy = c->tty.sy - status_line_size(c);
165 			*xpixel = c->tty.xpixel;
166 		        *ypixel = c->tty.ypixel;
167 		} else {
168 			if (w == NULL)
169 				goto manual;
170 			n = 0;
171 			TAILQ_FOREACH(loop, &clients, entry) {
172 				if (!ignore_client_size(loop) &&
173 				    session_has(loop->session, w)) {
174 					if (++n > 1)
175 						break;
176 				}
177 			}
178 			*sx = *sy = UINT_MAX;
179 			*xpixel = *ypixel = 0;
180 			TAILQ_FOREACH(loop, &clients, entry) {
181 				if (ignore_client_size(loop))
182 					continue;
183 				if (n > 1 && loop != w->latest)
184 					continue;
185 				s = loop->session;
186 
187 				cx = loop->tty.sx;
188 				cy = loop->tty.sy - status_line_size(loop);
189 
190 				if (cx < *sx)
191 					*sx = cx;
192 				if (cy < *sy)
193 					*sy = cy;
194 
195 				if (loop->tty.xpixel > *xpixel &&
196 				    loop->tty.ypixel > *ypixel) {
197 					*xpixel = loop->tty.xpixel;
198 					*ypixel = loop->tty.ypixel;
199 				}
200 			}
201 			if (*sx == UINT_MAX || *sy == UINT_MAX)
202 				goto manual;
203 		}
204 		break;
205 	case WINDOW_SIZE_MANUAL:
206 		goto manual;
207 	}
208 	goto done;
209 
210 manual:
211 	value = options_get_string(s->options, "default-size");
212 	if (sscanf(value, "%ux%u", sx, sy) != 2) {
213 		*sx = 80;
214 		*sy = 24;
215 	}
216 
217 done:
218 	if (*sx < WINDOW_MINIMUM)
219 		*sx = WINDOW_MINIMUM;
220 	if (*sx > WINDOW_MAXIMUM)
221 		*sx = WINDOW_MAXIMUM;
222 	if (*sy < WINDOW_MINIMUM)
223 		*sy = WINDOW_MINIMUM;
224 	if (*sy > WINDOW_MAXIMUM)
225 		*sy = WINDOW_MAXIMUM;
226 }
227 
228 void
229 recalculate_size(struct window *w)
230 {
231 	struct session	*s;
232 	struct client	*c;
233 	u_int		 sx, sy, cx, cy, xpixel = 0, ypixel = 0, n;
234 	int		 type, current, has, changed;
235 
236 	if (w->active == NULL)
237 		return;
238 	log_debug("%s: @%u is %u,%u", __func__, w->id, w->sx, w->sy);
239 
240 	type = options_get_number(w->options, "window-size");
241 	current = options_get_number(w->options, "aggressive-resize");
242 
243 	changed = 1;
244 	switch (type) {
245 	case WINDOW_SIZE_LARGEST:
246 		sx = sy = 0;
247 		TAILQ_FOREACH(c, &clients, entry) {
248 			if (ignore_client_size(c))
249 				continue;
250 			s = c->session;
251 
252 			if (current)
253 				has = (s->curw->window == w);
254 			else
255 				has = session_has(s, w);
256 			if (!has)
257 				continue;
258 
259 			cx = c->tty.sx;
260 			cy = c->tty.sy - status_line_size(c);
261 
262 			if (cx > sx)
263 				sx = cx;
264 			if (cy > sy)
265 				sy = cy;
266 
267 			if (c->tty.xpixel > xpixel && c->tty.ypixel > ypixel) {
268 				xpixel = c->tty.xpixel;
269 				ypixel = c->tty.ypixel;
270 			}
271 		}
272 		if (sx == 0 || sy == 0)
273 			changed = 0;
274 		break;
275 	case WINDOW_SIZE_SMALLEST:
276 		sx = sy = UINT_MAX;
277 		TAILQ_FOREACH(c, &clients, entry) {
278 			if (ignore_client_size(c))
279 				continue;
280 			s = c->session;
281 
282 			if (current)
283 				has = (s->curw->window == w);
284 			else
285 				has = session_has(s, w);
286 			if (!has)
287 				continue;
288 
289 			cx = c->tty.sx;
290 			cy = c->tty.sy - status_line_size(c);
291 
292 			if (cx < sx)
293 				sx = cx;
294 			if (cy < sy)
295 				sy = cy;
296 
297 			if (c->tty.xpixel > xpixel && c->tty.ypixel > ypixel) {
298 				xpixel = c->tty.xpixel;
299 				ypixel = c->tty.ypixel;
300 			}
301 		}
302 		if (sx == UINT_MAX || sy == UINT_MAX)
303 			changed = 0;
304 		break;
305 	case WINDOW_SIZE_LATEST:
306 		n = 0;
307 		TAILQ_FOREACH(c, &clients, entry) {
308 			if (!ignore_client_size(c) &&
309 			    session_has(c->session, w)) {
310 				if (++n > 1)
311 					break;
312 			}
313 		}
314 		sx = sy = UINT_MAX;
315 		TAILQ_FOREACH(c, &clients, entry) {
316 			if (ignore_client_size(c))
317 				continue;
318 			if (n > 1 && c != w->latest)
319 				continue;
320 			s = c->session;
321 
322 			if (current)
323 				has = (s->curw->window == w);
324 			else
325 				has = session_has(s, w);
326 			if (!has)
327 				continue;
328 
329 			cx = c->tty.sx;
330 			cy = c->tty.sy - status_line_size(c);
331 
332 			if (cx < sx)
333 				sx = cx;
334 			if (cy < sy)
335 				sy = cy;
336 
337 			if (c->tty.xpixel > xpixel && c->tty.ypixel > ypixel) {
338 				xpixel = c->tty.xpixel;
339 				ypixel = c->tty.ypixel;
340 			}
341 		}
342 		if (sx == UINT_MAX || sy == UINT_MAX)
343 			changed = 0;
344 		break;
345 	case WINDOW_SIZE_MANUAL:
346 		changed = 0;
347 		break;
348 	}
349 	if (changed && w->sx == sx && w->sy == sy)
350 		changed = 0;
351 
352 	if (!changed) {
353 		tty_update_window_offset(w);
354 		return;
355 	}
356 	log_debug("%s: @%u changed to %u,%u (%ux%u)", __func__, w->id, sx, sy,
357 	    xpixel, ypixel);
358 	resize_window(w, sx, sy, xpixel, ypixel);
359 }
360 
361 void
362 recalculate_sizes(void)
363 {
364 	struct session	*s;
365 	struct client	*c;
366 	struct window	*w;
367 
368 	/*
369 	 * Clear attached count and update saved status line information for
370 	 * each session.
371 	 */
372 	RB_FOREACH(s, sessions, &sessions) {
373 		s->attached = 0;
374 		status_update_cache(s);
375 	}
376 
377 	/*
378 	 * Increment attached count and check the status line size for each
379 	 * client.
380 	 */
381 	TAILQ_FOREACH(c, &clients, entry) {
382 		s = c->session;
383 		if (s != NULL && !(c->flags & CLIENT_UNATTACHEDFLAGS))
384 			s->attached++;
385 		if (ignore_client_size(c))
386 			continue;
387 		if (c->tty.sy <= s->statuslines || (c->flags & CLIENT_CONTROL))
388 			c->flags |= CLIENT_STATUSOFF;
389 		else
390 			c->flags &= ~CLIENT_STATUSOFF;
391 	}
392 
393 	/* Walk each window and adjust the size. */
394 	RB_FOREACH(w, windows, &windows)
395 		recalculate_size(w);
396 }
397