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