#include #include "tmate.h" #include "tmate-protocol.h" #include "window-copy.h" #define pack(what, ...) _pack(&tmate_session.encoder, what, ##__VA_ARGS__) void tmate_write_header(void) { pack(array, 3); pack(int, TMATE_OUT_HEADER); pack(int, TMATE_PROTOCOL_VERSION); pack(string, VERSION); } void tmate_write_uname(void) { struct utsname name; if (uname(&name) < 0) { tmate_debug("uname() failed"); return; } pack(array, 6); pack(int, TMATE_OUT_UNAME); pack(string, name.sysname); pack(string, name.nodename); pack(string, name.release); pack(string, name.version); pack(string, name.machine); } void tmate_write_ready(void) { pack(array, 1); pack(int, TMATE_OUT_READY); } void tmate_sync_layout(void) { struct session *s; struct winlink *wl; struct window *w; struct window_pane *wp; int num_panes = 0; int num_windows = 0; int active_pane_id; int active_window_idx = -1; /* * TODO this can get a little heavy. * We are shipping the full layout whenever a window name changes, * that is, at every shell command. * Might be better to do something incremental. */ /* * We only allow one session, it makes our lives easier. * Especially when the HTML5 client will come along. * We make no distinction between a winlink and its window except * that we send the winlink idx to draw the status bar properly. */ s = RB_MIN(sessions, &sessions); if (!s) return; num_windows = 0; RB_FOREACH(wl, winlinks, &s->windows) { if (wl->window) num_windows++; } if (!num_windows) return; pack(array, 5); pack(int, TMATE_OUT_SYNC_LAYOUT); pack(int, s->sx); pack(int, s->sy); pack(array, num_windows); RB_FOREACH(wl, winlinks, &s->windows) { w = wl->window; if (!w) continue; w->tmate_last_sync_active_pane = NULL; active_pane_id = -1; if (active_window_idx == -1) active_window_idx = wl->idx; pack(array, 4); pack(int, wl->idx); pack(string, w->name); num_panes = 0; TAILQ_FOREACH(wp, &w->panes, entry) num_panes++; pack(array, num_panes); TAILQ_FOREACH(wp, &w->panes, entry) { pack(array, 5); pack(int, wp->id); pack(int, wp->sx); pack(int, wp->sy); pack(int, wp->xoff); pack(int, wp->yoff); if (wp == w->active) { w->tmate_last_sync_active_pane = wp; active_pane_id = wp->id; } } pack(int, active_pane_id); } if (s->curw) active_window_idx = s->curw->idx; pack(int, active_window_idx); } /* TODO add a buffer for pty_data ? */ #define TMATE_MAX_PTY_SIZE (16*1024) void tmate_pty_data(struct window_pane *wp, const char *buf, size_t len) { size_t to_write; while (len > 0) { to_write = len < TMATE_MAX_PTY_SIZE ? len : TMATE_MAX_PTY_SIZE; pack(array, 3); pack(int, TMATE_OUT_PTY_DATA); pack(int, wp->id); pack(str, to_write); pack(str_body, buf, to_write); buf += to_write; len -= to_write; } } extern const struct cmd_entry cmd_bind_key_entry; extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_set_option_entry; extern const struct cmd_entry cmd_set_window_option_entry; static const struct cmd_entry *replicated_cmds[] = { &cmd_bind_key_entry, &cmd_unbind_key_entry, &cmd_set_option_entry, &cmd_set_window_option_entry, NULL }; int tmate_should_replicate_cmd(const struct cmd_entry *cmd) { const struct cmd_entry **ptr; for (ptr = replicated_cmds; *ptr; ptr++) if (*ptr == cmd) return 1; return 0; } #define sc (&session->saved_tmux_cmds) #define SAVED_TMUX_CMD_INITIAL_SIZE 256 static void __tmate_exec_cmd_args(int argc, const char **argv); static void append_saved_cmd(struct tmate_session *session, int argc, const char **argv) { if (!sc->cmds) { sc->capacity = SAVED_TMUX_CMD_INITIAL_SIZE; sc->cmds = xmalloc(sizeof(*sc->cmds) * sc->capacity); sc->tail = 0; } if (sc->tail == sc->capacity) { sc->capacity *= 2; sc->cmds = xrealloc(sc->cmds, sizeof(*sc->cmds) * sc->capacity); } sc->cmds[sc->tail].argc = argc; sc->cmds[sc->tail].argv = cmd_copy_argv(argc, (char **)argv); sc->tail++; } static void replay_saved_cmd(struct tmate_session *session) { unsigned int i; for (i = 0; i < sc->tail; i++) __tmate_exec_cmd_args(sc->cmds[i].argc, (const char **)sc->cmds[i].argv); } #undef sc struct args_entry { u_char flag; char *value; RB_ENTRY(args_entry) entry; }; static void extract_cmd(struct cmd *cmd, int *_argc, char ***_argv) { struct args_entry *entry; struct args* args = cmd->args; int argc = 0; char **argv; int next = 0, i; argc++; /* cmd name */ RB_FOREACH(entry, args_tree, &args->tree) { argc++; if (entry->value != NULL) argc++; } argc += args->argc; argv = xmalloc(sizeof(char *) * argc); argv[next++] = xstrdup(cmd->entry->name); RB_FOREACH(entry, args_tree, &args->tree) { xasprintf(&argv[next++], "-%c", entry->flag); if (entry->value != NULL) argv[next++] = xstrdup(entry->value); } for (i = 0; i < args->argc; i++) argv[next++] = xstrdup(args->argv[i]); *_argc = argc; *_argv = argv; } static void __tmate_exec_cmd_args(int argc, const char **argv) { int i; pack(array, argc + 1); pack(int, TMATE_OUT_EXEC_CMD); for (i = 0; i < argc; i++) pack(string, argv[i]); } void tmate_exec_cmd_args(int argc, const char **argv) { __tmate_exec_cmd_args(argc, argv); append_saved_cmd(&tmate_session, argc, argv); } void tmate_set_val(const char *name, const char *value) { char *buf; xasprintf(&buf, "%s=%s", name, value); tmate_exec_cmd_args(3, (const char *[]){"set-option", "tmate-set", buf}); free(buf); } void tmate_exec_cmd(struct cmd *cmd) { int argc; char **argv; extract_cmd(cmd, &argc, &argv); tmate_exec_cmd_args(argc, (const char **)argv); cmd_free_argv(argc, argv); } void tmate_failed_cmd(int client_id, const char *cause) { pack(array, 3); pack(int, TMATE_OUT_FAILED_CMD); pack(int, client_id); pack(string, cause); } void tmate_status(const char *left, const char *right) { static char *old_left, *old_right; if (old_left && !strcmp(old_left, left) && old_right && !strcmp(old_right, right)) return; pack(array, 3); pack(int, TMATE_OUT_STATUS); pack(string, left); pack(string, right); free(old_left); free(old_right); old_left = xstrdup(left); old_right = xstrdup(right); } void tmate_sync_copy_mode(struct window_pane *wp) { struct window_copy_mode_data *data = wp->modedata; pack(array, 3); pack(int, TMATE_OUT_SYNC_COPY_MODE); pack(int, wp->id); if (wp->mode != &window_copy_mode || data->inputtype == WINDOW_COPY_PASSWORD) { pack(array, 0); return; } pack(array, 6); pack(int, data->backing == &wp->base); pack(int, data->oy); pack(int, data->cx); pack(int, data->cy); if (data->screen.sel.flag) { pack(array, 3); pack(int, data->selx); pack(int, -data->sely + screen_hsize(data->backing) + screen_size_y(data->backing) - 1); pack(int, data->rectflag); } else pack(array, 0); if (data->inputprompt) { pack(array, 3); pack(int, data->inputtype); pack(string, data->inputprompt); pack(string, data->inputstr); } else pack(array, 0); } void tmate_write_copy_mode(struct window_pane *wp, const char *str) { pack(array, 3); pack(int, TMATE_OUT_WRITE_COPY_MODE); pack(int, wp->id); pack(string, str); } void tmate_write_fin(void) { pack(array, 1); pack(int, TMATE_OUT_FIN); } static void do_snapshot_grid(struct grid *grid, unsigned int max_history_lines) { struct grid_line *line; struct grid_cell gc; unsigned int line_i, i; unsigned int max_lines; size_t str_len; max_lines = max_history_lines + grid->sy; #define grid_num_lines(grid) (grid->hsize + grid->sy) if (grid_num_lines(grid) > max_lines) line_i = grid_num_lines(grid) - max_lines; else line_i = 0; pack(array, grid_num_lines(grid) - line_i); for (; line_i < grid_num_lines(grid); line_i++) { line = &grid->linedata[line_i]; pack(array, 2); str_len = 0; for (i = 0; i < line->cellsize; i++) { grid_get_cell(grid, i, line_i, &gc); str_len += gc.data.size; } pack(str, str_len); for (i = 0; i < line->cellsize; i++) { grid_get_cell(grid, i, line_i, &gc); pack(str_body, gc.data.data, gc.data.size); } pack(array, line->cellsize); for (i = 0; i < line->cellsize; i++) { grid_get_cell(grid, i, line_i, &gc); pack(unsigned_int, ((gc.flags << 24) | (gc.attr << 16) | (gc.bg << 8) | gc.fg )); } } } static void do_snapshot_pane(struct window_pane *wp, unsigned int max_history_lines) { struct screen *screen = &wp->base; pack(array, 4); pack(int, wp->id); pack(unsigned_int, screen->mode); pack(array, 3); pack(int, screen->cx); pack(int, screen->cy); do_snapshot_grid(screen->grid, max_history_lines); if (wp->saved_grid) { pack(array, 3); pack(int, wp->saved_cx); pack(int, wp->saved_cy); do_snapshot_grid(wp->saved_grid, max_history_lines); } else { pack(nil); } } static void tmate_send_session_snapshot(unsigned int max_history_lines) { struct session *s; struct winlink *wl; struct window *w; struct window_pane *pane; int num_panes; pack(array, 2); pack(int, TMATE_OUT_SNAPSHOT); s = RB_MIN(sessions, &sessions); if (!s) tmate_fatal("no session?"); num_panes = 0; RB_FOREACH(wl, winlinks, &s->windows) { w = wl->window; if (!w) continue; TAILQ_FOREACH(pane, &w->panes, entry) num_panes++; } pack(array, num_panes); RB_FOREACH(wl, winlinks, &s->windows) { w = wl->window; if (!w) continue; TAILQ_FOREACH(pane, &w->panes, entry) do_snapshot_pane(pane, max_history_lines); } } static void tmate_send_reconnection_data(struct tmate_session *session) { if (!session->reconnection_data) return; pack(array, 2); pack(int, TMATE_OUT_RECONNECT); pack(string, session->reconnection_data); } #define RECONNECTION_MAX_HISTORY_LINE 300 void tmate_send_reconnection_state(struct tmate_session *session) { /* Start with a fresh encoder */ tmate_encoder_destroy(&session->encoder); tmate_encoder_init(&session->encoder, NULL, session); tmate_write_header(); tmate_send_reconnection_data(session); replay_saved_cmd(session); /* TODO send all option variables */ tmate_write_uname(); tmate_write_ready(); tmate_sync_layout(); tmate_send_session_snapshot(RECONNECTION_MAX_HISTORY_LINE); }