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