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