1 /* $OpenBSD: window-client.c,v 1.23 2019/05/28 07:18:42 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@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_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 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 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 79 struct window_client_itemdata { 80 struct client *c; 81 }; 82 83 struct window_client_modedata { 84 struct window_pane *wp; 85 86 struct mode_tree_data *data; 87 char *format; 88 char *command; 89 90 struct window_client_itemdata **item_list; 91 u_int item_size; 92 }; 93 94 static struct window_client_itemdata * 95 window_client_add_item(struct window_client_modedata *data) 96 { 97 struct window_client_itemdata *item; 98 99 data->item_list = xreallocarray(data->item_list, data->item_size + 1, 100 sizeof *data->item_list); 101 item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); 102 return (item); 103 } 104 105 static void 106 window_client_free_item(struct window_client_itemdata *item) 107 { 108 server_client_unref(item->c); 109 free(item); 110 } 111 112 static int 113 window_client_cmp_name(const void *a0, const void *b0) 114 { 115 const struct window_client_itemdata *const *a = a0; 116 const struct window_client_itemdata *const *b = b0; 117 118 return (strcmp((*a)->c->name, (*b)->c->name)); 119 } 120 121 static int 122 window_client_cmp_size(const void *a0, const void *b0) 123 { 124 const struct window_client_itemdata *const *a = a0; 125 const struct window_client_itemdata *const *b = b0; 126 127 if ((*a)->c->tty.sx < (*b)->c->tty.sx) 128 return (-1); 129 if ((*a)->c->tty.sx > (*b)->c->tty.sx) 130 return (1); 131 if ((*a)->c->tty.sy < (*b)->c->tty.sy) 132 return (-1); 133 if ((*a)->c->tty.sy > (*b)->c->tty.sy) 134 return (1); 135 return (strcmp((*a)->c->name, (*b)->c->name)); 136 } 137 138 static int 139 window_client_cmp_creation_time(const void *a0, const void *b0) 140 { 141 const struct window_client_itemdata *const *a = a0; 142 const struct window_client_itemdata *const *b = b0; 143 144 if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, >)) 145 return (-1); 146 if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, <)) 147 return (1); 148 return (strcmp((*a)->c->name, (*b)->c->name)); 149 } 150 151 static int 152 window_client_cmp_activity_time(const void *a0, const void *b0) 153 { 154 const struct window_client_itemdata *const *a = a0; 155 const struct window_client_itemdata *const *b = b0; 156 157 if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, >)) 158 return (-1); 159 if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, <)) 160 return (1); 161 return (strcmp((*a)->c->name, (*b)->c->name)); 162 } 163 164 static void 165 window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag, 166 const char *filter) 167 { 168 struct window_client_modedata *data = modedata; 169 struct window_client_itemdata *item; 170 u_int i; 171 struct client *c; 172 char *text, *cp; 173 174 for (i = 0; i < data->item_size; i++) 175 window_client_free_item(data->item_list[i]); 176 free(data->item_list); 177 data->item_list = NULL; 178 data->item_size = 0; 179 180 TAILQ_FOREACH(c, &clients, entry) { 181 if (c->session == NULL || (c->flags & (CLIENT_DETACHING))) 182 continue; 183 184 item = window_client_add_item(data); 185 item->c = c; 186 187 c->references++; 188 } 189 190 switch (sort_type) { 191 case WINDOW_CLIENT_BY_NAME: 192 qsort(data->item_list, data->item_size, sizeof *data->item_list, 193 window_client_cmp_name); 194 break; 195 case WINDOW_CLIENT_BY_SIZE: 196 qsort(data->item_list, data->item_size, sizeof *data->item_list, 197 window_client_cmp_size); 198 break; 199 case WINDOW_CLIENT_BY_CREATION_TIME: 200 qsort(data->item_list, data->item_size, sizeof *data->item_list, 201 window_client_cmp_creation_time); 202 break; 203 case WINDOW_CLIENT_BY_ACTIVITY_TIME: 204 qsort(data->item_list, data->item_size, sizeof *data->item_list, 205 window_client_cmp_activity_time); 206 break; 207 } 208 209 for (i = 0; i < data->item_size; i++) { 210 item = data->item_list[i]; 211 c = item->c; 212 213 if (filter != NULL) { 214 cp = format_single(NULL, filter, c, NULL, NULL, NULL); 215 if (!format_true(cp)) { 216 free(cp); 217 continue; 218 } 219 free(cp); 220 } 221 222 text = format_single(NULL, data->format, c, NULL, NULL, NULL); 223 mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name, 224 text, -1); 225 free(text); 226 } 227 } 228 229 static void 230 window_client_draw(__unused void *modedata, void *itemdata, 231 struct screen_write_ctx *ctx, u_int sx, u_int sy) 232 { 233 struct window_client_itemdata *item = itemdata; 234 struct client *c = item->c; 235 struct screen *s = ctx->s; 236 struct window_pane *wp; 237 u_int cx = s->cx, cy = s->cy, lines, at; 238 239 if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) 240 return; 241 wp = c->session->curw->window->active; 242 243 lines = status_line_size(c); 244 if (lines >= sy) 245 lines = 0; 246 if (status_at_line(c) == 0) 247 at = lines; 248 else 249 at = 0; 250 251 screen_write_cursormove(ctx, cx, cy + at, 0); 252 screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines); 253 254 if (at != 0) 255 screen_write_cursormove(ctx, cx, cy + 2, 0); 256 else 257 screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0); 258 screen_write_hline(ctx, sx, 0, 0); 259 260 if (at != 0) 261 screen_write_cursormove(ctx, cx, cy, 0); 262 else 263 screen_write_cursormove(ctx, cx, cy + sy - lines, 0); 264 screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines); 265 } 266 267 static void 268 window_client_menu(void *modedata, struct client *c, key_code key) 269 { 270 struct window_client_modedata *data = modedata; 271 struct window_pane *wp = data->wp; 272 struct window_mode_entry *wme; 273 274 wme = TAILQ_FIRST(&wp->modes); 275 if (wme == NULL || wme->data != modedata) 276 return; 277 window_client_key(wme, c, NULL, NULL, key, NULL); 278 } 279 280 static struct screen * 281 window_client_init(struct window_mode_entry *wme, 282 __unused struct cmd_find_state *fs, struct args *args) 283 { 284 struct window_pane *wp = wme->wp; 285 struct window_client_modedata *data; 286 struct screen *s; 287 288 wme->data = data = xcalloc(1, sizeof *data); 289 data->wp = wp; 290 291 if (args == NULL || !args_has(args, 'F')) 292 data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT); 293 else 294 data->format = xstrdup(args_get(args, 'F')); 295 if (args == NULL || args->argc == 0) 296 data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND); 297 else 298 data->command = xstrdup(args->argv[0]); 299 300 data->data = mode_tree_start(wp, args, window_client_build, 301 window_client_draw, NULL, window_client_menu, data, 302 window_client_menu_items, window_client_sort_list, 303 nitems(window_client_sort_list), &s); 304 mode_tree_zoom(data->data, args); 305 306 mode_tree_build(data->data); 307 mode_tree_draw(data->data); 308 309 return (s); 310 } 311 312 static void 313 window_client_free(struct window_mode_entry *wme) 314 { 315 struct window_client_modedata *data = wme->data; 316 u_int i; 317 318 if (data == NULL) 319 return; 320 321 mode_tree_free(data->data); 322 323 for (i = 0; i < data->item_size; i++) 324 window_client_free_item(data->item_list[i]); 325 free(data->item_list); 326 327 free(data->format); 328 free(data->command); 329 330 free(data); 331 } 332 333 static void 334 window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy) 335 { 336 struct window_client_modedata *data = wme->data; 337 338 mode_tree_resize(data->data, sx, sy); 339 } 340 341 static void 342 window_client_do_detach(void* modedata, void *itemdata, 343 __unused struct client *c, key_code key) 344 { 345 struct window_client_modedata *data = modedata; 346 struct window_client_itemdata *item = itemdata; 347 348 if (item == mode_tree_get_current(data->data)) 349 mode_tree_down(data->data, 0); 350 if (key == 'd' || key == 'D') 351 server_client_detach(item->c, MSG_DETACH); 352 else if (key == 'x' || key == 'X') 353 server_client_detach(item->c, MSG_DETACHKILL); 354 else if (key == 'z' || key == 'Z') 355 server_client_suspend(item->c); 356 } 357 358 static void 359 window_client_key(struct window_mode_entry *wme, struct client *c, 360 __unused struct session *s, __unused struct winlink *wl, key_code key, 361 struct mouse_event *m) 362 { 363 struct window_pane *wp = wme->wp; 364 struct window_client_modedata *data = wme->data; 365 struct mode_tree_data *mtd = data->data; 366 struct window_client_itemdata *item; 367 int finished; 368 369 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); 370 switch (key) { 371 case 'd': 372 case 'x': 373 case 'z': 374 item = mode_tree_get_current(mtd); 375 window_client_do_detach(data, item, c, key); 376 mode_tree_build(mtd); 377 break; 378 case 'D': 379 case 'X': 380 case 'Z': 381 mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0); 382 mode_tree_build(mtd); 383 break; 384 case '\r': 385 item = mode_tree_get_current(mtd); 386 mode_tree_run_command(c, NULL, data->command, item->c->ttyname); 387 finished = 1; 388 break; 389 } 390 if (finished || server_client_how_many() == 0) 391 window_pane_reset_mode(wp); 392 else { 393 mode_tree_draw(mtd); 394 wp->flags |= PANE_REDRAW; 395 } 396 } 397