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