xref: /openbsd-src/usr.bin/tmux/window-client.c (revision e603c72f713dd59b67030a9b97ec661800da159e)
1 /* $OpenBSD: window-client.c,v 1.22 2019/05/12 08:58:09 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2017 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 #include <sys/time.h>
21 
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 
26 #include "tmux.h"
27 
28 static struct screen	*window_client_init(struct window_mode_entry *,
29 			     struct cmd_find_state *, struct args *);
30 static void		 window_client_free(struct window_mode_entry *);
31 static void		 window_client_resize(struct window_mode_entry *, u_int,
32 			     u_int);
33 static void		 window_client_key(struct window_mode_entry *,
34 			     struct client *, struct session *,
35 			     struct winlink *, key_code, struct mouse_event *);
36 
37 #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'"
38 
39 #define WINDOW_CLIENT_DEFAULT_FORMAT \
40 	"session #{session_name} " \
41 	"(#{client_width}x#{client_height}, #{t:client_activity})"
42 
43 #define WINDOW_CLIENT_MENU \
44 	"Detach,d,|" \
45 	"Detach Tagged,D,|" \
46 	"|" \
47 	"Tag,t,|" \
48 	"Tag All,C-t,|" \
49 	"Tag None,T,|" \
50 	"|" \
51 	"Cancel,q,"
52 
53 const struct window_mode window_client_mode = {
54 	.name = "client-mode",
55 	.default_format = WINDOW_CLIENT_DEFAULT_FORMAT,
56 
57 	.init = window_client_init,
58 	.free = window_client_free,
59 	.resize = window_client_resize,
60 	.key = window_client_key,
61 };
62 
63 enum window_client_sort_type {
64 	WINDOW_CLIENT_BY_NAME,
65 	WINDOW_CLIENT_BY_SIZE,
66 	WINDOW_CLIENT_BY_CREATION_TIME,
67 	WINDOW_CLIENT_BY_ACTIVITY_TIME,
68 };
69 static const char *window_client_sort_list[] = {
70 	"name",
71 	"size",
72 	"creation",
73 	"activity"
74 };
75 
76 struct window_client_itemdata {
77 	struct client	*c;
78 };
79 
80 struct window_client_modedata {
81 	struct window_pane		 *wp;
82 
83 	struct mode_tree_data		 *data;
84 	char				 *format;
85 	char				 *command;
86 
87 	struct window_client_itemdata	**item_list;
88 	u_int				  item_size;
89 };
90 
91 static struct window_client_itemdata *
92 window_client_add_item(struct window_client_modedata *data)
93 {
94 	struct window_client_itemdata	*item;
95 
96 	data->item_list = xreallocarray(data->item_list, data->item_size + 1,
97 	    sizeof *data->item_list);
98 	item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
99 	return (item);
100 }
101 
102 static void
103 window_client_free_item(struct window_client_itemdata *item)
104 {
105 	server_client_unref(item->c);
106 	free(item);
107 }
108 
109 static int
110 window_client_cmp_name(const void *a0, const void *b0)
111 {
112 	const struct window_client_itemdata *const *a = a0;
113 	const struct window_client_itemdata *const *b = b0;
114 
115 	return (strcmp((*a)->c->name, (*b)->c->name));
116 }
117 
118 static int
119 window_client_cmp_size(const void *a0, const void *b0)
120 {
121 	const struct window_client_itemdata *const *a = a0;
122 	const struct window_client_itemdata *const *b = b0;
123 
124 	if ((*a)->c->tty.sx < (*b)->c->tty.sx)
125 		return (-1);
126 	if ((*a)->c->tty.sx > (*b)->c->tty.sx)
127 		return (1);
128 	if ((*a)->c->tty.sy < (*b)->c->tty.sy)
129 		return (-1);
130 	if ((*a)->c->tty.sy > (*b)->c->tty.sy)
131 		return (1);
132 	return (strcmp((*a)->c->name, (*b)->c->name));
133 }
134 
135 static int
136 window_client_cmp_creation_time(const void *a0, const void *b0)
137 {
138 	const struct window_client_itemdata *const *a = a0;
139 	const struct window_client_itemdata *const *b = b0;
140 
141 	if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, >))
142 		return (-1);
143 	if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, <))
144 		return (1);
145 	return (strcmp((*a)->c->name, (*b)->c->name));
146 }
147 
148 static int
149 window_client_cmp_activity_time(const void *a0, const void *b0)
150 {
151 	const struct window_client_itemdata *const *a = a0;
152 	const struct window_client_itemdata *const *b = b0;
153 
154 	if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, >))
155 		return (-1);
156 	if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, <))
157 		return (1);
158 	return (strcmp((*a)->c->name, (*b)->c->name));
159 }
160 
161 static void
162 window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag,
163     const char *filter)
164 {
165 	struct window_client_modedata	*data = modedata;
166 	struct window_client_itemdata	*item;
167 	u_int				 i;
168 	struct client			*c;
169 	char				*text, *cp;
170 
171 	for (i = 0; i < data->item_size; i++)
172 		window_client_free_item(data->item_list[i]);
173 	free(data->item_list);
174 	data->item_list = NULL;
175 	data->item_size = 0;
176 
177 	TAILQ_FOREACH(c, &clients, entry) {
178 		if (c->session == NULL || (c->flags & (CLIENT_DETACHING)))
179 			continue;
180 
181 		item = window_client_add_item(data);
182 		item->c = c;
183 
184 		c->references++;
185 	}
186 
187 	switch (sort_type) {
188 	case WINDOW_CLIENT_BY_NAME:
189 		qsort(data->item_list, data->item_size, sizeof *data->item_list,
190 		    window_client_cmp_name);
191 		break;
192 	case WINDOW_CLIENT_BY_SIZE:
193 		qsort(data->item_list, data->item_size, sizeof *data->item_list,
194 		    window_client_cmp_size);
195 		break;
196 	case WINDOW_CLIENT_BY_CREATION_TIME:
197 		qsort(data->item_list, data->item_size, sizeof *data->item_list,
198 		    window_client_cmp_creation_time);
199 		break;
200 	case WINDOW_CLIENT_BY_ACTIVITY_TIME:
201 		qsort(data->item_list, data->item_size, sizeof *data->item_list,
202 		    window_client_cmp_activity_time);
203 		break;
204 	}
205 
206 	for (i = 0; i < data->item_size; i++) {
207 		item = data->item_list[i];
208 		c = item->c;
209 
210 		if (filter != NULL) {
211 			cp = format_single(NULL, filter, c, NULL, NULL, NULL);
212 			if (!format_true(cp)) {
213 				free(cp);
214 				continue;
215 			}
216 			free(cp);
217 		}
218 
219 		text = format_single(NULL, data->format, c, NULL, NULL, NULL);
220 		mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name,
221 		    text, -1);
222 		free(text);
223 	}
224 }
225 
226 static void
227 window_client_draw(__unused void *modedata, void *itemdata,
228     struct screen_write_ctx *ctx, u_int sx, u_int sy)
229 {
230 	struct window_client_itemdata	*item = itemdata;
231 	struct client			*c = item->c;
232 	struct screen			*s = ctx->s;
233 	struct window_pane		*wp;
234 	u_int				 cx = s->cx, cy = s->cy, lines, at;
235 
236 	if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING)))
237 		return;
238 	wp = c->session->curw->window->active;
239 
240 	lines = status_line_size(c);
241 	if (lines >= sy)
242 		lines = 0;
243 	if (status_at_line(c) == 0)
244 		at = lines;
245 	else
246 		at = 0;
247 
248 	screen_write_cursormove(ctx, cx, cy + at, 0);
249 	screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines);
250 
251 	if (at != 0)
252 		screen_write_cursormove(ctx, cx, cy + 2, 0);
253 	else
254 		screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0);
255 	screen_write_hline(ctx, sx, 0, 0);
256 
257 	if (at != 0)
258 		screen_write_cursormove(ctx, cx, cy, 0);
259 	else
260 		screen_write_cursormove(ctx, cx, cy + sy - lines, 0);
261 	screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines);
262 }
263 
264 static void
265 window_client_menu(void *modedata, struct client *c, key_code key)
266 {
267 	struct window_client_modedata	*data = modedata;
268 	struct window_pane		*wp = data->wp;
269 	struct window_mode_entry	*wme;
270 
271 	wme = TAILQ_FIRST(&wp->modes);
272 	if (wme == NULL || wme->data != modedata)
273 		return;
274 	window_client_key(wme, c, NULL, NULL, key, NULL);
275 }
276 
277 static struct screen *
278 window_client_init(struct window_mode_entry *wme,
279     __unused struct cmd_find_state *fs, struct args *args)
280 {
281 	struct window_pane		*wp = wme->wp;
282 	struct window_client_modedata	*data;
283 	struct screen			*s;
284 
285 	wme->data = data = xcalloc(1, sizeof *data);
286 	data->wp = wp;
287 
288 	if (args == NULL || !args_has(args, 'F'))
289 		data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT);
290 	else
291 		data->format = xstrdup(args_get(args, 'F'));
292 	if (args == NULL || args->argc == 0)
293 		data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND);
294 	else
295 		data->command = xstrdup(args->argv[0]);
296 
297 	data->data = mode_tree_start(wp, args, window_client_build,
298 	    window_client_draw, NULL, window_client_menu, data,
299 	    WINDOW_CLIENT_MENU, window_client_sort_list,
300 	    nitems(window_client_sort_list), &s);
301 	mode_tree_zoom(data->data, args);
302 
303 	mode_tree_build(data->data);
304 	mode_tree_draw(data->data);
305 
306 	return (s);
307 }
308 
309 static void
310 window_client_free(struct window_mode_entry *wme)
311 {
312 	struct window_client_modedata	*data = wme->data;
313 	u_int				 i;
314 
315 	if (data == NULL)
316 		return;
317 
318 	mode_tree_free(data->data);
319 
320 	for (i = 0; i < data->item_size; i++)
321 		window_client_free_item(data->item_list[i]);
322 	free(data->item_list);
323 
324 	free(data->format);
325 	free(data->command);
326 
327 	free(data);
328 }
329 
330 static void
331 window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
332 {
333 	struct window_client_modedata	*data = wme->data;
334 
335 	mode_tree_resize(data->data, sx, sy);
336 }
337 
338 static void
339 window_client_do_detach(void* modedata, void *itemdata,
340     __unused struct client *c, key_code key)
341 {
342 	struct window_client_modedata	*data = modedata;
343 	struct window_client_itemdata	*item = itemdata;
344 
345 	if (item == mode_tree_get_current(data->data))
346 		mode_tree_down(data->data, 0);
347 	if (key == 'd' || key == 'D')
348 		server_client_detach(item->c, MSG_DETACH);
349 	else if (key == 'x' || key == 'X')
350 		server_client_detach(item->c, MSG_DETACHKILL);
351 	else if (key == 'z' || key == 'Z')
352 		server_client_suspend(item->c);
353 }
354 
355 static void
356 window_client_key(struct window_mode_entry *wme, struct client *c,
357     __unused struct session *s, __unused struct winlink *wl, key_code key,
358     struct mouse_event *m)
359 {
360 	struct window_pane		*wp = wme->wp;
361 	struct window_client_modedata	*data = wme->data;
362 	struct mode_tree_data		*mtd = data->data;
363 	struct window_client_itemdata	*item;
364 	int				 finished;
365 
366 	finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
367 	switch (key) {
368 	case 'd':
369 	case 'x':
370 	case 'z':
371 		item = mode_tree_get_current(mtd);
372 		window_client_do_detach(data, item, c, key);
373 		mode_tree_build(mtd);
374 		break;
375 	case 'D':
376 	case 'X':
377 	case 'Z':
378 		mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0);
379 		mode_tree_build(mtd);
380 		break;
381 	case '\r':
382 		item = mode_tree_get_current(mtd);
383 		mode_tree_run_command(c, NULL, data->command, item->c->ttyname);
384 		finished = 1;
385 		break;
386 	}
387 	if (finished || server_client_how_many() == 0)
388 		window_pane_reset_mode(wp);
389 	else {
390 		mode_tree_draw(mtd);
391 		wp->flags |= PANE_REDRAW;
392 	}
393 }
394