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