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