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