|  | /* $OpenBSD$ */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> | 
|  | * | 
|  | * Permission to use, copy, modify, and distribute this software for any | 
|  | * purpose with or without fee is hereby granted, provided that the above | 
|  | * copyright notice and this permission notice appear in all copies. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
|  | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | 
|  | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | 
|  | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include <sys/types.h> | 
|  | #include <sys/ioctl.h> | 
|  | #include <sys/uio.h> | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <event.h> | 
|  | #include <fcntl.h> | 
|  | #include <imsg.h> | 
|  | #include <paths.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <time.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include "tmux.h" | 
|  |  | 
|  | static void	server_client_free(int, short, void *); | 
|  | static void	server_client_check_pane_resize(struct window_pane *); | 
|  | static void	server_client_check_pane_buffer(struct window_pane *); | 
|  | static void	server_client_check_window_resize(struct window *); | 
|  | static key_code	server_client_check_mouse(struct client *, struct key_event *); | 
|  | static void	server_client_repeat_timer(int, short, void *); | 
|  | static void	server_client_click_timer(int, short, void *); | 
|  | static void	server_client_check_exit(struct client *); | 
|  | static void	server_client_check_redraw(struct client *); | 
|  | static void	server_client_check_modes(struct client *); | 
|  | static void	server_client_set_title(struct client *); | 
|  | static void	server_client_set_path(struct client *); | 
|  | static void	server_client_reset_state(struct client *); | 
|  | static int	server_client_assume_paste(struct session *); | 
|  | static void	server_client_update_latest(struct client *); | 
|  |  | 
|  | static void	server_client_dispatch(struct imsg *, void *); | 
|  | static void	server_client_dispatch_command(struct client *, struct imsg *); | 
|  | static void	server_client_dispatch_identify(struct client *, struct imsg *); | 
|  | static void	server_client_dispatch_shell(struct client *); | 
|  |  | 
|  | /* Compare client windows. */ | 
|  | static int | 
|  | server_client_window_cmp(struct client_window *cw1, | 
|  | struct client_window *cw2) | 
|  | { | 
|  | if (cw1->window < cw2->window) | 
|  | return (-1); | 
|  | if (cw1->window > cw2->window) | 
|  | return (1); | 
|  | return (0); | 
|  | } | 
|  | RB_GENERATE(client_windows, client_window, entry, server_client_window_cmp); | 
|  |  | 
|  | /* Number of attached clients. */ | 
|  | u_int | 
|  | server_client_how_many(void) | 
|  | { | 
|  | struct client  	*c; | 
|  | u_int		 n; | 
|  |  | 
|  | n = 0; | 
|  | TAILQ_FOREACH(c, &clients, entry) { | 
|  | if (c->session != NULL && (~c->flags & CLIENT_UNATTACHEDFLAGS)) | 
|  | n++; | 
|  | } | 
|  | return (n); | 
|  | } | 
|  |  | 
|  | /* Overlay timer callback. */ | 
|  | static void | 
|  | server_client_overlay_timer(__unused int fd, __unused short events, void *data) | 
|  | { | 
|  | server_client_clear_overlay(data); | 
|  | } | 
|  |  | 
|  | /* Set an overlay on client. */ | 
|  | void | 
|  | server_client_set_overlay(struct client *c, u_int delay, | 
|  | overlay_check_cb checkcb, overlay_mode_cb modecb, | 
|  | overlay_draw_cb drawcb, overlay_key_cb keycb, overlay_free_cb freecb, | 
|  | overlay_resize_cb resizecb, void *data) | 
|  | { | 
|  | struct timeval	tv; | 
|  |  | 
|  | if (c->overlay_draw != NULL) | 
|  | server_client_clear_overlay(c); | 
|  |  | 
|  | tv.tv_sec = delay / 1000; | 
|  | tv.tv_usec = (delay % 1000) * 1000L; | 
|  |  | 
|  | if (event_initialized(&c->overlay_timer)) | 
|  | evtimer_del(&c->overlay_timer); | 
|  | evtimer_set(&c->overlay_timer, server_client_overlay_timer, c); | 
|  | if (delay != 0) | 
|  | evtimer_add(&c->overlay_timer, &tv); | 
|  |  | 
|  | c->overlay_check = checkcb; | 
|  | c->overlay_mode = modecb; | 
|  | c->overlay_draw = drawcb; | 
|  | c->overlay_key = keycb; | 
|  | c->overlay_free = freecb; | 
|  | c->overlay_resize = resizecb; | 
|  | c->overlay_data = data; | 
|  |  | 
|  | if (c->overlay_check == NULL) | 
|  | c->tty.flags |= TTY_FREEZE; | 
|  | if (c->overlay_mode == NULL) | 
|  | c->tty.flags |= TTY_NOCURSOR; | 
|  | server_redraw_client(c); | 
|  | } | 
|  |  | 
|  | /* Clear overlay mode on client. */ | 
|  | void | 
|  | server_client_clear_overlay(struct client *c) | 
|  | { | 
|  | if (c->overlay_draw == NULL) | 
|  | return; | 
|  |  | 
|  | if (event_initialized(&c->overlay_timer)) | 
|  | evtimer_del(&c->overlay_timer); | 
|  |  | 
|  | if (c->overlay_free != NULL) | 
|  | c->overlay_free(c, c->overlay_data); | 
|  |  | 
|  | c->overlay_check = NULL; | 
|  | c->overlay_mode = NULL; | 
|  | c->overlay_draw = NULL; | 
|  | c->overlay_key = NULL; | 
|  | c->overlay_free = NULL; | 
|  | c->overlay_data = NULL; | 
|  |  | 
|  | c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); | 
|  | server_redraw_client(c); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Given overlay position and dimensions, return parts of the input range which | 
|  | * are visible. | 
|  | */ | 
|  | void | 
|  | server_client_overlay_range(u_int x, u_int y, u_int sx, u_int sy, u_int px, | 
|  | u_int py, u_int nx, struct overlay_ranges *r) | 
|  | { | 
|  | u_int	ox, onx; | 
|  |  | 
|  | /* Return up to 2 ranges. */ | 
|  | r->px[2] = 0; | 
|  | r->nx[2] = 0; | 
|  |  | 
|  | /* Trivial case of no overlap in the y direction. */ | 
|  | if (py < y || py > y + sy - 1) { | 
|  | r->px[0] = px; | 
|  | r->nx[0] = nx; | 
|  | r->px[1] = 0; | 
|  | r->nx[1] = 0; | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Visible bit to the left of the popup. */ | 
|  | if (px < x) { | 
|  | r->px[0] = px; | 
|  | r->nx[0] = x - px; | 
|  | if (r->nx[0] > nx) | 
|  | r->nx[0] = nx; | 
|  | } else { | 
|  | r->px[0] = 0; | 
|  | r->nx[0] = 0; | 
|  | } | 
|  |  | 
|  | /* Visible bit to the right of the popup. */ | 
|  | ox = x + sx; | 
|  | if (px > ox) | 
|  | ox = px; | 
|  | onx = px + nx; | 
|  | if (onx > ox) { | 
|  | r->px[1] = ox; | 
|  | r->nx[1] = onx - ox; | 
|  | } else { | 
|  | r->px[1] = 0; | 
|  | r->nx[1] = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check if this client is inside this server. */ | 
|  | int | 
|  | server_client_check_nested(struct client *c) | 
|  | { | 
|  | struct environ_entry	*envent; | 
|  | struct window_pane	*wp; | 
|  |  | 
|  | envent = environ_find(c->environ, "TMUX"); | 
|  | if (envent == NULL || *envent->value == '\0') | 
|  | return (0); | 
|  |  | 
|  | RB_FOREACH(wp, window_pane_tree, &all_window_panes) { | 
|  | if (strcmp(wp->tty, c->ttyname) == 0) | 
|  | return (1); | 
|  | } | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Set client key table. */ | 
|  | void | 
|  | server_client_set_key_table(struct client *c, const char *name) | 
|  | { | 
|  | if (name == NULL) | 
|  | name = server_client_get_key_table(c); | 
|  |  | 
|  | key_bindings_unref_table(c->keytable); | 
|  | c->keytable = key_bindings_get_table(name, 1); | 
|  | c->keytable->references++; | 
|  | } | 
|  |  | 
|  | /* Get default key table. */ | 
|  | const char * | 
|  | server_client_get_key_table(struct client *c) | 
|  | { | 
|  | struct session	*s = c->session; | 
|  | const char	*name; | 
|  |  | 
|  | if (s == NULL) | 
|  | return ("root"); | 
|  |  | 
|  | name = options_get_string(s->options, "key-table"); | 
|  | if (*name == '\0') | 
|  | return ("root"); | 
|  | return (name); | 
|  | } | 
|  |  | 
|  | /* Is this table the default key table? */ | 
|  | static int | 
|  | server_client_is_default_key_table(struct client *c, struct key_table *table) | 
|  | { | 
|  | return (strcmp(table->name, server_client_get_key_table(c)) == 0); | 
|  | } | 
|  |  | 
|  | /* Create a new client. */ | 
|  | struct client * | 
|  | server_client_create(int fd) | 
|  | { | 
|  | struct client	*c; | 
|  |  | 
|  | setblocking(fd, 0); | 
|  |  | 
|  | c = xcalloc(1, sizeof *c); | 
|  | c->references = 1; | 
|  | c->peer = proc_add_peer(server_proc, fd, server_client_dispatch, c); | 
|  |  | 
|  | if (gettimeofday(&c->creation_time, NULL) != 0) | 
|  | fatal("gettimeofday failed"); | 
|  | memcpy(&c->activity_time, &c->creation_time, sizeof c->activity_time); | 
|  |  | 
|  | c->environ = environ_create(); | 
|  |  | 
|  | c->fd = -1; | 
|  | c->out_fd = -1; | 
|  |  | 
|  | c->queue = cmdq_new(); | 
|  | RB_INIT(&c->windows); | 
|  | RB_INIT(&c->files); | 
|  |  | 
|  | c->tty.sx = 80; | 
|  | c->tty.sy = 24; | 
|  |  | 
|  | status_init(c); | 
|  | c->flags |= CLIENT_FOCUSED; | 
|  |  | 
|  | c->keytable = key_bindings_get_table("root", 1); | 
|  | c->keytable->references++; | 
|  |  | 
|  | evtimer_set(&c->repeat_timer, server_client_repeat_timer, c); | 
|  | evtimer_set(&c->click_timer, server_client_click_timer, c); | 
|  |  | 
|  | TAILQ_INSERT_TAIL(&clients, c, entry); | 
|  | log_debug("new client %p", c); | 
|  | return (c); | 
|  | } | 
|  |  | 
|  | /* Open client terminal if needed. */ | 
|  | int | 
|  | server_client_open(struct client *c, char **cause) | 
|  | { | 
|  | const char	*ttynam = _PATH_TTY; | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | return (0); | 
|  |  | 
|  | if (strcmp(c->ttyname, ttynam) == 0|| | 
|  | ((isatty(STDIN_FILENO) && | 
|  | (ttynam = ttyname(STDIN_FILENO)) != NULL && | 
|  | strcmp(c->ttyname, ttynam) == 0) || | 
|  | (isatty(STDOUT_FILENO) && | 
|  | (ttynam = ttyname(STDOUT_FILENO)) != NULL && | 
|  | strcmp(c->ttyname, ttynam) == 0) || | 
|  | (isatty(STDERR_FILENO) && | 
|  | (ttynam = ttyname(STDERR_FILENO)) != NULL && | 
|  | strcmp(c->ttyname, ttynam) == 0))) { | 
|  | xasprintf(cause, "can't use %s", c->ttyname); | 
|  | return (-1); | 
|  | } | 
|  |  | 
|  | if (!(c->flags & CLIENT_TERMINAL)) { | 
|  | *cause = xstrdup("not a terminal"); | 
|  | return (-1); | 
|  | } | 
|  |  | 
|  | if (tty_open(&c->tty, cause) != 0) | 
|  | return (-1); | 
|  |  | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Lost an attached client. */ | 
|  | static void | 
|  | server_client_attached_lost(struct client *c) | 
|  | { | 
|  | struct session	*s; | 
|  | struct window	*w; | 
|  | struct client	*loop; | 
|  | struct client	*found; | 
|  |  | 
|  | log_debug("lost attached client %p", c); | 
|  |  | 
|  | /* | 
|  | * By this point the session in the client has been cleared so walk all | 
|  | * windows to find any with this client as the latest. | 
|  | */ | 
|  | RB_FOREACH(w, windows, &windows) { | 
|  | if (w->latest != c) | 
|  | continue; | 
|  |  | 
|  | found = NULL; | 
|  | TAILQ_FOREACH(loop, &clients, entry) { | 
|  | s = loop->session; | 
|  | if (loop == c || s == NULL || s->curw->window != w) | 
|  | continue; | 
|  | if (found == NULL || timercmp(&loop->activity_time, | 
|  | &found->activity_time, >)) | 
|  | found = loop; | 
|  | } | 
|  | if (found != NULL) | 
|  | server_client_update_latest(found); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Set client session. */ | 
|  | void | 
|  | server_client_set_session(struct client *c, struct session *s) | 
|  | { | 
|  | struct session	*old = c->session; | 
|  |  | 
|  | if (s != NULL && c->session != NULL && c->session != s) | 
|  | c->last_session = c->session; | 
|  | else if (s == NULL) | 
|  | c->last_session = NULL; | 
|  | c->session = s; | 
|  | c->flags |= CLIENT_FOCUSED; | 
|  |  | 
|  | if (old != NULL && old->curw != NULL) | 
|  | window_update_focus(old->curw->window); | 
|  | if (s != NULL) { | 
|  | recalculate_sizes(); | 
|  | window_update_focus(s->curw->window); | 
|  | session_update_activity(s, NULL); | 
|  | gettimeofday(&s->last_attached_time, NULL); | 
|  | s->curw->flags &= ~WINLINK_ALERTFLAGS; | 
|  | s->curw->window->latest = c; | 
|  | alerts_check_session(s); | 
|  | tty_update_client_offset(c); | 
|  | status_timer_start(c); | 
|  | notify_client("client-session-changed", c); | 
|  | server_redraw_client(c); | 
|  | } | 
|  |  | 
|  | server_check_unattached(); | 
|  | server_update_socket(); | 
|  | } | 
|  |  | 
|  | /* Lost a client. */ | 
|  | void | 
|  | server_client_lost(struct client *c) | 
|  | { | 
|  | struct client_file	*cf, *cf1; | 
|  | struct client_window	*cw, *cw1; | 
|  |  | 
|  | c->flags |= CLIENT_DEAD; | 
|  |  | 
|  | server_client_clear_overlay(c); | 
|  | status_prompt_clear(c); | 
|  | status_message_clear(c); | 
|  |  | 
|  | RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) { | 
|  | cf->error = EINTR; | 
|  | file_fire_done(cf); | 
|  | } | 
|  | RB_FOREACH_SAFE(cw, client_windows, &c->windows, cw1) { | 
|  | RB_REMOVE(client_windows, &c->windows, cw); | 
|  | free(cw); | 
|  | } | 
|  |  | 
|  | TAILQ_REMOVE(&clients, c, entry); | 
|  | log_debug("lost client %p", c); | 
|  |  | 
|  | if (c->flags & CLIENT_ATTACHED) { | 
|  | server_client_attached_lost(c); | 
|  | notify_client("client-detached", c); | 
|  | } | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | control_stop(c); | 
|  | if (c->flags & CLIENT_TERMINAL) | 
|  | tty_free(&c->tty); | 
|  | free(c->ttyname); | 
|  | free(c->clipboard_panes); | 
|  |  | 
|  | free(c->term_name); | 
|  | free(c->term_type); | 
|  | tty_term_free_list(c->term_caps, c->term_ncaps); | 
|  |  | 
|  | status_free(c); | 
|  |  | 
|  | free(c->title); | 
|  | free((void *)c->cwd); | 
|  |  | 
|  | evtimer_del(&c->repeat_timer); | 
|  | evtimer_del(&c->click_timer); | 
|  |  | 
|  | key_bindings_unref_table(c->keytable); | 
|  |  | 
|  | free(c->message_string); | 
|  | if (event_initialized(&c->message_timer)) | 
|  | evtimer_del(&c->message_timer); | 
|  |  | 
|  | free(c->prompt_saved); | 
|  | free(c->prompt_string); | 
|  | free(c->prompt_buffer); | 
|  |  | 
|  | format_lost_client(c); | 
|  | environ_free(c->environ); | 
|  |  | 
|  | proc_remove_peer(c->peer); | 
|  | c->peer = NULL; | 
|  |  | 
|  | if (c->out_fd != -1) | 
|  | close(c->out_fd); | 
|  | if (c->fd != -1) { | 
|  | close(c->fd); | 
|  | c->fd = -1; | 
|  | } | 
|  | server_client_unref(c); | 
|  |  | 
|  | server_add_accept(0); /* may be more file descriptors now */ | 
|  |  | 
|  | recalculate_sizes(); | 
|  | server_check_unattached(); | 
|  | server_update_socket(); | 
|  | } | 
|  |  | 
|  | /* Remove reference from a client. */ | 
|  | void | 
|  | server_client_unref(struct client *c) | 
|  | { | 
|  | log_debug("unref client %p (%d references)", c, c->references); | 
|  |  | 
|  | c->references--; | 
|  | if (c->references == 0) | 
|  | event_once(-1, EV_TIMEOUT, server_client_free, c, NULL); | 
|  | } | 
|  |  | 
|  | /* Free dead client. */ | 
|  | static void | 
|  | server_client_free(__unused int fd, __unused short events, void *arg) | 
|  | { | 
|  | struct client	*c = arg; | 
|  |  | 
|  | log_debug("free client %p (%d references)", c, c->references); | 
|  |  | 
|  | cmdq_free(c->queue); | 
|  |  | 
|  | if (c->references == 0) { | 
|  | free((void *)c->name); | 
|  | free(c); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Suspend a client. */ | 
|  | void | 
|  | server_client_suspend(struct client *c) | 
|  | { | 
|  | struct session	*s = c->session; | 
|  |  | 
|  | if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) | 
|  | return; | 
|  |  | 
|  | tty_stop_tty(&c->tty); | 
|  | c->flags |= CLIENT_SUSPENDED; | 
|  | proc_send(c->peer, MSG_SUSPEND, -1, NULL, 0); | 
|  | } | 
|  |  | 
|  | /* Detach a client. */ | 
|  | void | 
|  | server_client_detach(struct client *c, enum msgtype msgtype) | 
|  | { | 
|  | struct session	*s = c->session; | 
|  |  | 
|  | if (s == NULL || (c->flags & CLIENT_NODETACHFLAGS)) | 
|  | return; | 
|  |  | 
|  | c->flags |= CLIENT_EXIT; | 
|  |  | 
|  | c->exit_type = CLIENT_EXIT_DETACH; | 
|  | c->exit_msgtype = msgtype; | 
|  | c->exit_session = xstrdup(s->name); | 
|  | } | 
|  |  | 
|  | /* Execute command to replace a client. */ | 
|  | void | 
|  | server_client_exec(struct client *c, const char *cmd) | 
|  | { | 
|  | struct session	*s = c->session; | 
|  | char		*msg; | 
|  | const char	*shell; | 
|  | size_t		 cmdsize, shellsize; | 
|  |  | 
|  | if (*cmd == '\0') | 
|  | return; | 
|  | cmdsize = strlen(cmd) + 1; | 
|  |  | 
|  | if (s != NULL) | 
|  | shell = options_get_string(s->options, "default-shell"); | 
|  | else | 
|  | shell = options_get_string(global_s_options, "default-shell"); | 
|  | if (!checkshell(shell)) | 
|  | shell = _PATH_BSHELL; | 
|  | shellsize = strlen(shell) + 1; | 
|  |  | 
|  | msg = xmalloc(cmdsize + shellsize); | 
|  | memcpy(msg, cmd, cmdsize); | 
|  | memcpy(msg + cmdsize, shell, shellsize); | 
|  |  | 
|  | proc_send(c->peer, MSG_EXEC, -1, msg, cmdsize + shellsize); | 
|  | free(msg); | 
|  | } | 
|  |  | 
|  | /* Check for mouse keys. */ | 
|  | static key_code | 
|  | server_client_check_mouse(struct client *c, struct key_event *event) | 
|  | { | 
|  | struct mouse_event	*m = &event->m; | 
|  | struct session		*s = c->session; | 
|  | struct winlink		*wl; | 
|  | struct window_pane	*wp; | 
|  | u_int			 x, y, b, sx, sy, px, py; | 
|  | int			 ignore = 0; | 
|  | key_code		 key; | 
|  | struct timeval		 tv; | 
|  | struct style_range	*sr; | 
|  | enum { NOTYPE, | 
|  | MOVE, | 
|  | DOWN, | 
|  | UP, | 
|  | DRAG, | 
|  | WHEEL, | 
|  | SECOND, | 
|  | DOUBLE, | 
|  | TRIPLE } type = NOTYPE; | 
|  | enum { NOWHERE, | 
|  | PANE, | 
|  | STATUS, | 
|  | STATUS_LEFT, | 
|  | STATUS_RIGHT, | 
|  | STATUS_DEFAULT, | 
|  | BORDER } where = NOWHERE; | 
|  |  | 
|  | log_debug("%s mouse %02x at %u,%u (last %u,%u) (%d)", c->name, m->b, | 
|  | m->x, m->y, m->lx, m->ly, c->tty.mouse_drag_flag); | 
|  |  | 
|  | /* What type of event is this? */ | 
|  | if (event->key == KEYC_DOUBLECLICK) { | 
|  | type = DOUBLE; | 
|  | x = m->x, y = m->y, b = m->b; | 
|  | ignore = 1; | 
|  | log_debug("double-click at %u,%u", x, y); | 
|  | } else if ((m->sgr_type != ' ' && | 
|  | MOUSE_DRAG(m->sgr_b) && | 
|  | MOUSE_RELEASE(m->sgr_b)) || | 
|  | (m->sgr_type == ' ' && | 
|  | MOUSE_DRAG(m->b) && | 
|  | MOUSE_RELEASE(m->b) && | 
|  | MOUSE_RELEASE(m->lb))) { | 
|  | type = MOVE; | 
|  | x = m->x, y = m->y, b = 0; | 
|  | log_debug("move at %u,%u", x, y); | 
|  | } else if (MOUSE_DRAG(m->b)) { | 
|  | type = DRAG; | 
|  | if (c->tty.mouse_drag_flag) { | 
|  | x = m->x, y = m->y, b = m->b; | 
|  | if (x == m->lx && y == m->ly) | 
|  | return (KEYC_UNKNOWN); | 
|  | log_debug("drag update at %u,%u", x, y); | 
|  | } else { | 
|  | x = m->lx, y = m->ly, b = m->lb; | 
|  | log_debug("drag start at %u,%u", x, y); | 
|  | } | 
|  | } else if (MOUSE_WHEEL(m->b)) { | 
|  | type = WHEEL; | 
|  | x = m->x, y = m->y, b = m->b; | 
|  | log_debug("wheel at %u,%u", x, y); | 
|  | } else if (MOUSE_RELEASE(m->b)) { | 
|  | type = UP; | 
|  | x = m->x, y = m->y, b = m->lb; | 
|  | log_debug("up at %u,%u", x, y); | 
|  | } else { | 
|  | if (c->flags & CLIENT_DOUBLECLICK) { | 
|  | evtimer_del(&c->click_timer); | 
|  | c->flags &= ~CLIENT_DOUBLECLICK; | 
|  | if (m->b == c->click_button) { | 
|  | type = SECOND; | 
|  | x = m->x, y = m->y, b = m->b; | 
|  | log_debug("second-click at %u,%u", x, y); | 
|  | c->flags |= CLIENT_TRIPLECLICK; | 
|  | } | 
|  | } else if (c->flags & CLIENT_TRIPLECLICK) { | 
|  | evtimer_del(&c->click_timer); | 
|  | c->flags &= ~CLIENT_TRIPLECLICK; | 
|  | if (m->b == c->click_button) { | 
|  | type = TRIPLE; | 
|  | x = m->x, y = m->y, b = m->b; | 
|  | log_debug("triple-click at %u,%u", x, y); | 
|  | goto have_event; | 
|  | } | 
|  | } else { | 
|  | type = DOWN; | 
|  | x = m->x, y = m->y, b = m->b; | 
|  | log_debug("down at %u,%u", x, y); | 
|  | c->flags |= CLIENT_DOUBLECLICK; | 
|  | } | 
|  |  | 
|  | if (KEYC_CLICK_TIMEOUT != 0) { | 
|  | memcpy(&c->click_event, m, sizeof c->click_event); | 
|  | c->click_button = m->b; | 
|  |  | 
|  | log_debug("click timer started"); | 
|  | tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000; | 
|  | tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L; | 
|  | evtimer_del(&c->click_timer); | 
|  | evtimer_add(&c->click_timer, &tv); | 
|  | } | 
|  | } | 
|  |  | 
|  | have_event: | 
|  | if (type == NOTYPE) | 
|  | return (KEYC_UNKNOWN); | 
|  |  | 
|  | /* Save the session. */ | 
|  | m->s = s->id; | 
|  | m->w = -1; | 
|  | m->ignore = ignore; | 
|  |  | 
|  | /* Is this on the status line? */ | 
|  | m->statusat = status_at_line(c); | 
|  | m->statuslines = status_line_size(c); | 
|  | if (m->statusat != -1 && | 
|  | y >= (u_int)m->statusat && | 
|  | y < m->statusat + m->statuslines) { | 
|  | sr = status_get_range(c, x, y - m->statusat); | 
|  | if (sr == NULL) { | 
|  | where = STATUS_DEFAULT; | 
|  | } else { | 
|  | switch (sr->type) { | 
|  | case STYLE_RANGE_NONE: | 
|  | return (KEYC_UNKNOWN); | 
|  | case STYLE_RANGE_LEFT: | 
|  | where = STATUS_LEFT; | 
|  | break; | 
|  | case STYLE_RANGE_RIGHT: | 
|  | where = STATUS_RIGHT; | 
|  | break; | 
|  | case STYLE_RANGE_WINDOW: | 
|  | wl = winlink_find_by_index(&s->windows, | 
|  | sr->argument); | 
|  | if (wl == NULL) | 
|  | return (KEYC_UNKNOWN); | 
|  | m->w = wl->window->id; | 
|  |  | 
|  | where = STATUS; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Not on status line. Adjust position and check for border or pane. */ | 
|  | if (where == NOWHERE) { | 
|  | px = x; | 
|  | if (m->statusat == 0 && y >= m->statuslines) | 
|  | py = y - m->statuslines; | 
|  | else if (m->statusat > 0 && y >= (u_int)m->statusat) | 
|  | py = m->statusat - 1; | 
|  | else | 
|  | py = y; | 
|  |  | 
|  | tty_window_offset(&c->tty, &m->ox, &m->oy, &sx, &sy); | 
|  | log_debug("mouse window @%u at %u,%u (%ux%u)", | 
|  | s->curw->window->id, m->ox, m->oy, sx, sy); | 
|  | if (px > sx || py > sy) | 
|  | return (KEYC_UNKNOWN); | 
|  | px = px + m->ox; | 
|  | py = py + m->oy; | 
|  |  | 
|  | /* Try the pane borders if not zoomed. */ | 
|  | if (~s->curw->window->flags & WINDOW_ZOOMED) { | 
|  | TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { | 
|  | if ((wp->xoff + wp->sx == px && | 
|  | wp->yoff <= 1 + py && | 
|  | wp->yoff + wp->sy >= py) || | 
|  | (wp->yoff + wp->sy == py && | 
|  | wp->xoff <= 1 + px && | 
|  | wp->xoff + wp->sx >= px)) | 
|  | break; | 
|  | } | 
|  | if (wp != NULL) | 
|  | where = BORDER; | 
|  | } | 
|  |  | 
|  | /* Otherwise try inside the pane. */ | 
|  | if (where == NOWHERE) { | 
|  | wp = window_get_active_at(s->curw->window, px, py); | 
|  | if (wp != NULL) | 
|  | where = PANE; | 
|  | else | 
|  | return (KEYC_UNKNOWN); | 
|  | } | 
|  | if (where == PANE) | 
|  | log_debug("mouse %u,%u on pane %%%u", x, y, wp->id); | 
|  | else if (where == BORDER) | 
|  | log_debug("mouse on pane %%%u border", wp->id); | 
|  | m->wp = wp->id; | 
|  | m->w = wp->window->id; | 
|  | } else | 
|  | m->wp = -1; | 
|  |  | 
|  | /* Stop dragging if needed. */ | 
|  | if (type != DRAG && type != WHEEL && c->tty.mouse_drag_flag != 0) { | 
|  | if (c->tty.mouse_drag_release != NULL) | 
|  | c->tty.mouse_drag_release(c, m); | 
|  |  | 
|  | c->tty.mouse_drag_update = NULL; | 
|  | c->tty.mouse_drag_release = NULL; | 
|  |  | 
|  | /* | 
|  | * End a mouse drag by passing a MouseDragEnd key corresponding | 
|  | * to the button that started the drag. | 
|  | */ | 
|  | switch (c->tty.mouse_drag_flag - 1) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND10_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND10_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND10_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND10_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND10_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND10_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAGEND11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAGEND11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAGEND11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAGEND11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAGEND11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAGEND11_BORDER; | 
|  | break; | 
|  | default: | 
|  | key = KEYC_MOUSE; | 
|  | break; | 
|  | } | 
|  | c->tty.mouse_drag_flag = 0; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Convert to a key binding. */ | 
|  | key = KEYC_UNKNOWN; | 
|  | switch (type) { | 
|  | case NOTYPE: | 
|  | break; | 
|  | case MOVE: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEMOVE_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEMOVE_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEMOVE_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEMOVE_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEMOVE_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEMOVE_BORDER; | 
|  | break; | 
|  | case DRAG: | 
|  | if (c->tty.mouse_drag_update != NULL) | 
|  | key = KEYC_DRAGGING; | 
|  | else { | 
|  | switch (MOUSE_BUTTONS(b)) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG10_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG10_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG10_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG10_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG10_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG10_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDRAG11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDRAG11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDRAG11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDRAG11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDRAG11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDRAG11_BORDER; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Begin a drag by setting the flag to a non-zero value that | 
|  | * corresponds to the mouse button in use. | 
|  | */ | 
|  | c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; | 
|  | break; | 
|  | case WHEEL: | 
|  | if (MOUSE_BUTTONS(b) == MOUSE_WHEEL_UP) { | 
|  | if (where == PANE) | 
|  | key = KEYC_WHEELUP_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_WHEELUP_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_WHEELUP_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_WHEELUP_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_WHEELUP_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_WHEELUP_BORDER; | 
|  | } else { | 
|  | if (where == PANE) | 
|  | key = KEYC_WHEELDOWN_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_WHEELDOWN_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_WHEELDOWN_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_WHEELDOWN_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_WHEELDOWN_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_WHEELDOWN_BORDER; | 
|  | } | 
|  | break; | 
|  | case UP: | 
|  | switch (MOUSE_BUTTONS(b)) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEUP11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEUP11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEUP11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEUP11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEUP11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEUP11_BORDER; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case DOWN: | 
|  | switch (MOUSE_BUTTONS(b)) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN10_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN10_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN10_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN10_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN10_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN10_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_MOUSEDOWN11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_MOUSEDOWN11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_MOUSEDOWN11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_MOUSEDOWN11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_MOUSEDOWN11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_MOUSEDOWN11_BORDER; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case SECOND: | 
|  | switch (MOUSE_BUTTONS(b)) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK10_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK10_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK10_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK10_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK10_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK10_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_SECONDCLICK11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_SECONDCLICK11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_SECONDCLICK11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_SECONDCLICK11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_SECONDCLICK11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_SECONDCLICK11_BORDER; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case DOUBLE: | 
|  | switch (MOUSE_BUTTONS(b)) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK10_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK10_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK10_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK10_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK10_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK10_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_DOUBLECLICK11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_DOUBLECLICK11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_DOUBLECLICK11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_DOUBLECLICK11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_DOUBLECLICK11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_DOUBLECLICK11_BORDER; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case TRIPLE: | 
|  | switch (MOUSE_BUTTONS(b)) { | 
|  | case MOUSE_BUTTON_1: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK1_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK1_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK1_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK1_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK1_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK1_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_2: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK2_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK2_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK2_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK2_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK2_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK2_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_3: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK3_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK3_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK3_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK3_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK3_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK3_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_6: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK6_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK6_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK6_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK6_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK6_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK6_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_7: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK7_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK7_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK7_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK7_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK7_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK7_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_8: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK8_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK8_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK8_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK8_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK8_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK8_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_9: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK9_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK9_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK9_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK9_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK9_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK9_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_10: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK10_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK10_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK10_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK10_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK10_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK10_BORDER; | 
|  | break; | 
|  | case MOUSE_BUTTON_11: | 
|  | if (where == PANE) | 
|  | key = KEYC_TRIPLECLICK11_PANE; | 
|  | if (where == STATUS) | 
|  | key = KEYC_TRIPLECLICK11_STATUS; | 
|  | if (where == STATUS_LEFT) | 
|  | key = KEYC_TRIPLECLICK11_STATUS_LEFT; | 
|  | if (where == STATUS_RIGHT) | 
|  | key = KEYC_TRIPLECLICK11_STATUS_RIGHT; | 
|  | if (where == STATUS_DEFAULT) | 
|  | key = KEYC_TRIPLECLICK11_STATUS_DEFAULT; | 
|  | if (where == BORDER) | 
|  | key = KEYC_TRIPLECLICK11_BORDER; | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  | if (key == KEYC_UNKNOWN) | 
|  | return (KEYC_UNKNOWN); | 
|  |  | 
|  | out: | 
|  | /* Apply modifiers if any. */ | 
|  | if (b & MOUSE_MASK_META) | 
|  | key |= KEYC_META; | 
|  | if (b & MOUSE_MASK_CTRL) | 
|  | key |= KEYC_CTRL; | 
|  | if (b & MOUSE_MASK_SHIFT) | 
|  | key |= KEYC_SHIFT; | 
|  |  | 
|  | if (log_get_level() != 0) | 
|  | log_debug("mouse key is %s", key_string_lookup_key (key, 1)); | 
|  | return (key); | 
|  | } | 
|  |  | 
|  | /* Is this fast enough to probably be a paste? */ | 
|  | static int | 
|  | server_client_assume_paste(struct session *s) | 
|  | { | 
|  | struct timeval	tv; | 
|  | int		t; | 
|  |  | 
|  | if ((t = options_get_number(s->options, "assume-paste-time")) == 0) | 
|  | return (0); | 
|  |  | 
|  | timersub(&s->activity_time, &s->last_activity_time, &tv); | 
|  | if (tv.tv_sec == 0 && tv.tv_usec < t * 1000) { | 
|  | log_debug("session %s pasting (flag %d)", s->name, | 
|  | !!(s->flags & SESSION_PASTING)); | 
|  | if (s->flags & SESSION_PASTING) | 
|  | return (1); | 
|  | s->flags |= SESSION_PASTING; | 
|  | return (0); | 
|  | } | 
|  | log_debug("session %s not pasting", s->name); | 
|  | s->flags &= ~SESSION_PASTING; | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Has the latest client changed? */ | 
|  | static void | 
|  | server_client_update_latest(struct client *c) | 
|  | { | 
|  | struct window	*w; | 
|  |  | 
|  | if (c->session == NULL) | 
|  | return; | 
|  | w = c->session->curw->window; | 
|  |  | 
|  | if (w->latest == c) | 
|  | return; | 
|  | w->latest = c; | 
|  |  | 
|  | if (options_get_number(w->options, "window-size") == WINDOW_SIZE_LATEST) | 
|  | recalculate_size(w, 0); | 
|  |  | 
|  | notify_client("client-active", c); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Handle data key input from client. This owns and can modify the key event it | 
|  | * is given and is responsible for freeing it. | 
|  | */ | 
|  | static enum cmd_retval | 
|  | server_client_key_callback(struct cmdq_item *item, void *data) | 
|  | { | 
|  | struct client			*c = cmdq_get_client(item); | 
|  | struct key_event		*event = data; | 
|  | key_code			 key = event->key; | 
|  | struct mouse_event		*m = &event->m; | 
|  | struct session			*s = c->session; | 
|  | struct winlink			*wl; | 
|  | struct window_pane		*wp; | 
|  | struct window_mode_entry	*wme; | 
|  | struct timeval			 tv; | 
|  | struct key_table		*table, *first; | 
|  | struct key_binding		*bd; | 
|  | int				 xtimeout, flags; | 
|  | struct cmd_find_state		 fs; | 
|  | key_code			 key0; | 
|  |  | 
|  | /* Check the client is good to accept input. */ | 
|  | if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) | 
|  | goto out; | 
|  | wl = s->curw; | 
|  |  | 
|  | /* Update the activity timer. */ | 
|  | if (gettimeofday(&c->activity_time, NULL) != 0) | 
|  | fatal("gettimeofday failed"); | 
|  | session_update_activity(s, &c->activity_time); | 
|  |  | 
|  | /* Check for mouse keys. */ | 
|  | m->valid = 0; | 
|  | if (key == KEYC_MOUSE || key == KEYC_DOUBLECLICK) { | 
|  | if (c->flags & CLIENT_READONLY) | 
|  | goto out; | 
|  | key = server_client_check_mouse(c, event); | 
|  | if (key == KEYC_UNKNOWN) | 
|  | goto out; | 
|  |  | 
|  | m->valid = 1; | 
|  | m->key = key; | 
|  |  | 
|  | /* | 
|  | * Mouse drag is in progress, so fire the callback (now that | 
|  | * the mouse event is valid). | 
|  | */ | 
|  | if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) { | 
|  | c->tty.mouse_drag_update(c, m); | 
|  | goto out; | 
|  | } | 
|  | event->key = key; | 
|  | } | 
|  |  | 
|  | /* Find affected pane. */ | 
|  | if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) | 
|  | cmd_find_from_client(&fs, c, 0); | 
|  | wp = fs.wp; | 
|  |  | 
|  | /* Forward mouse keys if disabled. */ | 
|  | if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse")) | 
|  | goto forward_key; | 
|  |  | 
|  | /* Treat everything as a regular key when pasting is detected. */ | 
|  | if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) | 
|  | goto forward_key; | 
|  |  | 
|  | /* | 
|  | * Work out the current key table. If the pane is in a mode, use | 
|  | * the mode table instead of the default key table. | 
|  | */ | 
|  | if (server_client_is_default_key_table(c, c->keytable) && | 
|  | wp != NULL && | 
|  | (wme = TAILQ_FIRST(&wp->modes)) != NULL && | 
|  | wme->mode->key_table != NULL) | 
|  | table = key_bindings_get_table(wme->mode->key_table(wme), 1); | 
|  | else | 
|  | table = c->keytable; | 
|  | first = table; | 
|  |  | 
|  | table_changed: | 
|  | /* | 
|  | * The prefix always takes precedence and forces a switch to the prefix | 
|  | * table, unless we are already there. | 
|  | */ | 
|  | key0 = (key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)); | 
|  | if ((key0 == (key_code)options_get_number(s->options, "prefix") || | 
|  | key0 == (key_code)options_get_number(s->options, "prefix2")) && | 
|  | strcmp(table->name, "prefix") != 0) { | 
|  | server_client_set_key_table(c, "prefix"); | 
|  | server_status_client(c); | 
|  | goto out; | 
|  | } | 
|  | flags = c->flags; | 
|  |  | 
|  | try_again: | 
|  | /* Log key table. */ | 
|  | if (wp == NULL) | 
|  | log_debug("key table %s (no pane)", table->name); | 
|  | else | 
|  | log_debug("key table %s (pane %%%u)", table->name, wp->id); | 
|  | if (c->flags & CLIENT_REPEAT) | 
|  | log_debug("currently repeating"); | 
|  |  | 
|  | /* Try to see if there is a key binding in the current table. */ | 
|  | bd = key_bindings_get(table, key0); | 
|  | if (bd != NULL) { | 
|  | /* | 
|  | * Key was matched in this table. If currently repeating but a | 
|  | * non-repeating binding was found, stop repeating and try | 
|  | * again in the root table. | 
|  | */ | 
|  | if ((c->flags & CLIENT_REPEAT) && | 
|  | (~bd->flags & KEY_BINDING_REPEAT)) { | 
|  | log_debug("found in key table %s (not repeating)", | 
|  | table->name); | 
|  | server_client_set_key_table(c, NULL); | 
|  | first = table = c->keytable; | 
|  | c->flags &= ~CLIENT_REPEAT; | 
|  | server_status_client(c); | 
|  | goto table_changed; | 
|  | } | 
|  | log_debug("found in key table %s", table->name); | 
|  |  | 
|  | /* | 
|  | * Take a reference to this table to make sure the key binding | 
|  | * doesn't disappear. | 
|  | */ | 
|  | table->references++; | 
|  |  | 
|  | /* | 
|  | * If this is a repeating key, start the timer. Otherwise reset | 
|  | * the client back to the root table. | 
|  | */ | 
|  | xtimeout = options_get_number(s->options, "repeat-time"); | 
|  | if (xtimeout != 0 && (bd->flags & KEY_BINDING_REPEAT)) { | 
|  | c->flags |= CLIENT_REPEAT; | 
|  |  | 
|  | tv.tv_sec = xtimeout / 1000; | 
|  | tv.tv_usec = (xtimeout % 1000) * 1000L; | 
|  | evtimer_del(&c->repeat_timer); | 
|  | evtimer_add(&c->repeat_timer, &tv); | 
|  | } else { | 
|  | c->flags &= ~CLIENT_REPEAT; | 
|  | server_client_set_key_table(c, NULL); | 
|  | } | 
|  | server_status_client(c); | 
|  |  | 
|  | /* Execute the key binding. */ | 
|  | key_bindings_dispatch(bd, item, c, event, &fs); | 
|  | key_bindings_unref_table(table); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * No match, try the ANY key. | 
|  | */ | 
|  | if (key0 != KEYC_ANY) { | 
|  | key0 = KEYC_ANY; | 
|  | goto try_again; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * No match in this table. If not in the root table or if repeating, | 
|  | * switch the client back to the root table and try again. | 
|  | */ | 
|  | log_debug("not found in key table %s", table->name); | 
|  | if (!server_client_is_default_key_table(c, table) || | 
|  | (c->flags & CLIENT_REPEAT)) { | 
|  | log_debug("trying in root table"); | 
|  | server_client_set_key_table(c, NULL); | 
|  | table = c->keytable; | 
|  | if (c->flags & CLIENT_REPEAT) | 
|  | first = table; | 
|  | c->flags &= ~CLIENT_REPEAT; | 
|  | server_status_client(c); | 
|  | goto table_changed; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * No match in the root table either. If this wasn't the first table | 
|  | * tried, don't pass the key to the pane. | 
|  | */ | 
|  | if (first != table && (~flags & CLIENT_REPEAT)) { | 
|  | server_client_set_key_table(c, NULL); | 
|  | server_status_client(c); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | forward_key: | 
|  | if (c->flags & CLIENT_READONLY) | 
|  | goto out; | 
|  | if (wp != NULL) | 
|  | window_pane_key(wp, c, s, wl, key, m); | 
|  |  | 
|  | out: | 
|  | if (s != NULL && key != KEYC_FOCUS_OUT) | 
|  | server_client_update_latest(c); | 
|  | free(event); | 
|  | return (CMD_RETURN_NORMAL); | 
|  | } | 
|  |  | 
|  | /* Handle a key event. */ | 
|  | int | 
|  | server_client_handle_key(struct client *c, struct key_event *event) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct cmdq_item	*item; | 
|  |  | 
|  | /* Check the client is good to accept input. */ | 
|  | if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) | 
|  | return (0); | 
|  |  | 
|  | /* | 
|  | * Key presses in overlay mode and the command prompt are a special | 
|  | * case. The queue might be blocked so they need to be processed | 
|  | * immediately rather than queued. | 
|  | */ | 
|  | if (~c->flags & CLIENT_READONLY) { | 
|  | if (c->message_string != NULL) { | 
|  | if (c->message_ignore_keys) | 
|  | return (0); | 
|  | status_message_clear(c); | 
|  | } | 
|  | if (c->overlay_key != NULL) { | 
|  | switch (c->overlay_key(c, c->overlay_data, event)) { | 
|  | case 0: | 
|  | return (0); | 
|  | case 1: | 
|  | server_client_clear_overlay(c); | 
|  | return (0); | 
|  | } | 
|  | } | 
|  | server_client_clear_overlay(c); | 
|  | if (c->prompt_string != NULL) { | 
|  | if (status_prompt_key(c, event->key) == 0) | 
|  | return (0); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Add the key to the queue so it happens after any commands queued by | 
|  | * previous keys. | 
|  | */ | 
|  | item = cmdq_get_callback(server_client_key_callback, event); | 
|  | cmdq_append(c, item); | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | /* Client functions that need to happen every loop. */ | 
|  | void | 
|  | server_client_loop(void) | 
|  | { | 
|  | struct client		*c; | 
|  | struct window		*w; | 
|  | struct window_pane	*wp; | 
|  |  | 
|  | /* Check for window resize. This is done before redrawing. */ | 
|  | RB_FOREACH(w, windows, &windows) | 
|  | server_client_check_window_resize(w); | 
|  |  | 
|  | /* Check clients. */ | 
|  | TAILQ_FOREACH(c, &clients, entry) { | 
|  | server_client_check_exit(c); | 
|  | if (c->session != NULL) { | 
|  | server_client_check_modes(c); | 
|  | server_client_check_redraw(c); | 
|  | server_client_reset_state(c); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Any windows will have been redrawn as part of clients, so clear | 
|  | * their flags now. | 
|  | */ | 
|  | RB_FOREACH(w, windows, &windows) { | 
|  | TAILQ_FOREACH(wp, &w->panes, entry) { | 
|  | if (wp->fd != -1) { | 
|  | server_client_check_pane_resize(wp); | 
|  | server_client_check_pane_buffer(wp); | 
|  | } | 
|  | wp->flags &= ~PANE_REDRAW; | 
|  | } | 
|  | check_window_name(w); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check if window needs to be resized. */ | 
|  | static void | 
|  | server_client_check_window_resize(struct window *w) | 
|  | { | 
|  | struct winlink	*wl; | 
|  |  | 
|  | if (~w->flags & WINDOW_RESIZE) | 
|  | return; | 
|  |  | 
|  | TAILQ_FOREACH(wl, &w->winlinks, wentry) { | 
|  | if (wl->session->attached != 0 && wl->session->curw == wl) | 
|  | break; | 
|  | } | 
|  | if (wl == NULL) | 
|  | return; | 
|  |  | 
|  | log_debug("%s: resizing window @%u", __func__, w->id); | 
|  | resize_window(w, w->new_sx, w->new_sy, w->new_xpixel, w->new_ypixel); | 
|  | } | 
|  |  | 
|  | /* Resize timer event. */ | 
|  | static void | 
|  | server_client_resize_timer(__unused int fd, __unused short events, void *data) | 
|  | { | 
|  | struct window_pane	*wp = data; | 
|  |  | 
|  | log_debug("%s: %%%u resize timer expired", __func__, wp->id); | 
|  | evtimer_del(&wp->resize_timer); | 
|  | } | 
|  |  | 
|  | /* Check if pane should be resized. */ | 
|  | static void | 
|  | server_client_check_pane_resize(struct window_pane *wp) | 
|  | { | 
|  | struct window_pane_resize	*r; | 
|  | struct window_pane_resize	*r1; | 
|  | struct window_pane_resize	*first; | 
|  | struct window_pane_resize	*last; | 
|  | struct timeval			 tv = { .tv_usec = 250000 }; | 
|  |  | 
|  | if (TAILQ_EMPTY(&wp->resize_queue)) | 
|  | return; | 
|  |  | 
|  | if (!event_initialized(&wp->resize_timer)) | 
|  | evtimer_set(&wp->resize_timer, server_client_resize_timer, wp); | 
|  | if (evtimer_pending(&wp->resize_timer, NULL)) | 
|  | return; | 
|  |  | 
|  | log_debug("%s: %%%u needs to be resized", __func__, wp->id); | 
|  | TAILQ_FOREACH(r, &wp->resize_queue, entry) { | 
|  | log_debug("queued resize: %ux%u -> %ux%u", r->osx, r->osy, | 
|  | r->sx, r->sy); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * There are three cases that matter: | 
|  | * | 
|  | * - Only one resize. It can just be applied. | 
|  | * | 
|  | * - Multiple resizes and the ending size is different from the | 
|  | *   starting size. We can discard all resizes except the most recent. | 
|  | * | 
|  | * - Multiple resizes and the ending size is the same as the starting | 
|  | *   size. We must resize at least twice to force the application to | 
|  | *   redraw. So apply the first and leave the last on the queue for | 
|  | *   next time. | 
|  | */ | 
|  | first = TAILQ_FIRST(&wp->resize_queue); | 
|  | last = TAILQ_LAST(&wp->resize_queue, window_pane_resizes); | 
|  | if (first == last) { | 
|  | /* Only one resize. */ | 
|  | window_pane_send_resize(wp, first->sx, first->sy); | 
|  | TAILQ_REMOVE(&wp->resize_queue, first, entry); | 
|  | free(first); | 
|  | } else if (last->sx != first->osx || last->sy != first->osy) { | 
|  | /* Multiple resizes ending up with a different size. */ | 
|  | window_pane_send_resize(wp, last->sx, last->sy); | 
|  | TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { | 
|  | TAILQ_REMOVE(&wp->resize_queue, r, entry); | 
|  | free(r); | 
|  | } | 
|  | } else { | 
|  | /* | 
|  | * Multiple resizes ending up with the same size. There will | 
|  | * not be more than one to the same size in succession so we | 
|  | * can just use the last-but-one on the list and leave the last | 
|  | * for later. We reduce the time until the next check to avoid | 
|  | * a long delay between the resizes. | 
|  | */ | 
|  | r = TAILQ_PREV(last, window_pane_resizes, entry); | 
|  | window_pane_send_resize(wp, r->sx, r->sy); | 
|  | TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { | 
|  | if (r == last) | 
|  | break; | 
|  | TAILQ_REMOVE(&wp->resize_queue, r, entry); | 
|  | free(r); | 
|  | } | 
|  | tv.tv_usec = 10000; | 
|  | } | 
|  | evtimer_add(&wp->resize_timer, &tv); | 
|  | } | 
|  |  | 
|  | /* Check pane buffer size. */ | 
|  | static void | 
|  | server_client_check_pane_buffer(struct window_pane *wp) | 
|  | { | 
|  | struct evbuffer			*evb = wp->event->input; | 
|  | size_t				 minimum; | 
|  | struct client			*c; | 
|  | struct window_pane_offset	*wpo; | 
|  | int				 off = 1, flag; | 
|  | u_int				 attached_clients = 0; | 
|  | size_t				 new_size; | 
|  |  | 
|  | /* | 
|  | * Work out the minimum used size. This is the most that can be removed | 
|  | * from the buffer. | 
|  | */ | 
|  | minimum = wp->offset.used; | 
|  | if (wp->pipe_fd != -1 && wp->pipe_offset.used < minimum) | 
|  | minimum = wp->pipe_offset.used; | 
|  | TAILQ_FOREACH(c, &clients, entry) { | 
|  | if (c->session == NULL) | 
|  | continue; | 
|  | attached_clients++; | 
|  |  | 
|  | if (~c->flags & CLIENT_CONTROL) { | 
|  | off = 0; | 
|  | continue; | 
|  | } | 
|  | wpo = control_pane_offset(c, wp, &flag); | 
|  | if (wpo == NULL) { | 
|  | if (!flag) | 
|  | off = 0; | 
|  | continue; | 
|  | } | 
|  | if (!flag) | 
|  | off = 0; | 
|  |  | 
|  | window_pane_get_new_data(wp, wpo, &new_size); | 
|  | log_debug("%s: %s has %zu bytes used and %zu left for %%%u", | 
|  | __func__, c->name, wpo->used - wp->base_offset, new_size, | 
|  | wp->id); | 
|  | if (wpo->used < minimum) | 
|  | minimum = wpo->used; | 
|  | } | 
|  | if (attached_clients == 0) | 
|  | off = 0; | 
|  | minimum -= wp->base_offset; | 
|  | if (minimum == 0) | 
|  | goto out; | 
|  |  | 
|  | /* Drain the buffer. */ | 
|  | log_debug("%s: %%%u has %zu minimum (of %zu) bytes used", __func__, | 
|  | wp->id, minimum, EVBUFFER_LENGTH(evb)); | 
|  | evbuffer_drain(evb, minimum); | 
|  |  | 
|  | /* | 
|  | * Adjust the base offset. If it would roll over, all the offsets into | 
|  | * the buffer need to be adjusted. | 
|  | */ | 
|  | if (wp->base_offset > SIZE_MAX - minimum) { | 
|  | log_debug("%s: %%%u base offset has wrapped", __func__, wp->id); | 
|  | wp->offset.used -= wp->base_offset; | 
|  | if (wp->pipe_fd != -1) | 
|  | wp->pipe_offset.used -= wp->base_offset; | 
|  | TAILQ_FOREACH(c, &clients, entry) { | 
|  | if (c->session == NULL || (~c->flags & CLIENT_CONTROL)) | 
|  | continue; | 
|  | wpo = control_pane_offset(c, wp, &flag); | 
|  | if (wpo != NULL && !flag) | 
|  | wpo->used -= wp->base_offset; | 
|  | } | 
|  | wp->base_offset = minimum; | 
|  | } else | 
|  | wp->base_offset += minimum; | 
|  |  | 
|  | out: | 
|  | /* | 
|  | * If there is data remaining, and there are no clients able to consume | 
|  | * it, do not read any more. This is true when there are attached | 
|  | * clients, all of which are control clients which are not able to | 
|  | * accept any more data. | 
|  | */ | 
|  | log_debug("%s: pane %%%u is %s", __func__, wp->id, off ? "off" : "on"); | 
|  | if (off) | 
|  | bufferevent_disable(wp->event, EV_READ); | 
|  | else | 
|  | bufferevent_enable(wp->event, EV_READ); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Update cursor position and mode settings. The scroll region and attributes | 
|  | * are cleared when idle (waiting for an event) as this is the most likely time | 
|  | * a user may interrupt tmux, for example with ~^Z in ssh(1). This is a | 
|  | * compromise between excessive resets and likelihood of an interrupt. | 
|  | * | 
|  | * tty_region/tty_reset/tty_update_mode already take care of not resetting | 
|  | * things that are already in their default state. | 
|  | */ | 
|  | static void | 
|  | server_client_reset_state(struct client *c) | 
|  | { | 
|  | struct tty		*tty = &c->tty; | 
|  | struct window		*w = c->session->curw->window; | 
|  | struct window_pane	*wp = server_client_get_pane(c), *loop; | 
|  | struct screen		*s = NULL; | 
|  | struct options		*oo = c->session->options; | 
|  | int			 mode = 0, cursor, flags, n; | 
|  | u_int			 cx = 0, cy = 0, ox, oy, sx, sy; | 
|  |  | 
|  | if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) | 
|  | return; | 
|  |  | 
|  | /* Disable the block flag. */ | 
|  | flags = (tty->flags & TTY_BLOCK); | 
|  | tty->flags &= ~TTY_BLOCK; | 
|  |  | 
|  | /* Get mode from overlay if any, else from screen. */ | 
|  | if (c->overlay_draw != NULL) { | 
|  | if (c->overlay_mode != NULL) | 
|  | s = c->overlay_mode(c, c->overlay_data, &cx, &cy); | 
|  | } else | 
|  | s = wp->screen; | 
|  | if (s != NULL) | 
|  | mode = s->mode; | 
|  | if (log_get_level() != 0) { | 
|  | log_debug("%s: client %s mode %s", __func__, c->name, | 
|  | screen_mode_to_string(mode)); | 
|  | } | 
|  |  | 
|  | /* Reset region and margin. */ | 
|  | tty_region_off(tty); | 
|  | tty_margin_off(tty); | 
|  |  | 
|  | /* Move cursor to pane cursor and offset. */ | 
|  | if (c->prompt_string != NULL) { | 
|  | n = options_get_number(c->session->options, "status-position"); | 
|  | if (n == 0) | 
|  | cy = 0; | 
|  | else { | 
|  | n = status_line_size(c); | 
|  | if (n == 0) | 
|  | cy = tty->sy - 1; | 
|  | else | 
|  | cy = tty->sy - n; | 
|  | } | 
|  | cx = c->prompt_cursor; | 
|  | mode &= ~MODE_CURSOR; | 
|  | } else if (c->overlay_draw == NULL) { | 
|  | cursor = 0; | 
|  | tty_window_offset(tty, &ox, &oy, &sx, &sy); | 
|  | if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && | 
|  | wp->yoff + s->cy >= oy && wp->yoff + s->cy <= oy + sy) { | 
|  | cursor = 1; | 
|  |  | 
|  | cx = wp->xoff + s->cx - ox; | 
|  | cy = wp->yoff + s->cy - oy; | 
|  |  | 
|  | if (status_at_line(c) == 0) | 
|  | cy += status_line_size(c); | 
|  | } | 
|  | if (!cursor) | 
|  | mode &= ~MODE_CURSOR; | 
|  | } | 
|  | log_debug("%s: cursor to %u,%u", __func__, cx, cy); | 
|  | tty_cursor(tty, cx, cy); | 
|  |  | 
|  | /* | 
|  | * Set mouse mode if requested. To support dragging, always use button | 
|  | * mode. | 
|  | */ | 
|  | if (options_get_number(oo, "mouse")) { | 
|  | if (c->overlay_draw == NULL) { | 
|  | mode &= ~ALL_MOUSE_MODES; | 
|  | TAILQ_FOREACH(loop, &w->panes, entry) { | 
|  | if (loop->screen->mode & MODE_MOUSE_ALL) | 
|  | mode |= MODE_MOUSE_ALL; | 
|  | } | 
|  | } | 
|  | if (~mode & MODE_MOUSE_ALL) | 
|  | mode |= MODE_MOUSE_BUTTON; | 
|  | } | 
|  |  | 
|  | /* Clear bracketed paste mode if at the prompt. */ | 
|  | if (c->overlay_draw == NULL && c->prompt_string != NULL) | 
|  | mode &= ~MODE_BRACKETPASTE; | 
|  |  | 
|  | /* Set the terminal mode and reset attributes. */ | 
|  | tty_update_mode(tty, mode, s); | 
|  | tty_reset(tty); | 
|  |  | 
|  | /* All writing must be done, send a sync end (if it was started). */ | 
|  | tty_sync_end(tty); | 
|  | tty->flags |= flags; | 
|  | } | 
|  |  | 
|  | /* Repeat time callback. */ | 
|  | static void | 
|  | server_client_repeat_timer(__unused int fd, __unused short events, void *data) | 
|  | { | 
|  | struct client	*c = data; | 
|  |  | 
|  | if (c->flags & CLIENT_REPEAT) { | 
|  | server_client_set_key_table(c, NULL); | 
|  | c->flags &= ~CLIENT_REPEAT; | 
|  | server_status_client(c); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Double-click callback. */ | 
|  | static void | 
|  | server_client_click_timer(__unused int fd, __unused short events, void *data) | 
|  | { | 
|  | struct client		*c = data; | 
|  | struct key_event	*event; | 
|  |  | 
|  | log_debug("click timer expired"); | 
|  |  | 
|  | if (c->flags & CLIENT_TRIPLECLICK) { | 
|  | /* | 
|  | * Waiting for a third click that hasn't happened, so this must | 
|  | * have been a double click. | 
|  | */ | 
|  | event = xmalloc(sizeof *event); | 
|  | event->key = KEYC_DOUBLECLICK; | 
|  | memcpy(&event->m, &c->click_event, sizeof event->m); | 
|  | if (!server_client_handle_key(c, event)) | 
|  | free(event); | 
|  | } | 
|  | c->flags &= ~(CLIENT_DOUBLECLICK|CLIENT_TRIPLECLICK); | 
|  | } | 
|  |  | 
|  | /* Check if client should be exited. */ | 
|  | static void | 
|  | server_client_check_exit(struct client *c) | 
|  | { | 
|  | struct client_file	*cf; | 
|  | const char		*name = c->exit_session; | 
|  | char			*data; | 
|  | size_t			 size, msize; | 
|  |  | 
|  | if (c->flags & (CLIENT_DEAD|CLIENT_EXITED)) | 
|  | return; | 
|  | if (~c->flags & CLIENT_EXIT) | 
|  | return; | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL) { | 
|  | control_discard(c); | 
|  | if (!control_all_done(c)) | 
|  | return; | 
|  | } | 
|  | RB_FOREACH(cf, client_files, &c->files) { | 
|  | if (EVBUFFER_LENGTH(cf->buffer) != 0) | 
|  | return; | 
|  | } | 
|  | c->flags |= CLIENT_EXITED; | 
|  |  | 
|  | switch (c->exit_type) { | 
|  | case CLIENT_EXIT_RETURN: | 
|  | if (c->exit_message != NULL) | 
|  | msize = strlen(c->exit_message) + 1; | 
|  | else | 
|  | msize = 0; | 
|  | size = (sizeof c->retval) + msize; | 
|  | data = xmalloc(size); | 
|  | memcpy(data, &c->retval, sizeof c->retval); | 
|  | if (c->exit_message != NULL) | 
|  | memcpy(data + sizeof c->retval, c->exit_message, msize); | 
|  | proc_send(c->peer, MSG_EXIT, -1, data, size); | 
|  | free(data); | 
|  | break; | 
|  | case CLIENT_EXIT_SHUTDOWN: | 
|  | proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0); | 
|  | break; | 
|  | case CLIENT_EXIT_DETACH: | 
|  | proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1); | 
|  | break; | 
|  | } | 
|  | free(c->exit_session); | 
|  | free(c->exit_message); | 
|  | } | 
|  |  | 
|  | /* Redraw timer callback. */ | 
|  | static void | 
|  | server_client_redraw_timer(__unused int fd, __unused short events, | 
|  | __unused void *data) | 
|  | { | 
|  | log_debug("redraw timer fired"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check if modes need to be updated. Only modes in the current window are | 
|  | * updated and it is done when the status line is redrawn. | 
|  | */ | 
|  | static void | 
|  | server_client_check_modes(struct client *c) | 
|  | { | 
|  | struct window			*w = c->session->curw->window; | 
|  | struct window_pane		*wp; | 
|  | struct window_mode_entry	*wme; | 
|  |  | 
|  | if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) | 
|  | return; | 
|  | if (~c->flags & CLIENT_REDRAWSTATUS) | 
|  | return; | 
|  | TAILQ_FOREACH(wp, &w->panes, entry) { | 
|  | wme = TAILQ_FIRST(&wp->modes); | 
|  | if (wme != NULL && wme->mode->update != NULL) | 
|  | wme->mode->update(wme); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check for client redraws. */ | 
|  | static void | 
|  | server_client_check_redraw(struct client *c) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct tty		*tty = &c->tty; | 
|  | struct window		*w = c->session->curw->window; | 
|  | struct window_pane	*wp; | 
|  | int			 needed, flags, mode = tty->mode, new_flags = 0; | 
|  | int			 redraw; | 
|  | u_int			 bit = 0; | 
|  | struct timeval		 tv = { .tv_usec = 1000 }; | 
|  | static struct event	 ev; | 
|  | size_t			 left; | 
|  |  | 
|  | if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) | 
|  | return; | 
|  | if (c->flags & CLIENT_ALLREDRAWFLAGS) { | 
|  | log_debug("%s: redraw%s%s%s%s%s", c->name, | 
|  | (c->flags & CLIENT_REDRAWWINDOW) ? " window" : "", | 
|  | (c->flags & CLIENT_REDRAWSTATUS) ? " status" : "", | 
|  | (c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "", | 
|  | (c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "", | 
|  | (c->flags & CLIENT_REDRAWPANES) ? " panes" : ""); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If there is outstanding data, defer the redraw until it has been | 
|  | * consumed. We can just add a timer to get out of the event loop and | 
|  | * end up back here. | 
|  | */ | 
|  | needed = 0; | 
|  | if (c->flags & CLIENT_ALLREDRAWFLAGS) | 
|  | needed = 1; | 
|  | else { | 
|  | TAILQ_FOREACH(wp, &w->panes, entry) { | 
|  | if (wp->flags & PANE_REDRAW) { | 
|  | needed = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (needed) | 
|  | new_flags |= CLIENT_REDRAWPANES; | 
|  | } | 
|  | if (needed && (left = EVBUFFER_LENGTH(tty->out)) != 0) { | 
|  | log_debug("%s: redraw deferred (%zu left)", c->name, left); | 
|  | if (!evtimer_initialized(&ev)) | 
|  | evtimer_set(&ev, server_client_redraw_timer, NULL); | 
|  | if (!evtimer_pending(&ev, NULL)) { | 
|  | log_debug("redraw timer started"); | 
|  | evtimer_add(&ev, &tv); | 
|  | } | 
|  |  | 
|  | if (~c->flags & CLIENT_REDRAWWINDOW) { | 
|  | TAILQ_FOREACH(wp, &w->panes, entry) { | 
|  | if (wp->flags & PANE_REDRAW) { | 
|  | log_debug("%s: pane %%%u needs redraw", | 
|  | c->name, wp->id); | 
|  | c->redraw_panes |= (1 << bit); | 
|  | } | 
|  | if (++bit == 64) { | 
|  | /* | 
|  | * If more that 64 panes, give up and | 
|  | * just redraw the window. | 
|  | */ | 
|  | new_flags &= CLIENT_REDRAWPANES; | 
|  | new_flags |= CLIENT_REDRAWWINDOW; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (c->redraw_panes != 0) | 
|  | c->flags |= CLIENT_REDRAWPANES; | 
|  | } | 
|  | c->flags |= new_flags; | 
|  | return; | 
|  | } else if (needed) | 
|  | log_debug("%s: redraw needed", c->name); | 
|  |  | 
|  | flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); | 
|  | tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE))|TTY_NOCURSOR; | 
|  |  | 
|  | if (~c->flags & CLIENT_REDRAWWINDOW) { | 
|  | /* | 
|  | * If not redrawing the entire window, check whether each pane | 
|  | * needs to be redrawn. | 
|  | */ | 
|  | TAILQ_FOREACH(wp, &w->panes, entry) { | 
|  | redraw = 0; | 
|  | if (wp->flags & PANE_REDRAW) | 
|  | redraw = 1; | 
|  | else if (c->flags & CLIENT_REDRAWPANES) | 
|  | redraw = !!(c->redraw_panes & (1 << bit)); | 
|  | bit++; | 
|  | if (!redraw) | 
|  | continue; | 
|  | log_debug("%s: redrawing pane %%%u", __func__, wp->id); | 
|  | screen_redraw_pane(c, wp); | 
|  | } | 
|  | c->redraw_panes = 0; | 
|  | c->flags &= ~CLIENT_REDRAWPANES; | 
|  | } | 
|  |  | 
|  | if (c->flags & CLIENT_ALLREDRAWFLAGS) { | 
|  | if (options_get_number(s->options, "set-titles")) { | 
|  | server_client_set_title(c); | 
|  | server_client_set_path(c); | 
|  | } | 
|  | screen_redraw_screen(c); | 
|  | } | 
|  |  | 
|  | tty->flags = (tty->flags & ~TTY_NOCURSOR)|(flags & TTY_NOCURSOR); | 
|  | tty_update_mode(tty, mode, NULL); | 
|  | tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR))|flags; | 
|  |  | 
|  | c->flags &= ~(CLIENT_ALLREDRAWFLAGS|CLIENT_STATUSFORCE); | 
|  |  | 
|  | if (needed) { | 
|  | /* | 
|  | * We would have deferred the redraw unless the output buffer | 
|  | * was empty, so we can record how many bytes the redraw | 
|  | * generated. | 
|  | */ | 
|  | c->redraw = EVBUFFER_LENGTH(tty->out); | 
|  | log_debug("%s: redraw added %zu bytes", c->name, c->redraw); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Set client title. */ | 
|  | static void | 
|  | server_client_set_title(struct client *c) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | const char		*template; | 
|  | char			*title; | 
|  | struct format_tree	*ft; | 
|  |  | 
|  | template = options_get_string(s->options, "set-titles-string"); | 
|  |  | 
|  | ft = format_create(c, NULL, FORMAT_NONE, 0); | 
|  | format_defaults(ft, c, NULL, NULL, NULL); | 
|  |  | 
|  | title = format_expand_time(ft, template); | 
|  | if (c->title == NULL || strcmp(title, c->title) != 0) { | 
|  | free(c->title); | 
|  | c->title = xstrdup(title); | 
|  | tty_set_title(&c->tty, c->title); | 
|  | } | 
|  | free(title); | 
|  |  | 
|  | format_free(ft); | 
|  | } | 
|  |  | 
|  | /* Set client path. */ | 
|  | static void | 
|  | server_client_set_path(struct client *c) | 
|  | { | 
|  | struct session	*s = c->session; | 
|  | const char	*path; | 
|  |  | 
|  | if (s->curw == NULL) | 
|  | return; | 
|  | if (s->curw->window->active->base.path == NULL) | 
|  | path = ""; | 
|  | else | 
|  | path = s->curw->window->active->base.path; | 
|  | if (c->path == NULL || strcmp(path, c->path) != 0) { | 
|  | free(c->path); | 
|  | c->path = xstrdup(path); | 
|  | tty_set_path(&c->tty, c->path); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Dispatch message from client. */ | 
|  | static void | 
|  | server_client_dispatch(struct imsg *imsg, void *arg) | 
|  | { | 
|  | struct client	*c = arg; | 
|  | ssize_t		 datalen; | 
|  | struct session	*s; | 
|  |  | 
|  | if (c->flags & CLIENT_DEAD) | 
|  | return; | 
|  |  | 
|  | if (imsg == NULL) { | 
|  | server_client_lost(c); | 
|  | return; | 
|  | } | 
|  |  | 
|  | datalen = imsg->hdr.len - IMSG_HEADER_SIZE; | 
|  |  | 
|  | switch (imsg->hdr.type) { | 
|  | case MSG_IDENTIFY_CLIENTPID: | 
|  | case MSG_IDENTIFY_CWD: | 
|  | case MSG_IDENTIFY_ENVIRON: | 
|  | case MSG_IDENTIFY_FEATURES: | 
|  | case MSG_IDENTIFY_FLAGS: | 
|  | case MSG_IDENTIFY_LONGFLAGS: | 
|  | case MSG_IDENTIFY_STDIN: | 
|  | case MSG_IDENTIFY_STDOUT: | 
|  | case MSG_IDENTIFY_TERM: | 
|  | case MSG_IDENTIFY_TERMINFO: | 
|  | case MSG_IDENTIFY_TTYNAME: | 
|  | case MSG_IDENTIFY_DONE: | 
|  | server_client_dispatch_identify(c, imsg); | 
|  | break; | 
|  | case MSG_COMMAND: | 
|  | server_client_dispatch_command(c, imsg); | 
|  | break; | 
|  | case MSG_RESIZE: | 
|  | if (datalen != 0) | 
|  | fatalx("bad MSG_RESIZE size"); | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | break; | 
|  | server_client_update_latest(c); | 
|  | tty_resize(&c->tty); | 
|  | recalculate_sizes(); | 
|  | if (c->overlay_resize == NULL) | 
|  | server_client_clear_overlay(c); | 
|  | else | 
|  | c->overlay_resize(c, c->overlay_data); | 
|  | server_redraw_client(c); | 
|  | if (c->session != NULL) | 
|  | notify_client("client-resized", c); | 
|  | break; | 
|  | case MSG_EXITING: | 
|  | if (datalen != 0) | 
|  | fatalx("bad MSG_EXITING size"); | 
|  | server_client_set_session(c, NULL); | 
|  | recalculate_sizes(); | 
|  | tty_close(&c->tty); | 
|  | proc_send(c->peer, MSG_EXITED, -1, NULL, 0); | 
|  | break; | 
|  | case MSG_WAKEUP: | 
|  | case MSG_UNLOCK: | 
|  | if (datalen != 0) | 
|  | fatalx("bad MSG_WAKEUP size"); | 
|  |  | 
|  | if (!(c->flags & CLIENT_SUSPENDED)) | 
|  | break; | 
|  | c->flags &= ~CLIENT_SUSPENDED; | 
|  |  | 
|  | if (c->fd == -1 || c->session == NULL) /* exited already */ | 
|  | break; | 
|  | s = c->session; | 
|  |  | 
|  | if (gettimeofday(&c->activity_time, NULL) != 0) | 
|  | fatal("gettimeofday failed"); | 
|  |  | 
|  | tty_start_tty(&c->tty); | 
|  | server_redraw_client(c); | 
|  | recalculate_sizes(); | 
|  |  | 
|  | if (s != NULL) | 
|  | session_update_activity(s, &c->activity_time); | 
|  | break; | 
|  | case MSG_SHELL: | 
|  | if (datalen != 0) | 
|  | fatalx("bad MSG_SHELL size"); | 
|  |  | 
|  | server_client_dispatch_shell(c); | 
|  | break; | 
|  | case MSG_WRITE_READY: | 
|  | file_write_ready(&c->files, imsg); | 
|  | break; | 
|  | case MSG_READ: | 
|  | file_read_data(&c->files, imsg); | 
|  | break; | 
|  | case MSG_READ_DONE: | 
|  | file_read_done(&c->files, imsg); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Callback when command is not allowed. */ | 
|  | static enum cmd_retval | 
|  | server_client_read_only(struct cmdq_item *item, __unused void *data) | 
|  | { | 
|  | cmdq_error(item, "client is read-only"); | 
|  | return (CMD_RETURN_ERROR); | 
|  | } | 
|  |  | 
|  | /* Callback when command is done. */ | 
|  | static enum cmd_retval | 
|  | server_client_command_done(struct cmdq_item *item, __unused void *data) | 
|  | { | 
|  | struct client	*c = cmdq_get_client(item); | 
|  |  | 
|  | if (~c->flags & CLIENT_ATTACHED) | 
|  | c->flags |= CLIENT_EXIT; | 
|  | else if (~c->flags & CLIENT_EXIT) { | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | control_ready(c); | 
|  | tty_send_requests(&c->tty); | 
|  | } | 
|  | return (CMD_RETURN_NORMAL); | 
|  | } | 
|  |  | 
|  | /* Handle command message. */ | 
|  | static void | 
|  | server_client_dispatch_command(struct client *c, struct imsg *imsg) | 
|  | { | 
|  | struct msg_command	  data; | 
|  | char			 *buf; | 
|  | size_t			  len; | 
|  | int			  argc; | 
|  | char			**argv, *cause; | 
|  | struct cmd_parse_result	 *pr; | 
|  | struct args_value	 *values; | 
|  | struct cmdq_item	 *new_item; | 
|  |  | 
|  | if (c->flags & CLIENT_EXIT) | 
|  | return; | 
|  |  | 
|  | if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data) | 
|  | fatalx("bad MSG_COMMAND size"); | 
|  | memcpy(&data, imsg->data, sizeof data); | 
|  |  | 
|  | buf = (char *)imsg->data + sizeof data; | 
|  | len = imsg->hdr.len  - IMSG_HEADER_SIZE - sizeof data; | 
|  | if (len > 0 && buf[len - 1] != '\0') | 
|  | fatalx("bad MSG_COMMAND string"); | 
|  |  | 
|  | argc = data.argc; | 
|  | if (cmd_unpack_argv(buf, len, argc, &argv) != 0) { | 
|  | cause = xstrdup("command too long"); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | if (argc == 0) { | 
|  | argc = 1; | 
|  | argv = xcalloc(1, sizeof *argv); | 
|  | *argv = xstrdup("new-session"); | 
|  | } | 
|  |  | 
|  | values = args_from_vector(argc, argv); | 
|  | pr = cmd_parse_from_arguments(values, argc, NULL); | 
|  | switch (pr->status) { | 
|  | case CMD_PARSE_ERROR: | 
|  | cause = pr->error; | 
|  | goto error; | 
|  | case CMD_PARSE_SUCCESS: | 
|  | break; | 
|  | } | 
|  | args_free_values(values, argc); | 
|  | free(values); | 
|  | cmd_free_argv(argc, argv); | 
|  |  | 
|  | if ((c->flags & CLIENT_READONLY) && | 
|  | !cmd_list_all_have(pr->cmdlist, CMD_READONLY)) | 
|  | new_item = cmdq_get_callback(server_client_read_only, NULL); | 
|  | else | 
|  | new_item = cmdq_get_command(pr->cmdlist, NULL); | 
|  | cmdq_append(c, new_item); | 
|  | cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); | 
|  |  | 
|  | cmd_list_free(pr->cmdlist); | 
|  | return; | 
|  |  | 
|  | error: | 
|  | cmd_free_argv(argc, argv); | 
|  |  | 
|  | cmdq_append(c, cmdq_get_error(cause)); | 
|  | free(cause); | 
|  |  | 
|  | c->flags |= CLIENT_EXIT; | 
|  | } | 
|  |  | 
|  | /* Handle identify message. */ | 
|  | static void | 
|  | server_client_dispatch_identify(struct client *c, struct imsg *imsg) | 
|  | { | 
|  | const char	*data, *home; | 
|  | size_t		 datalen; | 
|  | int		 flags, feat; | 
|  | uint64_t	 longflags; | 
|  | char		*name; | 
|  |  | 
|  | if (c->flags & CLIENT_IDENTIFIED) | 
|  | fatalx("out-of-order identify message"); | 
|  |  | 
|  | data = imsg->data; | 
|  | datalen = imsg->hdr.len - IMSG_HEADER_SIZE; | 
|  |  | 
|  | switch (imsg->hdr.type)	{ | 
|  | case MSG_IDENTIFY_FEATURES: | 
|  | if (datalen != sizeof feat) | 
|  | fatalx("bad MSG_IDENTIFY_FEATURES size"); | 
|  | memcpy(&feat, data, sizeof feat); | 
|  | c->term_features |= feat; | 
|  | log_debug("client %p IDENTIFY_FEATURES %s", c, | 
|  | tty_get_features(feat)); | 
|  | break; | 
|  | case MSG_IDENTIFY_FLAGS: | 
|  | if (datalen != sizeof flags) | 
|  | fatalx("bad MSG_IDENTIFY_FLAGS size"); | 
|  | memcpy(&flags, data, sizeof flags); | 
|  | c->flags |= flags; | 
|  | log_debug("client %p IDENTIFY_FLAGS %#x", c, flags); | 
|  | break; | 
|  | case MSG_IDENTIFY_LONGFLAGS: | 
|  | if (datalen != sizeof longflags) | 
|  | fatalx("bad MSG_IDENTIFY_LONGFLAGS size"); | 
|  | memcpy(&longflags, data, sizeof longflags); | 
|  | c->flags |= longflags; | 
|  | log_debug("client %p IDENTIFY_LONGFLAGS %#llx", c, | 
|  | (unsigned long long)longflags); | 
|  | break; | 
|  | case MSG_IDENTIFY_TERM: | 
|  | if (datalen == 0 || data[datalen - 1] != '\0') | 
|  | fatalx("bad MSG_IDENTIFY_TERM string"); | 
|  | if (*data == '\0') | 
|  | c->term_name = xstrdup("unknown"); | 
|  | else | 
|  | c->term_name = xstrdup(data); | 
|  | log_debug("client %p IDENTIFY_TERM %s", c, data); | 
|  | break; | 
|  | case MSG_IDENTIFY_TERMINFO: | 
|  | if (datalen == 0 || data[datalen - 1] != '\0') | 
|  | fatalx("bad MSG_IDENTIFY_TERMINFO string"); | 
|  | c->term_caps = xreallocarray(c->term_caps, c->term_ncaps + 1, | 
|  | sizeof *c->term_caps); | 
|  | c->term_caps[c->term_ncaps++] = xstrdup(data); | 
|  | log_debug("client %p IDENTIFY_TERMINFO %s", c, data); | 
|  | break; | 
|  | case MSG_IDENTIFY_TTYNAME: | 
|  | if (datalen == 0 || data[datalen - 1] != '\0') | 
|  | fatalx("bad MSG_IDENTIFY_TTYNAME string"); | 
|  | c->ttyname = xstrdup(data); | 
|  | log_debug("client %p IDENTIFY_TTYNAME %s", c, data); | 
|  | break; | 
|  | case MSG_IDENTIFY_CWD: | 
|  | if (datalen == 0 || data[datalen - 1] != '\0') | 
|  | fatalx("bad MSG_IDENTIFY_CWD string"); | 
|  | if (access(data, X_OK) == 0) | 
|  | c->cwd = xstrdup(data); | 
|  | else if ((home = find_home()) != NULL) | 
|  | c->cwd = xstrdup(home); | 
|  | else | 
|  | c->cwd = xstrdup("/"); | 
|  | log_debug("client %p IDENTIFY_CWD %s", c, data); | 
|  | break; | 
|  | case MSG_IDENTIFY_STDIN: | 
|  | if (datalen != 0) | 
|  | fatalx("bad MSG_IDENTIFY_STDIN size"); | 
|  | c->fd = imsg->fd; | 
|  | log_debug("client %p IDENTIFY_STDIN %d", c, imsg->fd); | 
|  | break; | 
|  | case MSG_IDENTIFY_STDOUT: | 
|  | if (datalen != 0) | 
|  | fatalx("bad MSG_IDENTIFY_STDOUT size"); | 
|  | c->out_fd = imsg->fd; | 
|  | log_debug("client %p IDENTIFY_STDOUT %d", c, imsg->fd); | 
|  | break; | 
|  | case MSG_IDENTIFY_ENVIRON: | 
|  | if (datalen == 0 || data[datalen - 1] != '\0') | 
|  | fatalx("bad MSG_IDENTIFY_ENVIRON string"); | 
|  | if (strchr(data, '=') != NULL) | 
|  | environ_put(c->environ, data, 0); | 
|  | log_debug("client %p IDENTIFY_ENVIRON %s", c, data); | 
|  | break; | 
|  | case MSG_IDENTIFY_CLIENTPID: | 
|  | if (datalen != sizeof c->pid) | 
|  | fatalx("bad MSG_IDENTIFY_CLIENTPID size"); | 
|  | memcpy(&c->pid, data, sizeof c->pid); | 
|  | log_debug("client %p IDENTIFY_CLIENTPID %ld", c, (long)c->pid); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (imsg->hdr.type != MSG_IDENTIFY_DONE) | 
|  | return; | 
|  | c->flags |= CLIENT_IDENTIFIED; | 
|  |  | 
|  | if (*c->ttyname != '\0') | 
|  | name = xstrdup(c->ttyname); | 
|  | else | 
|  | xasprintf(&name, "client-%ld", (long)c->pid); | 
|  | c->name = name; | 
|  | log_debug("client %p name is %s", c, c->name); | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | control_start(c); | 
|  | else if (c->fd != -1) { | 
|  | if (tty_init(&c->tty, c) != 0) { | 
|  | close(c->fd); | 
|  | c->fd = -1; | 
|  | } else { | 
|  | tty_resize(&c->tty); | 
|  | c->flags |= CLIENT_TERMINAL; | 
|  | } | 
|  | close(c->out_fd); | 
|  | c->out_fd = -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If this is the first client, load configuration files. Any later | 
|  | * clients are allowed to continue with their command even if the | 
|  | * config has not been loaded - they might have been run from inside it | 
|  | */ | 
|  | if ((~c->flags & CLIENT_EXIT) && | 
|  | !cfg_finished && | 
|  | c == TAILQ_FIRST(&clients)) | 
|  | start_cfg(); | 
|  | } | 
|  |  | 
|  | /* Handle shell message. */ | 
|  | static void | 
|  | server_client_dispatch_shell(struct client *c) | 
|  | { | 
|  | const char	*shell; | 
|  |  | 
|  | shell = options_get_string(global_s_options, "default-shell"); | 
|  | if (!checkshell(shell)) | 
|  | shell = _PATH_BSHELL; | 
|  | proc_send(c->peer, MSG_SHELL, -1, shell, strlen(shell) + 1); | 
|  |  | 
|  | proc_kill_peer(c->peer); | 
|  | } | 
|  |  | 
|  | /* Get client working directory. */ | 
|  | const char * | 
|  | server_client_get_cwd(struct client *c, struct session *s) | 
|  | { | 
|  | const char	*home; | 
|  |  | 
|  | if (!cfg_finished && cfg_client != NULL) | 
|  | return (cfg_client->cwd); | 
|  | if (c != NULL && c->session == NULL && c->cwd != NULL) | 
|  | return (c->cwd); | 
|  | if (s != NULL && s->cwd != NULL) | 
|  | return (s->cwd); | 
|  | if (c != NULL && (s = c->session) != NULL && s->cwd != NULL) | 
|  | return (s->cwd); | 
|  | if ((home = find_home()) != NULL) | 
|  | return (home); | 
|  | return ("/"); | 
|  | } | 
|  |  | 
|  | /* Get control client flags. */ | 
|  | static uint64_t | 
|  | server_client_control_flags(struct client *c, const char *next) | 
|  | { | 
|  | if (strcmp(next, "pause-after") == 0) { | 
|  | c->pause_age = 0; | 
|  | return (CLIENT_CONTROL_PAUSEAFTER); | 
|  | } | 
|  | if (sscanf(next, "pause-after=%u", &c->pause_age) == 1) { | 
|  | c->pause_age *= 1000; | 
|  | return (CLIENT_CONTROL_PAUSEAFTER); | 
|  | } | 
|  | if (strcmp(next, "no-output") == 0) | 
|  | return (CLIENT_CONTROL_NOOUTPUT); | 
|  | if (strcmp(next, "wait-exit") == 0) | 
|  | return (CLIENT_CONTROL_WAITEXIT); | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Set client flags. */ | 
|  | void | 
|  | server_client_set_flags(struct client *c, const char *flags) | 
|  | { | 
|  | char	*s, *copy, *next; | 
|  | uint64_t flag; | 
|  | int	 not; | 
|  |  | 
|  | s = copy = xstrdup(flags); | 
|  | while ((next = strsep(&s, ",")) != NULL) { | 
|  | not = (*next == '!'); | 
|  | if (not) | 
|  | next++; | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | flag = server_client_control_flags(c, next); | 
|  | else | 
|  | flag = 0; | 
|  | if (strcmp(next, "read-only") == 0) | 
|  | flag = CLIENT_READONLY; | 
|  | else if (strcmp(next, "ignore-size") == 0) | 
|  | flag = CLIENT_IGNORESIZE; | 
|  | else if (strcmp(next, "active-pane") == 0) | 
|  | flag = CLIENT_ACTIVEPANE; | 
|  | if (flag == 0) | 
|  | continue; | 
|  |  | 
|  | log_debug("client %s set flag %s", c->name, next); | 
|  | if (not) { | 
|  | if (c->flags & CLIENT_READONLY) | 
|  | flag &= ~CLIENT_READONLY; | 
|  | c->flags &= ~flag; | 
|  | } else | 
|  | c->flags |= flag; | 
|  | if (flag == CLIENT_CONTROL_NOOUTPUT) | 
|  | control_reset_offsets(c); | 
|  | } | 
|  | free(copy); | 
|  | proc_send(c->peer, MSG_FLAGS, -1, &c->flags, sizeof c->flags); | 
|  | } | 
|  |  | 
|  | /* Get client flags. This is only flags useful to show to users. */ | 
|  | const char * | 
|  | server_client_get_flags(struct client *c) | 
|  | { | 
|  | static char	s[256]; | 
|  | char	 	tmp[32]; | 
|  |  | 
|  | *s = '\0'; | 
|  | if (c->flags & CLIENT_ATTACHED) | 
|  | strlcat(s, "attached,", sizeof s); | 
|  | if (c->flags & CLIENT_FOCUSED) | 
|  | strlcat(s, "focused,", sizeof s); | 
|  | if (c->flags & CLIENT_CONTROL) | 
|  | strlcat(s, "control-mode,", sizeof s); | 
|  | if (c->flags & CLIENT_IGNORESIZE) | 
|  | strlcat(s, "ignore-size,", sizeof s); | 
|  | if (c->flags & CLIENT_CONTROL_NOOUTPUT) | 
|  | strlcat(s, "no-output,", sizeof s); | 
|  | if (c->flags & CLIENT_CONTROL_WAITEXIT) | 
|  | strlcat(s, "wait-exit,", sizeof s); | 
|  | if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { | 
|  | xsnprintf(tmp, sizeof tmp, "pause-after=%u,", | 
|  | c->pause_age / 1000); | 
|  | strlcat(s, tmp, sizeof s); | 
|  | } | 
|  | if (c->flags & CLIENT_READONLY) | 
|  | strlcat(s, "read-only,", sizeof s); | 
|  | if (c->flags & CLIENT_ACTIVEPANE) | 
|  | strlcat(s, "active-pane,", sizeof s); | 
|  | if (c->flags & CLIENT_SUSPENDED) | 
|  | strlcat(s, "suspended,", sizeof s); | 
|  | if (c->flags & CLIENT_UTF8) | 
|  | strlcat(s, "UTF-8,", sizeof s); | 
|  | if (*s != '\0') | 
|  | s[strlen(s) - 1] = '\0'; | 
|  | return (s); | 
|  | } | 
|  |  | 
|  | /* Get client window. */ | 
|  | struct client_window * | 
|  | server_client_get_client_window(struct client *c, u_int id) | 
|  | { | 
|  | struct client_window	cw = { .window = id }; | 
|  |  | 
|  | return (RB_FIND(client_windows, &c->windows, &cw)); | 
|  | } | 
|  |  | 
|  | /* Add client window. */ | 
|  | struct client_window * | 
|  | server_client_add_client_window(struct client *c, u_int id) | 
|  | { | 
|  | struct client_window	*cw; | 
|  |  | 
|  | cw = server_client_get_client_window(c, id); | 
|  | if (cw == NULL) { | 
|  | cw = xcalloc(1, sizeof *cw); | 
|  | cw->window = id; | 
|  | RB_INSERT(client_windows, &c->windows, cw); | 
|  | } | 
|  | return (cw); | 
|  | } | 
|  |  | 
|  | /* Get client active pane. */ | 
|  | struct window_pane * | 
|  | server_client_get_pane(struct client *c) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct client_window	*cw; | 
|  |  | 
|  | if (s == NULL) | 
|  | return (NULL); | 
|  |  | 
|  | if (~c->flags & CLIENT_ACTIVEPANE) | 
|  | return (s->curw->window->active); | 
|  | cw = server_client_get_client_window(c, s->curw->window->id); | 
|  | if (cw == NULL) | 
|  | return (s->curw->window->active); | 
|  | return (cw->pane); | 
|  | } | 
|  |  | 
|  | /* Set client active pane. */ | 
|  | void | 
|  | server_client_set_pane(struct client *c, struct window_pane *wp) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct client_window	*cw; | 
|  |  | 
|  | if (s == NULL) | 
|  | return; | 
|  |  | 
|  | cw = server_client_add_client_window(c, s->curw->window->id); | 
|  | cw->pane = wp; | 
|  | log_debug("%s pane now %%%u", c->name, wp->id); | 
|  | } | 
|  |  | 
|  | /* Remove pane from client lists. */ | 
|  | void | 
|  | server_client_remove_pane(struct window_pane *wp) | 
|  | { | 
|  | struct client		*c; | 
|  | struct window		*w = wp->window; | 
|  | struct client_window	*cw; | 
|  |  | 
|  | TAILQ_FOREACH(c, &clients, entry) { | 
|  | cw = server_client_get_client_window(c, w->id); | 
|  | if (cw != NULL && cw->pane == wp) { | 
|  | RB_REMOVE(client_windows, &c->windows, cw); | 
|  | free(cw); | 
|  | } | 
|  | } | 
|  | } |