1 /* $OpenBSD: window-client.c,v 1.6 2017/06/09 16:01:39 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 const struct window_mode window_client_mode = { 40 .name = "client-mode", 41 42 .init = window_client_init, 43 .free = window_client_free, 44 .resize = window_client_resize, 45 .key = window_client_key, 46 }; 47 48 enum window_client_sort_type { 49 WINDOW_CLIENT_BY_NAME, 50 WINDOW_CLIENT_BY_SIZE, 51 WINDOW_CLIENT_BY_CREATION_TIME, 52 WINDOW_CLIENT_BY_ACTIVITY_TIME, 53 }; 54 static const char *window_client_sort_list[] = { 55 "name", 56 "size", 57 "creation", 58 "activity" 59 }; 60 61 struct window_client_itemdata { 62 struct client *c; 63 }; 64 65 struct window_client_modedata { 66 struct mode_tree_data *data; 67 char *command; 68 69 struct window_client_itemdata **item_list; 70 u_int item_size; 71 }; 72 73 static struct window_client_itemdata * 74 window_client_add_item(struct window_client_modedata *data) 75 { 76 struct window_client_itemdata *item; 77 78 data->item_list = xreallocarray(data->item_list, data->item_size + 1, 79 sizeof *data->item_list); 80 item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); 81 return (item); 82 } 83 84 static void 85 window_client_free_item(struct window_client_itemdata *item) 86 { 87 server_client_unref(item->c); 88 free(item); 89 } 90 91 static int 92 window_client_cmp_name(const void *a0, const void *b0) 93 { 94 const struct window_client_itemdata *const *a = a0; 95 const struct window_client_itemdata *const *b = b0; 96 97 return (strcmp((*a)->c->name, (*b)->c->name)); 98 } 99 100 static int 101 window_client_cmp_size(const void *a0, const void *b0) 102 { 103 const struct window_client_itemdata *const *a = a0; 104 const struct window_client_itemdata *const *b = b0; 105 106 if ((*a)->c->tty.sx < (*b)->c->tty.sx) 107 return (-1); 108 if ((*a)->c->tty.sx > (*b)->c->tty.sx) 109 return (1); 110 if ((*a)->c->tty.sy < (*b)->c->tty.sy) 111 return (-1); 112 if ((*a)->c->tty.sy > (*b)->c->tty.sy) 113 return (1); 114 return (strcmp((*a)->c->name, (*b)->c->name)); 115 } 116 117 static int 118 window_client_cmp_creation_time(const void *a0, const void *b0) 119 { 120 const struct window_client_itemdata *const *a = a0; 121 const struct window_client_itemdata *const *b = b0; 122 123 if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, >)) 124 return (-1); 125 if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, <)) 126 return (1); 127 return (strcmp((*a)->c->name, (*b)->c->name)); 128 } 129 130 static int 131 window_client_cmp_activity_time(const void *a0, const void *b0) 132 { 133 const struct window_client_itemdata *const *a = a0; 134 const struct window_client_itemdata *const *b = b0; 135 136 if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, >)) 137 return (-1); 138 if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, <)) 139 return (1); 140 return (strcmp((*a)->c->name, (*b)->c->name)); 141 } 142 143 static void 144 window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag, 145 const char *filter) 146 { 147 struct window_client_modedata *data = modedata; 148 struct window_client_itemdata *item; 149 u_int i; 150 struct client *c; 151 char *tim, *text, *cp; 152 153 for (i = 0; i < data->item_size; i++) 154 window_client_free_item(data->item_list[i]); 155 free(data->item_list); 156 data->item_list = NULL; 157 data->item_size = 0; 158 159 TAILQ_FOREACH(c, &clients, entry) { 160 if (c->session == NULL || (c->flags & (CLIENT_DETACHING))) 161 continue; 162 163 item = window_client_add_item(data); 164 item->c = c; 165 166 c->references++; 167 } 168 169 switch (sort_type) { 170 case WINDOW_CLIENT_BY_NAME: 171 qsort(data->item_list, data->item_size, sizeof *data->item_list, 172 window_client_cmp_name); 173 break; 174 case WINDOW_CLIENT_BY_SIZE: 175 qsort(data->item_list, data->item_size, sizeof *data->item_list, 176 window_client_cmp_size); 177 break; 178 case WINDOW_CLIENT_BY_CREATION_TIME: 179 qsort(data->item_list, data->item_size, sizeof *data->item_list, 180 window_client_cmp_creation_time); 181 break; 182 case WINDOW_CLIENT_BY_ACTIVITY_TIME: 183 qsort(data->item_list, data->item_size, sizeof *data->item_list, 184 window_client_cmp_activity_time); 185 break; 186 } 187 188 for (i = 0; i < data->item_size; i++) { 189 item = data->item_list[i]; 190 c = item->c; 191 192 if (filter != NULL) { 193 cp = format_single(NULL, filter, c, NULL, NULL, NULL); 194 if (!format_true(cp)) { 195 free(cp); 196 continue; 197 } 198 free(cp); 199 } 200 201 tim = ctime(&c->activity_time.tv_sec); 202 *strchr(tim, '\n') = '\0'; 203 204 xasprintf(&text, "session %s (%ux%u, %s)", c->session->name, 205 c->tty.sx, c->tty.sy, tim); 206 mode_tree_add(data->data, NULL, item, (uint64_t)c, c->name, 207 text, -1); 208 free(text); 209 } 210 } 211 212 static struct screen * 213 window_client_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) 214 { 215 struct window_client_itemdata *item = itemdata; 216 struct client *c = item->c; 217 struct window_pane *wp; 218 static struct screen s; 219 struct screen_write_ctx ctx; 220 221 if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) 222 return (NULL); 223 wp = c->session->curw->window->active; 224 225 screen_init(&s, sx, sy, 0); 226 227 screen_write_start(&ctx, NULL, &s); 228 screen_write_clearscreen(&ctx, 8); 229 230 screen_write_preview(&ctx, &wp->base, sx, sy - 3); 231 232 screen_write_cursormove(&ctx, 0, sy - 2); 233 screen_write_line(&ctx, sx, 0, 0); 234 235 screen_write_cursormove(&ctx, 0, sy - 1); 236 if (c->old_status != NULL) 237 screen_write_copy(&ctx, c->old_status, 0, 0, sx, 1, NULL, NULL); 238 else 239 screen_write_copy(&ctx, &c->status, 0, 0, sx, 1, NULL, NULL); 240 241 screen_write_stop(&ctx); 242 return (&s); 243 } 244 245 static struct screen * 246 window_client_init(struct window_pane *wp, __unused struct cmd_find_state *fs, 247 struct args *args) 248 { 249 struct window_client_modedata *data; 250 struct screen *s; 251 252 wp->modedata = data = xcalloc(1, sizeof *data); 253 254 if (args == NULL || args->argc == 0) 255 data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND); 256 else 257 data->command = xstrdup(args->argv[0]); 258 259 data->data = mode_tree_start(wp, args, window_client_build, 260 window_client_draw, NULL, data, window_client_sort_list, 261 nitems(window_client_sort_list), &s); 262 263 mode_tree_build(data->data); 264 mode_tree_draw(data->data); 265 266 return (s); 267 } 268 269 static void 270 window_client_free(struct window_pane *wp) 271 { 272 struct window_client_modedata *data = wp->modedata; 273 u_int i; 274 275 if (data == NULL) 276 return; 277 278 mode_tree_free(data->data); 279 280 for (i = 0; i < data->item_size; i++) 281 window_client_free_item(data->item_list[i]); 282 free(data->item_list); 283 284 free(data->command); 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, 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 window_client_itemdata *item; 318 char *command, *name; 319 int finished; 320 321 /* 322 * t = toggle tag 323 * T = tag none 324 * C-t = tag all 325 * q = exit 326 * O = change sort order 327 * 328 * d = detach client 329 * D = detach tagged clients 330 * x = detach and kill client 331 * X = detach and kill tagged clients 332 * z = suspend client 333 * Z = suspend tagged clients 334 * Enter = detach client 335 */ 336 337 finished = mode_tree_key(data->data, c, &key, m); 338 switch (key) { 339 case 'd': 340 case 'x': 341 case 'z': 342 item = mode_tree_get_current(data->data); 343 window_client_do_detach(data, item, key); 344 mode_tree_build(data->data); 345 break; 346 case 'D': 347 case 'X': 348 case 'Z': 349 mode_tree_each_tagged(data->data, window_client_do_detach, key, 350 0); 351 mode_tree_build(data->data); 352 break; 353 case '\r': 354 item = mode_tree_get_current(data->data); 355 command = xstrdup(data->command); 356 name = xstrdup(item->c->ttyname); 357 window_pane_reset_mode(wp); 358 mode_tree_run_command(c, NULL, command, name); 359 free(name); 360 free(command); 361 return; 362 } 363 if (finished || server_client_how_many() == 0) 364 window_pane_reset_mode(wp); 365 else { 366 mode_tree_draw(data->data); 367 wp->flags |= PANE_REDRAW; 368 } 369 } 370