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