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