1 /* $OpenBSD: window-client.c,v 1.12 2017/11/03 17:02: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_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 *, key_code, 35 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->old_status != NULL) 233 screen_write_fast_copy(ctx, c->old_status, 0, 0, sx, 1); 234 else 235 screen_write_fast_copy(ctx, &c->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 260 mode_tree_build(data->data); 261 mode_tree_draw(data->data); 262 263 return (s); 264 } 265 266 static void 267 window_client_free(struct window_pane *wp) 268 { 269 struct window_client_modedata *data = wp->modedata; 270 u_int i; 271 272 if (data == NULL) 273 return; 274 275 mode_tree_free(data->data); 276 277 for (i = 0; i < data->item_size; i++) 278 window_client_free_item(data->item_list[i]); 279 free(data->item_list); 280 281 free(data->format); 282 free(data->command); 283 284 free(data); 285 } 286 287 static void 288 window_client_resize(struct window_pane *wp, u_int sx, u_int sy) 289 { 290 struct window_client_modedata *data = wp->modedata; 291 292 mode_tree_resize(data->data, sx, sy); 293 } 294 295 static void 296 window_client_do_detach(void* modedata, void *itemdata, 297 __unused struct client *c, key_code key) 298 { 299 struct window_client_modedata *data = modedata; 300 struct window_client_itemdata *item = itemdata; 301 302 if (item == mode_tree_get_current(data->data)) 303 mode_tree_down(data->data, 0); 304 if (key == 'd' || key == 'D') 305 server_client_detach(item->c, MSG_DETACH); 306 else if (key == 'x' || key == 'X') 307 server_client_detach(item->c, MSG_DETACHKILL); 308 else if (key == 'z' || key == 'Z') 309 server_client_suspend(item->c); 310 } 311 312 static void 313 window_client_key(struct window_pane *wp, struct client *c, 314 __unused struct session *s, key_code key, struct mouse_event *m) 315 { 316 struct window_client_modedata *data = wp->modedata; 317 struct mode_tree_data *mtd = data->data; 318 struct window_client_itemdata *item; 319 int finished; 320 321 finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); 322 switch (key) { 323 case 'd': 324 case 'x': 325 case 'z': 326 item = mode_tree_get_current(mtd); 327 window_client_do_detach(data, item, c, key); 328 mode_tree_build(mtd); 329 break; 330 case 'D': 331 case 'X': 332 case 'Z': 333 mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0); 334 mode_tree_build(mtd); 335 break; 336 case '\r': 337 item = mode_tree_get_current(mtd); 338 mode_tree_run_command(c, NULL, data->command, item->c->ttyname); 339 finished = 1; 340 break; 341 } 342 if (finished || server_client_how_many() == 0) 343 window_pane_reset_mode(wp); 344 else { 345 mode_tree_draw(mtd); 346 wp->flags |= PANE_REDRAW; 347 } 348 } 349