diff --git a/server-client.c b/server-client.c index 5c15d502..dd386001 100644 --- a/server-client.c +++ b/server-client.c @@ -27,10 +27,11 @@ #include "tmux.h" -void server_client_handle_data(struct client *); +void server_client_handle_key(int, struct mouse_event *, void *); void server_client_repeat_timer(int, short, void *); void server_client_check_redraw(struct client *); void server_client_set_title(struct client *); +void server_client_reset_state(struct client *); int server_client_msg_dispatch(struct client *); void server_client_msg_command(struct client *, struct msg_command_data *); @@ -226,6 +227,127 @@ server_client_status_timer(void) } } +/* Handle data key input from client. */ +void +server_client_handle_key(int key, struct mouse_event *mouse, void *data) +{ + struct client *c = data; + struct session *s; + struct window *w; + struct window_pane *wp; + struct options *oo; + struct timeval tv; + struct key_binding *bd; + struct keylist *keylist; + int xtimeout, isprefix; + u_int i; + + /* Check the client is good to accept input. */ + if ((c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) + return; + if (c->session == NULL) + return; + s = c->session; + + /* Update the activity timer. */ + if (gettimeofday(&c->activity_time, NULL) != 0) + fatal("gettimeofday failed"); + memcpy(&s->activity_time, &c->activity_time, sizeof s->activity_time); + + w = c->session->curw->window; + wp = w->active; + oo = &c->session->options; + + /* Special case: number keys jump to pane in identify mode. */ + if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { + wp = window_pane_at_index(w, key - '0'); + if (wp != NULL && window_pane_visible(wp)) + window_set_active_pane(w, wp); + server_clear_identify(c); + return; + } + + /* Handle status line. */ + status_message_clear(c); + server_clear_identify(c); + if (c->prompt_string != NULL) { + status_prompt_key(c, key); + return; + } + + /* Check for mouse keys. */ + if (key == KEYC_MOUSE) { + if (options_get_number(oo, "mouse-select-pane")) { + window_set_active_at(w, mouse->x, mouse->y); + wp = w->active; + } + window_pane_mouse(wp, c, mouse); + return; + } + + /* Is this a prefix key? */ + keylist = options_get_data(&c->session->options, "prefix"); + isprefix = 0; + for (i = 0; i < ARRAY_LENGTH(keylist); i++) { + if (key == ARRAY_ITEM(keylist, i)) { + isprefix = 1; + break; + } + } + + /* No previous prefix key. */ + if (!(c->flags & CLIENT_PREFIX)) { + if (isprefix) + c->flags |= CLIENT_PREFIX; + else { + /* Try as a non-prefix key binding. */ + if ((bd = key_bindings_lookup(key)) == NULL) + window_pane_key(wp, c, key); + else + key_bindings_dispatch(bd, c); + } + return; + } + + /* Prefix key already pressed. Reset prefix and lookup key. */ + c->flags &= ~CLIENT_PREFIX; + if ((bd = key_bindings_lookup(key | KEYC_PREFIX)) == NULL) { + /* If repeating, treat this as a key, else ignore. */ + if (c->flags & CLIENT_REPEAT) { + c->flags &= ~CLIENT_REPEAT; + if (isprefix) + c->flags |= CLIENT_PREFIX; + else + window_pane_key(wp, c, key); + } + return; + } + + /* If already repeating, but this key can't repeat, skip it. */ + if (c->flags & CLIENT_REPEAT && !bd->can_repeat) { + c->flags &= ~CLIENT_REPEAT; + if (isprefix) + c->flags |= CLIENT_PREFIX; + else + window_pane_key(wp, c, key); + return; + } + + /* If this key can repeat, reset the repeat flags and timer. */ + xtimeout = options_get_number(&c->session->options, "repeat-time"); + if (xtimeout != 0 && bd->can_repeat) { + c->flags |= CLIENT_PREFIX|CLIENT_REPEAT; + + tv.tv_sec = xtimeout / 1000; + tv.tv_usec = (xtimeout % 1000) * 1000L; + evtimer_del(&c->repeat_timer); + evtimer_add(&c->repeat_timer, &tv); + } + + /* Dispatch the command. */ + key_bindings_dispatch(bd, c); +} + /* Client functions that need to happen every loop. */ void server_client_loop(void) @@ -240,9 +362,8 @@ server_client_loop(void) if (c == NULL || c->session == NULL) continue; - server_client_handle_data(c); - if (c->session != NULL) - server_client_check_redraw(c); + server_client_check_redraw(c); + server_client_reset_state(c); } /* @@ -260,143 +381,24 @@ server_client_loop(void) } } -/* Handle data input or output from client. */ +/* + * 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. + */ void -server_client_handle_data(struct client *c) +server_client_reset_state(struct client *c) { - struct window *w; - struct window_pane *wp; - struct screen *s; - struct options *oo; - struct timeval tv, tv_now; - struct key_binding *bd; - struct keylist *keylist; - struct mouse_event mouse; - int key, status, xtimeout, mode, isprefix; - u_int i; + struct window *w = c->session->curw->window; + struct window_pane *wp = w->active; + struct screen *s = wp->screen; + struct options *oo = &c->session->options; + int status, mode; - /* Get the time for the activity timer. */ - if (gettimeofday(&tv_now, NULL) != 0) - fatal("gettimeofday failed"); - xtimeout = options_get_number(&c->session->options, "repeat-time"); - - /* Process keys. */ - keylist = options_get_data(&c->session->options, "prefix"); - while (tty_keys_next(&c->tty, &key, &mouse) == 0) { - if (c->session == NULL) - return; - w = c->session->curw->window; - wp = w->active; /* could die */ - oo = &c->session->options; - - /* Update activity timer. */ - memcpy(&c->activity_time, &tv_now, sizeof c->activity_time); - memcpy(&c->session->activity_time, - &tv_now, sizeof c->session->activity_time); - - /* Special case: number keys jump to pane in identify mode. */ - if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { - wp = window_pane_at_index(w, key - '0'); - if (wp != NULL && window_pane_visible(wp)) - window_set_active_pane(w, wp); - server_clear_identify(c); - continue; - } - - status_message_clear(c); - server_clear_identify(c); - if (c->prompt_string != NULL) { - status_prompt_key(c, key); - continue; - } - - /* Check for mouse keys. */ - if (key == KEYC_MOUSE) { - if (options_get_number(oo, "mouse-select-pane")) { - window_set_active_at(w, mouse.x, mouse.y); - wp = w->active; - } - window_pane_mouse(wp, c, &mouse); - continue; - } - - /* Is this a prefix key? */ - isprefix = 0; - for (i = 0; i < ARRAY_LENGTH(keylist); i++) { - if (key == ARRAY_ITEM(keylist, i)) { - isprefix = 1; - break; - } - } - - /* No previous prefix key. */ - if (!(c->flags & CLIENT_PREFIX)) { - if (isprefix) - c->flags |= CLIENT_PREFIX; - else { - /* Try as a non-prefix key binding. */ - if ((bd = key_bindings_lookup(key)) == NULL) - window_pane_key(wp, c, key); - else - key_bindings_dispatch(bd, c); - } - continue; - } - - /* Prefix key already pressed. Reset prefix and lookup key. */ - c->flags &= ~CLIENT_PREFIX; - if ((bd = key_bindings_lookup(key | KEYC_PREFIX)) == NULL) { - /* If repeating, treat this as a key, else ignore. */ - if (c->flags & CLIENT_REPEAT) { - c->flags &= ~CLIENT_REPEAT; - if (isprefix) - c->flags |= CLIENT_PREFIX; - else - window_pane_key(wp, c, key); - } - continue; - } - - /* If already repeating, but this key can't repeat, skip it. */ - if (c->flags & CLIENT_REPEAT && !bd->can_repeat) { - c->flags &= ~CLIENT_REPEAT; - if (isprefix) - c->flags |= CLIENT_PREFIX; - else - window_pane_key(wp, c, key); - continue; - } - - /* If this key can repeat, reset the repeat flags and timer. */ - if (xtimeout != 0 && bd->can_repeat) { - c->flags |= CLIENT_PREFIX|CLIENT_REPEAT; - - tv.tv_sec = xtimeout / 1000; - tv.tv_usec = (xtimeout % 1000) * 1000L; - evtimer_del(&c->repeat_timer); - evtimer_add(&c->repeat_timer, &tv); - } - - /* Dispatch the command. */ - key_bindings_dispatch(bd, c); - } - if (c->session == NULL) - return; - w = c->session->curw->window; - wp = w->active; - oo = &c->session->options; - s = wp->screen; - - /* - * Update cursor position and mode settings. The scroll region and - * attributes are cleared across poll(2) 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. - */ tty_region(&c->tty, 0, c->tty.sy - 1); status = options_get_number(oo, "status"); @@ -715,6 +717,8 @@ server_client_msg_identify( c->tty.term_flags |= TERM_256COLOURS; else if (data->flags & IDENTIFY_88COLOURS) c->tty.term_flags |= TERM_88COLOURS; + c->tty.key_callback = server_client_handle_key; + c->tty.key_data = c; tty_resize(&c->tty); diff --git a/tmux.h b/tmux.h index f3263f4a..f1b0f7ac 100644 --- a/tmux.h +++ b/tmux.h @@ -1002,7 +1002,9 @@ struct tty { int term_flags; - struct timeval key_timer; + void (*key_callback)(int, struct mouse_event *, void *); + void *key_data; + struct event key_timer; size_t ksize; /* maximum key size */ RB_HEAD(tty_keys, tty_key) ktree; @@ -1360,7 +1362,7 @@ int tty_keys_cmp(struct tty_key *, struct tty_key *); RB_PROTOTYPE(tty_keys, tty_key, entry, tty_keys_cmp); void tty_keys_init(struct tty *); void tty_keys_free(struct tty *); -int tty_keys_next(struct tty *, int *, struct mouse_event *); +int tty_keys_next(struct tty *); /* options-cmd.c */ const char *set_option_print( diff --git a/tty-keys.c b/tty-keys.c index 995f2994..c5c14541 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -30,6 +30,7 @@ */ void tty_keys_add(struct tty *, const char *, int, int); +void tty_keys_callback(int, short, void *); int tty_keys_mouse(char *, size_t, size_t *, struct mouse_event *); struct tty_key_ent { @@ -294,25 +295,27 @@ tty_keys_find(struct tty *tty, char *buf, size_t len, size_t *size) } int -tty_keys_next(struct tty *tty, int *key, struct mouse_event *mouse) +tty_keys_next(struct tty *tty) { - struct tty_key *tk; - struct timeval tv; - char *buf; - u_char ch; - size_t len, size; - cc_t bspace; + struct tty_key *tk; + struct timeval tv; + struct mouse_event mouse; + char *buf; + size_t len, size; + cc_t bspace; + int key; + u_char ch; buf = EVBUFFER_DATA(tty->event->input); len = EVBUFFER_LENGTH(tty->event->input); if (len == 0) - return (1); + return (0); log_debug("keys are %zu (%.*s)", len, (int) len, buf); /* If a normal key, return it. */ if (*buf != '\033') { bufferevent_read(tty->event, &ch, 1); - *key = ch; + key = ch; /* * Check for backspace key using termios VERASE - the terminfo @@ -320,8 +323,8 @@ tty_keys_next(struct tty *tty, int *key, struct mouse_event *mouse) * used. termios should have a better idea. */ bspace = tty->tio.c_cc[VERASE]; - if (bspace != _POSIX_VDISABLE && *key == bspace) - *key = KEYC_BSPACE; + if (bspace != _POSIX_VDISABLE && key == bspace) + key = KEYC_BSPACE; goto found; } @@ -329,36 +332,24 @@ tty_keys_next(struct tty *tty, int *key, struct mouse_event *mouse) tk = tty_keys_find(tty, buf + 1, len - 1, &size); if (tk != NULL) { evbuffer_drain(tty->event->input, size + 1); - *key = tk->key; + key = tk->key; goto found; } /* Not found. Is this a mouse key press? */ - *key = tty_keys_mouse(buf, len, &size, mouse); - if (*key != KEYC_NONE) { + key = tty_keys_mouse(buf, len, &size, &mouse); + if (key != KEYC_NONE) { evbuffer_drain(tty->event->input, size); goto found; } /* Not found. Try to parse a key with an xterm-style modifier. */ - *key = xterm_keys_find(buf, len, &size); - if (*key != KEYC_NONE) { + key = xterm_keys_find(buf, len, &size); + if (key != KEYC_NONE) { evbuffer_drain(tty->event->input, size); goto found; } - /* Escape but no key string. If the timer isn't started, start it. */ - if (!(tty->flags & TTY_ESCAPE)) { - tv.tv_sec = 0; - tv.tv_usec = ESCAPE_PERIOD * 1000L; - if (gettimeofday(&tty->key_timer, NULL) != 0) - fatal("gettimeofday failed"); - timeradd(&tty->key_timer, &tv, &tty->key_timer); - - tty->flags |= TTY_ESCAPE; - return (1); - } - /* Skip the escape. */ buf++; len--; @@ -367,7 +358,7 @@ tty_keys_next(struct tty *tty, int *key, struct mouse_event *mouse) if (len != 0 && *buf != '\033') { evbuffer_drain(tty->event->input, 1); bufferevent_read(tty->event, &ch, 1); - *key = ch | KEYC_ESCAPE; + key = ch | KEYC_ESCAPE; goto found; } @@ -376,24 +367,46 @@ tty_keys_next(struct tty *tty, int *key, struct mouse_event *mouse) tk = tty_keys_find(tty, buf + 1, len - 1, &size); if (tk != NULL) { evbuffer_drain(tty->event->input, size + 2); - *key = tk->key | KEYC_ESCAPE; + key = tk->key | KEYC_ESCAPE; goto found; } } - /* If the timer hasn't expired, keep waiting. */ - if (gettimeofday(&tv, NULL) != 0) - fatal("gettimeofday failed"); - if (timercmp(&tty->key_timer, &tv, >)) - return (1); + /* + * Escape but no key string. If have, already seen an escape, then the + * timer must have expired, so give up waiting and send the escape. + */ + if (tty->flags & TTY_ESCAPE) { + evbuffer_drain(tty->event->input, 1); + key = '\033'; + goto found; + } - /* Give up and return the escape. */ - evbuffer_drain(tty->event->input, 1); - *key = '\033'; + /* Start the timer and wait for expiry or more data. */ + tv.tv_sec = 0; + tv.tv_usec = ESCAPE_PERIOD * 1000L; + + evtimer_del(&tty->key_timer); + evtimer_set(&tty->key_timer, tty_keys_callback, tty); + evtimer_add(&tty->key_timer, &tv); + + tty->flags |= TTY_ESCAPE; + return (0); found: + evtimer_del(&tty->key_timer); + tty->key_callback(key, &mouse, tty->key_data); tty->flags &= ~TTY_ESCAPE; - return (0); + return (1); +} + +void +tty_keys_callback(unused int fd, unused short events, void *data) +{ + struct tty *tty = data; + + if (tty->flags & TTY_ESCAPE) + tty_keys_next(tty); } int diff --git a/tty.c b/tty.c index e194febb..6ab3a211 100644 --- a/tty.c +++ b/tty.c @@ -28,6 +28,7 @@ #include "tmux.h" +void tty_read_callback(struct bufferevent *, void *); void tty_error_callback(struct bufferevent *, short, void *); void tty_fill_acs(struct tty *); @@ -113,7 +114,7 @@ tty_open(struct tty *tty, const char *overrides, char **cause) tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_ESCAPE); tty->event = bufferevent_new( - tty->fd, NULL, NULL, tty_error_callback, tty); + tty->fd, tty_read_callback, NULL, tty_error_callback, tty); tty_start_tty(tty); @@ -124,6 +125,15 @@ tty_open(struct tty *tty, const char *overrides, char **cause) return (0); } +void +tty_read_callback(unused struct bufferevent *bufev, void *data) +{ + struct tty *tty = data; + + while (tty_keys_next(tty)) + ; +} + void tty_error_callback( unused struct bufferevent *bufev, unused short what, unused void *data) @@ -259,6 +269,7 @@ tty_close(struct tty *tty) tty->log_fd = -1; } + evtimer_del(&tty->key_timer); tty_stop_tty(tty); if (tty->flags & TTY_OPENED) {