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