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