Annotation of embedaddon/tmux/server-client.c, revision 1.1
1.1 ! misho 1: /* $OpenBSD$ */
! 2:
! 3: /*
! 4: * Copyright (c) 2009 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/ioctl.h>
! 21: #include <sys/uio.h>
! 22:
! 23: #include <errno.h>
! 24: #include <event.h>
! 25: #include <fcntl.h>
! 26: #include <stdlib.h>
! 27: #include <string.h>
! 28: #include <time.h>
! 29: #include <unistd.h>
! 30:
! 31: #include "tmux.h"
! 32:
! 33: static void server_client_free(int, short, void *);
! 34: static void server_client_check_focus(struct window_pane *);
! 35: static void server_client_check_resize(struct window_pane *);
! 36: static key_code server_client_check_mouse(struct client *);
! 37: static void server_client_repeat_timer(int, short, void *);
! 38: static void server_client_click_timer(int, short, void *);
! 39: static void server_client_check_exit(struct client *);
! 40: static void server_client_check_redraw(struct client *);
! 41: static void server_client_set_title(struct client *);
! 42: static void server_client_reset_state(struct client *);
! 43: static int server_client_assume_paste(struct session *);
! 44:
! 45: static void server_client_dispatch(struct imsg *, void *);
! 46: static void server_client_dispatch_command(struct client *, struct imsg *);
! 47: static void server_client_dispatch_identify(struct client *, struct imsg *);
! 48: static void server_client_dispatch_shell(struct client *);
! 49:
! 50: /* Idenfity mode callback. */
! 51: static void
! 52: server_client_callback_identify(__unused int fd, __unused short events, void *data)
! 53: {
! 54: server_client_clear_identify(data, NULL);
! 55: }
! 56:
! 57: /* Set identify mode on client. */
! 58: void
! 59: server_client_set_identify(struct client *c)
! 60: {
! 61: struct timeval tv;
! 62: int delay;
! 63:
! 64: delay = options_get_number(c->session->options, "display-panes-time");
! 65: tv.tv_sec = delay / 1000;
! 66: tv.tv_usec = (delay % 1000) * 1000L;
! 67:
! 68: if (event_initialized(&c->identify_timer))
! 69: evtimer_del(&c->identify_timer);
! 70: evtimer_set(&c->identify_timer, server_client_callback_identify, c);
! 71: evtimer_add(&c->identify_timer, &tv);
! 72:
! 73: c->flags |= CLIENT_IDENTIFY;
! 74: c->tty.flags |= (TTY_FREEZE|TTY_NOCURSOR);
! 75: server_redraw_client(c);
! 76: }
! 77:
! 78: /* Clear identify mode on client. */
! 79: void
! 80: server_client_clear_identify(struct client *c, struct window_pane *wp)
! 81: {
! 82: if (~c->flags & CLIENT_IDENTIFY)
! 83: return;
! 84: c->flags &= ~CLIENT_IDENTIFY;
! 85:
! 86: if (c->identify_callback != NULL)
! 87: c->identify_callback(c, wp);
! 88:
! 89: c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
! 90: server_redraw_client(c);
! 91: }
! 92:
! 93: /* Check if this client is inside this server. */
! 94: int
! 95: server_client_check_nested(struct client *c)
! 96: {
! 97: struct environ_entry *envent;
! 98: struct window_pane *wp;
! 99:
! 100: envent = environ_find(c->environ, "TMUX");
! 101: if (envent == NULL || *envent->value == '\0')
! 102: return (0);
! 103:
! 104: RB_FOREACH(wp, window_pane_tree, &all_window_panes) {
! 105: if (strcmp(wp->tty, c->ttyname) == 0)
! 106: return (1);
! 107: }
! 108: return (0);
! 109: }
! 110:
! 111: /* Set client key table. */
! 112: void
! 113: server_client_set_key_table(struct client *c, const char *name)
! 114: {
! 115: if (name == NULL)
! 116: name = server_client_get_key_table(c);
! 117:
! 118: key_bindings_unref_table(c->keytable);
! 119: c->keytable = key_bindings_get_table(name, 1);
! 120: c->keytable->references++;
! 121: }
! 122:
! 123: /* Get default key table. */
! 124: const char *
! 125: server_client_get_key_table(struct client *c)
! 126: {
! 127: struct session *s = c->session;
! 128: const char *name;
! 129:
! 130: if (s == NULL)
! 131: return ("root");
! 132:
! 133: name = options_get_string(s->options, "key-table");
! 134: if (*name == '\0')
! 135: return ("root");
! 136: return (name);
! 137: }
! 138:
! 139: /* Is this client using the default key table? */
! 140: int
! 141: server_client_is_default_key_table(struct client *c)
! 142: {
! 143: return (strcmp(c->keytable->name, server_client_get_key_table(c)) == 0);
! 144: }
! 145:
! 146: /* Create a new client. */
! 147: void
! 148: server_client_create(int fd)
! 149: {
! 150: struct client *c;
! 151:
! 152: setblocking(fd, 0);
! 153:
! 154: c = xcalloc(1, sizeof *c);
! 155: c->references = 1;
! 156: c->peer = proc_add_peer(server_proc, fd, server_client_dispatch, c);
! 157:
! 158: if (gettimeofday(&c->creation_time, NULL) != 0)
! 159: fatal("gettimeofday failed");
! 160: memcpy(&c->activity_time, &c->creation_time, sizeof c->activity_time);
! 161:
! 162: c->environ = environ_create();
! 163:
! 164: c->fd = -1;
! 165: c->cwd = NULL;
! 166:
! 167: TAILQ_INIT(&c->queue);
! 168:
! 169: c->stdin_data = evbuffer_new();
! 170: c->stdout_data = evbuffer_new();
! 171: c->stderr_data = evbuffer_new();
! 172:
! 173: c->tty.fd = -1;
! 174: c->title = NULL;
! 175:
! 176: c->session = NULL;
! 177: c->last_session = NULL;
! 178: c->tty.sx = 80;
! 179: c->tty.sy = 24;
! 180:
! 181: screen_init(&c->status, c->tty.sx, 1, 0);
! 182:
! 183: c->message_string = NULL;
! 184: TAILQ_INIT(&c->message_log);
! 185:
! 186: c->prompt_string = NULL;
! 187: c->prompt_buffer = NULL;
! 188: c->prompt_index = 0;
! 189:
! 190: c->flags |= CLIENT_FOCUSED;
! 191:
! 192: c->keytable = key_bindings_get_table("root", 1);
! 193: c->keytable->references++;
! 194:
! 195: evtimer_set(&c->repeat_timer, server_client_repeat_timer, c);
! 196: evtimer_set(&c->click_timer, server_client_click_timer, c);
! 197:
! 198: TAILQ_INSERT_TAIL(&clients, c, entry);
! 199: log_debug("new client %p", c);
! 200: }
! 201:
! 202: /* Open client terminal if needed. */
! 203: int
! 204: server_client_open(struct client *c, char **cause)
! 205: {
! 206: if (c->flags & CLIENT_CONTROL)
! 207: return (0);
! 208:
! 209: if (strcmp(c->ttyname, "/dev/tty") == 0) {
! 210: *cause = xstrdup("can't use /dev/tty");
! 211: return (-1);
! 212: }
! 213:
! 214: if (!(c->flags & CLIENT_TERMINAL)) {
! 215: *cause = xstrdup("not a terminal");
! 216: return (-1);
! 217: }
! 218:
! 219: if (tty_open(&c->tty, cause) != 0)
! 220: return (-1);
! 221:
! 222: return (0);
! 223: }
! 224:
! 225: /* Lost a client. */
! 226: void
! 227: server_client_lost(struct client *c)
! 228: {
! 229: struct message_entry *msg, *msg1;
! 230:
! 231: c->flags |= CLIENT_DEAD;
! 232:
! 233: server_client_clear_identify(c, NULL);
! 234: status_prompt_clear(c);
! 235: status_message_clear(c);
! 236:
! 237: if (c->stdin_callback != NULL)
! 238: c->stdin_callback(c, 1, c->stdin_callback_data);
! 239:
! 240: TAILQ_REMOVE(&clients, c, entry);
! 241: log_debug("lost client %p", c);
! 242:
! 243: /*
! 244: * If CLIENT_TERMINAL hasn't been set, then tty_init hasn't been called
! 245: * and tty_free might close an unrelated fd.
! 246: */
! 247: if (c->flags & CLIENT_TERMINAL)
! 248: tty_free(&c->tty);
! 249: free(c->ttyname);
! 250: free(c->term);
! 251:
! 252: evbuffer_free(c->stdin_data);
! 253: evbuffer_free(c->stdout_data);
! 254: if (c->stderr_data != c->stdout_data)
! 255: evbuffer_free(c->stderr_data);
! 256:
! 257: if (event_initialized(&c->status_timer))
! 258: evtimer_del(&c->status_timer);
! 259: screen_free(&c->status);
! 260:
! 261: free(c->title);
! 262: free((void *)c->cwd);
! 263:
! 264: evtimer_del(&c->repeat_timer);
! 265: evtimer_del(&c->click_timer);
! 266:
! 267: key_bindings_unref_table(c->keytable);
! 268:
! 269: if (event_initialized(&c->identify_timer))
! 270: evtimer_del(&c->identify_timer);
! 271:
! 272: free(c->message_string);
! 273: if (event_initialized(&c->message_timer))
! 274: evtimer_del(&c->message_timer);
! 275: TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) {
! 276: free(msg->msg);
! 277: TAILQ_REMOVE(&c->message_log, msg, entry);
! 278: free(msg);
! 279: }
! 280:
! 281: free(c->prompt_string);
! 282: free(c->prompt_buffer);
! 283:
! 284: environ_free(c->environ);
! 285:
! 286: proc_remove_peer(c->peer);
! 287: c->peer = NULL;
! 288:
! 289: server_client_unref(c);
! 290:
! 291: server_add_accept(0); /* may be more file descriptors now */
! 292:
! 293: recalculate_sizes();
! 294: server_check_unattached();
! 295: server_update_socket();
! 296: }
! 297:
! 298: /* Remove reference from a client. */
! 299: void
! 300: server_client_unref(struct client *c)
! 301: {
! 302: log_debug("unref client %p (%d references)", c, c->references);
! 303:
! 304: c->references--;
! 305: if (c->references == 0)
! 306: event_once(-1, EV_TIMEOUT, server_client_free, c, NULL);
! 307: }
! 308:
! 309: /* Free dead client. */
! 310: static void
! 311: server_client_free(__unused int fd, __unused short events, void *arg)
! 312: {
! 313: struct client *c = arg;
! 314:
! 315: log_debug("free client %p (%d references)", c, c->references);
! 316:
! 317: if (!TAILQ_EMPTY(&c->queue))
! 318: fatalx("queue not empty");
! 319:
! 320: if (c->references == 0) {
! 321: free((void *)c->name);
! 322: free(c);
! 323: }
! 324: }
! 325:
! 326: /* Detach a client. */
! 327: void
! 328: server_client_detach(struct client *c, enum msgtype msgtype)
! 329: {
! 330: struct session *s = c->session;
! 331:
! 332: if (s == NULL)
! 333: return;
! 334:
! 335: notify_client("client-detached", c);
! 336: proc_send_s(c->peer, msgtype, s->name);
! 337: }
! 338:
! 339: /* Execute command to replace a client. */
! 340: void
! 341: server_client_exec(struct client *c, const char *cmd)
! 342: {
! 343: struct session *s = c->session;
! 344: char *msg;
! 345: const char *shell;
! 346: size_t cmdsize, shellsize;
! 347:
! 348: if (*cmd == '\0')
! 349: return;
! 350: cmdsize = strlen(cmd) + 1;
! 351:
! 352: if (s != NULL)
! 353: shell = options_get_string(s->options, "default-shell");
! 354: else
! 355: shell = options_get_string(global_s_options, "default-shell");
! 356: shellsize = strlen(shell) + 1;
! 357:
! 358: msg = xmalloc(cmdsize + shellsize);
! 359: memcpy(msg, cmd, cmdsize);
! 360: memcpy(msg + cmdsize, shell, shellsize);
! 361:
! 362: proc_send(c->peer, MSG_EXEC, -1, msg, cmdsize + shellsize);
! 363: free(msg);
! 364: }
! 365:
! 366: /* Check for mouse keys. */
! 367: static key_code
! 368: server_client_check_mouse(struct client *c)
! 369: {
! 370: struct session *s = c->session;
! 371: struct mouse_event *m = &c->tty.mouse;
! 372: struct window *w;
! 373: struct window_pane *wp;
! 374: u_int x, y, b;
! 375: int flag;
! 376: key_code key;
! 377: struct timeval tv;
! 378: enum { NOTYPE, MOVE, DOWN, UP, DRAG, WHEEL, DOUBLE, TRIPLE } type;
! 379: enum { NOWHERE, PANE, STATUS, BORDER } where;
! 380:
! 381: type = NOTYPE;
! 382: where = NOWHERE;
! 383:
! 384: log_debug("mouse %02x at %u,%u (last %u,%u) (%d)", m->b, m->x, m->y,
! 385: m->lx, m->ly, c->tty.mouse_drag_flag);
! 386:
! 387: /* What type of event is this? */
! 388: if ((m->sgr_type != ' ' &&
! 389: MOUSE_DRAG(m->sgr_b) &&
! 390: MOUSE_BUTTONS(m->sgr_b) == 3) ||
! 391: (m->sgr_type == ' ' &&
! 392: MOUSE_DRAG(m->b) &&
! 393: MOUSE_BUTTONS(m->b) == 3 &&
! 394: MOUSE_BUTTONS(m->lb) == 3)) {
! 395: type = MOVE;
! 396: x = m->x, y = m->y, b = 0;
! 397: log_debug("move at %u,%u", x, y);
! 398: } else if (MOUSE_DRAG(m->b)) {
! 399: type = DRAG;
! 400: if (c->tty.mouse_drag_flag) {
! 401: x = m->x, y = m->y, b = m->b;
! 402: log_debug("drag update at %u,%u", x, y);
! 403: } else {
! 404: x = m->lx, y = m->ly, b = m->lb;
! 405: log_debug("drag start at %u,%u", x, y);
! 406: }
! 407: } else if (MOUSE_WHEEL(m->b)) {
! 408: type = WHEEL;
! 409: x = m->x, y = m->y, b = m->b;
! 410: log_debug("wheel at %u,%u", x, y);
! 411: } else if (MOUSE_RELEASE(m->b)) {
! 412: type = UP;
! 413: x = m->x, y = m->y, b = m->lb;
! 414: log_debug("up at %u,%u", x, y);
! 415: } else {
! 416: if (c->flags & CLIENT_DOUBLECLICK) {
! 417: evtimer_del(&c->click_timer);
! 418: c->flags &= ~CLIENT_DOUBLECLICK;
! 419: if (m->b == c->click_button) {
! 420: type = DOUBLE;
! 421: x = m->x, y = m->y, b = m->b;
! 422: log_debug("double-click at %u,%u", x, y);
! 423: flag = CLIENT_TRIPLECLICK;
! 424: goto add_timer;
! 425: }
! 426: } else if (c->flags & CLIENT_TRIPLECLICK) {
! 427: evtimer_del(&c->click_timer);
! 428: c->flags &= ~CLIENT_TRIPLECLICK;
! 429: if (m->b == c->click_button) {
! 430: type = TRIPLE;
! 431: x = m->x, y = m->y, b = m->b;
! 432: log_debug("triple-click at %u,%u", x, y);
! 433: goto have_event;
! 434: }
! 435: }
! 436:
! 437: type = DOWN;
! 438: x = m->x, y = m->y, b = m->b;
! 439: log_debug("down at %u,%u", x, y);
! 440: flag = CLIENT_DOUBLECLICK;
! 441:
! 442: add_timer:
! 443: if (KEYC_CLICK_TIMEOUT != 0) {
! 444: c->flags |= flag;
! 445: c->click_button = m->b;
! 446:
! 447: tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000;
! 448: tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L;
! 449: evtimer_del(&c->click_timer);
! 450: evtimer_add(&c->click_timer, &tv);
! 451: }
! 452: }
! 453:
! 454: have_event:
! 455: if (type == NOTYPE)
! 456: return (KEYC_UNKNOWN);
! 457:
! 458: /* Always save the session. */
! 459: m->s = s->id;
! 460:
! 461: /* Is this on the status line? */
! 462: m->statusat = status_at_line(c);
! 463: if (m->statusat != -1 && y == (u_int)m->statusat) {
! 464: w = status_get_window_at(c, x);
! 465: if (w == NULL)
! 466: return (KEYC_UNKNOWN);
! 467: m->w = w->id;
! 468: where = STATUS;
! 469: } else
! 470: m->w = -1;
! 471:
! 472: /* Not on status line. Adjust position and check for border or pane. */
! 473: if (where == NOWHERE) {
! 474: if (m->statusat == 0 && y > 0)
! 475: y--;
! 476: else if (m->statusat > 0 && y >= (u_int)m->statusat)
! 477: y = m->statusat - 1;
! 478:
! 479: TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
! 480: if ((wp->xoff + wp->sx == x &&
! 481: wp->yoff <= 1 + y &&
! 482: wp->yoff + wp->sy >= y) ||
! 483: (wp->yoff + wp->sy == y &&
! 484: wp->xoff <= 1 + x &&
! 485: wp->xoff + wp->sx >= x))
! 486: break;
! 487: }
! 488: if (wp != NULL)
! 489: where = BORDER;
! 490: else {
! 491: wp = window_get_active_at(s->curw->window, x, y);
! 492: if (wp != NULL) {
! 493: where = PANE;
! 494: log_debug("mouse at %u,%u is on pane %%%u",
! 495: x, y, wp->id);
! 496: }
! 497: }
! 498: if (where == NOWHERE)
! 499: return (KEYC_UNKNOWN);
! 500: m->wp = wp->id;
! 501: m->w = wp->window->id;
! 502: } else
! 503: m->wp = -1;
! 504:
! 505: /* Stop dragging if needed. */
! 506: if (type != DRAG && type != WHEEL && c->tty.mouse_drag_flag) {
! 507: if (c->tty.mouse_drag_release != NULL)
! 508: c->tty.mouse_drag_release(c, m);
! 509:
! 510: c->tty.mouse_drag_update = NULL;
! 511: c->tty.mouse_drag_release = NULL;
! 512:
! 513: /*
! 514: * End a mouse drag by passing a MouseDragEnd key corresponding
! 515: * to the button that started the drag.
! 516: */
! 517: switch (c->tty.mouse_drag_flag) {
! 518: case 1:
! 519: if (where == PANE)
! 520: key = KEYC_MOUSEDRAGEND1_PANE;
! 521: if (where == STATUS)
! 522: key = KEYC_MOUSEDRAGEND1_STATUS;
! 523: if (where == BORDER)
! 524: key = KEYC_MOUSEDRAGEND1_BORDER;
! 525: break;
! 526: case 2:
! 527: if (where == PANE)
! 528: key = KEYC_MOUSEDRAGEND2_PANE;
! 529: if (where == STATUS)
! 530: key = KEYC_MOUSEDRAGEND2_STATUS;
! 531: if (where == BORDER)
! 532: key = KEYC_MOUSEDRAGEND2_BORDER;
! 533: break;
! 534: case 3:
! 535: if (where == PANE)
! 536: key = KEYC_MOUSEDRAGEND3_PANE;
! 537: if (where == STATUS)
! 538: key = KEYC_MOUSEDRAGEND3_STATUS;
! 539: if (where == BORDER)
! 540: key = KEYC_MOUSEDRAGEND3_BORDER;
! 541: break;
! 542: default:
! 543: key = KEYC_MOUSE;
! 544: break;
! 545: }
! 546: c->tty.mouse_drag_flag = 0;
! 547:
! 548: return (key);
! 549: }
! 550:
! 551: /* Convert to a key binding. */
! 552: key = KEYC_UNKNOWN;
! 553: switch (type) {
! 554: case NOTYPE:
! 555: break;
! 556: case MOVE:
! 557: if (where == PANE)
! 558: key = KEYC_MOUSEMOVE_PANE;
! 559: if (where == STATUS)
! 560: key = KEYC_MOUSEMOVE_STATUS;
! 561: if (where == BORDER)
! 562: key = KEYC_MOUSEMOVE_BORDER;
! 563: break;
! 564: case DRAG:
! 565: if (c->tty.mouse_drag_update != NULL)
! 566: key = KEYC_DRAGGING;
! 567: else {
! 568: switch (MOUSE_BUTTONS(b)) {
! 569: case 0:
! 570: if (where == PANE)
! 571: key = KEYC_MOUSEDRAG1_PANE;
! 572: if (where == STATUS)
! 573: key = KEYC_MOUSEDRAG1_STATUS;
! 574: if (where == BORDER)
! 575: key = KEYC_MOUSEDRAG1_BORDER;
! 576: break;
! 577: case 1:
! 578: if (where == PANE)
! 579: key = KEYC_MOUSEDRAG2_PANE;
! 580: if (where == STATUS)
! 581: key = KEYC_MOUSEDRAG2_STATUS;
! 582: if (where == BORDER)
! 583: key = KEYC_MOUSEDRAG2_BORDER;
! 584: break;
! 585: case 2:
! 586: if (where == PANE)
! 587: key = KEYC_MOUSEDRAG3_PANE;
! 588: if (where == STATUS)
! 589: key = KEYC_MOUSEDRAG3_STATUS;
! 590: if (where == BORDER)
! 591: key = KEYC_MOUSEDRAG3_BORDER;
! 592: break;
! 593: }
! 594: }
! 595:
! 596: /*
! 597: * Begin a drag by setting the flag to a non-zero value that
! 598: * corresponds to the mouse button in use.
! 599: */
! 600: c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1;
! 601: break;
! 602: case WHEEL:
! 603: if (MOUSE_BUTTONS(b) == MOUSE_WHEEL_UP) {
! 604: if (where == PANE)
! 605: key = KEYC_WHEELUP_PANE;
! 606: if (where == STATUS)
! 607: key = KEYC_WHEELUP_STATUS;
! 608: if (where == BORDER)
! 609: key = KEYC_WHEELUP_BORDER;
! 610: } else {
! 611: if (where == PANE)
! 612: key = KEYC_WHEELDOWN_PANE;
! 613: if (where == STATUS)
! 614: key = KEYC_WHEELDOWN_STATUS;
! 615: if (where == BORDER)
! 616: key = KEYC_WHEELDOWN_BORDER;
! 617: }
! 618: break;
! 619: case UP:
! 620: switch (MOUSE_BUTTONS(b)) {
! 621: case 0:
! 622: if (where == PANE)
! 623: key = KEYC_MOUSEUP1_PANE;
! 624: if (where == STATUS)
! 625: key = KEYC_MOUSEUP1_STATUS;
! 626: if (where == BORDER)
! 627: key = KEYC_MOUSEUP1_BORDER;
! 628: break;
! 629: case 1:
! 630: if (where == PANE)
! 631: key = KEYC_MOUSEUP2_PANE;
! 632: if (where == STATUS)
! 633: key = KEYC_MOUSEUP2_STATUS;
! 634: if (where == BORDER)
! 635: key = KEYC_MOUSEUP2_BORDER;
! 636: break;
! 637: case 2:
! 638: if (where == PANE)
! 639: key = KEYC_MOUSEUP3_PANE;
! 640: if (where == STATUS)
! 641: key = KEYC_MOUSEUP3_STATUS;
! 642: if (where == BORDER)
! 643: key = KEYC_MOUSEUP3_BORDER;
! 644: break;
! 645: }
! 646: break;
! 647: case DOWN:
! 648: switch (MOUSE_BUTTONS(b)) {
! 649: case 0:
! 650: if (where == PANE)
! 651: key = KEYC_MOUSEDOWN1_PANE;
! 652: if (where == STATUS)
! 653: key = KEYC_MOUSEDOWN1_STATUS;
! 654: if (where == BORDER)
! 655: key = KEYC_MOUSEDOWN1_BORDER;
! 656: break;
! 657: case 1:
! 658: if (where == PANE)
! 659: key = KEYC_MOUSEDOWN2_PANE;
! 660: if (where == STATUS)
! 661: key = KEYC_MOUSEDOWN2_STATUS;
! 662: if (where == BORDER)
! 663: key = KEYC_MOUSEDOWN2_BORDER;
! 664: break;
! 665: case 2:
! 666: if (where == PANE)
! 667: key = KEYC_MOUSEDOWN3_PANE;
! 668: if (where == STATUS)
! 669: key = KEYC_MOUSEDOWN3_STATUS;
! 670: if (where == BORDER)
! 671: key = KEYC_MOUSEDOWN3_BORDER;
! 672: break;
! 673: }
! 674: break;
! 675: case DOUBLE:
! 676: switch (MOUSE_BUTTONS(b)) {
! 677: case 0:
! 678: if (where == PANE)
! 679: key = KEYC_DOUBLECLICK1_PANE;
! 680: if (where == STATUS)
! 681: key = KEYC_DOUBLECLICK1_STATUS;
! 682: if (where == BORDER)
! 683: key = KEYC_DOUBLECLICK1_BORDER;
! 684: break;
! 685: case 1:
! 686: if (where == PANE)
! 687: key = KEYC_DOUBLECLICK2_PANE;
! 688: if (where == STATUS)
! 689: key = KEYC_DOUBLECLICK2_STATUS;
! 690: if (where == BORDER)
! 691: key = KEYC_DOUBLECLICK2_BORDER;
! 692: break;
! 693: case 2:
! 694: if (where == PANE)
! 695: key = KEYC_DOUBLECLICK3_PANE;
! 696: if (where == STATUS)
! 697: key = KEYC_DOUBLECLICK3_STATUS;
! 698: if (where == BORDER)
! 699: key = KEYC_DOUBLECLICK3_BORDER;
! 700: break;
! 701: }
! 702: break;
! 703: case TRIPLE:
! 704: switch (MOUSE_BUTTONS(b)) {
! 705: case 0:
! 706: if (where == PANE)
! 707: key = KEYC_TRIPLECLICK1_PANE;
! 708: if (where == STATUS)
! 709: key = KEYC_TRIPLECLICK1_STATUS;
! 710: if (where == BORDER)
! 711: key = KEYC_TRIPLECLICK1_BORDER;
! 712: break;
! 713: case 1:
! 714: if (where == PANE)
! 715: key = KEYC_TRIPLECLICK2_PANE;
! 716: if (where == STATUS)
! 717: key = KEYC_TRIPLECLICK2_STATUS;
! 718: if (where == BORDER)
! 719: key = KEYC_TRIPLECLICK2_BORDER;
! 720: break;
! 721: case 2:
! 722: if (where == PANE)
! 723: key = KEYC_TRIPLECLICK3_PANE;
! 724: if (where == STATUS)
! 725: key = KEYC_TRIPLECLICK3_STATUS;
! 726: if (where == BORDER)
! 727: key = KEYC_TRIPLECLICK3_BORDER;
! 728: break;
! 729: }
! 730: break;
! 731: }
! 732: if (key == KEYC_UNKNOWN)
! 733: return (KEYC_UNKNOWN);
! 734:
! 735: /* Apply modifiers if any. */
! 736: if (b & MOUSE_MASK_META)
! 737: key |= KEYC_ESCAPE;
! 738: if (b & MOUSE_MASK_CTRL)
! 739: key |= KEYC_CTRL;
! 740: if (b & MOUSE_MASK_SHIFT)
! 741: key |= KEYC_SHIFT;
! 742:
! 743: return (key);
! 744: }
! 745:
! 746: /* Is this fast enough to probably be a paste? */
! 747: static int
! 748: server_client_assume_paste(struct session *s)
! 749: {
! 750: struct timeval tv;
! 751: int t;
! 752:
! 753: if ((t = options_get_number(s->options, "assume-paste-time")) == 0)
! 754: return (0);
! 755:
! 756: timersub(&s->activity_time, &s->last_activity_time, &tv);
! 757: if (tv.tv_sec == 0 && tv.tv_usec < t * 1000) {
! 758: log_debug("session %s pasting (flag %d)", s->name,
! 759: !!(s->flags & SESSION_PASTING));
! 760: if (s->flags & SESSION_PASTING)
! 761: return (1);
! 762: s->flags |= SESSION_PASTING;
! 763: return (0);
! 764: }
! 765: log_debug("session %s not pasting", s->name);
! 766: s->flags &= ~SESSION_PASTING;
! 767: return (0);
! 768: }
! 769:
! 770: /* Handle data key input from client. */
! 771: void
! 772: server_client_handle_key(struct client *c, key_code key)
! 773: {
! 774: struct mouse_event *m = &c->tty.mouse;
! 775: struct session *s = c->session;
! 776: struct window *w;
! 777: struct window_pane *wp;
! 778: struct timeval tv;
! 779: const char *name;
! 780: struct key_table *table;
! 781: struct key_binding bd_find, *bd;
! 782: int xtimeout;
! 783: struct cmd_find_state fs;
! 784:
! 785: /* Check the client is good to accept input. */
! 786: if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
! 787: return;
! 788: w = s->curw->window;
! 789:
! 790: /* Update the activity timer. */
! 791: if (gettimeofday(&c->activity_time, NULL) != 0)
! 792: fatal("gettimeofday failed");
! 793: session_update_activity(s, &c->activity_time);
! 794:
! 795: /* Number keys jump to pane in identify mode. */
! 796: if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') {
! 797: if (c->flags & CLIENT_READONLY)
! 798: return;
! 799: window_unzoom(w);
! 800: wp = window_pane_at_index(w, key - '0');
! 801: if (wp != NULL && !window_pane_visible(wp))
! 802: wp = NULL;
! 803: server_client_clear_identify(c, wp);
! 804: return;
! 805: }
! 806:
! 807: /* Handle status line. */
! 808: if (!(c->flags & CLIENT_READONLY)) {
! 809: status_message_clear(c);
! 810: server_client_clear_identify(c, NULL);
! 811: }
! 812: if (c->prompt_string != NULL) {
! 813: if (c->flags & CLIENT_READONLY)
! 814: return;
! 815: if (status_prompt_key(c, key) == 0)
! 816: return;
! 817: }
! 818:
! 819: /* Check for mouse keys. */
! 820: m->valid = 0;
! 821: if (key == KEYC_MOUSE) {
! 822: if (c->flags & CLIENT_READONLY)
! 823: return;
! 824: key = server_client_check_mouse(c);
! 825: if (key == KEYC_UNKNOWN)
! 826: return;
! 827:
! 828: m->valid = 1;
! 829: m->key = key;
! 830:
! 831: /*
! 832: * Mouse drag is in progress, so fire the callback (now that
! 833: * the mouse event is valid).
! 834: */
! 835: if (key == KEYC_DRAGGING) {
! 836: c->tty.mouse_drag_update(c, m);
! 837: return;
! 838: }
! 839: } else
! 840: m->valid = 0;
! 841:
! 842: /* Find affected pane. */
! 843: if (KEYC_IS_MOUSE(key) && m->valid)
! 844: wp = cmd_mouse_pane(m, NULL, NULL);
! 845: else
! 846: wp = w->active;
! 847:
! 848: /* Forward mouse keys if disabled. */
! 849: if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse"))
! 850: goto forward;
! 851:
! 852: /* Treat everything as a regular key when pasting is detected. */
! 853: if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s))
! 854: goto forward;
! 855:
! 856: retry:
! 857: /*
! 858: * Work out the current key table. If the pane is in a mode, use
! 859: * the mode table instead of the default key table.
! 860: */
! 861: name = NULL;
! 862: if (wp != NULL && wp->mode != NULL && wp->mode->key_table != NULL)
! 863: name = wp->mode->key_table(wp);
! 864: if (name == NULL || !server_client_is_default_key_table(c))
! 865: table = c->keytable;
! 866: else
! 867: table = key_bindings_get_table(name, 1);
! 868: if (wp == NULL)
! 869: log_debug("key table %s (no pane)", table->name);
! 870: else
! 871: log_debug("key table %s (pane %%%u)", table->name, wp->id);
! 872:
! 873: /*
! 874: * The prefix always takes precedence and forces a switch to the prefix
! 875: * table, unless we are already there.
! 876: */
! 877: if ((key == (key_code)options_get_number(s->options, "prefix") ||
! 878: key == (key_code)options_get_number(s->options, "prefix2")) &&
! 879: strcmp(table->name, "prefix") != 0) {
! 880: server_client_set_key_table(c, "prefix");
! 881: server_status_client(c);
! 882: return;
! 883: }
! 884:
! 885: /* Try to see if there is a key binding in the current table. */
! 886: bd_find.key = key;
! 887: bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
! 888: if (bd != NULL) {
! 889: /*
! 890: * Key was matched in this table. If currently repeating but a
! 891: * non-repeating binding was found, stop repeating and try
! 892: * again in the root table.
! 893: */
! 894: if ((c->flags & CLIENT_REPEAT) && !bd->can_repeat) {
! 895: server_client_set_key_table(c, NULL);
! 896: c->flags &= ~CLIENT_REPEAT;
! 897: server_status_client(c);
! 898: goto retry;
! 899: }
! 900:
! 901: /*
! 902: * Take a reference to this table to make sure the key binding
! 903: * doesn't disappear.
! 904: */
! 905: table->references++;
! 906:
! 907: /*
! 908: * If this is a repeating key, start the timer. Otherwise reset
! 909: * the client back to the root table.
! 910: */
! 911: xtimeout = options_get_number(s->options, "repeat-time");
! 912: if (xtimeout != 0 && bd->can_repeat) {
! 913: c->flags |= CLIENT_REPEAT;
! 914:
! 915: tv.tv_sec = xtimeout / 1000;
! 916: tv.tv_usec = (xtimeout % 1000) * 1000L;
! 917: evtimer_del(&c->repeat_timer);
! 918: evtimer_add(&c->repeat_timer, &tv);
! 919: } else {
! 920: c->flags &= ~CLIENT_REPEAT;
! 921: server_client_set_key_table(c, NULL);
! 922: }
! 923: server_status_client(c);
! 924:
! 925: /* Find default state if the pane is known. */
! 926: cmd_find_clear_state(&fs, NULL, 0);
! 927: if (wp != NULL) {
! 928: fs.s = s;
! 929: fs.wl = fs.s->curw;
! 930: fs.w = fs.wl->window;
! 931: fs.wp = wp;
! 932: cmd_find_log_state(__func__, &fs);
! 933:
! 934: if (!cmd_find_valid_state(&fs))
! 935: fatalx("invalid key state");
! 936: }
! 937:
! 938: /* Dispatch the key binding. */
! 939: key_bindings_dispatch(bd, c, m, &fs);
! 940: key_bindings_unref_table(table);
! 941: return;
! 942: }
! 943:
! 944: /*
! 945: * No match in this table. If repeating, switch the client back to the
! 946: * root table and try again.
! 947: */
! 948: if (c->flags & CLIENT_REPEAT) {
! 949: server_client_set_key_table(c, NULL);
! 950: c->flags &= ~CLIENT_REPEAT;
! 951: server_status_client(c);
! 952: goto retry;
! 953: }
! 954:
! 955: /* If no match and we're not in the root table, that's it. */
! 956: if (name == NULL && !server_client_is_default_key_table(c)) {
! 957: log_debug("no key in key table %s", table->name);
! 958: server_client_set_key_table(c, NULL);
! 959: server_status_client(c);
! 960: return;
! 961: }
! 962:
! 963: forward:
! 964: if (c->flags & CLIENT_READONLY)
! 965: return;
! 966: if (wp != NULL)
! 967: window_pane_key(wp, c, s, key, m);
! 968: }
! 969:
! 970: /* Client functions that need to happen every loop. */
! 971: void
! 972: server_client_loop(void)
! 973: {
! 974: struct client *c;
! 975: struct window *w;
! 976: struct window_pane *wp;
! 977: int focus;
! 978:
! 979: TAILQ_FOREACH(c, &clients, entry) {
! 980: server_client_check_exit(c);
! 981: if (c->session != NULL) {
! 982: server_client_check_redraw(c);
! 983: server_client_reset_state(c);
! 984: }
! 985: }
! 986:
! 987: /*
! 988: * Any windows will have been redrawn as part of clients, so clear
! 989: * their flags now. Also check pane focus and resize.
! 990: */
! 991: focus = options_get_number(global_options, "focus-events");
! 992: RB_FOREACH(w, windows, &windows) {
! 993: TAILQ_FOREACH(wp, &w->panes, entry) {
! 994: if (wp->fd != -1) {
! 995: if (focus)
! 996: server_client_check_focus(wp);
! 997: server_client_check_resize(wp);
! 998: }
! 999: wp->flags &= ~PANE_REDRAW;
! 1000: }
! 1001: check_window_name(w);
! 1002: }
! 1003: }
! 1004:
! 1005: /* Resize timer event. */
! 1006: static void
! 1007: server_client_resize_event(__unused int fd, __unused short events, void *data)
! 1008: {
! 1009: struct window_pane *wp = data;
! 1010: struct winsize ws;
! 1011:
! 1012: evtimer_del(&wp->resize_timer);
! 1013:
! 1014: if (!(wp->flags & PANE_RESIZE))
! 1015: return;
! 1016:
! 1017: memset(&ws, 0, sizeof ws);
! 1018: ws.ws_col = wp->sx;
! 1019: ws.ws_row = wp->sy;
! 1020:
! 1021: if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) {
! 1022: #ifdef __sun
! 1023: /*
! 1024: * Some versions of Solaris apparently can return an error when
! 1025: * resizing; don't know why this happens, can't reproduce on
! 1026: * other platforms and ignoring it doesn't seem to cause any
! 1027: * issues.
! 1028: */
! 1029: if (errno != EINVAL && errno != ENXIO)
! 1030: #endif
! 1031: fatal("ioctl failed");
! 1032: }
! 1033:
! 1034: wp->flags &= ~PANE_RESIZE;
! 1035: }
! 1036:
! 1037: /* Check if pane should be resized. */
! 1038: static void
! 1039: server_client_check_resize(struct window_pane *wp)
! 1040: {
! 1041: struct timeval tv = { .tv_usec = 250000 };
! 1042:
! 1043: if (!(wp->flags & PANE_RESIZE))
! 1044: return;
! 1045:
! 1046: if (!event_initialized(&wp->resize_timer))
! 1047: evtimer_set(&wp->resize_timer, server_client_resize_event, wp);
! 1048:
! 1049: /*
! 1050: * The first resize should happen immediately, so if the timer is not
! 1051: * running, do it now.
! 1052: */
! 1053: if (!evtimer_pending(&wp->resize_timer, NULL))
! 1054: server_client_resize_event(-1, 0, wp);
! 1055:
! 1056: /*
! 1057: * If the pane is in the alternate screen, let the timer expire and
! 1058: * resize to give the application a chance to redraw. If not, keep
! 1059: * pushing the timer back.
! 1060: */
! 1061: if (wp->saved_grid != NULL && evtimer_pending(&wp->resize_timer, NULL))
! 1062: return;
! 1063: evtimer_del(&wp->resize_timer);
! 1064: evtimer_add(&wp->resize_timer, &tv);
! 1065: }
! 1066:
! 1067: /* Check whether pane should be focused. */
! 1068: static void
! 1069: server_client_check_focus(struct window_pane *wp)
! 1070: {
! 1071: struct client *c;
! 1072: int push;
! 1073:
! 1074: /* Do we need to push the focus state? */
! 1075: push = wp->flags & PANE_FOCUSPUSH;
! 1076: wp->flags &= ~PANE_FOCUSPUSH;
! 1077:
! 1078: /* If we don't care about focus, forget it. */
! 1079: if (!(wp->base.mode & MODE_FOCUSON))
! 1080: return;
! 1081:
! 1082: /* If we're not the active pane in our window, we're not focused. */
! 1083: if (wp->window->active != wp)
! 1084: goto not_focused;
! 1085:
! 1086: /* If we're in a mode, we're not focused. */
! 1087: if (wp->screen != &wp->base)
! 1088: goto not_focused;
! 1089:
! 1090: /*
! 1091: * If our window is the current window in any focused clients with an
! 1092: * attached session, we're focused.
! 1093: */
! 1094: TAILQ_FOREACH(c, &clients, entry) {
! 1095: if (c->session == NULL || !(c->flags & CLIENT_FOCUSED))
! 1096: continue;
! 1097: if (c->session->flags & SESSION_UNATTACHED)
! 1098: continue;
! 1099:
! 1100: if (c->session->curw->window == wp->window)
! 1101: goto focused;
! 1102: }
! 1103:
! 1104: not_focused:
! 1105: if (push || (wp->flags & PANE_FOCUSED))
! 1106: bufferevent_write(wp->event, "\033[O", 3);
! 1107: wp->flags &= ~PANE_FOCUSED;
! 1108: return;
! 1109:
! 1110: focused:
! 1111: if (push || !(wp->flags & PANE_FOCUSED))
! 1112: bufferevent_write(wp->event, "\033[I", 3);
! 1113: wp->flags |= PANE_FOCUSED;
! 1114: }
! 1115:
! 1116: /*
! 1117: * Update cursor position and mode settings. The scroll region and attributes
! 1118: * are cleared when idle (waiting for an event) as this is the most likely time
! 1119: * a user may interrupt tmux, for example with ~^Z in ssh(1). This is a
! 1120: * compromise between excessive resets and likelihood of an interrupt.
! 1121: *
! 1122: * tty_region/tty_reset/tty_update_mode already take care of not resetting
! 1123: * things that are already in their default state.
! 1124: */
! 1125: static void
! 1126: server_client_reset_state(struct client *c)
! 1127: {
! 1128: struct window *w = c->session->curw->window;
! 1129: struct window_pane *wp = w->active, *loop;
! 1130: struct screen *s = wp->screen;
! 1131: struct options *oo = c->session->options;
! 1132: int status, mode, o;
! 1133:
! 1134: if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
! 1135: return;
! 1136:
! 1137: tty_region_off(&c->tty);
! 1138: tty_margin_off(&c->tty);
! 1139:
! 1140: status = options_get_number(oo, "status");
! 1141: if (!window_pane_visible(wp) || wp->yoff + s->cy >= c->tty.sy - status)
! 1142: tty_cursor(&c->tty, 0, 0);
! 1143: else {
! 1144: o = status && options_get_number(oo, "status-position") == 0;
! 1145: tty_cursor(&c->tty, wp->xoff + s->cx, o + wp->yoff + s->cy);
! 1146: }
! 1147:
! 1148: /*
! 1149: * Set mouse mode if requested. To support dragging, always use button
! 1150: * mode.
! 1151: */
! 1152: mode = s->mode;
! 1153: if (options_get_number(oo, "mouse")) {
! 1154: mode &= ~ALL_MOUSE_MODES;
! 1155: TAILQ_FOREACH(loop, &w->panes, entry) {
! 1156: if (loop->screen->mode & MODE_MOUSE_ALL)
! 1157: mode |= MODE_MOUSE_ALL;
! 1158: }
! 1159: if (~mode & MODE_MOUSE_ALL)
! 1160: mode |= MODE_MOUSE_BUTTON;
! 1161: }
! 1162:
! 1163: /* Clear bracketed paste mode if at the prompt. */
! 1164: if (c->prompt_string != NULL)
! 1165: mode &= ~MODE_BRACKETPASTE;
! 1166:
! 1167: /* Set the terminal mode and reset attributes. */
! 1168: tty_update_mode(&c->tty, mode, s);
! 1169: tty_reset(&c->tty);
! 1170: }
! 1171:
! 1172: /* Repeat time callback. */
! 1173: static void
! 1174: server_client_repeat_timer(__unused int fd, __unused short events, void *data)
! 1175: {
! 1176: struct client *c = data;
! 1177:
! 1178: if (c->flags & CLIENT_REPEAT) {
! 1179: server_client_set_key_table(c, NULL);
! 1180: c->flags &= ~CLIENT_REPEAT;
! 1181: server_status_client(c);
! 1182: }
! 1183: }
! 1184:
! 1185: /* Double-click callback. */
! 1186: static void
! 1187: server_client_click_timer(__unused int fd, __unused short events, void *data)
! 1188: {
! 1189: struct client *c = data;
! 1190:
! 1191: c->flags &= ~(CLIENT_DOUBLECLICK|CLIENT_TRIPLECLICK);
! 1192: }
! 1193:
! 1194: /* Check if client should be exited. */
! 1195: static void
! 1196: server_client_check_exit(struct client *c)
! 1197: {
! 1198: if (!(c->flags & CLIENT_EXIT))
! 1199: return;
! 1200:
! 1201: if (EVBUFFER_LENGTH(c->stdin_data) != 0)
! 1202: return;
! 1203: if (EVBUFFER_LENGTH(c->stdout_data) != 0)
! 1204: return;
! 1205: if (EVBUFFER_LENGTH(c->stderr_data) != 0)
! 1206: return;
! 1207:
! 1208: proc_send(c->peer, MSG_EXIT, -1, &c->retval, sizeof c->retval);
! 1209: c->flags &= ~CLIENT_EXIT;
! 1210: }
! 1211:
! 1212: /* Check for client redraws. */
! 1213: static void
! 1214: server_client_check_redraw(struct client *c)
! 1215: {
! 1216: struct session *s = c->session;
! 1217: struct tty *tty = &c->tty;
! 1218: struct window_pane *wp;
! 1219: int flags, masked;
! 1220:
! 1221: if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
! 1222: return;
! 1223:
! 1224: if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) {
! 1225: if (options_get_number(s->options, "set-titles"))
! 1226: server_client_set_title(c);
! 1227: screen_redraw_update(c); /* will adjust flags */
! 1228: }
! 1229:
! 1230: flags = tty->flags & (TTY_FREEZE|TTY_NOCURSOR);
! 1231: tty->flags = (tty->flags & ~TTY_FREEZE) | TTY_NOCURSOR;
! 1232:
! 1233: if (c->flags & CLIENT_REDRAW) {
! 1234: tty_update_mode(tty, tty->mode, NULL);
! 1235: screen_redraw_screen(c, 1, 1, 1);
! 1236: c->flags &= ~(CLIENT_STATUS|CLIENT_BORDERS);
! 1237: } else {
! 1238: TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) {
! 1239: if (wp->flags & PANE_REDRAW) {
! 1240: tty_update_mode(tty, tty->mode, NULL);
! 1241: screen_redraw_pane(c, wp);
! 1242: }
! 1243: }
! 1244: }
! 1245:
! 1246: masked = c->flags & (CLIENT_BORDERS|CLIENT_STATUS);
! 1247: if (masked != 0)
! 1248: tty_update_mode(tty, tty->mode, NULL);
! 1249: if (masked == CLIENT_BORDERS)
! 1250: screen_redraw_screen(c, 0, 0, 1);
! 1251: else if (masked == CLIENT_STATUS)
! 1252: screen_redraw_screen(c, 0, 1, 0);
! 1253: else if (masked != 0)
! 1254: screen_redraw_screen(c, 0, 1, 1);
! 1255:
! 1256: tty->flags = (tty->flags & ~(TTY_FREEZE|TTY_NOCURSOR)) | flags;
! 1257: tty_update_mode(tty, tty->mode, NULL);
! 1258:
! 1259: c->flags &= ~(CLIENT_REDRAW|CLIENT_BORDERS|CLIENT_STATUS|
! 1260: CLIENT_STATUSFORCE);
! 1261: }
! 1262:
! 1263: /* Set client title. */
! 1264: static void
! 1265: server_client_set_title(struct client *c)
! 1266: {
! 1267: struct session *s = c->session;
! 1268: const char *template;
! 1269: char *title;
! 1270: struct format_tree *ft;
! 1271:
! 1272: template = options_get_string(s->options, "set-titles-string");
! 1273:
! 1274: ft = format_create(NULL, FORMAT_NONE, 0);
! 1275: format_defaults(ft, c, NULL, NULL, NULL);
! 1276:
! 1277: title = format_expand_time(ft, template, time(NULL));
! 1278: if (c->title == NULL || strcmp(title, c->title) != 0) {
! 1279: free(c->title);
! 1280: c->title = xstrdup(title);
! 1281: tty_set_title(&c->tty, c->title);
! 1282: }
! 1283: free(title);
! 1284:
! 1285: format_free(ft);
! 1286: }
! 1287:
! 1288: /* Dispatch message from client. */
! 1289: static void
! 1290: server_client_dispatch(struct imsg *imsg, void *arg)
! 1291: {
! 1292: struct client *c = arg;
! 1293: struct msg_stdin_data stdindata;
! 1294: const char *data;
! 1295: ssize_t datalen;
! 1296: struct session *s;
! 1297:
! 1298: if (c->flags & CLIENT_DEAD)
! 1299: return;
! 1300:
! 1301: if (imsg == NULL) {
! 1302: server_client_lost(c);
! 1303: return;
! 1304: }
! 1305:
! 1306: data = imsg->data;
! 1307: datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
! 1308:
! 1309: switch (imsg->hdr.type) {
! 1310: case MSG_IDENTIFY_FLAGS:
! 1311: case MSG_IDENTIFY_TERM:
! 1312: case MSG_IDENTIFY_TTYNAME:
! 1313: case MSG_IDENTIFY_CWD:
! 1314: case MSG_IDENTIFY_STDIN:
! 1315: case MSG_IDENTIFY_ENVIRON:
! 1316: case MSG_IDENTIFY_CLIENTPID:
! 1317: case MSG_IDENTIFY_DONE:
! 1318: server_client_dispatch_identify(c, imsg);
! 1319: break;
! 1320: case MSG_COMMAND:
! 1321: server_client_dispatch_command(c, imsg);
! 1322: break;
! 1323: case MSG_STDIN:
! 1324: if (datalen != sizeof stdindata)
! 1325: fatalx("bad MSG_STDIN size");
! 1326: memcpy(&stdindata, data, sizeof stdindata);
! 1327:
! 1328: if (c->stdin_callback == NULL)
! 1329: break;
! 1330: if (stdindata.size <= 0)
! 1331: c->stdin_closed = 1;
! 1332: else {
! 1333: evbuffer_add(c->stdin_data, stdindata.data,
! 1334: stdindata.size);
! 1335: }
! 1336: c->stdin_callback(c, c->stdin_closed,
! 1337: c->stdin_callback_data);
! 1338: break;
! 1339: case MSG_RESIZE:
! 1340: if (datalen != 0)
! 1341: fatalx("bad MSG_RESIZE size");
! 1342:
! 1343: if (c->flags & CLIENT_CONTROL)
! 1344: break;
! 1345: if (tty_resize(&c->tty)) {
! 1346: recalculate_sizes();
! 1347: server_redraw_client(c);
! 1348: }
! 1349: if (c->session != NULL)
! 1350: notify_client("client-resized", c);
! 1351: break;
! 1352: case MSG_EXITING:
! 1353: if (datalen != 0)
! 1354: fatalx("bad MSG_EXITING size");
! 1355:
! 1356: c->session = NULL;
! 1357: tty_close(&c->tty);
! 1358: proc_send(c->peer, MSG_EXITED, -1, NULL, 0);
! 1359: break;
! 1360: case MSG_WAKEUP:
! 1361: case MSG_UNLOCK:
! 1362: if (datalen != 0)
! 1363: fatalx("bad MSG_WAKEUP size");
! 1364:
! 1365: if (!(c->flags & CLIENT_SUSPENDED))
! 1366: break;
! 1367: c->flags &= ~CLIENT_SUSPENDED;
! 1368:
! 1369: if (c->tty.fd == -1) /* exited in the meantime */
! 1370: break;
! 1371: s = c->session;
! 1372:
! 1373: if (gettimeofday(&c->activity_time, NULL) != 0)
! 1374: fatal("gettimeofday failed");
! 1375:
! 1376: tty_start_tty(&c->tty);
! 1377: server_redraw_client(c);
! 1378: recalculate_sizes();
! 1379:
! 1380: if (s != NULL)
! 1381: session_update_activity(s, &c->activity_time);
! 1382: break;
! 1383: case MSG_SHELL:
! 1384: if (datalen != 0)
! 1385: fatalx("bad MSG_SHELL size");
! 1386:
! 1387: server_client_dispatch_shell(c);
! 1388: break;
! 1389: }
! 1390: }
! 1391:
! 1392: /* Callback when command is done. */
! 1393: static enum cmd_retval
! 1394: server_client_command_done(struct cmdq_item *item, __unused void *data)
! 1395: {
! 1396: struct client *c = item->client;
! 1397:
! 1398: if (~c->flags & CLIENT_ATTACHED)
! 1399: c->flags |= CLIENT_EXIT;
! 1400: return (CMD_RETURN_NORMAL);
! 1401: }
! 1402:
! 1403: /* Show an error message. */
! 1404: static enum cmd_retval
! 1405: server_client_command_error(struct cmdq_item *item, void *data)
! 1406: {
! 1407: char *error = data;
! 1408:
! 1409: cmdq_error(item, "%s", error);
! 1410: free(error);
! 1411:
! 1412: return (CMD_RETURN_NORMAL);
! 1413: }
! 1414:
! 1415: /* Handle command message. */
! 1416: static void
! 1417: server_client_dispatch_command(struct client *c, struct imsg *imsg)
! 1418: {
! 1419: struct msg_command_data data;
! 1420: char *buf;
! 1421: size_t len;
! 1422: struct cmd_list *cmdlist = NULL;
! 1423: int argc;
! 1424: char **argv, *cause;
! 1425:
! 1426: if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data)
! 1427: fatalx("bad MSG_COMMAND size");
! 1428: memcpy(&data, imsg->data, sizeof data);
! 1429:
! 1430: buf = (char *)imsg->data + sizeof data;
! 1431: len = imsg->hdr.len - IMSG_HEADER_SIZE - sizeof data;
! 1432: if (len > 0 && buf[len - 1] != '\0')
! 1433: fatalx("bad MSG_COMMAND string");
! 1434:
! 1435: argc = data.argc;
! 1436: if (cmd_unpack_argv(buf, len, argc, &argv) != 0) {
! 1437: cause = xstrdup("command too long");
! 1438: goto error;
! 1439: }
! 1440:
! 1441: if (argc == 0) {
! 1442: argc = 1;
! 1443: argv = xcalloc(1, sizeof *argv);
! 1444: *argv = xstrdup("new-session");
! 1445: }
! 1446:
! 1447: if ((cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause)) == NULL) {
! 1448: cmd_free_argv(argc, argv);
! 1449: goto error;
! 1450: }
! 1451: cmd_free_argv(argc, argv);
! 1452:
! 1453: cmdq_append(c, cmdq_get_command(cmdlist, NULL, NULL, 0));
! 1454: cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL));
! 1455: cmd_list_free(cmdlist);
! 1456: return;
! 1457:
! 1458: error:
! 1459: cmdq_append(c, cmdq_get_callback(server_client_command_error, cause));
! 1460:
! 1461: if (cmdlist != NULL)
! 1462: cmd_list_free(cmdlist);
! 1463:
! 1464: c->flags |= CLIENT_EXIT;
! 1465: }
! 1466:
! 1467: /* Handle identify message. */
! 1468: static void
! 1469: server_client_dispatch_identify(struct client *c, struct imsg *imsg)
! 1470: {
! 1471: const char *data, *home;
! 1472: size_t datalen;
! 1473: int flags;
! 1474: char *name;
! 1475:
! 1476: if (c->flags & CLIENT_IDENTIFIED)
! 1477: fatalx("out-of-order identify message");
! 1478:
! 1479: data = imsg->data;
! 1480: datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
! 1481:
! 1482: switch (imsg->hdr.type) {
! 1483: case MSG_IDENTIFY_FLAGS:
! 1484: if (datalen != sizeof flags)
! 1485: fatalx("bad MSG_IDENTIFY_FLAGS size");
! 1486: memcpy(&flags, data, sizeof flags);
! 1487: c->flags |= flags;
! 1488: log_debug("client %p IDENTIFY_FLAGS %#x", c, flags);
! 1489: break;
! 1490: case MSG_IDENTIFY_TERM:
! 1491: if (datalen == 0 || data[datalen - 1] != '\0')
! 1492: fatalx("bad MSG_IDENTIFY_TERM string");
! 1493: c->term = xstrdup(data);
! 1494: log_debug("client %p IDENTIFY_TERM %s", c, data);
! 1495: break;
! 1496: case MSG_IDENTIFY_TTYNAME:
! 1497: if (datalen == 0 || data[datalen - 1] != '\0')
! 1498: fatalx("bad MSG_IDENTIFY_TTYNAME string");
! 1499: c->ttyname = xstrdup(data);
! 1500: log_debug("client %p IDENTIFY_TTYNAME %s", c, data);
! 1501: break;
! 1502: case MSG_IDENTIFY_CWD:
! 1503: if (datalen == 0 || data[datalen - 1] != '\0')
! 1504: fatalx("bad MSG_IDENTIFY_CWD string");
! 1505: if (access(data, X_OK) == 0)
! 1506: c->cwd = xstrdup(data);
! 1507: else if ((home = find_home()) != NULL)
! 1508: c->cwd = xstrdup(home);
! 1509: else
! 1510: c->cwd = xstrdup("/");
! 1511: log_debug("client %p IDENTIFY_CWD %s", c, data);
! 1512: break;
! 1513: case MSG_IDENTIFY_STDIN:
! 1514: if (datalen != 0)
! 1515: fatalx("bad MSG_IDENTIFY_STDIN size");
! 1516: c->fd = imsg->fd;
! 1517: log_debug("client %p IDENTIFY_STDIN %d", c, imsg->fd);
! 1518: break;
! 1519: case MSG_IDENTIFY_ENVIRON:
! 1520: if (datalen == 0 || data[datalen - 1] != '\0')
! 1521: fatalx("bad MSG_IDENTIFY_ENVIRON string");
! 1522: if (strchr(data, '=') != NULL)
! 1523: environ_put(c->environ, data);
! 1524: log_debug("client %p IDENTIFY_ENVIRON %s", c, data);
! 1525: break;
! 1526: case MSG_IDENTIFY_CLIENTPID:
! 1527: if (datalen != sizeof c->pid)
! 1528: fatalx("bad MSG_IDENTIFY_CLIENTPID size");
! 1529: memcpy(&c->pid, data, sizeof c->pid);
! 1530: log_debug("client %p IDENTIFY_CLIENTPID %ld", c, (long)c->pid);
! 1531: break;
! 1532: default:
! 1533: break;
! 1534: }
! 1535:
! 1536: if (imsg->hdr.type != MSG_IDENTIFY_DONE)
! 1537: return;
! 1538: c->flags |= CLIENT_IDENTIFIED;
! 1539:
! 1540: if (*c->ttyname != '\0')
! 1541: name = xstrdup(c->ttyname);
! 1542: else
! 1543: xasprintf(&name, "client-%ld", (long)c->pid);
! 1544: c->name = name;
! 1545: log_debug("client %p name is %s", c, c->name);
! 1546:
! 1547: #ifdef __CYGWIN__
! 1548: c->fd = open(c->ttyname, O_RDWR|O_NOCTTY);
! 1549: #endif
! 1550:
! 1551: if (c->flags & CLIENT_CONTROL) {
! 1552: c->stdin_callback = control_callback;
! 1553:
! 1554: evbuffer_free(c->stderr_data);
! 1555: c->stderr_data = c->stdout_data;
! 1556:
! 1557: if (c->flags & CLIENT_CONTROLCONTROL)
! 1558: evbuffer_add_printf(c->stdout_data, "\033P1000p");
! 1559: proc_send(c->peer, MSG_STDIN, -1, NULL, 0);
! 1560:
! 1561: c->tty.fd = -1;
! 1562:
! 1563: close(c->fd);
! 1564: c->fd = -1;
! 1565:
! 1566: return;
! 1567: }
! 1568:
! 1569: if (c->fd == -1)
! 1570: return;
! 1571: if (tty_init(&c->tty, c, c->fd, c->term) != 0) {
! 1572: close(c->fd);
! 1573: c->fd = -1;
! 1574: return;
! 1575: }
! 1576: if (c->flags & CLIENT_UTF8)
! 1577: c->tty.flags |= TTY_UTF8;
! 1578: if (c->flags & CLIENT_256COLOURS)
! 1579: c->tty.term_flags |= TERM_256COLOURS;
! 1580:
! 1581: tty_resize(&c->tty);
! 1582:
! 1583: if (!(c->flags & CLIENT_CONTROL))
! 1584: c->flags |= CLIENT_TERMINAL;
! 1585: }
! 1586:
! 1587: /* Handle shell message. */
! 1588: static void
! 1589: server_client_dispatch_shell(struct client *c)
! 1590: {
! 1591: const char *shell;
! 1592:
! 1593: shell = options_get_string(global_s_options, "default-shell");
! 1594: if (*shell == '\0' || areshell(shell))
! 1595: shell = _PATH_BSHELL;
! 1596: proc_send_s(c->peer, MSG_SHELL, shell);
! 1597:
! 1598: proc_kill_peer(c->peer);
! 1599: }
! 1600:
! 1601: /* Event callback to push more stdout data if any left. */
! 1602: static void
! 1603: server_client_stdout_cb(__unused int fd, __unused short events, void *arg)
! 1604: {
! 1605: struct client *c = arg;
! 1606:
! 1607: if (~c->flags & CLIENT_DEAD)
! 1608: server_client_push_stdout(c);
! 1609: server_client_unref(c);
! 1610: }
! 1611:
! 1612: /* Push stdout to client if possible. */
! 1613: void
! 1614: server_client_push_stdout(struct client *c)
! 1615: {
! 1616: struct msg_stdout_data data;
! 1617: size_t sent, left;
! 1618:
! 1619: left = EVBUFFER_LENGTH(c->stdout_data);
! 1620: while (left != 0) {
! 1621: sent = left;
! 1622: if (sent > sizeof data.data)
! 1623: sent = sizeof data.data;
! 1624: memcpy(data.data, EVBUFFER_DATA(c->stdout_data), sent);
! 1625: data.size = sent;
! 1626:
! 1627: if (proc_send(c->peer, MSG_STDOUT, -1, &data, sizeof data) != 0)
! 1628: break;
! 1629: evbuffer_drain(c->stdout_data, sent);
! 1630:
! 1631: left = EVBUFFER_LENGTH(c->stdout_data);
! 1632: log_debug("%s: client %p, sent %zu, left %zu", __func__, c,
! 1633: sent, left);
! 1634: }
! 1635: if (left != 0) {
! 1636: c->references++;
! 1637: event_once(-1, EV_TIMEOUT, server_client_stdout_cb, c, NULL);
! 1638: log_debug("%s: client %p, queued", __func__, c);
! 1639: }
! 1640: }
! 1641:
! 1642: /* Event callback to push more stderr data if any left. */
! 1643: static void
! 1644: server_client_stderr_cb(__unused int fd, __unused short events, void *arg)
! 1645: {
! 1646: struct client *c = arg;
! 1647:
! 1648: if (~c->flags & CLIENT_DEAD)
! 1649: server_client_push_stderr(c);
! 1650: server_client_unref(c);
! 1651: }
! 1652:
! 1653: /* Push stderr to client if possible. */
! 1654: void
! 1655: server_client_push_stderr(struct client *c)
! 1656: {
! 1657: struct msg_stderr_data data;
! 1658: size_t sent, left;
! 1659:
! 1660: if (c->stderr_data == c->stdout_data) {
! 1661: server_client_push_stdout(c);
! 1662: return;
! 1663: }
! 1664:
! 1665: left = EVBUFFER_LENGTH(c->stderr_data);
! 1666: while (left != 0) {
! 1667: sent = left;
! 1668: if (sent > sizeof data.data)
! 1669: sent = sizeof data.data;
! 1670: memcpy(data.data, EVBUFFER_DATA(c->stderr_data), sent);
! 1671: data.size = sent;
! 1672:
! 1673: if (proc_send(c->peer, MSG_STDERR, -1, &data, sizeof data) != 0)
! 1674: break;
! 1675: evbuffer_drain(c->stderr_data, sent);
! 1676:
! 1677: left = EVBUFFER_LENGTH(c->stderr_data);
! 1678: log_debug("%s: client %p, sent %zu, left %zu", __func__, c,
! 1679: sent, left);
! 1680: }
! 1681: if (left != 0) {
! 1682: c->references++;
! 1683: event_once(-1, EV_TIMEOUT, server_client_stderr_cb, c, NULL);
! 1684: log_debug("%s: client %p, queued", __func__, c);
! 1685: }
! 1686: }
! 1687:
! 1688: /* Add to client message log. */
! 1689: void
! 1690: server_client_add_message(struct client *c, const char *fmt, ...)
! 1691: {
! 1692: struct message_entry *msg, *msg1;
! 1693: char *s;
! 1694: va_list ap;
! 1695: u_int limit;
! 1696:
! 1697: va_start(ap, fmt);
! 1698: xvasprintf(&s, fmt, ap);
! 1699: va_end(ap);
! 1700:
! 1701: log_debug("message %s (client %p)", s, c);
! 1702:
! 1703: msg = xcalloc(1, sizeof *msg);
! 1704: msg->msg_time = time(NULL);
! 1705: msg->msg_num = c->message_next++;
! 1706: msg->msg = s;
! 1707: TAILQ_INSERT_TAIL(&c->message_log, msg, entry);
! 1708:
! 1709: limit = options_get_number(global_options, "message-limit");
! 1710: TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) {
! 1711: if (msg->msg_num + limit >= c->message_next)
! 1712: break;
! 1713: free(msg->msg);
! 1714: TAILQ_REMOVE(&c->message_log, msg, entry);
! 1715: free(msg);
! 1716: }
! 1717: }
! 1718:
! 1719: /* Get client working directory. */
! 1720: const char *
! 1721: server_client_get_cwd(struct client *c)
! 1722: {
! 1723: struct session *s;
! 1724:
! 1725: if (c != NULL && c->session == NULL && c->cwd != NULL)
! 1726: return (c->cwd);
! 1727: if (c != NULL && (s = c->session) != NULL && s->cwd != NULL)
! 1728: return (s->cwd);
! 1729: return (".");
! 1730: }
! 1731:
! 1732: /* Resolve an absolute path or relative to client working directory. */
! 1733: char *
! 1734: server_client_get_path(struct client *c, const char *file)
! 1735: {
! 1736: char *path, resolved[PATH_MAX];
! 1737:
! 1738: if (*file == '/')
! 1739: path = xstrdup(file);
! 1740: else
! 1741: xasprintf(&path, "%s/%s", server_client_get_cwd(c), file);
! 1742: if (realpath(path, resolved) == NULL)
! 1743: return (path);
! 1744: free(path);
! 1745: return (xstrdup(resolved));
! 1746: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>