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