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