1*e340e6ccSnicm /* $OpenBSD: window-client.c,v 1.34 2023/08/08 07:41:04 nicm Exp $ */
2a42faf7dSnicm
3a42faf7dSnicm /*
4a42faf7dSnicm * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
5a42faf7dSnicm *
6a42faf7dSnicm * Permission to use, copy, modify, and distribute this software for any
7a42faf7dSnicm * purpose with or without fee is hereby granted, provided that the above
8a42faf7dSnicm * copyright notice and this permission notice appear in all copies.
9a42faf7dSnicm *
10a42faf7dSnicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11a42faf7dSnicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12a42faf7dSnicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13a42faf7dSnicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14a42faf7dSnicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15a42faf7dSnicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16a42faf7dSnicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17a42faf7dSnicm */
18a42faf7dSnicm
19a42faf7dSnicm #include <sys/types.h>
20a42faf7dSnicm #include <sys/time.h>
21a42faf7dSnicm
22a42faf7dSnicm #include <stdlib.h>
23a42faf7dSnicm #include <string.h>
24a40cc5d0Snicm #include <time.h>
25a42faf7dSnicm
26a42faf7dSnicm #include "tmux.h"
27a42faf7dSnicm
2830a94f45Snicm static struct screen *window_client_init(struct window_mode_entry *,
29a42faf7dSnicm struct cmd_find_state *, struct args *);
3030a94f45Snicm static void window_client_free(struct window_mode_entry *);
3130a94f45Snicm static void window_client_resize(struct window_mode_entry *, u_int,
32a42faf7dSnicm u_int);
33734f37e4Snicm static void window_client_update(struct window_mode_entry *);
3430a94f45Snicm static void window_client_key(struct window_mode_entry *,
35bf52409eSnicm struct client *, struct session *,
36bf52409eSnicm struct winlink *, key_code, struct mouse_event *);
37a42faf7dSnicm
38a42faf7dSnicm #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'"
39a42faf7dSnicm
40bf38e336Snicm #define WINDOW_CLIENT_DEFAULT_FORMAT \
416c6f347cSnicm "#{t/p:client_activity}: session #{session_name}"
42bf38e336Snicm
43438eed14Snicm #define WINDOW_CLIENT_DEFAULT_KEY_FORMAT \
44438eed14Snicm "#{?#{e|<:#{line},10}," \
45438eed14Snicm "#{line}" \
46438eed14Snicm "," \
47438eed14Snicm "#{?#{e|<:#{line},36}," \
48438eed14Snicm "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
49438eed14Snicm "," \
50438eed14Snicm "" \
51438eed14Snicm "}" \
52438eed14Snicm "}"
53438eed14Snicm
541335341aSnicm static const struct menu_item window_client_menu_items[] = {
551335341aSnicm { "Detach", 'd', NULL },
561335341aSnicm { "Detach Tagged", 'D', NULL },
571335341aSnicm { "", KEYC_NONE, NULL },
581335341aSnicm { "Tag", 't', NULL },
591335341aSnicm { "Tag All", '\024', NULL },
601335341aSnicm { "Tag None", 'T', NULL },
611335341aSnicm { "", KEYC_NONE, NULL },
621335341aSnicm { "Cancel", 'q', NULL },
631335341aSnicm
641335341aSnicm { NULL, KEYC_NONE, NULL }
651335341aSnicm };
66f43bc87cSnicm
67a42faf7dSnicm const struct window_mode window_client_mode = {
68a42faf7dSnicm .name = "client-mode",
6971431f24Snicm .default_format = WINDOW_CLIENT_DEFAULT_FORMAT,
70a42faf7dSnicm
71a42faf7dSnicm .init = window_client_init,
72a42faf7dSnicm .free = window_client_free,
73a42faf7dSnicm .resize = window_client_resize,
74734f37e4Snicm .update = window_client_update,
75a42faf7dSnicm .key = window_client_key,
76a42faf7dSnicm };
77a42faf7dSnicm
78a42faf7dSnicm enum window_client_sort_type {
79a42faf7dSnicm WINDOW_CLIENT_BY_NAME,
8057d50a52Snicm WINDOW_CLIENT_BY_SIZE,
81a42faf7dSnicm WINDOW_CLIENT_BY_CREATION_TIME,
82a42faf7dSnicm WINDOW_CLIENT_BY_ACTIVITY_TIME,
83a42faf7dSnicm };
84a42faf7dSnicm static const char *window_client_sort_list[] = {
85a42faf7dSnicm "name",
8657d50a52Snicm "size",
87b38aa712Snicm "creation",
88b38aa712Snicm "activity"
89a42faf7dSnicm };
903f6f9f7dSnicm static struct mode_tree_sort_criteria *window_client_sort;
91a42faf7dSnicm
92a42faf7dSnicm struct window_client_itemdata {
93a42faf7dSnicm struct client *c;
94a42faf7dSnicm };
95a42faf7dSnicm
96a42faf7dSnicm struct window_client_modedata {
97f43bc87cSnicm struct window_pane *wp;
98f43bc87cSnicm
99a42faf7dSnicm struct mode_tree_data *data;
100bf38e336Snicm char *format;
101438eed14Snicm char *key_format;
102a42faf7dSnicm char *command;
103a42faf7dSnicm
104a42faf7dSnicm struct window_client_itemdata **item_list;
105a42faf7dSnicm u_int item_size;
106a42faf7dSnicm };
107a42faf7dSnicm
108a42faf7dSnicm static struct window_client_itemdata *
window_client_add_item(struct window_client_modedata * data)109a42faf7dSnicm window_client_add_item(struct window_client_modedata *data)
110a42faf7dSnicm {
111a42faf7dSnicm struct window_client_itemdata *item;
112a42faf7dSnicm
113a42faf7dSnicm data->item_list = xreallocarray(data->item_list, data->item_size + 1,
114a42faf7dSnicm sizeof *data->item_list);
115a42faf7dSnicm item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item);
116a42faf7dSnicm return (item);
117a42faf7dSnicm }
118a42faf7dSnicm
119a42faf7dSnicm static void
window_client_free_item(struct window_client_itemdata * item)120a42faf7dSnicm window_client_free_item(struct window_client_itemdata *item)
121a42faf7dSnicm {
122a42faf7dSnicm server_client_unref(item->c);
123a42faf7dSnicm free(item);
124a42faf7dSnicm }
125a42faf7dSnicm
126a42faf7dSnicm static int
window_client_cmp(const void * a0,const void * b0)1273f6f9f7dSnicm window_client_cmp(const void *a0, const void *b0)
128a42faf7dSnicm {
129a42faf7dSnicm const struct window_client_itemdata *const *a = a0;
130a42faf7dSnicm const struct window_client_itemdata *const *b = b0;
1313f6f9f7dSnicm const struct window_client_itemdata *itema = *a;
1323f6f9f7dSnicm const struct window_client_itemdata *itemb = *b;
1333f6f9f7dSnicm struct client *ca = itema->c;
1343f6f9f7dSnicm struct client *cb = itemb->c;
1353f6f9f7dSnicm int result = 0;
136a42faf7dSnicm
1373f6f9f7dSnicm switch (window_client_sort->field) {
1383f6f9f7dSnicm case WINDOW_CLIENT_BY_SIZE:
1393f6f9f7dSnicm result = ca->tty.sx - cb->tty.sx;
1403f6f9f7dSnicm if (result == 0)
1413f6f9f7dSnicm result = ca->tty.sy - cb->tty.sy;
1423f6f9f7dSnicm break;
1433f6f9f7dSnicm case WINDOW_CLIENT_BY_CREATION_TIME:
1443f6f9f7dSnicm if (timercmp(&ca->creation_time, &cb->creation_time, >))
1453f6f9f7dSnicm result = -1;
1463f6f9f7dSnicm else if (timercmp(&ca->creation_time, &cb->creation_time, <))
1473f6f9f7dSnicm result = 1;
1483f6f9f7dSnicm break;
1493f6f9f7dSnicm case WINDOW_CLIENT_BY_ACTIVITY_TIME:
1503f6f9f7dSnicm if (timercmp(&ca->activity_time, &cb->activity_time, >))
1513f6f9f7dSnicm result = -1;
1523f6f9f7dSnicm else if (timercmp(&ca->activity_time, &cb->activity_time, <))
1533f6f9f7dSnicm result = 1;
1543f6f9f7dSnicm break;
155a42faf7dSnicm }
156a42faf7dSnicm
1573f6f9f7dSnicm /* Use WINDOW_CLIENT_BY_NAME as default order and tie breaker. */
1583f6f9f7dSnicm if (result == 0)
1593f6f9f7dSnicm result = strcmp(ca->name, cb->name);
16057d50a52Snicm
1613f6f9f7dSnicm if (window_client_sort->reversed)
1623f6f9f7dSnicm result = -result;
1633f6f9f7dSnicm return (result);
164a42faf7dSnicm }
165a42faf7dSnicm
166a42faf7dSnicm static void
window_client_build(void * modedata,struct mode_tree_sort_criteria * sort_crit,__unused uint64_t * tag,const char * filter)1673f6f9f7dSnicm window_client_build(void *modedata, struct mode_tree_sort_criteria *sort_crit,
1683f6f9f7dSnicm __unused uint64_t *tag, const char *filter)
169a42faf7dSnicm {
170a42faf7dSnicm struct window_client_modedata *data = modedata;
171a42faf7dSnicm struct window_client_itemdata *item;
172a42faf7dSnicm u_int i;
173a42faf7dSnicm struct client *c;
174bf38e336Snicm char *text, *cp;
175a42faf7dSnicm
176a42faf7dSnicm for (i = 0; i < data->item_size; i++)
177a42faf7dSnicm window_client_free_item(data->item_list[i]);
178a42faf7dSnicm free(data->item_list);
179a42faf7dSnicm data->item_list = NULL;
180a42faf7dSnicm data->item_size = 0;
181a42faf7dSnicm
182a42faf7dSnicm TAILQ_FOREACH(c, &clients, entry) {
183a34cf9c8Snicm if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
184a42faf7dSnicm continue;
185a42faf7dSnicm
186a42faf7dSnicm item = window_client_add_item(data);
187a42faf7dSnicm item->c = c;
188a42faf7dSnicm
189a42faf7dSnicm c->references++;
190a42faf7dSnicm }
191a42faf7dSnicm
1923f6f9f7dSnicm window_client_sort = sort_crit;
193a42faf7dSnicm qsort(data->item_list, data->item_size, sizeof *data->item_list,
1943f6f9f7dSnicm window_client_cmp);
195a42faf7dSnicm
196a42faf7dSnicm for (i = 0; i < data->item_size; i++) {
197a42faf7dSnicm item = data->item_list[i];
198a42faf7dSnicm c = item->c;
199a42faf7dSnicm
200024c311aSnicm if (filter != NULL) {
201024c311aSnicm cp = format_single(NULL, filter, c, NULL, NULL, NULL);
202024c311aSnicm if (!format_true(cp)) {
203024c311aSnicm free(cp);
204024c311aSnicm continue;
205024c311aSnicm }
206024c311aSnicm free(cp);
207024c311aSnicm }
208024c311aSnicm
209bf38e336Snicm text = format_single(NULL, data->format, c, NULL, NULL, NULL);
210a42faf7dSnicm mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name,
211a42faf7dSnicm text, -1);
212a42faf7dSnicm free(text);
213a42faf7dSnicm }
214a42faf7dSnicm }
215a42faf7dSnicm
2162b7e51f7Snicm static void
window_client_draw(__unused void * modedata,void * itemdata,struct screen_write_ctx * ctx,u_int sx,u_int sy)2172b7e51f7Snicm window_client_draw(__unused void *modedata, void *itemdata,
2182b7e51f7Snicm struct screen_write_ctx *ctx, u_int sx, u_int sy)
219a42faf7dSnicm {
220a42faf7dSnicm struct window_client_itemdata *item = itemdata;
221a42faf7dSnicm struct client *c = item->c;
2224ffcb1c8Snicm struct screen *s = ctx->s;
223a42faf7dSnicm struct window_pane *wp;
2244ffcb1c8Snicm u_int cx = s->cx, cy = s->cy, lines, at;
225a42faf7dSnicm
226b5f8268cSnicm if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS))
2272b7e51f7Snicm return;
228a42faf7dSnicm wp = c->session->curw->window->active;
229a42faf7dSnicm
2304ffcb1c8Snicm lines = status_line_size(c);
2314ffcb1c8Snicm if (lines >= sy)
2324ffcb1c8Snicm lines = 0;
2334ffcb1c8Snicm if (status_at_line(c) == 0)
2344ffcb1c8Snicm at = lines;
2354ffcb1c8Snicm else
2364ffcb1c8Snicm at = 0;
237a42faf7dSnicm
2384ffcb1c8Snicm screen_write_cursormove(ctx, cx, cy + at, 0);
2394ffcb1c8Snicm screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines);
2404ffcb1c8Snicm
2414ffcb1c8Snicm if (at != 0)
2424ffcb1c8Snicm screen_write_cursormove(ctx, cx, cy + 2, 0);
2434ffcb1c8Snicm else
2444ffcb1c8Snicm screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0);
245*e340e6ccSnicm screen_write_hline(ctx, sx, 0, 0, BOX_LINES_DEFAULT, NULL);
246a42faf7dSnicm
2474ffcb1c8Snicm if (at != 0)
2484ffcb1c8Snicm screen_write_cursormove(ctx, cx, cy, 0);
2494ffcb1c8Snicm else
2504ffcb1c8Snicm screen_write_cursormove(ctx, cx, cy + sy - lines, 0);
2514ffcb1c8Snicm screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines);
252a42faf7dSnicm }
253a42faf7dSnicm
254f43bc87cSnicm static void
window_client_menu(void * modedata,struct client * c,key_code key)255f43bc87cSnicm window_client_menu(void *modedata, struct client *c, key_code key)
256f43bc87cSnicm {
257f43bc87cSnicm struct window_client_modedata *data = modedata;
258f43bc87cSnicm struct window_pane *wp = data->wp;
259f43bc87cSnicm struct window_mode_entry *wme;
260f43bc87cSnicm
261f43bc87cSnicm wme = TAILQ_FIRST(&wp->modes);
262f43bc87cSnicm if (wme == NULL || wme->data != modedata)
263f43bc87cSnicm return;
264f43bc87cSnicm window_client_key(wme, c, NULL, NULL, key, NULL);
265f43bc87cSnicm }
266f43bc87cSnicm
267438eed14Snicm static key_code
window_client_get_key(void * modedata,void * itemdata,u_int line)268438eed14Snicm window_client_get_key(void *modedata, void *itemdata, u_int line)
269438eed14Snicm {
270438eed14Snicm struct window_client_modedata *data = modedata;
271438eed14Snicm struct window_client_itemdata *item = itemdata;
272438eed14Snicm struct format_tree *ft;
273438eed14Snicm char *expanded;
274438eed14Snicm key_code key;
275438eed14Snicm
276438eed14Snicm ft = format_create(NULL, NULL, FORMAT_NONE, 0);
277438eed14Snicm format_defaults(ft, item->c, NULL, 0, NULL);
278438eed14Snicm format_add(ft, "line", "%u", line);
279438eed14Snicm
280438eed14Snicm expanded = format_expand(ft, data->key_format);
281438eed14Snicm key = key_string_lookup_string(expanded);
282438eed14Snicm free(expanded);
283438eed14Snicm format_free(ft);
2843e8355bdSnicm return (key);
285438eed14Snicm }
286438eed14Snicm
287a42faf7dSnicm static struct screen *
window_client_init(struct window_mode_entry * wme,__unused struct cmd_find_state * fs,struct args * args)28830a94f45Snicm window_client_init(struct window_mode_entry *wme,
28930a94f45Snicm __unused struct cmd_find_state *fs, struct args *args)
290a42faf7dSnicm {
29130a94f45Snicm struct window_pane *wp = wme->wp;
292a42faf7dSnicm struct window_client_modedata *data;
293a42faf7dSnicm struct screen *s;
294a42faf7dSnicm
29530a94f45Snicm wme->data = data = xcalloc(1, sizeof *data);
296f43bc87cSnicm data->wp = wp;
297a42faf7dSnicm
298bf38e336Snicm if (args == NULL || !args_has(args, 'F'))
299bf38e336Snicm data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT);
300bf38e336Snicm else
301bf38e336Snicm data->format = xstrdup(args_get(args, 'F'));
302438eed14Snicm if (args == NULL || !args_has(args, 'K'))
303438eed14Snicm data->key_format = xstrdup(WINDOW_CLIENT_DEFAULT_KEY_FORMAT);
304438eed14Snicm else
305438eed14Snicm data->key_format = xstrdup(args_get(args, 'K'));
3061693b10bSnicm if (args == NULL || args_count(args) == 0)
307a42faf7dSnicm data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND);
308a42faf7dSnicm else
3091693b10bSnicm data->command = xstrdup(args_string(args, 0));
310a42faf7dSnicm
311b38aa712Snicm data->data = mode_tree_start(wp, args, window_client_build,
312438eed14Snicm window_client_draw, NULL, window_client_menu, NULL,
313438eed14Snicm window_client_get_key, data, window_client_menu_items,
314438eed14Snicm window_client_sort_list, nitems(window_client_sort_list), &s);
3154f5e4c93Snicm mode_tree_zoom(data->data, args);
316a42faf7dSnicm
317a42faf7dSnicm mode_tree_build(data->data);
318a42faf7dSnicm mode_tree_draw(data->data);
319a42faf7dSnicm
320a42faf7dSnicm return (s);
321a42faf7dSnicm }
322a42faf7dSnicm
323a42faf7dSnicm static void
window_client_free(struct window_mode_entry * wme)32430a94f45Snicm window_client_free(struct window_mode_entry *wme)
325a42faf7dSnicm {
32630a94f45Snicm struct window_client_modedata *data = wme->data;
327a42faf7dSnicm u_int i;
328a42faf7dSnicm
329a42faf7dSnicm if (data == NULL)
330a42faf7dSnicm return;
331a42faf7dSnicm
332a42faf7dSnicm mode_tree_free(data->data);
333a42faf7dSnicm
334a42faf7dSnicm for (i = 0; i < data->item_size; i++)
335a42faf7dSnicm window_client_free_item(data->item_list[i]);
336a42faf7dSnicm free(data->item_list);
337a42faf7dSnicm
338bf38e336Snicm free(data->format);
339438eed14Snicm free(data->key_format);
340a42faf7dSnicm free(data->command);
341bf38e336Snicm
342a42faf7dSnicm free(data);
343a42faf7dSnicm }
344a42faf7dSnicm
345a42faf7dSnicm static void
window_client_resize(struct window_mode_entry * wme,u_int sx,u_int sy)34630a94f45Snicm window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
347a42faf7dSnicm {
34830a94f45Snicm struct window_client_modedata *data = wme->data;
349a42faf7dSnicm
350a42faf7dSnicm mode_tree_resize(data->data, sx, sy);
351a42faf7dSnicm }
352a42faf7dSnicm
353a42faf7dSnicm static void
window_client_update(struct window_mode_entry * wme)354734f37e4Snicm window_client_update(struct window_mode_entry *wme)
355734f37e4Snicm {
356734f37e4Snicm struct window_client_modedata *data = wme->data;
357734f37e4Snicm
358734f37e4Snicm mode_tree_build(data->data);
359734f37e4Snicm mode_tree_draw(data->data);
360734f37e4Snicm data->wp->flags |= PANE_REDRAW;
361734f37e4Snicm }
362734f37e4Snicm
363734f37e4Snicm static void
window_client_do_detach(void * modedata,void * itemdata,__unused struct client * c,key_code key)364d7af2c28Snicm window_client_do_detach(void *modedata, void *itemdata,
365d7af2c28Snicm __unused struct client *c, key_code key)
366a42faf7dSnicm {
367a42faf7dSnicm struct window_client_modedata *data = modedata;
368a42faf7dSnicm struct window_client_itemdata *item = itemdata;
369a42faf7dSnicm
370a42faf7dSnicm if (item == mode_tree_get_current(data->data))
371a42faf7dSnicm mode_tree_down(data->data, 0);
372a42faf7dSnicm if (key == 'd' || key == 'D')
373a42faf7dSnicm server_client_detach(item->c, MSG_DETACH);
374a42faf7dSnicm else if (key == 'x' || key == 'X')
375a42faf7dSnicm server_client_detach(item->c, MSG_DETACHKILL);
376a42faf7dSnicm else if (key == 'z' || key == 'Z')
377a42faf7dSnicm server_client_suspend(item->c);
378a42faf7dSnicm }
379a42faf7dSnicm
380a42faf7dSnicm static void
window_client_key(struct window_mode_entry * wme,struct client * c,__unused struct session * s,__unused struct winlink * wl,key_code key,struct mouse_event * m)38130a94f45Snicm window_client_key(struct window_mode_entry *wme, struct client *c,
382bf52409eSnicm __unused struct session *s, __unused struct winlink *wl, key_code key,
383bf52409eSnicm struct mouse_event *m)
384a42faf7dSnicm {
38530a94f45Snicm struct window_pane *wp = wme->wp;
38630a94f45Snicm struct window_client_modedata *data = wme->data;
387d7af2c28Snicm struct mode_tree_data *mtd = data->data;
388a42faf7dSnicm struct window_client_itemdata *item;
389a42faf7dSnicm int finished;
390a42faf7dSnicm
39163c9949dSnicm finished = mode_tree_key(mtd, c, &key, m, NULL, NULL);
392a42faf7dSnicm switch (key) {
393a42faf7dSnicm case 'd':
394a42faf7dSnicm case 'x':
395a42faf7dSnicm case 'z':
396d7af2c28Snicm item = mode_tree_get_current(mtd);
397d7af2c28Snicm window_client_do_detach(data, item, c, key);
398d7af2c28Snicm mode_tree_build(mtd);
399a42faf7dSnicm break;
400a42faf7dSnicm case 'D':
401a42faf7dSnicm case 'X':
402a42faf7dSnicm case 'Z':
403d7af2c28Snicm mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0);
404d7af2c28Snicm mode_tree_build(mtd);
405a42faf7dSnicm break;
406a42faf7dSnicm case '\r':
407d7af2c28Snicm item = mode_tree_get_current(mtd);
408d7af2c28Snicm mode_tree_run_command(c, NULL, data->command, item->c->ttyname);
409d7af2c28Snicm finished = 1;
410d7af2c28Snicm break;
411a42faf7dSnicm }
412a42faf7dSnicm if (finished || server_client_how_many() == 0)
413a42faf7dSnicm window_pane_reset_mode(wp);
414a42faf7dSnicm else {
415d7af2c28Snicm mode_tree_draw(mtd);
416a42faf7dSnicm wp->flags |= PANE_REDRAW;
417a42faf7dSnicm }
418a42faf7dSnicm }
419