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 | |
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_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 | |
43 | static const struct menu_item [] = { |
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 | |
56 | const 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 | |
66 | enum 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 | }; |
72 | static const char *window_client_sort_list[] = { |
73 | "name" , |
74 | "size" , |
75 | "creation" , |
76 | "activity" |
77 | }; |
78 | static struct mode_tree_sort_criteria *window_client_sort; |
79 | |
80 | struct window_client_itemdata { |
81 | struct client *c; |
82 | }; |
83 | |
84 | struct 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 | |
95 | static struct window_client_itemdata * |
96 | window_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 | |
106 | static void |
107 | window_client_free_item(struct window_client_itemdata *item) |
108 | { |
109 | server_client_unref(item->c); |
110 | free(item); |
111 | } |
112 | |
113 | static int |
114 | window_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 | |
153 | static void |
154 | window_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 | |
203 | static void |
204 | window_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 | |
241 | static void |
242 | (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 | |
254 | static struct screen * |
255 | window_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 | |
286 | static void |
287 | window_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 | |
307 | static void |
308 | window_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 | |
315 | static void |
316 | window_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 | |
332 | static void |
333 | window_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 | |