diff --git a/.gitignore b/.gitignore index c3906ada..7721edac 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ tmux Makefile Makefile.in configure +tmate +cscope.* +ctags diff --git a/Makefile.am b/Makefile.am index 2ce54b1a..b17ca578 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,8 +1,8 @@ # $Id$ # Obvious program stuff. -bin_PROGRAMS = tmux -dist_man1_MANS = tmux.1 +bin_PROGRAMS = tmate +dist_man1_MANS = tmate.1 # Distribution tarball options. EXTRA_DIST = \ @@ -21,6 +21,8 @@ if IS_GLIBC CFLAGS += -D_GNU_SOURCE endif +CFLAGS += -Wno-unused-parameter -Wno-unused-variable + # Set flags for gcc. gcc4 whines abouts silly stuff so it needs slightly # different flags. if IS_GCC @@ -57,7 +59,7 @@ CFLAGS += -erroff=E_EMPTY_DECLARATION endif # List of sources. -dist_tmux_SOURCES = \ +dist_tmate_SOURCES = \ arguments.c \ attributes.c \ cfg.c \ @@ -171,6 +173,10 @@ dist_tmux_SOURCES = \ session.c \ signal.c \ status.c \ + tmate-ssh-client.c \ + tmate-encoder.c \ + tmate-decoder.c \ + tmate.c \ tmux.c \ tty-acs.c \ tty-keys.c \ @@ -183,66 +189,51 @@ dist_tmux_SOURCES = \ window.c \ xmalloc.c \ xterm-keys.c -nodist_tmux_SOURCES = osdep-@PLATFORM@.c +nodist_tmate_SOURCES = osdep-@PLATFORM@.c # Pile in all the compat/ stuff that is needed. if NO_FORKPTY -nodist_tmux_SOURCES += compat/forkpty-@PLATFORM@.c +nodist_tmate_SOURCES += compat/forkpty-@PLATFORM@.c endif if NO_IMSG -nodist_tmux_SOURCES += compat/imsg.c compat/imsg-buffer.c +nodist_tmate_SOURCES += compat/imsg.c compat/imsg-buffer.c endif if NO_CLOSEFROM -nodist_tmux_SOURCES += compat/closefrom.c +nodist_tmate_SOURCES += compat/closefrom.c endif if NO_DAEMON -nodist_tmux_SOURCES += compat/daemon.c +nodist_tmate_SOURCES += compat/daemon.c endif if NO_SETENV -nodist_tmux_SOURCES += compat/setenv.c +nodist_tmate_SOURCES += compat/setenv.c endif if NO_STRLCAT -nodist_tmux_SOURCES += compat/strlcat.c +nodist_tmate_SOURCES += compat/strlcat.c endif if NO_STRLCPY -nodist_tmux_SOURCES += compat/strlcpy.c +nodist_tmate_SOURCES += compat/strlcpy.c endif if NO_ASPRINTF -nodist_tmux_SOURCES += compat/asprintf.c +nodist_tmate_SOURCES += compat/asprintf.c endif if NO_FGETLN -nodist_tmux_SOURCES += compat/fgetln.c +nodist_tmate_SOURCES += compat/fgetln.c endif if NO_GETOPT -nodist_tmux_SOURCES += compat/getopt.c +nodist_tmate_SOURCES += compat/getopt.c endif if NO_STRCASESTR -nodist_tmux_SOURCES += compat/strcasestr.c +nodist_tmate_SOURCES += compat/strcasestr.c endif if NO_STRSEP -nodist_tmux_SOURCES += compat/strsep.c +nodist_tmate_SOURCES += compat/strsep.c endif if NO_VIS -nodist_tmux_SOURCES += compat/vis.c compat/unvis.c +nodist_tmate_SOURCES += compat/vis.c compat/unvis.c endif if NO_STRTONUM -nodist_tmux_SOURCES += compat/strtonum.c +nodist_tmate_SOURCES += compat/strtonum.c endif if NO_B64_NTOP -nodist_tmux_SOURCES += compat/b64_ntop.c +nodist_tmate_SOURCES += compat/b64_ntop.c endif - -# Update SF web site. -upload-index.html: update-index.html - scp www/index.html www/main.css www/images/*.png \ - ${USER},tmux@web.sf.net:/home/groups/t/tm/tmux/htdocs - rm -f www/index.html www/images/small-* - -update-index.html: - (cd www/images && \ - rm -f small-* && \ - for i in *.png; do \ - convert "$$i" -resize 200x150 "small-$$i"; \ - done \ - ) - sed "s/%%VERSION%%/${VERSION}/g" www/index.html.in >www/index.html diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 8ed9a1a6..a0e85513 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -41,6 +41,10 @@ const struct cmd_entry cmd_break_pane_entry = { enum cmd_retval cmd_break_pane_exec(struct cmd *self, struct cmd_q *cmdq) { +#ifdef TMATE + cmdq_error(cmdq, "break pane is not supported with tmate"); + return (CMD_RETURN_ERROR); +#else struct args *args = self->args; struct winlink *wl; struct session *s; @@ -112,4 +116,5 @@ cmd_break_pane_exec(struct cmd *self, struct cmd_q *cmdq) format_free(ft); } return (CMD_RETURN_NORMAL); +#endif } diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 2cf587e0..971bc0b9 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -76,6 +76,10 @@ cmd_join_pane_exec(struct cmd *self, struct cmd_q *cmdq) enum cmd_retval join_pane(struct cmd *self, struct cmd_q *cmdq, int not_same_window) { +#ifdef TMATE + cmdq_error(cmdq, "join pane is not supported with tmate"); + return (CMD_RETURN_ERROR); +#else struct args *args = self->args; struct session *dst_s; struct winlink *src_wl, *dst_wl; @@ -170,4 +174,5 @@ join_pane(struct cmd *self, struct cmd_q *cmdq, int not_same_window) notify_window_layout_changed(dst_w); return (CMD_RETURN_NORMAL); +#endif } diff --git a/configure.ac b/configure.ac index c0ef34c6..bfffefa1 100644 --- a/configure.ac +++ b/configure.ac @@ -107,6 +107,9 @@ AC_MSG_RESULT($found_glibc) # Look for clock_gettime. Must come before event_init. AC_SEARCH_LIBS(clock_gettime, rt) +AC_SEARCH_LIBS(ssh_new, ssh) +AC_SEARCH_LIBS(msgpack_object_print, msgpack) + # Look for libevent. PKG_CHECK_MODULES( LIBEVENT, diff --git a/resize.c b/resize.c index 5c365dfe..7f2ed236 100644 --- a/resize.c +++ b/resize.c @@ -21,6 +21,7 @@ #include #include "tmux.h" +#include "tmate.h" /* * Recalculate window and session sizes. @@ -71,6 +72,17 @@ recalculate_sizes(void) ssy = c->tty.sy; } } + +#ifdef TMATE + /* We assume a single session */ + if (tmate_sx > 0 && tmate_sy > 0) { + if ((u_int)tmate_sx < ssx) + ssx = tmate_sx; + if ((u_int)tmate_sy < ssy) + ssy = tmate_sy; + } +#endif + if (ssx == UINT_MAX || ssy == UINT_MAX) { s->flags |= SESSION_UNATTACHED; continue; diff --git a/server-client.c b/server-client.c index 77e6de78..241e1bed 100644 --- a/server-client.c +++ b/server-client.c @@ -27,6 +27,7 @@ #include #include "tmux.h" +#include "tmate.h" void server_client_check_focus(struct window_pane *); void server_client_check_resize(struct window_pane *); @@ -511,6 +512,9 @@ server_client_loop(void) if (w == NULL) continue; + if (w->flags & WINDOW_REDRAW) + tmate_sync_window(w); + w->flags &= ~WINDOW_REDRAW; TAILQ_FOREACH(wp, &w->panes, entry) { server_client_check_focus(wp); diff --git a/server.c b/server.c index 4bfa9185..352255f7 100644 --- a/server.c +++ b/server.c @@ -36,6 +36,7 @@ #include #include "tmux.h" +#include "tmate.h" /* * Main server functions. @@ -195,6 +196,9 @@ server_start(int lockfd, char *lockfile) evtimer_add(&server_ev_second, &tv); set_signals(server_signal_callback); + + tmate_client_start(); + server_loop(); exit(0); } diff --git a/session.c b/session.c index 74eb06a5..d3d82ea2 100644 --- a/session.c +++ b/session.c @@ -25,6 +25,7 @@ #include #include "tmux.h" +#include "tmate.h" /* Global session list. */ struct sessions sessions; @@ -90,6 +91,11 @@ session_create(const char *name, const char *cmd, const char *cwd, { struct session *s; + if (next_session_id != 0) { + xasprintf(cause, "multi sessions is not supported with tmate"); + return NULL; + } + s = xmalloc(sizeof *s); s->references = 0; s->flags = 0; diff --git a/tmate-decoder.c b/tmate-decoder.c new file mode 100644 index 00000000..476077af --- /dev/null +++ b/tmate-decoder.c @@ -0,0 +1,118 @@ +#include "tmate.h" + +int tmate_sx = -1; +int tmate_sy = -1; + +struct tmate_unpacker { + msgpack_object *argv; + int argc; +}; + +static void decoder_error(void) +{ + tmate_fatal("Received a bad message"); +} + +static void init_unpacker(struct tmate_unpacker *uk, + msgpack_object obj) +{ + if (obj.type != MSGPACK_OBJECT_ARRAY) + decoder_error(); + + uk->argv = obj.via.array.ptr; + uk->argc = obj.via.array.size; +} + +static int64_t unpack_int(struct tmate_unpacker *uk) +{ + int64_t val; + + if (uk->argc == 0) + decoder_error(); + + if (uk->argv[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER && + uk->argv[0].type != MSGPACK_OBJECT_NEGATIVE_INTEGER) + decoder_error(); + + val = uk->argv[0].via.i64; + + uk->argv++; + uk->argc--; + + return val; +} + +static void tmate_client_key(struct tmate_unpacker *uk) +{ + struct client *c; + int key = unpack_int(uk); + + /* Very gross. other clients cannot even detach */ + + if (ARRAY_LENGTH(&clients) > 0) { + c = ARRAY_ITEM(&clients, 0); + server_client_handle_key(c, key); + } +} + +static void tmate_client_resize(struct tmate_unpacker *uk) +{ + /* A bit gross as well */ + tmate_sx = unpack_int(uk); + tmate_sy = unpack_int(uk); + recalculate_sizes(); + + /* TODO Handle reconnection cases */ +} + +static void handle_message(msgpack_object obj) +{ + struct tmate_unpacker _uk; + struct tmate_unpacker *uk = &_uk; + int cmd; + + init_unpacker(uk, obj); + + switch (unpack_int(uk)) { + case TMATE_CLIENT_KEY: tmate_client_key(uk); break; + case TMATE_CLIENT_RESIZE: tmate_client_resize(uk); break; + default: decoder_error(); + } +} + +void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len) +{ + msgpack_unpacked result; + + msgpack_unpacker_buffer_consumed(&decoder->unpacker, len); + + msgpack_unpacked_init(&result); + while (msgpack_unpacker_next(&decoder->unpacker, &result)) { + handle_message(result.data); + } + msgpack_unpacked_destroy(&result); + + if (msgpack_unpacker_message_size(&decoder->unpacker) > + TMATE_MAX_MESSAGE_SIZE) { + tmate_fatal("Message too big"); + } +} + +void tmate_decoder_get_buffer(struct tmate_decoder *decoder, + char **buf, size_t *len) +{ + /* rewind the buffer if possible */ + if (msgpack_unpacker_buffer_capacity(&decoder->unpacker) < + TMATE_MAX_MESSAGE_SIZE) { + msgpack_unpacker_expand_buffer(&decoder->unpacker, 0); + } + + *buf = msgpack_unpacker_buffer(&decoder->unpacker); + *len = msgpack_unpacker_buffer_capacity(&decoder->unpacker); +} + +void tmate_decoder_init(struct tmate_decoder *decoder) +{ + if (!msgpack_unpacker_init(&decoder->unpacker, 2*TMATE_MAX_MESSAGE_SIZE)) + tmate_fatal("cannot initialize the unpacker"); +} diff --git a/tmate-encoder.c b/tmate-encoder.c new file mode 100644 index 00000000..a4449e5c --- /dev/null +++ b/tmate-encoder.c @@ -0,0 +1,89 @@ +#include + +#include "tmate.h" + +static int msgpack_write(void *data, const char *buf, unsigned int len) +{ + struct tmate_encoder *encoder = data; + + evbuffer_add(encoder->buffer, buf, len); + + if ((encoder->ev_readable.ev_flags & EVLIST_INSERTED) && + !(encoder->ev_readable.ev_flags & EVLIST_ACTIVE)) { + event_active(&encoder->ev_readable, EV_READ, 0); + } + + return 0; +} + +void tmate_encoder_init(struct tmate_encoder *encoder) +{ + msgpack_packer_init(&encoder->pk, encoder, &msgpack_write); + encoder->buffer = evbuffer_new(); +} + +#define msgpack_pack_string(pk, str) do { \ + int __strlen = strlen(str); \ + msgpack_pack_raw(pk, __strlen); \ + msgpack_pack_raw_body(pk, str, __strlen); \ +} while(0) + +#define pack(what, ...) msgpack_pack_##what(&tmate_encoder->pk, __VA_ARGS__) + +void tmate_write_header(void) +{ + pack(array, 2); + pack(int, TMATE_HEADER); + pack(int, TMATE_PROTOCOL_VERSION); +} + +void tmate_sync_window(struct window *w) +{ + struct window_pane *wp; + int num_panes = 0; + int active_pane_id = -1; + + pack(array, 7); + pack(int, TMATE_SYNC_WINDOW); + + pack(int, w->id); + pack(string, w->name); + pack(int, w->sx); + pack(int, w->sy); + + 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) + active_pane_id = wp->id; + } + pack(int, active_pane_id); +} + +void tmate_pty_data(struct window_pane *wp, const char *buf, size_t len) +{ + size_t max_write, to_write; + + max_write = TMATE_MAX_MESSAGE_SIZE - 4; + while (len > 0) { + to_write = len < max_write ? len : max_write; + + pack(array, 3); + pack(int, TMATE_PTY_DATA); + pack(int, wp->id); + pack(raw, to_write); + pack(raw_body, buf, to_write); + + buf += to_write; + len -= to_write; + } +} diff --git a/tmate-ssh-client.c b/tmate-ssh-client.c new file mode 100644 index 00000000..581ca772 --- /dev/null +++ b/tmate-ssh-client.c @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include +#include + +#include "tmate.h" + +static void consume_channel(struct tmate_ssh_client *client); +static void flush_input_stream(struct tmate_ssh_client *client); +static void __flush_input_stream(evutil_socket_t fd, short what, void *arg); +static void __on_session_event(evutil_socket_t fd, short what, void *arg); +static void disconnect_session(struct tmate_ssh_client *client); +static void reconnect_session(struct tmate_ssh_client *client); + +static void log_function(ssh_session session, int priority, + const char *message, void *userdata) +{ + tmate_debug("[%d] %s", priority, message); +} + +static struct ssh_callbacks_struct ssh_session_callbacks = { + .log_function = log_function +}; + + +static void register_session_fd_event(struct tmate_ssh_client *client) +{ + if (!event_initialized(&client->ev_ssh)) { + int flag = 1; + setsockopt(ssh_get_fd(client->session), IPPROTO_TCP, + TCP_NODELAY, &flag, sizeof(flag)); + + event_assign(&client->ev_ssh, ev_base, ssh_get_fd(client->session), + EV_READ | EV_PERSIST, __on_session_event, client); + event_add(&client->ev_ssh, NULL); + } +} + +static void register_input_stream_event(struct tmate_ssh_client *client) +{ + if (!event_initialized(&client->encoder->ev_readable)) { + event_assign(&client->encoder->ev_readable, ev_base, -1, + EV_READ | EV_PERSIST, __flush_input_stream, client); + event_add(&client->encoder->ev_readable, NULL); + } +} + +static int __ssh_userauth_autopubkey(ssh_session session, const char *passphrase) +{ + int ret; + + /* For some reason, auth doesn't work in blocking mode :( */ + ssh_set_blocking(session, 1); + ret = ssh_userauth_autopubkey(session, passphrase); + ssh_set_blocking(session, 0); + + return ret; +} + +static void consume_channel(struct tmate_ssh_client *client) +{ + char *buf; + ssize_t len; + + for (;;) { + tmate_decoder_get_buffer(client->decoder, &buf, &len); + len = ssh_channel_read_nonblocking(client->channel, buf, len, 0); + if (len < 0) { + tmate_debug("Error reading from channel: %s", + ssh_get_error(client->session)); + reconnect_session(client); + break; + } + + if (len == 0) + break; + + tmate_decoder_commit(client->decoder, len); + } +} + +static void on_session_event(struct tmate_ssh_client *client) +{ + int verbosity = SSH_LOG_RARE; + int port = 2200; + + ssh_session session = client->session; + ssh_channel channel = client->channel; + + switch (client->state) { + case SSH_INIT: + client->session = session = ssh_new(); + if (!session) { + tmate_fatal("cannot initialize"); + return; + } + + ssh_set_callbacks(session, &ssh_session_callbacks); + + client->channel = channel = ssh_channel_new(session); + if (!channel) { + tmate_fatal("cannot initialize"); + return; + } + + ssh_set_blocking(session, 0); + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(session, SSH_OPTIONS_PORT, &port); + ssh_options_set(session, SSH_OPTIONS_USER, "tmate"); + + tmate_debug("Connecting..."); + client->state = SSH_CONNECT; + /* fall through */ + + case SSH_CONNECT: + switch (ssh_connect(session)) { + case SSH_AGAIN: + register_session_fd_event(client); + return; + case SSH_ERROR: + tmate_debug("Error connecting: %s", ssh_get_error(session)); + reconnect_session(client); + return; + case SSH_OK: + register_session_fd_event(client); + tmate_debug("Connected"); + client->state = SSH_AUTH; + /* fall through */ + } + + /* TODO Authenticate server */ + + case SSH_AUTH: + switch (__ssh_userauth_autopubkey(session, NULL)) { + case SSH_AUTH_AGAIN: + return; + case SSH_AUTH_PARTIAL: + case SSH_AUTH_INFO: + case SSH_AUTH_DENIED: + tmate_debug("Access denied. Try again later."); + disconnect_session(client); + return; + case SSH_AUTH_ERROR: + tmate_debug("Auth error: %s", ssh_get_error(session)); + reconnect_session(client); + return; + case SSH_AUTH_SUCCESS: + tmate_debug("Auth successful"); + client->state = SSH_OPEN_CHANNEL; + /* fall through */ + } + + case SSH_OPEN_CHANNEL: + switch (ssh_channel_open_session(channel)) { + case SSH_AGAIN: + return; + case SSH_ERROR: + tmate_debug("Error opening session: %s", ssh_get_error(session)); + reconnect_session(client); + return; + case SSH_OK: + tmate_debug("Session opened, initalizing tmate"); + client->state = SSH_BOOTSTRAP; + /* fall through */ + } + + case SSH_BOOTSTRAP: + switch (ssh_channel_request_subsystem(channel, "tmate")) { + case SSH_AGAIN: + return; + case SSH_ERROR: + tmate_debug("Error initializing tmate: %s", ssh_get_error(session)); + reconnect_session(client); + return; + case SSH_OK: + tmate_debug("Ready"); + + /* Writes are now performed in a blocking fashion */ + ssh_set_blocking(session, 1); + + client->state = SSH_READY; + register_input_stream_event(client); + flush_input_stream(client); + /* fall through */ + } + + case SSH_READY: + consume_channel(client); + if (!ssh_is_connected(session)) { + tmate_debug("Disconnected"); + reconnect_session(client); + return; + } + } +} + +static void flush_input_stream(struct tmate_ssh_client *client) +{ + struct evbuffer *evb = client->encoder->buffer; + ssize_t len, written; + char *buf; + + if (client->state < SSH_READY) + return; + + for (;;) { + len = evbuffer_get_length(evb); + if (!len) + break; + + buf = evbuffer_pullup(evb, -1); + + written = ssh_channel_write(client->channel, buf, len); + if (written < 0) { + tmate_debug("Error writing to channel: %s", + ssh_get_error(client->session)); + reconnect_session(client); + return; + } + + evbuffer_drain(evb, written); + } +} + +static void __flush_input_stream(evutil_socket_t fd, short what, void *arg) +{ + flush_input_stream(arg); +} + +static void __on_session_event(evutil_socket_t fd, short what, void *arg) +{ + on_session_event(arg); +} + +static void disconnect_session(struct tmate_ssh_client *client) +{ + if (event_initialized(&client->ev_ssh)) { + event_del(&client->ev_ssh); + client->ev_ssh.ev_flags = 0; + } + + if (event_initialized(&client->encoder->ev_readable)) { + event_del(&client->encoder->ev_readable); + client->encoder->ev_readable.ev_flags = 0; + } + + if (client->session) { + /* ssh_free() also frees the associated channels. */ + ssh_free(client->session); + client->session = NULL; + client->channel = NULL; + } + + client->state = SSH_NONE; +} + +static void connect_session(struct tmate_ssh_client *client) +{ + if (!client->session) { + client->state = SSH_INIT; + on_session_event(client); + } +} + +static void on_reconnect_timer(evutil_socket_t fd, short what, void *arg) +{ + connect_session(arg); +} + +static void reconnect_session(struct tmate_ssh_client *client) +{ + struct timeval tv; + + disconnect_session(client); + + tv.tv_sec = 1; + tv.tv_usec = 0; + evtimer_add(&client->ev_ssh_reconnect, &tv); +} + +void tmate_ssh_client_init(struct tmate_ssh_client *client, + struct tmate_encoder *encoder, + struct tmate_decoder *decoder) +{ + ssh_callbacks_init(&ssh_session_callbacks); + + client->state = SSH_NONE; + client->session = NULL; + client->channel = NULL; + + client->encoder = encoder; + client->decoder = decoder; + + evtimer_assign(&client->ev_ssh_reconnect, ev_base, + on_reconnect_timer, client); + + connect_session(client); +} diff --git a/tmate.1 b/tmate.1 new file mode 120000 index 00000000..44d7405b --- /dev/null +++ b/tmate.1 @@ -0,0 +1 @@ +tmux.1 \ No newline at end of file diff --git a/tmate.c b/tmate.c new file mode 100644 index 00000000..c670d39c --- /dev/null +++ b/tmate.c @@ -0,0 +1,18 @@ +#include "tmate.h" + +struct tmate_encoder *tmate_encoder; + +static struct tmate_ssh_client client; +static struct tmate_encoder encoder; +static struct tmate_decoder decoder; + +void tmate_client_start(void) +{ + tmate_encoder_init(&encoder); + tmate_decoder_init(&decoder); + tmate_encoder = &encoder; + + tmate_ssh_client_init(&client, &encoder, &decoder); + + tmate_write_header(); +} diff --git a/tmate.h b/tmate.h new file mode 100644 index 00000000..78d0e422 --- /dev/null +++ b/tmate.h @@ -0,0 +1,94 @@ +#ifndef TMATE_H +#define TMATE_H + +#include +#include +#include + +#include "tmux.h" + +#define tmate_debug(...) log_debug("[tmate] " __VA_ARGS__) +#define tmate_warn(...) log_warn("[tmate] " __VA_ARGS__) +#define tmate_info(...) log_info("[tmate] " __VA_ARGS__) +#define tmate_fatal(...) log_fatal("[tmate] " __VA_ARGS__) + +/* tmate-encoder.c */ + +#define TMATE_MAX_MESSAGE_SIZE (16*1024) + +#define TMATE_PROTOCOL_VERSION 1 + +enum tmate_commands { + TMATE_HEADER, + TMATE_SYNC_WINDOW, + TMATE_PTY_DATA, +}; + +struct tmate_encoder { + msgpack_packer pk; + struct evbuffer *buffer; + struct event ev_readable; +}; + +extern void tmate_encoder_init(struct tmate_encoder *encoder); + +extern void tmate_write_header(void); +extern void tmate_sync_window(struct window *w); +extern void tmate_pty_data(struct window_pane *wp, const char *buf, size_t len); + +/* tmate-decoder.c */ + +enum tmate_notifications { + TMATE_CLIENT_KEY, + TMATE_CLIENT_RESIZE, +}; + +struct tmate_decoder { + struct msgpack_unpacker unpacker; +}; + +extern int tmate_sx; +extern int tmate_sy; + +extern void tmate_decoder_init(struct tmate_decoder *decoder); +extern void tmate_decoder_get_buffer(struct tmate_decoder *decoder, + char **buf, size_t *len); +extern void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len); + +/* tmate-ssh-client.c */ + +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_channel_struct* ssh_channel; + +enum tmate_ssh_client_state_types { + SSH_NONE, + SSH_INIT, + SSH_CONNECT, + SSH_AUTH, + SSH_OPEN_CHANNEL, + SSH_BOOTSTRAP, + SSH_READY, +}; + +struct tmate_ssh_client { + int state; + ssh_session session; + ssh_channel channel; + + struct tmate_encoder *encoder; + struct tmate_decoder *decoder; + + struct event ev_ssh; + struct event ev_ssh_reconnect; +}; + +extern void tmate_ssh_client_init(struct tmate_ssh_client *client, + struct tmate_encoder *encoder, + struct tmate_decoder *decoder); + +/* tmate.c */ + +extern struct tmate_encoder *tmate_encoder; +extern void tmate_client_start(void); + +#endif diff --git a/tmux.c b/tmux.c index 8ea91ebe..65c94389 100644 --- a/tmux.c +++ b/tmux.c @@ -168,9 +168,9 @@ makesocketpath(const char *label) uid = getuid(); if ((s = getenv("TMPDIR")) == NULL || *s == '\0') - xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid); + xsnprintf(base, sizeof base, "%s/tmate-%u", _PATH_TMP, uid); else - xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid); + xsnprintf(base, sizeof base, "%s/tmate-%u", s, uid); if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) return (NULL); @@ -244,7 +244,8 @@ main(int argc, char **argv) malloc_options = (char *) "AFGJPX"; #endif - quiet = flags = 0; + flags = IDENTIFY_256COLOURS | IDENTIFY_UTF8; + quiet = 0; label = path = NULL; login_shell = (**argv == '-'); while ((opt = getopt(argc, argv, "28c:Cdf:lL:qS:uUvV")) != -1) { diff --git a/tmux.h b/tmux.h index 9c91d6a4..c8afa93e 100644 --- a/tmux.h +++ b/tmux.h @@ -19,6 +19,8 @@ #ifndef TMUX_H #define TMUX_H +#define TMATE + #define PROTOCOL_VERSION 7 #include @@ -959,6 +961,8 @@ struct window_pane { struct bufferevent *pipe_event; size_t pipe_off; + size_t tmate_off; + struct screen *screen; struct screen base; diff --git a/window.c b/window.c index 7678adc6..f4bccadb 100644 --- a/window.c +++ b/window.c @@ -30,6 +30,7 @@ #include #include "tmux.h" +#include "tmate.h" /* * Each window is attached to a number of panes, each of which is a pty. This @@ -328,6 +329,8 @@ window_create(const char *name, const char *cmd, const char *shell, } else w->name = default_window_name(w); + tmate_sync_window(w); + return (w); } @@ -374,6 +377,7 @@ window_set_name(struct window *w, const char *new_name) free(w->name); w->name = xstrdup(new_name); notify_window_renamed(w); + tmate_sync_window(w); } void @@ -691,6 +695,8 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->pipe_off = 0; wp->pipe_event = NULL; + wp->tmate_off = 0; + wp->saved_grid = NULL; screen_init(&wp->base, sx, sy, hlimit); @@ -872,13 +878,24 @@ window_pane_read_callback(unused struct bufferevent *bufev, void *data) new_size = EVBUFFER_LENGTH(wp->event->input) - wp->pipe_off; if (wp->pipe_fd != -1 && new_size > 0) { + /* FIXME tmux: + * - new_data = EVBUFFER_DATA(wp->event->input); + * + new_data = EVBUFFER_DATA(wp->event->input) + wp->pipe_off; + * also, can the buffer be too small? + */ new_data = EVBUFFER_DATA(wp->event->input); bufferevent_write(wp->pipe_event, new_data, new_size); } + new_size = EVBUFFER_LENGTH(wp->event->input) - wp->tmate_off; + new_data = EVBUFFER_DATA(wp->event->input) + wp->tmate_off; + if (new_size > 0) + tmate_pty_data(wp, new_data, new_size); + input_parse(wp); wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); + wp->tmate_off = EVBUFFER_LENGTH(wp->event->input); /* * If we get here, we're not outputting anymore, so set the silence