From 35876eaab991efc7759802f184cdd54663ea8a94 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 1 Jun 2009 22:58:49 +0000 Subject: [PATCH] Import tmux, a terminal multiplexor allowing (among other things) a single terminal to be switched between several different windows and programs displayed on one terminal be detached from one terminal and moved to another. ok deraadt pirofti --- Makefile | 48 ++ arg.c | 194 +++++ array.h | 119 +++ attributes.c | 92 +++ buffer-poll.c | 97 +++ buffer.c | 227 ++++++ cfg.c | 132 ++++ client-fn.c | 92 +++ client-msg.c | 159 ++++ client.c | 226 ++++++ clock.c | 161 ++++ cmd-attach-session.c | 80 ++ cmd-bind-key.c | 157 ++++ cmd-break-pane.c | 92 +++ cmd-choose-session.c | 107 +++ cmd-choose-window.c | 106 +++ cmd-clear-history.c | 68 ++ cmd-clock-mode.c | 54 ++ cmd-command-prompt.c | 178 +++++ cmd-confirm-before.c | 141 ++++ cmd-copy-buffer.c | 222 ++++++ cmd-copy-mode.c | 56 ++ cmd-delete-buffer.c | 61 ++ cmd-detach-client.c | 54 ++ cmd-down-pane.c | 61 ++ cmd-find-window.c | 160 ++++ cmd-generic.c | 694 ++++++++++++++++ cmd-has-session.c | 51 ++ cmd-kill-pane.c | 72 ++ cmd-kill-server.c | 51 ++ cmd-kill-session.c | 68 ++ cmd-kill-window.c | 69 ++ cmd-last-window.c | 60 ++ cmd-link-window.c | 109 +++ cmd-list-buffers.c | 91 +++ cmd-list-clients.c | 67 ++ cmd-list-commands.c | 51 ++ cmd-list-keys.c | 59 ++ cmd-list-sessions.c | 67 ++ cmd-list-windows.c | 88 +++ cmd-list.c | 154 ++++ cmd-load-buffer.c | 106 +++ cmd-lock-server.c | 54 ++ cmd-move-window.c | 123 +++ cmd-new-session.c | 248 ++++++ cmd-new-window.c | 241 ++++++ cmd-next-layout.c | 55 ++ cmd-next-window.c | 78 ++ cmd-paste-buffer.c | 78 ++ cmd-previous-layout.c | 55 ++ cmd-previous-window.c | 78 ++ cmd-refresh-client.c | 54 ++ cmd-rename-session.c | 57 ++ cmd-rename-window.c | 61 ++ cmd-resize-pane.c | 105 +++ cmd-respawn-window.c | 87 ++ cmd-rotate-window.c | 111 +++ cmd-save-buffer.c | 90 +++ cmd-scroll-mode.c | 72 ++ cmd-select-layout.c | 86 ++ cmd-select-pane.c | 69 ++ cmd-select-prompt.c | 93 +++ cmd-select-window.c | 71 ++ cmd-send-keys.c | 184 +++++ cmd-send-prefix.c | 57 ++ cmd-server-info.c | 179 +++++ cmd-set-buffer.c | 64 ++ cmd-set-option.c | 169 ++++ cmd-set-password.c | 169 ++++ cmd-set-window-option.c | 170 ++++ cmd-show-buffer.c | 89 +++ cmd-show-options.c | 110 +++ cmd-show-window-options.c | 110 +++ cmd-source-file.c | 147 ++++ cmd-split-window.c | 235 ++++++ cmd-start-server.c | 46 ++ cmd-string.c | 309 ++++++++ cmd-suspend-client.c | 64 ++ cmd-swap-pane.c | 285 +++++++ cmd-swap-window.c | 75 ++ cmd-switch-client.c | 161 ++++ cmd-unbind-key.c | 120 +++ cmd-unlink-window.c | 74 ++ cmd-up-pane.c | 61 ++ cmd.c | 393 +++++++++ colour.c | 123 +++ grid-view.c | 211 +++++ grid.c | 497 ++++++++++++ input-keys.c | 227 ++++++ input.c | 1276 ++++++++++++++++++++++++++++++ key-bindings.c | 237 ++++++ key-string.c | 198 +++++ layout-manual.c | 183 +++++ layout.c | 373 +++++++++ log.c | 250 ++++++ mode-key.c | 210 +++++ names.c | 110 +++ options-cmd.c | 182 +++++ options.c | 160 ++++ paste.c | 131 +++ procname.c | 130 +++ resize.c | 138 ++++ screen-redraw.c | 171 ++++ screen-write.c | 684 ++++++++++++++++ screen.c | 240 ++++++ server-fn.c | 242 ++++++ server-msg.c | 304 +++++++ server.c | 1105 ++++++++++++++++++++++++++ session.c | 378 +++++++++ status.c | 960 ++++++++++++++++++++++ tmux.1 | 1329 +++++++++++++++++++++++++++++++ tmux.c | 487 ++++++++++++ tmux.cat1 | 813 +++++++++++++++++++ tmux.h | 1576 +++++++++++++++++++++++++++++++++++++ tty-keys.c | 412 ++++++++++ tty-term.c | 393 +++++++++ tty-write.c | 91 +++ tty.c | 1076 +++++++++++++++++++++++++ utf8.c | 317 ++++++++ util.c | 72 ++ window-choose.c | 361 +++++++++ window-clock.c | 124 +++ window-copy.c | 968 +++++++++++++++++++++++ window-more.c | 252 ++++++ window-scroll.c | 296 +++++++ window.c | 625 +++++++++++++++ xmalloc.c | 144 ++++ 127 files changed, 28294 insertions(+) create mode 100644 Makefile create mode 100644 arg.c create mode 100644 array.h create mode 100644 attributes.c create mode 100644 buffer-poll.c create mode 100644 buffer.c create mode 100644 cfg.c create mode 100644 client-fn.c create mode 100644 client-msg.c create mode 100644 client.c create mode 100644 clock.c create mode 100644 cmd-attach-session.c create mode 100644 cmd-bind-key.c create mode 100644 cmd-break-pane.c create mode 100644 cmd-choose-session.c create mode 100644 cmd-choose-window.c create mode 100644 cmd-clear-history.c create mode 100644 cmd-clock-mode.c create mode 100644 cmd-command-prompt.c create mode 100644 cmd-confirm-before.c create mode 100644 cmd-copy-buffer.c create mode 100644 cmd-copy-mode.c create mode 100644 cmd-delete-buffer.c create mode 100644 cmd-detach-client.c create mode 100644 cmd-down-pane.c create mode 100644 cmd-find-window.c create mode 100644 cmd-generic.c create mode 100644 cmd-has-session.c create mode 100644 cmd-kill-pane.c create mode 100644 cmd-kill-server.c create mode 100644 cmd-kill-session.c create mode 100644 cmd-kill-window.c create mode 100644 cmd-last-window.c create mode 100644 cmd-link-window.c create mode 100644 cmd-list-buffers.c create mode 100644 cmd-list-clients.c create mode 100644 cmd-list-commands.c create mode 100644 cmd-list-keys.c create mode 100644 cmd-list-sessions.c create mode 100644 cmd-list-windows.c create mode 100644 cmd-list.c create mode 100644 cmd-load-buffer.c create mode 100644 cmd-lock-server.c create mode 100644 cmd-move-window.c create mode 100644 cmd-new-session.c create mode 100644 cmd-new-window.c create mode 100644 cmd-next-layout.c create mode 100644 cmd-next-window.c create mode 100644 cmd-paste-buffer.c create mode 100644 cmd-previous-layout.c create mode 100644 cmd-previous-window.c create mode 100644 cmd-refresh-client.c create mode 100644 cmd-rename-session.c create mode 100644 cmd-rename-window.c create mode 100644 cmd-resize-pane.c create mode 100644 cmd-respawn-window.c create mode 100644 cmd-rotate-window.c create mode 100644 cmd-save-buffer.c create mode 100644 cmd-scroll-mode.c create mode 100644 cmd-select-layout.c create mode 100644 cmd-select-pane.c create mode 100644 cmd-select-prompt.c create mode 100644 cmd-select-window.c create mode 100644 cmd-send-keys.c create mode 100644 cmd-send-prefix.c create mode 100644 cmd-server-info.c create mode 100644 cmd-set-buffer.c create mode 100644 cmd-set-option.c create mode 100644 cmd-set-password.c create mode 100644 cmd-set-window-option.c create mode 100644 cmd-show-buffer.c create mode 100644 cmd-show-options.c create mode 100644 cmd-show-window-options.c create mode 100644 cmd-source-file.c create mode 100644 cmd-split-window.c create mode 100644 cmd-start-server.c create mode 100644 cmd-string.c create mode 100644 cmd-suspend-client.c create mode 100644 cmd-swap-pane.c create mode 100644 cmd-swap-window.c create mode 100644 cmd-switch-client.c create mode 100644 cmd-unbind-key.c create mode 100644 cmd-unlink-window.c create mode 100644 cmd-up-pane.c create mode 100644 cmd.c create mode 100644 colour.c create mode 100644 grid-view.c create mode 100644 grid.c create mode 100644 input-keys.c create mode 100644 input.c create mode 100644 key-bindings.c create mode 100644 key-string.c create mode 100644 layout-manual.c create mode 100644 layout.c create mode 100644 log.c create mode 100644 mode-key.c create mode 100644 names.c create mode 100644 options-cmd.c create mode 100644 options.c create mode 100644 paste.c create mode 100644 procname.c create mode 100644 resize.c create mode 100644 screen-redraw.c create mode 100644 screen-write.c create mode 100644 screen.c create mode 100644 server-fn.c create mode 100644 server-msg.c create mode 100644 server.c create mode 100644 session.c create mode 100644 status.c create mode 100644 tmux.1 create mode 100644 tmux.c create mode 100644 tmux.cat1 create mode 100644 tmux.h create mode 100644 tty-keys.c create mode 100644 tty-term.c create mode 100644 tty-write.c create mode 100644 tty.c create mode 100644 utf8.c create mode 100644 util.c create mode 100644 window-choose.c create mode 100644 window-clock.c create mode 100644 window-copy.c create mode 100644 window-more.c create mode 100644 window-scroll.c create mode 100644 window.c create mode 100644 xmalloc.c diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..66204957 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +# $OpenBSD$ + +.PATH: ${.CURDIR}/.. + +PROG= tmux +SRCS= arg.c attributes.c buffer-poll.c buffer.c cfg.c client-fn.c \ + client-msg.c client.c clock.c cmd-attach-session.c cmd-bind-key.c \ + cmd-break-pane.c cmd-choose-session.c cmd-choose-window.c \ + cmd-clear-history.c cmd-clock-mode.c cmd-command-prompt.c \ + cmd-confirm-before.c cmd-copy-buffer.c cmd-copy-mode.c \ + cmd-delete-buffer.c cmd-detach-client.c cmd-down-pane.c \ + cmd-find-window.c cmd-generic.c cmd-has-session.c cmd-kill-pane.c \ + cmd-kill-server.c cmd-kill-session.c cmd-kill-window.c \ + cmd-last-window.c cmd-link-window.c cmd-list-buffers.c \ + cmd-list-clients.c cmd-list-commands.c cmd-list-keys.c \ + cmd-list-sessions.c cmd-list-windows.c cmd-list.c cmd-load-buffer.c \ + cmd-lock-server.c cmd-move-window.c cmd-new-session.c cmd-new-window.c \ + cmd-next-layout.c cmd-next-window.c cmd-paste-buffer.c \ + cmd-previous-layout.c cmd-previous-window.c cmd-refresh-client.c \ + cmd-rename-session.c cmd-rename-window.c cmd-resize-pane.c \ + cmd-respawn-window.c cmd-rotate-window.c cmd-save-buffer.c \ + cmd-scroll-mode.c cmd-select-layout.c cmd-select-pane.c \ + cmd-select-prompt.c cmd-select-window.c cmd-send-keys.c \ + cmd-send-prefix.c cmd-server-info.c cmd-set-buffer.c cmd-set-option.c \ + cmd-set-password.c cmd-set-window-option.c cmd-show-buffer.c \ + cmd-show-options.c cmd-show-window-options.c cmd-source-file.c \ + cmd-split-window.c cmd-start-server.c cmd-string.c \ + cmd-suspend-client.c cmd-swap-pane.c cmd-swap-window.c \ + cmd-switch-client.c cmd-unbind-key.c cmd-unlink-window.c \ + cmd-up-pane.c cmd.c colour.c grid-view.c grid.c input-keys.c \ + input.c key-bindings.c key-string.c layout-manual.c layout.c log.c \ + mode-key.c names.c options-cmd.c options.c paste.c procname.c \ + resize.c screen-redraw.c screen-write.c screen.c server-fn.c \ + server-msg.c server.c session.c status.c tmux.c tty-keys.c tty-term.c \ + tty-write.c tty.c utf8.c util.c window-choose.c window-clock.c \ + window-copy.c window-more.c window-scroll.c window.c xmalloc.c + +CFLAGS+= -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 +CFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations +CFLAGS+= -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare +CFLAGS+= -Wundef -Wbad-function-cast -Winline -Wcast-align + +LDADD= -lutil -lncurses +DPADD= ${LIBUTIL} + +MAN= tmux.1 + +.include diff --git a/arg.c b/arg.c new file mode 100644 index 00000000..c02b2663 --- /dev/null +++ b/arg.c @@ -0,0 +1,194 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#include "tmux.h" + +struct client *arg_lookup_client(const char *); +struct session *arg_lookup_session(const char *); + +struct client * +arg_lookup_client(const char *name) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c != NULL && strcmp(name, c->tty.path) == 0) + return (c); + } + + return (NULL); +} + +struct session * +arg_lookup_session(const char *name) +{ + struct session *s, *newest = NULL; + struct timeval *tv; + u_int i; + + tv = NULL; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL || fnmatch(name, s->name, 0) != 0) + continue; + + if (tv == NULL || timercmp(&s->tv, tv, >)) { + newest = s; + tv = &s->tv; + } + } + + return (newest); +} + +struct client * +arg_parse_client(const char *arg) +{ + struct client *c; + char *arg2; + size_t n; + + if (arg != NULL && (arg[0] != ':' || arg[1] != '\0')) { + arg2 = xstrdup(arg); + + /* Trim a trailing : if any from the argument. */ + n = strlen(arg2); + if (arg2[n - 1] == ':') + arg2[n - 1] = '\0'; + + /* Try and look up the client name. */ + c = arg_lookup_client(arg2); + xfree(arg2); + return (c); + } + + return (NULL); +} + +struct session * +arg_parse_session(const char *arg) +{ + struct session *s; + struct client *c; + char *arg2; + size_t n; + + if (arg != NULL && (arg[0] != ':' || arg[1] != '\0')) { + arg2 = xstrdup(arg); + + /* Trim a trailing : if any from the argument. */ + n = strlen(arg2); + if (arg2[n - 1] == ':') + arg2[n - 1] = '\0'; + + /* See if the argument matches a session. */ + if ((s = arg_lookup_session(arg2)) != NULL) { + xfree(arg2); + return (s); + } + + /* If not try a client. */ + if ((c = arg_lookup_client(arg2)) != NULL) { + xfree(arg2); + return (c->session); + } + + xfree(arg2); + } + + return (NULL); +} + +int +arg_parse_window(const char *arg, struct session **s, int *idx) +{ + char *arg2, *ptr; + const char *errstr; + + *idx = -1; + + /* Handle no argument or a single :. */ + if (arg == NULL || (arg[0] == ':' && arg[1] == '\0')) { + *s = arg_parse_session(NULL); + return (0); + } + + /* Find the separator if any. */ + arg2 = xstrdup(arg); + ptr = strrchr(arg2, ':'); + + /* + * If it is first, this means no session name, so use current session + * and try to convert the rest as index. + */ + if (ptr == arg2) { + *idx = strtonum(ptr + 1, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xfree(arg2); + return (1); + } + + xfree(arg2); + *s = arg_parse_session(NULL); + return (0); + } + + /* If missing, try as an index, else look up immediately. */ + if (ptr == NULL) { + *idx = strtonum(arg2, 0, INT_MAX, &errstr); + if (errstr == NULL) { + /* This is good as an index; use current session. */ + xfree(arg2); + *s = arg_parse_session(NULL); + return (0); + } + + *idx = -1; + goto lookup; + } + + /* If last, strip it and look up as a session. */ + if (ptr[1] == '\0') { + *ptr = '\0'; + goto lookup; + } + + /* Present but not first and not last. Break and convert both. */ + *ptr = '\0'; + *idx = strtonum(ptr + 1, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xfree(arg2); + return (1); + } + +lookup: + /* Look up as session. */ + *s = arg_parse_session(arg2); + xfree(arg2); + if (*s == NULL) + return (1); + return (0); +} diff --git a/array.h b/array.h new file mode 100644 index 00000000..46855d77 --- /dev/null +++ b/array.h @@ -0,0 +1,119 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2006 Nicholas Marriott + * + * 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. + */ + +#ifndef ARRAY_H +#define ARRAY_H + +#define ARRAY_DECL(n, c) \ + struct n { \ + c *list; \ + u_int num; \ + size_t space; \ + } + +#define ARRAY_ITEM(a, i) ((a)->list[i]) +#define ARRAY_ITEMSIZE(a) (sizeof *(a)->list) +#define ARRAY_INITIALSPACE(a) (10 * ARRAY_ITEMSIZE(a)) + +#define ARRAY_ENSURE(a, n) do { \ + if (UINT_MAX - (n) < (a)->num) \ + fatalx("number too big"); \ + if (SIZE_MAX / ((a)->num + (n)) < ARRAY_ITEMSIZE(a)) \ + fatalx("size too big"); \ + if ((a)->space == 0) { \ + (a)->space = ARRAY_INITIALSPACE(a); \ + (a)->list = xrealloc((a)->list, 1, (a)->space); \ + } \ + while ((a)->space <= ((a)->num + (n)) * ARRAY_ITEMSIZE(a)) { \ + (a)->list = xrealloc((a)->list, 2, (a)->space); \ + (a)->space *= 2; \ + } \ +} while (0) + +#define ARRAY_EMPTY(a) ((a) == NULL || (a)->num == 0) +#define ARRAY_LENGTH(a) ((a)->num) +#define ARRAY_DATA(a) ((a)->list) + +#define ARRAY_FIRST(a) ARRAY_ITEM(a, 0) +#define ARRAY_LAST(a) ARRAY_ITEM(a, (a)->num - 1) + +#define ARRAY_INIT(a) do { \ + (a)->num = 0; \ + (a)->list = NULL; \ + (a)->space = 0; \ +} while (0) +#define ARRAY_CLEAR(a) do { \ + (a)->num = 0; \ +} while (0) + +#define ARRAY_SET(a, i, s) do { \ + (a)->list[i] = s; \ +} while (0) + +#define ARRAY_ADD(a, s) do { \ + ARRAY_ENSURE(a, 1); \ + (a)->list[(a)->num] = s; \ + (a)->num++; \ +} while (0) +#define ARRAY_INSERT(a, i, s) do { \ + ARRAY_ENSURE(a, 1); \ + if ((i) < (a)->num) { \ + memmove((a)->list + (i) + 1, (a)->list + (i), \ + ARRAY_ITEMSIZE(a) * ((a)->num - (i))); \ + } \ + (a)->list[i] = s; \ + (a)->num++; \ +} while (0) +#define ARRAY_REMOVE(a, i) do { \ + if ((i) < (a)->num - 1) { \ + memmove((a)->list + (i), (a)->list + (i) + 1, \ + ARRAY_ITEMSIZE(a) * ((a)->num - (i) - 1)); \ + } \ + (a)->num--; \ + if ((a)->num == 0) \ + ARRAY_FREE(a); \ +} while (0) + +#define ARRAY_EXPAND(a, n) do { \ + ARRAY_ENSURE(a, n); \ + (a)->num += n; \ +} while (0) +#define ARRAY_TRUNC(a, n) do { \ + if ((a)->num > n) \ + (a)->num -= n; \ + else \ + ARRAY_FREE(a); \ +} while (0) + +#define ARRAY_CONCAT(a, b) do { \ + ARRAY_ENSURE(a, (b)->num); \ + memcpy((a)->list + (a)->num, (b)->list, (b)->num * ARRAY_ITEMSIZE(a)) \ + (a)->num += (b)->num; \ +} while (0) + +#define ARRAY_FREE(a) do { \ + if ((a)->list != NULL) \ + xfree((a)->list); \ + ARRAY_INIT(a); \ +} while (0) +#define ARRAY_FREEALL(a) do { \ + ARRAY_FREE(a); \ + xfree(a); \ +} while (0) + +#endif diff --git a/attributes.c b/attributes.c new file mode 100644 index 00000000..cfe46ecd --- /dev/null +++ b/attributes.c @@ -0,0 +1,92 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Joshua Elsasser + * + * 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 + +#include + +#include "tmux.h" + +const char * +attributes_tostring(u_char ch) +{ + static char buf[128]; + + if (ch == 0) + return ("default"); + + buf[0] = '\0'; + if (ch & GRID_ATTR_BRIGHT) + strlcat(buf, "bright,", sizeof (buf)); + if (ch & GRID_ATTR_DIM) + strlcat(buf, "dim,", sizeof (buf)); + if (ch & GRID_ATTR_UNDERSCORE) + strlcat(buf, "underscore,", sizeof (buf)); + if (ch & GRID_ATTR_BLINK) + strlcat(buf, "blink,", sizeof (buf)); + if (ch & GRID_ATTR_REVERSE) + strlcat(buf, "reverse,", sizeof (buf)); + if (ch & GRID_ATTR_HIDDEN) + strlcat(buf, "hidden,", sizeof (buf)); + if (ch & GRID_ATTR_ITALICS) + strlcat(buf, "italics,", sizeof (buf)); + *(strrchr(buf, ',')) = '\0'; + + return (buf); +} + +int +attributes_fromstring(const char *str) +{ + const char delimiters[] = " ,|"; + u_char ch; + size_t end; + + if (*str == '\0' || strcspn(str, delimiters) == 0) + return (-1); + if (strchr(delimiters, str[strlen(str) - 1]) != NULL) + return (-1); + + if (strcasecmp(str, "default") == 0) + return (0); + + ch = 0; + do { + end = strcspn(str, delimiters); + if ((end == 6 && strncasecmp(str, "bright", end) == 0) || + (end == 4 && strncasecmp(str, "bold", end) == 0)) + ch |= GRID_ATTR_BRIGHT; + else if (end == 3 && strncasecmp(str, "dim", end) == 0) + ch |= GRID_ATTR_DIM; + else if (end == 10 && strncasecmp(str, "underscore", end) == 0) + ch |= GRID_ATTR_UNDERSCORE; + else if (end == 5 && strncasecmp(str, "blink", end) == 0) + ch |= GRID_ATTR_BLINK; + else if (end == 7 && strncasecmp(str, "reverse", end) == 0) + ch |= GRID_ATTR_REVERSE; + else if (end == 6 && strncasecmp(str, "hidden", end) == 0) + ch |= GRID_ATTR_HIDDEN; + else if (end == 7 && strncasecmp(str, "italics", end) == 0) + ch |= GRID_ATTR_ITALICS; + else + return (-1); + str += end + strspn(str + end, delimiters); + } while (*str != '\0'); + + return (ch); +} diff --git a/buffer-poll.c b/buffer-poll.c new file mode 100644 index 00000000..4dd4d234 --- /dev/null +++ b/buffer-poll.c @@ -0,0 +1,97 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* Set up pollfd for buffers. */ +void +buffer_set( + struct pollfd *pfd, int fd, unused struct buffer *in, struct buffer *out) +{ + pfd->fd = fd; + pfd->events = POLLIN; + if (BUFFER_USED(out) > 0) + pfd->events |= POLLOUT; +} + +/* Fill buffers from socket based on poll results. */ +int +buffer_poll(struct pollfd *pfd, struct buffer *in, struct buffer *out) +{ + ssize_t n; + +#if 0 + log_debug("buffer_poll (%ld): fd=%d, revents=%d; out=%zu in=%zu", + (long) getpid(), + pfd->fd, pfd->revents, BUFFER_USED(out), BUFFER_USED(in)); +#endif + + if (pfd->revents & (POLLERR|POLLNVAL|POLLHUP)) + return (-1); + if (pfd->revents & POLLIN) { + buffer_ensure(in, BUFSIZ); + n = read(pfd->fd, BUFFER_IN(in), BUFFER_FREE(in)); +#if 0 + log_debug("buffer_poll: fd=%d, read=%zd", pfd->fd, n); +#endif + if (n == 0) + return (-1); + if (n == -1) { + if (errno != EINTR && errno != EAGAIN) + return (-1); + } else + buffer_add(in, n); + } + if (BUFFER_USED(out) > 0 && pfd->revents & POLLOUT) { + n = write(pfd->fd, BUFFER_OUT(out), BUFFER_USED(out)); +#if 0 + log_debug("buffer_poll: fd=%d, write=%zd", pfd->fd, n); +#endif + if (n == -1) { + if (errno != EINTR && errno != EAGAIN) + return (-1); + } else + buffer_remove(out, n); + } + return (0); +} + +/* Flush buffer output to socket. */ +void +buffer_flush(int fd, struct buffer *in, struct buffer *out) +{ + struct pollfd pfd; + + while (BUFFER_USED(out) > 0) { + buffer_set(&pfd, fd, in, out); + + if (poll(&pfd, 1, INFTIM) == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + fatal("poll failed"); + } + + if (buffer_poll(&pfd, in, out) != 0) + break; + } +} diff --git a/buffer.c b/buffer.c new file mode 100644 index 00000000..eea1fce7 --- /dev/null +++ b/buffer.c @@ -0,0 +1,227 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* Create a buffer. */ +struct buffer * +buffer_create(size_t size) +{ + struct buffer *b; + + if (size == 0) + fatalx("zero size"); + + b = xcalloc(1, sizeof *b); + + b->base = xmalloc(size); + b->space = size; + + return (b); +} + +/* Destroy a buffer. */ +void +buffer_destroy(struct buffer *b) +{ + xfree(b->base); + xfree(b); +} + +/* Empty a buffer. */ +void +buffer_clear(struct buffer *b) +{ + b->size = 0; + b->off = 0; +} + +/* Ensure free space for size in buffer. */ +void +buffer_ensure(struct buffer *b, size_t size) +{ + if (size == 0) + fatalx("zero size"); + + if (BUFFER_FREE(b) >= size) + return; + + if (b->off > 0) { + if (b->size > 0) + memmove(b->base, b->base + b->off, b->size); + b->off = 0; + } + + if (SIZE_MAX - b->size < size) + fatalx("size too big"); + while (b->space < b->size + size) { + b->base = xrealloc(b->base, 2, b->space); + b->space *= 2; + } +} + +/* Adjust buffer after data appended. */ +void +buffer_add(struct buffer *b, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (size > b->space - b->size) + fatalx("overflow"); + + b->size += size; +} + +/* Reverse buffer add. */ +void +buffer_reverse_add(struct buffer *b, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (size > b->size) + fatalx("underflow"); + + b->size -= size; +} + +/* Adjust buffer after data removed. */ +void +buffer_remove(struct buffer *b, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (size > b->size) + fatalx("underflow"); + + b->size -= size; + b->off += size; +} + +/* Reverse buffer remove. */ +void +buffer_reverse_remove(struct buffer *b, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (size > b->off) + fatalx("overflow"); + + b->size += size; + b->off -= size; +} + +/* Insert a section into the buffer. */ +void +buffer_insert_range(struct buffer *b, size_t base, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (base > b->size) + fatalx("range outside buffer"); + + buffer_ensure(b, size); + memmove(b->base + b->off + base + size, + b->base + b->off + base, b->size - base); + b->size += size; +} + +/* Delete a section from the buffer. */ +void +buffer_delete_range(struct buffer *b, size_t base, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (size > b->size) + fatalx("size too big"); + if (base + size > b->size) + fatalx("range outside buffer"); + + memmove(b->base + b->off + base, + b->base + b->off + base + size, b->size - base - size); + b->size -= size; +} + +/* Copy data into a buffer. */ +void +buffer_write(struct buffer *b, const void *data, size_t size) +{ + if (size == 0) + fatalx("zero size"); + + buffer_ensure(b, size); + memcpy(BUFFER_IN(b), data, size); + buffer_add(b, size); +} + +/* Copy data out of a buffer. */ +void +buffer_read(struct buffer *b, void *data, size_t size) +{ + if (size == 0) + fatalx("zero size"); + if (size > b->size) + fatalx("underflow"); + + memcpy(data, BUFFER_OUT(b), size); + buffer_remove(b, size); +} + +/* Store an 8-bit value. */ +void +buffer_write8(struct buffer *b, uint8_t n) +{ + buffer_ensure(b, 1); + BUFFER_IN(b)[0] = n; + buffer_add(b, 1); +} + +/* Store a 16-bit value. */ +void +buffer_write16(struct buffer *b, uint16_t n) +{ + buffer_ensure(b, 2); + BUFFER_IN(b)[0] = n & 0xff; + BUFFER_IN(b)[1] = n >> 8; + buffer_add(b, 2); +} + +/* Extract an 8-bit value. */ +uint8_t +buffer_read8(struct buffer *b) +{ + uint8_t n; + + n = BUFFER_OUT(b)[0]; + buffer_remove(b, 1); + return (n); +} + +/* Extract a 16-bit value. */ +uint16_t +buffer_read16(struct buffer *b) +{ + uint16_t n; + + n = BUFFER_OUT(b)[0] | (BUFFER_OUT(b)[1] << 8); + buffer_remove(b, 2); + return (n); +} diff --git a/cfg.c b/cfg.c new file mode 100644 index 00000000..e4278e14 --- /dev/null +++ b/cfg.c @@ -0,0 +1,132 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include + +#include "tmux.h" + +/* + * Config file parser. Pretty quick and simple, each line is parsed into a + * argv array and executed as a command. + */ + +char *cfg_string(FILE *, char, int); +void printflike2 cfg_print(struct cmd_ctx *, const char *, ...); +void printflike2 cfg_error(struct cmd_ctx *, const char *, ...); + +char *cfg_cause; + +void printflike2 +cfg_print(unused struct cmd_ctx *ctx, unused const char *fmt, ...) +{ +} + +void printflike2 +cfg_error(unused struct cmd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + xvasprintf(&cfg_cause, fmt, ap); + va_end(ap); +} + +int +load_cfg(const char *path, char **cause) +{ + FILE *f; + u_int n; + struct stat sb; + char *buf, *line, *ptr; + size_t len; + struct cmd_list *cmdlist; + struct cmd_ctx ctx; + + if (stat(path, &sb) != 0) { + xasprintf(cause, "%s: %s", path, strerror(errno)); + return (-1); + } + if (!S_ISREG(sb.st_mode)) { + xasprintf(cause, "%s: not a regular file", path); + return (-1); + } + + if ((f = fopen(path, "rb")) == NULL) { + xasprintf(cause, "%s: %s", path, strerror(errno)); + return (1); + } + n = 0; + + line = NULL; + while ((buf = fgetln(f, &len))) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + else { + line = xrealloc(line, 1, len + 1); + memcpy(line, buf, len); + line[len] = '\0'; + buf = line; + } + n++; + + if (cmd_string_parse(buf, &cmdlist, cause) != 0) { + if (*cause == NULL) + continue; + goto error; + } + if (cmdlist == NULL) + continue; + cfg_cause = NULL; + + ctx.msgdata = NULL; + ctx.cursession = NULL; + ctx.curclient = NULL; + + ctx.error = cfg_error; + ctx.print = cfg_print; + ctx.info = cfg_print; + + ctx.cmdclient = NULL; + + cfg_cause = NULL; + cmd_list_exec(cmdlist, &ctx); + cmd_list_free(cmdlist); + if (cfg_cause != NULL) { + *cause = cfg_cause; + goto error; + } + } + if (line != NULL) + xfree(line); + fclose(f); + + return (0); + +error: + fclose(f); + + xasprintf(&ptr, "%s: %s at line %u", path, *cause, n); + xfree(*cause); + *cause = ptr; + return (1); +} diff --git a/client-fn.c b/client-fn.c new file mode 100644 index 00000000..3f42a105 --- /dev/null +++ b/client-fn.c @@ -0,0 +1,92 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +void +client_fill_session(struct msg_command_data *data) +{ + char *env, *ptr1, *ptr2, buf[256]; + size_t len; + const char *errstr; + long long ll; + + data->pid = -1; + if ((env = getenv("TMUX")) == NULL) + return; + + if ((ptr2 = strrchr(env, ',')) == NULL || ptr2 == env) + return; + for (ptr1 = ptr2 - 1; ptr1 > env && *ptr1 != ','; ptr1--) + ; + if (*ptr1 != ',') + return; + ptr1++; + ptr2++; + + len = ptr2 - ptr1 - 1; + if (len > (sizeof buf) - 1) + return; + memcpy(buf, ptr1, len); + buf[len] = '\0'; + + ll = strtonum(buf, 0, LONG_MAX, &errstr); + if (errstr != NULL) + return; + data->pid = ll; + + ll = strtonum(ptr2, 0, UINT_MAX, &errstr); + if (errstr != NULL) + return; + data->idx = ll; +} + +void +client_write_server( + struct client_ctx *cctx, enum hdrtype type, void *buf, size_t len) +{ + struct hdr hdr; + + hdr.type = type; + hdr.size = len; + buffer_write(cctx->srv_out, &hdr, sizeof hdr); + + if (buf != NULL && len > 0) + buffer_write(cctx->srv_out, buf, len); +} + +void +client_write_server2(struct client_ctx *cctx, + enum hdrtype type, void *buf1, size_t len1, void *buf2, size_t len2) +{ + struct hdr hdr; + + hdr.type = type; + hdr.size = len1 + len2; + buffer_write(cctx->srv_out, &hdr, sizeof hdr); + + if (buf1 != NULL && len1 > 0) + buffer_write(cctx->srv_out, buf1, len1); + if (buf2 != NULL && len2 > 0) + buffer_write(cctx->srv_out, buf2, len2); +} diff --git a/client-msg.c b/client-msg.c new file mode 100644 index 00000000..fd48d403 --- /dev/null +++ b/client-msg.c @@ -0,0 +1,159 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include +#include +#include + +#include "tmux.h" + +int client_msg_fn_detach(struct hdr *, struct client_ctx *, char **); +int client_msg_fn_error(struct hdr *, struct client_ctx *, char **); +int client_msg_fn_shutdown(struct hdr *, struct client_ctx *, char **); +int client_msg_fn_exit(struct hdr *, struct client_ctx *, char **); +int client_msg_fn_exited(struct hdr *, struct client_ctx *, char **); +int client_msg_fn_suspend(struct hdr *, struct client_ctx *, char **); + +struct client_msg { + enum hdrtype type; + int (*fn)(struct hdr *, struct client_ctx *, char **); +}; +struct client_msg client_msg_table[] = { + { MSG_DETACH, client_msg_fn_detach }, + { MSG_ERROR, client_msg_fn_error }, + { MSG_EXIT, client_msg_fn_exit }, + { MSG_EXITED, client_msg_fn_exited }, + { MSG_SHUTDOWN, client_msg_fn_shutdown }, + { MSG_SUSPEND, client_msg_fn_suspend }, +}; + +int +client_msg_dispatch(struct client_ctx *cctx, char **error) +{ + struct hdr hdr; + struct client_msg *msg; + u_int i; + + if (BUFFER_USED(cctx->srv_in) < sizeof hdr) + return (1); + memcpy(&hdr, BUFFER_OUT(cctx->srv_in), sizeof hdr); + if (BUFFER_USED(cctx->srv_in) < (sizeof hdr) + hdr.size) + return (1); + buffer_remove(cctx->srv_in, sizeof hdr); + + for (i = 0; i < nitems(client_msg_table); i++) { + msg = client_msg_table + i; + if (msg->type == hdr.type) { + if (msg->fn(&hdr, cctx, error) != 0) + return (-1); + return (0); + } + } + fatalx("unexpected message"); +} + +int +client_msg_fn_error(struct hdr *hdr, struct client_ctx *cctx, char **error) +{ + if (hdr->size > SIZE_MAX - 1) + fatalx("bad MSG_ERROR size"); + + *error = xmalloc(hdr->size + 1); + buffer_read(cctx->srv_in, *error, hdr->size); + (*error)[hdr->size] = '\0'; + + return (-1); +} + +int +client_msg_fn_detach( + struct hdr *hdr, unused struct client_ctx *cctx, unused char **error) +{ + if (hdr->size != 0) + fatalx("bad MSG_DETACH size"); + + client_write_server(cctx, MSG_EXITING, NULL, 0); + cctx->flags |= CCTX_DETACH; + + return (0); +} + +int +client_msg_fn_shutdown( + struct hdr *hdr, unused struct client_ctx *cctx, unused char **error) +{ + if (hdr->size != 0) + fatalx("bad MSG_SHUTDOWN size"); + + client_write_server(cctx, MSG_EXITING, NULL, 0); + cctx->flags |= CCTX_SHUTDOWN; + + return (0); +} + +int +client_msg_fn_exit( + struct hdr *hdr, unused struct client_ctx *cctx, unused char **error) +{ + if (hdr->size != 0) + fatalx("bad MSG_EXIT size"); + + client_write_server(cctx, MSG_EXITING, NULL, 0); + cctx->flags |= CCTX_EXIT; + + return (0); +} + +int +client_msg_fn_exited( + struct hdr *hdr, unused struct client_ctx *cctx, unused char **error) +{ + if (hdr->size != 0) + fatalx("bad MSG_EXITED size"); + + return (-1); +} + +int +client_msg_fn_suspend( + struct hdr *hdr, unused struct client_ctx *cctx, unused char **error) +{ + struct sigaction act; + + if (hdr->size != 0) + fatalx("bad MSG_SUSPEND size"); + + memset(&act, 0, sizeof act); + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + act.sa_handler = SIG_DFL; + if (sigaction(SIGTSTP, &act, NULL) != 0) + fatal("sigaction failed"); + + act.sa_handler = sighandler; + if (sigaction(SIGCONT, &act, NULL) != 0) + fatal("sigaction failed"); + + kill(getpid(), SIGTSTP); + + return (0); +} diff --git a/client.c b/client.c new file mode 100644 index 00000000..12041eed --- /dev/null +++ b/client.c @@ -0,0 +1,226 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +void client_handle_winch(struct client_ctx *); + +int +client_init(char *path, struct client_ctx *cctx, int start_server, int flags) +{ + struct sockaddr_un sa; + struct stat sb; + struct msg_identify_data data; + struct winsize ws; + size_t size; + int mode; + struct buffer *b; + char *name; + + if (lstat(path, &sb) != 0) { + if (start_server && errno == ENOENT) { + if ((cctx->srv_fd = server_start(path)) == -1) + goto start_failed; + goto server_started; + } + goto not_found; + } + if (!S_ISSOCK(sb.st_mode)) { + errno = ENOTSOCK; + goto not_found; + } + + memset(&sa, 0, sizeof sa); + sa.sun_family = AF_UNIX; + size = strlcpy(sa.sun_path, path, sizeof sa.sun_path); + if (size >= sizeof sa.sun_path) { + errno = ENAMETOOLONG; + goto not_found; + } + + if ((cctx->srv_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + fatal("socket"); + + if (connect( + cctx->srv_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1) { + if (errno == ECONNREFUSED) { + if (unlink(path) != 0 || !start_server) + goto not_found; + if ((cctx->srv_fd = server_start(path)) == -1) + goto start_failed; + goto server_started; + } + goto not_found; + } + +server_started: + if ((mode = fcntl(cctx->srv_fd, F_GETFL)) == -1) + fatal("fcntl failed"); + if (fcntl(cctx->srv_fd, F_SETFL, mode|O_NONBLOCK) == -1) + fatal("fcntl failed"); + cctx->srv_in = buffer_create(BUFSIZ); + cctx->srv_out = buffer_create(BUFSIZ); + + if (isatty(STDIN_FILENO)) { + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) + fatal("ioctl(TIOCGWINSZ)"); + data.version = PROTOCOL_VERSION; + data.flags = flags; + data.sx = ws.ws_col; + data.sy = ws.ws_row; + *data.tty = '\0'; + if (getcwd(data.cwd, sizeof data.cwd) == NULL) + *data.cwd = '\0'; + + if ((name = ttyname(STDIN_FILENO)) == NULL) + fatal("ttyname failed"); + if (strlcpy(data.tty, name, sizeof data.tty) >= sizeof data.tty) + fatalx("ttyname failed"); + + b = buffer_create(BUFSIZ); + cmd_send_string(b, getenv("TERM")); + client_write_server2(cctx, MSG_IDENTIFY, + &data, sizeof data, BUFFER_OUT(b), BUFFER_USED(b)); + buffer_destroy(b); + } + + return (0); + +start_failed: + log_warnx("server failed to start"); + return (1); + +not_found: + log_warn("server not found"); + return (1); +} + +int +client_main(struct client_ctx *cctx) +{ + struct pollfd pfd; + char *error; + int xtimeout; /* Yay for ncurses namespace! */ + + siginit(); + + logfile("client"); + setproctitle("client"); + + error = NULL; + xtimeout = INFTIM; + while (!sigterm) { + if (sigchld) { + waitpid(WAIT_ANY, NULL, WNOHANG); + sigchld = 0; + } + if (sigwinch) + client_handle_winch(cctx); + if (sigcont) { + siginit(); + client_write_server(cctx, MSG_WAKEUP, NULL, 0); + sigcont = 0; + } + + switch (client_msg_dispatch(cctx, &error)) { + case -1: + goto out; + case 0: + /* May be more in buffer, don't let poll block. */ + xtimeout = 0; + break; + default: + /* Out of data, poll may block. */ + xtimeout = INFTIM; + break; + } + + pfd.fd = cctx->srv_fd; + pfd.events = POLLIN; + if (BUFFER_USED(cctx->srv_out) > 0) + pfd.events |= POLLOUT; + + if (poll(&pfd, 1, xtimeout) == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + fatal("poll failed"); + } + + if (buffer_poll(&pfd, cctx->srv_in, cctx->srv_out) != 0) + goto server_dead; + } + +out: + if (sigterm) { + printf("[terminated]\n"); + return (1); + } + + if (cctx->flags & CCTX_SHUTDOWN) { + printf("[server exited]\n"); + return (0); + } + + if (cctx->flags & CCTX_EXIT) { + printf("[exited]\n"); + return (0); + } + + if (cctx->flags & CCTX_DETACH) { + printf("[detached]\n"); + return (0); + } + + printf("[error: %s]\n", error); + return (1); + +server_dead: + printf("[lost server]\n"); + return (0); +} + +void +client_handle_winch(struct client_ctx *cctx) +{ + struct msg_resize_data data; + struct winsize ws; + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) + fatal("ioctl failed"); + + data.sx = ws.ws_col; + data.sy = ws.ws_row; + client_write_server(cctx, MSG_RESIZE, &data, sizeof data); + + sigwinch = 0; +} diff --git a/clock.c b/clock.c new file mode 100644 index 00000000..60248f47 --- /dev/null +++ b/clock.c @@ -0,0 +1,161 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +const char clock_table[14][5][5] = { + { { 1,1,1,1,1 }, /* 0 */ + { 1,0,0,0,1 }, + { 1,0,0,0,1 }, + { 1,0,0,0,1 }, + { 1,1,1,1,1 } }, + { { 0,0,0,0,1 }, /* 1 */ + { 0,0,0,0,1 }, + { 0,0,0,0,1 }, + { 0,0,0,0,1 }, + { 0,0,0,0,1 } }, + { { 1,1,1,1,1 }, /* 2 */ + { 0,0,0,0,1 }, + { 1,1,1,1,1 }, + { 1,0,0,0,0 }, + { 1,1,1,1,1 } }, + { { 1,1,1,1,1 }, /* 3 */ + { 0,0,0,0,1 }, + { 1,1,1,1,1 }, + { 0,0,0,0,1 }, + { 1,1,1,1,1 } }, + { { 1,0,0,0,1 }, /* 4 */ + { 1,0,0,0,1 }, + { 1,1,1,1,1 }, + { 0,0,0,0,1 }, + { 0,0,0,0,1 } }, + { { 1,1,1,1,1 }, /* 5 */ + { 1,0,0,0,0 }, + { 1,1,1,1,1 }, + { 0,0,0,0,1 }, + { 1,1,1,1,1 } }, + { { 1,1,1,1,1 }, /* 6 */ + { 1,0,0,0,0 }, + { 1,1,1,1,1 }, + { 1,0,0,0,1 }, + { 1,1,1,1,1 } }, + { { 1,1,1,1,1 }, /* 7 */ + { 0,0,0,0,1 }, + { 0,0,0,0,1 }, + { 0,0,0,0,1 }, + { 0,0,0,0,1 } }, + { { 1,1,1,1,1 }, /* 8 */ + { 1,0,0,0,1 }, + { 1,1,1,1,1 }, + { 1,0,0,0,1 }, + { 1,1,1,1,1 } }, + { { 1,1,1,1,1 }, /* 9 */ + { 1,0,0,0,1 }, + { 1,1,1,1,1 }, + { 0,0,0,0,1 }, + { 1,1,1,1,1 } }, + { { 0,0,0,0,0 }, /* : */ + { 0,0,1,0,0 }, + { 0,0,0,0,0 }, + { 0,0,1,0,0 }, + { 0,0,0,0,0 } }, + { { 1,1,1,1,1 }, /* A */ + { 1,0,0,0,1 }, + { 1,1,1,1,1 }, + { 1,0,0,0,1 }, + { 1,0,0,0,1 } }, + { { 1,1,1,1,1 }, /* P */ + { 1,0,0,0,1 }, + { 1,1,1,1,1 }, + { 1,0,0,0,0 }, + { 1,0,0,0,0 } }, + { { 1,0,0,0,1 }, /* M */ + { 1,1,0,1,1 }, + { 1,0,1,0,1 }, + { 1,0,0,0,1 }, + { 1,0,0,0,1 } }, +}; + +void +clock_draw(struct screen_write_ctx *ctx, u_int colour, int style) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + char tim[64], *ptr; + time_t t; + u_int i, j, x, y, idx; + + t = time(NULL); + if (style == 0) + strftime(tim, sizeof tim, "%l:%M %p", localtime(&t)); + else + strftime(tim, sizeof tim, "%H:%M", localtime(&t)); + + screen_write_clearscreen(ctx); + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.fg = colour; + + if (screen_size_x(s) < 6 * strlen(tim) || screen_size_y(s) < 6) { + if (screen_size_x(s) >= strlen(tim) && screen_size_y(s) != 0) { + x = (screen_size_x(s) / 2) - (strlen(tim) / 2); + y = screen_size_y(s) / 2; + screen_write_cursormove(ctx, x, y); + + gc.fg = colour; + screen_write_puts(ctx, &gc, "%s", tim); + } + return; + } + + x = (screen_size_x(s) / 2) - 3 * strlen(tim); + y = (screen_size_y(s) / 2) - 3; + + for (ptr = tim; *ptr != '\0'; ptr++) { + if (*ptr >= '0' && *ptr <= '9') + idx = *ptr - '0'; + else if (*ptr == ':') + idx = 10; + else if (*ptr == 'A') + idx = 11; + else if (*ptr == 'P') + idx = 12; + else if (*ptr == 'M') + idx = 13; + else { + x += 6; + continue; + } + + for (j = 0; j < 5; j++) { + screen_write_cursormove(ctx, x, y + j); + for (i = 0; i < 5; i++) { + if (clock_table[idx][j][i]) + gc.attr |= GRID_ATTR_REVERSE; + else + gc.attr &= ~GRID_ATTR_REVERSE; + screen_write_putc(ctx, &gc, ' '); + } + } + x += 6; + } +} diff --git a/cmd-attach-session.c b/cmd-attach-session.c new file mode 100644 index 00000000..1de4eba6 --- /dev/null +++ b/cmd-attach-session.c @@ -0,0 +1,80 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Attach existing session to the current terminal. + */ + +int cmd_attach_session_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_attach_session_entry = { + "attach-session", "attach", + "[-d] " CMD_TARGET_SESSION_USAGE, + CMD_DFLAG|CMD_CANTNEST|CMD_STARTSERVER, + cmd_target_init, + cmd_target_parse, + cmd_attach_session_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_attach_session_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + char *cause; + + if (ctx->curclient != NULL) + return (0); + + if (ARRAY_LENGTH(&sessions) == 0) { + ctx->error(ctx, "no sessions"); + return (-1); + } + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (!(ctx->cmdclient->flags & CLIENT_TERMINAL)) { + ctx->error(ctx, "not a terminal"); + return (-1); + } + + if (tty_open(&ctx->cmdclient->tty, &cause) != 0) { + ctx->error(ctx, "terminal open failed: %s", cause); + xfree(cause); + return (-1); + } + + if (data->flags & CMD_DFLAG) + server_write_session(s, MSG_DETACH, NULL, 0); + ctx->cmdclient->session = s; + + server_write_client(ctx->cmdclient, MSG_READY, NULL, 0); + recalculate_sizes(); + server_redraw_client(ctx->cmdclient); + + return (1); +} + diff --git a/cmd-bind-key.c b/cmd-bind-key.c new file mode 100644 index 00000000..f0988899 --- /dev/null +++ b/cmd-bind-key.c @@ -0,0 +1,157 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Bind a key to a command, this recurses through cmd_*. + */ + +int cmd_bind_key_parse(struct cmd *, int, char **, char **); +int cmd_bind_key_exec(struct cmd *, struct cmd_ctx *); +void cmd_bind_key_send(struct cmd *, struct buffer *); +void cmd_bind_key_recv(struct cmd *, struct buffer *); +void cmd_bind_key_free(struct cmd *); +size_t cmd_bind_key_print(struct cmd *, char *, size_t); + +struct cmd_bind_key_data { + int key; + int can_repeat; + struct cmd_list *cmdlist; +}; + +const struct cmd_entry cmd_bind_key_entry = { + "bind-key", "bind", + "[-r] key command [arguments]", + 0, + NULL, + cmd_bind_key_parse, + cmd_bind_key_exec, + cmd_bind_key_send, + cmd_bind_key_recv, + cmd_bind_key_free, + cmd_bind_key_print +}; + +int +cmd_bind_key_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_bind_key_data *data; + int opt; + + self->data = data = xmalloc(sizeof *data); + data->can_repeat = 0; + data->cmdlist = NULL; + + while ((opt = getopt(argc, argv, "r")) != -1) { + switch (opt) { + case 'r': + data->can_repeat = 1; + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc < 1) + goto usage; + + if ((data->key = key_string_lookup_string(argv[0])) == KEYC_NONE) { + xasprintf(cause, "unknown key: %s", argv[0]); + goto error; + } + + argc--; + argv++; + if ((data->cmdlist = cmd_list_parse(argc, argv, cause)) == NULL) + goto error; + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + self->entry->free(self); + return (-1); +} + +int +cmd_bind_key_exec(struct cmd *self, unused struct cmd_ctx *ctx) +{ + struct cmd_bind_key_data *data = self->data; + + if (data == NULL) + return (0); + + key_bindings_add(data->key, data->can_repeat, data->cmdlist); + data->cmdlist = NULL; /* avoid free */ + + return (0); +} + +void +cmd_bind_key_send(struct cmd *self, struct buffer *b) +{ + struct cmd_bind_key_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_list_send(data->cmdlist, b); +} + +void +cmd_bind_key_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_bind_key_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->cmdlist = cmd_list_recv(b); +} + +void +cmd_bind_key_free(struct cmd *self) +{ + struct cmd_bind_key_data *data = self->data; + + if (data->cmdlist != NULL) + cmd_list_free(data->cmdlist); + xfree(data); +} + +size_t +cmd_bind_key_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_bind_key_data *data = self->data; + size_t off = 0; + const char *skey; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len) { + skey = key_string_lookup_key(data->key); + off += xsnprintf(buf + off, len - off, " %s ", skey); + } + if (off < len) + off += cmd_list_print(data->cmdlist, buf + off, len - off); + return (off); +} diff --git a/cmd-break-pane.c b/cmd-break-pane.c new file mode 100644 index 00000000..f2b51edf --- /dev/null +++ b/cmd-break-pane.c @@ -0,0 +1,92 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Break pane off into a window. + */ + +int cmd_break_pane_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_break_pane_entry = { + "break-pane", "breakp", + CMD_PANE_WINDOW_USAGE " [-d]", + CMD_DFLAG, + cmd_pane_init, + cmd_pane_parse, + cmd_break_pane_exec, + cmd_pane_send, + cmd_pane_recv, + cmd_pane_free, + cmd_pane_print +}; + +int +cmd_break_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_pane_data *data = self->data; + struct winlink *wl; + struct session *s; + struct window_pane *wp; + struct window *w; + char *cause; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + if (data->pane == -1) + wp = wl->window->active; + else { + wp = window_pane_at_index(wl->window, data->pane); + if (wp == NULL) { + ctx->error(ctx, "no pane: %d", data->pane); + return (-1); + } + } + + if (window_count_panes(wl->window) == 1) { + ctx->error(ctx, "can't break pane: %d", data->pane); + return (-1); + } + + TAILQ_REMOVE(&wl->window->panes, wp, entry); + if (wl->window->active == wp) { + wl->window->active = TAILQ_PREV(wp, window_panes, entry); + if (wl->window->active == NULL) + wl->window->active = TAILQ_NEXT(wp, entry); + } + layout_refresh(wl->window, 0); + + w = wp->window = window_create1(s->sx, s->sy); + TAILQ_INSERT_HEAD(&w->panes, wp, entry); + w->active = wp; + w->name = default_window_name(w); + + wl = session_attach(s, w, -1, &cause); /* can't fail */ + if (!(data->flags & CMD_DFLAG)) + session_select(s, wl->idx); + layout_refresh(w, 0); + + server_redraw_session(s); + + return (0); +} diff --git a/cmd-choose-session.c b/cmd-choose-session.c new file mode 100644 index 00000000..d1f6ba23 --- /dev/null +++ b/cmd-choose-session.c @@ -0,0 +1,107 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Enter choice mode to choose a session. + */ + +int cmd_choose_session_exec(struct cmd *, struct cmd_ctx *); + +void cmd_choose_session_callback(void *, int); + +const struct cmd_entry cmd_choose_session_entry = { + "choose-session", NULL, + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_choose_session_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +struct cmd_choose_session_data { + u_int client; +}; + +int +cmd_choose_session_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct cmd_choose_session_data *cdata; + struct winlink *wl; + struct session *s; + u_int i, idx, cur; + + if (ctx->curclient == NULL) { + ctx->error(ctx, "must be run interactively"); + return (-1); + } + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) + return (0); + + cur = idx = 0; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL) + continue; + if (s == ctx->curclient->session) + cur = idx; + idx++; + + window_choose_add(wl->window->active, i, + "%s: %u windows [%ux%u]%s", s->name, + winlink_count(&s->windows), s->sx, s->sy, + s->flags & SESSION_UNATTACHED ? "" : " (attached)"); + } + + cdata = xmalloc(sizeof *cdata); + cdata->client = server_client_index(ctx->curclient); + + window_choose_ready( + wl->window->active, cur, cmd_choose_session_callback, cdata); + + return (0); +} + +void +cmd_choose_session_callback(void *data, int idx) +{ + struct cmd_choose_session_data *cdata = data; + struct client *c; + + if (idx != -1 && cdata->client <= ARRAY_LENGTH(&clients) - 1) { + c = ARRAY_ITEM(&clients, cdata->client); + if (c != NULL && (u_int) idx <= ARRAY_LENGTH(&sessions) - 1) { + c->session = ARRAY_ITEM(&sessions, idx); + recalculate_sizes(); + server_redraw_client(c); + } + } + xfree(cdata); +} diff --git a/cmd-choose-window.c b/cmd-choose-window.c new file mode 100644 index 00000000..64eab882 --- /dev/null +++ b/cmd-choose-window.c @@ -0,0 +1,106 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Enter choice mode to choose a window. + */ + +int cmd_choose_window_exec(struct cmd *, struct cmd_ctx *); + +void cmd_choose_window_callback(void *, int); + +const struct cmd_entry cmd_choose_window_entry = { + "choose-window", NULL, + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_choose_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +struct cmd_choose_window_data { + u_int session; +}; + +int +cmd_choose_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct cmd_choose_window_data *cdata; + struct session *s; + struct winlink *wl, *wm; + struct window *w; + u_int idx, cur; + + if (ctx->curclient == NULL) { + ctx->error(ctx, "must be run interactively"); + return (-1); + } + s = ctx->curclient->session; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) + return (0); + + cur = idx = 0; + RB_FOREACH(wm, winlinks, &s->windows) { + w = wm->window; + + if (wm == s->curw) + cur = idx; + idx++; + + window_choose_add(wl->window->active, + wm->idx, "%3d: %s [%ux%u %s] (%u panes)", wm->idx, w->name, + w->sx, w->sy, layout_name(w), window_count_panes(w)); + } + + cdata = xmalloc(sizeof *cdata); + if (session_index(s, &cdata->session) != 0) + fatalx("session not found"); + + window_choose_ready( + wl->window->active, cur, cmd_choose_window_callback, cdata); + + return (0); +} + +void +cmd_choose_window_callback(void *data, int idx) +{ + struct cmd_choose_window_data *cdata = data; + struct session *s; + + if (idx != -1 && cdata->session <= ARRAY_LENGTH(&sessions) - 1) { + s = ARRAY_ITEM(&sessions, cdata->session); + if (s != NULL && session_select(s, idx) == 0) + server_redraw_session(s); + recalculate_sizes(); + } + xfree(cdata); +} diff --git a/cmd-clear-history.c b/cmd-clear-history.c new file mode 100644 index 00000000..0c08f9b5 --- /dev/null +++ b/cmd-clear-history.c @@ -0,0 +1,68 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Clear pane history. + */ + +void cmd_clear_history_init(struct cmd *, int); +int cmd_clear_history_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_clear_history_entry = { + "clear-history", "clearhist", + CMD_PANE_WINDOW_USAGE, + 0, + cmd_pane_init, + cmd_pane_parse, + cmd_clear_history_exec, + cmd_pane_send, + cmd_pane_recv, + cmd_pane_free, + cmd_pane_print +}; + +int +cmd_clear_history_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_pane_data *data = self->data; + struct winlink *wl; + struct window_pane *wp; + struct grid *gd; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + if (data->pane == -1) + wp = wl->window->active; + else { + wp = window_pane_at_index(wl->window, data->pane); + if (wp == NULL) { + ctx->error(ctx, "no pane: %d", data->pane); + return (-1); + } + } + gd = wp->base.grid; + + grid_move_lines(gd, 0, gd->hsize, gd->sy); + gd->hsize = 0; + + return (0); +} diff --git a/cmd-clock-mode.c b/cmd-clock-mode.c new file mode 100644 index 00000000..7f3f0d07 --- /dev/null +++ b/cmd-clock-mode.c @@ -0,0 +1,54 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Enter clock mode. + */ + +int cmd_clock_mode_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_clock_mode_entry = { + "clock-mode", NULL, + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_clock_mode_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_clock_mode_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + window_pane_set_mode(wl->window->active, &window_clock_mode); + + return (0); +} diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c new file mode 100644 index 00000000..cf914648 --- /dev/null +++ b/cmd-command-prompt.c @@ -0,0 +1,178 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Prompt for command in client. + */ + +void cmd_command_prompt_init(struct cmd *, int); +int cmd_command_prompt_exec(struct cmd *, struct cmd_ctx *); + +int cmd_command_prompt_callback(void *, const char *); + +const struct cmd_entry cmd_command_prompt_entry = { + "command-prompt", NULL, + CMD_TARGET_CLIENT_USAGE " [template]", + CMD_ARG01, + cmd_command_prompt_init, + cmd_target_parse, + cmd_command_prompt_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +struct cmd_command_prompt_data { + struct client *c; + char *template; +}; + +void +cmd_command_prompt_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + switch (key) { + case ',': + data->arg = xstrdup("rename-window '%%'"); + break; + case '.': + data->arg = xstrdup("move-window -t '%%'"); + break; + case 'f': + data->arg = xstrdup("find-window '%%'"); + break; + } +} + +int +cmd_command_prompt_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct cmd_command_prompt_data *cdata; + struct client *c; + char *hdr, *ptr; + + if ((c = cmd_find_client(ctx, data->target)) == NULL) + return (-1); + + if (c->prompt_string != NULL) + return (0); + + cdata = xmalloc(sizeof *cdata); + cdata->c = c; + if (data->arg != NULL) { + cdata->template = xstrdup(data->arg); + if ((ptr = strchr(data->arg, ' ')) == NULL) + ptr = strchr(data->arg, '\0'); + xasprintf(&hdr, "(%.*s) ", (int) (ptr - data->arg), data->arg); + } else { + cdata->template = NULL; + hdr = xstrdup(":"); + } + status_prompt_set(c, hdr, cmd_command_prompt_callback, cdata, 0); + xfree(hdr); + + return (0); +} + +int +cmd_command_prompt_callback(void *data, const char *s) +{ + struct cmd_command_prompt_data *cdata = data; + struct client *c = cdata->c; + struct cmd_list *cmdlist; + struct cmd_ctx ctx; + char *cause, *ptr, *buf, ch; + size_t len, slen; + + if (s == NULL) { + xfree(cdata); + return (0); + } + slen = strlen(s); + + len = 0; + buf = NULL; + if (cdata->template != NULL) { + ptr = cdata->template; + while (*ptr != '\0') { + switch (ch = *ptr++) { + case '%': + if (*ptr != '%') + break; + ptr++; + + buf = xrealloc(buf, 1, len + slen + 1); + memcpy(buf + len, s, slen); + len += slen; + break; + default: + buf = xrealloc(buf, 1, len + 2); + buf[len++] = ch; + break; + } + } + xfree(cdata->template); + + buf[len] = '\0'; + s = buf; + } + xfree(cdata); + + if (cmd_string_parse(s, &cmdlist, &cause) != 0) { + if (cause == NULL) + return (0); + *cause = toupper((u_char) *cause); + status_message_set(c, cause); + xfree(cause); + cmdlist = NULL; + } + if (buf != NULL) + xfree(buf); + if (cmdlist == NULL) + return (0); + + ctx.msgdata = NULL; + ctx.cursession = c->session; + ctx.curclient = c; + + ctx.error = key_bindings_error; + ctx.print = key_bindings_print; + ctx.info = key_bindings_info; + + ctx.cmdclient = NULL; + + cmd_list_exec(cmdlist, &ctx); + cmd_list_free(cmdlist); + + if (c->prompt_callback != (void *) &cmd_command_prompt_callback) + return (1); + return (0); +} diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c new file mode 100644 index 00000000..f544d810 --- /dev/null +++ b/cmd-confirm-before.c @@ -0,0 +1,141 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Tiago Cunha + * + * 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 +#include + +#include "tmux.h" + +/* + * Asks for confirmation before executing a command. + */ + +int cmd_confirm_before_exec(struct cmd *, struct cmd_ctx *); +void cmd_confirm_before_init(struct cmd *, int); + +int cmd_confirm_before_callback(void *, const char *); + +struct cmd_confirm_before_data { + struct client *c; + char *cmd; +}; + +const struct cmd_entry cmd_confirm_before_entry = { + "confirm-before", "confirm", + CMD_TARGET_CLIENT_USAGE " command", + CMD_ARG1, + cmd_confirm_before_init, + cmd_target_parse, + cmd_confirm_before_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_confirm_before_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + switch (key) { + case '&': + data->arg = xstrdup("kill-window"); + break; + case 'x': + data->arg = xstrdup("kill-pane"); + break; + } +} + +int +cmd_confirm_before_exec(unused struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct cmd_confirm_before_data *cdata; + struct client *c; + char *buf, *cmd, *ptr; + + if (ctx->curclient == NULL) { + ctx->error(ctx, "must be run interactively"); + return (-1); + } + + if ((c = cmd_find_client(ctx, data->target)) == NULL) + return (-1); + + ptr = xstrdup(data->arg); + if ((cmd = strtok(ptr, " \t")) == NULL) + cmd = ptr; + xasprintf(&buf, "Confirm '%s'? (y/n) ", cmd); + xfree(ptr); + + cdata = xmalloc(sizeof *cdata); + cdata->cmd = xstrdup(data->arg); + cdata->c = c; + status_prompt_set( + cdata->c, buf, cmd_confirm_before_callback, cdata, PROMPT_SINGLE); + + xfree(buf); + return (1); +} + +int +cmd_confirm_before_callback(void *data, const char *s) +{ + struct cmd_confirm_before_data *cdata = data; + struct client *c = cdata->c; + struct cmd_list *cmdlist; + struct cmd_ctx ctx; + char *cause; + + if (s == NULL || tolower((u_char) s[0]) != 'y' || s[1] != '\0') + goto out; + + if (cmd_string_parse(cdata->cmd, &cmdlist, &cause) != 0) { + if (cause != NULL) { + *cause = toupper((u_char) *cause); + status_message_set(c, cause); + xfree(cause); + } + goto out; + } + + ctx.msgdata = NULL; + ctx.cursession = c->session; + ctx.curclient = c; + + ctx.error = key_bindings_error; + ctx.print = key_bindings_print; + ctx.info = key_bindings_info; + + ctx.cmdclient = NULL; + + cmd_list_exec(cmdlist, &ctx); + cmd_list_free(cmdlist); + +out: + if (cdata->cmd != NULL) + xfree(cdata->cmd); + xfree(cdata); + + return (0); +} diff --git a/cmd-copy-buffer.c b/cmd-copy-buffer.c new file mode 100644 index 00000000..ab6fe14b --- /dev/null +++ b/cmd-copy-buffer.c @@ -0,0 +1,222 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Tiago Cunha + * + * 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 + +#include "tmux.h" + +/* + * Copies a session paste buffer to another session. + */ + +int cmd_copy_buffer_parse(struct cmd *, int, char **, char **); +int cmd_copy_buffer_exec(struct cmd *, struct cmd_ctx *); +void cmd_copy_buffer_send(struct cmd *, struct buffer *); +void cmd_copy_buffer_recv(struct cmd *, struct buffer *); +void cmd_copy_buffer_free(struct cmd *); +void cmd_copy_buffer_init(struct cmd *, int); +size_t cmd_copy_buffer_print(struct cmd *, char *, size_t); + +struct cmd_copy_buffer_data { + char *dst_session; + char *src_session; + int dst_idx; + int src_idx; +}; + +const struct cmd_entry cmd_copy_buffer_entry = { + "copy-buffer", "copyb", + "[-a src-index] [-b dst-index] [-s src-session] [-t dst-session]", + 0, + cmd_copy_buffer_init, + cmd_copy_buffer_parse, + cmd_copy_buffer_exec, + cmd_copy_buffer_send, + cmd_copy_buffer_recv, + cmd_copy_buffer_free, + cmd_copy_buffer_print +}; + +void +cmd_copy_buffer_init(struct cmd *self, unused int arg) +{ + struct cmd_copy_buffer_data *data; + + self->data = data = xmalloc(sizeof *data); + data->dst_session = NULL; + data->src_session = NULL; + data->dst_idx = -1; + data->src_idx = -1; +} + +int +cmd_copy_buffer_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_copy_buffer_data *data; + const char *errstr; + int n, opt; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "a:b:s:t:")) != -1) { + switch (opt) { + case 'a': + if (data->src_idx == -1) { + n = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "buffer %s", errstr); + goto error; + } + data->src_idx = n; + } + break; + case 'b': + if (data->dst_idx == -1) { + n = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "buffer %s", errstr); + goto error; + } + data->dst_idx = n; + } + break; + case 's': + if (data->src_session == NULL) + data->src_session = xstrdup(optarg); + break; + case 't': + if (data->dst_session == NULL) + data->dst_session = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + self->entry->free(self); + return (-1); +} + +int +cmd_copy_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_copy_buffer_data *data = self->data; + struct paste_buffer *pb; + struct session *dst_session, *src_session; + u_int limit; + + if ((dst_session = cmd_find_session(ctx, data->dst_session)) == NULL || + (src_session = cmd_find_session(ctx, data->src_session)) == NULL) + return (-1); + + if (data->src_idx == -1) { + if ((pb = paste_get_top(&src_session->buffers)) == NULL) { + ctx->error(ctx, "no buffers"); + return (-1); + } + } else { + if ((pb = paste_get_index(&src_session->buffers, + data->src_idx)) == NULL) { + ctx->error(ctx, "no buffer %d", data->src_idx); + return (-1); + } + } + + limit = options_get_number(&dst_session->options, "buffer-limit"); + if (data->dst_idx == -1) { + paste_add(&dst_session->buffers, xstrdup(pb->data), limit); + return (0); + } + if (paste_replace(&dst_session->buffers, data->dst_idx, + xstrdup(pb->data)) != 0) { + ctx->error(ctx, "no buffer %d", data->dst_idx); + return (-1); + } + + return (0); +} + +void +cmd_copy_buffer_send(struct cmd *self, struct buffer *b) +{ + struct cmd_copy_buffer_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->dst_session); + cmd_send_string(b, data->src_session); +} + +void +cmd_copy_buffer_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_copy_buffer_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->dst_session = cmd_recv_string(b); + data->src_session = cmd_recv_string(b); +} + +void +cmd_copy_buffer_free(struct cmd *self) +{ + struct cmd_copy_buffer_data *data = self->data; + + if (data->dst_session != NULL) + xfree(data->dst_session); + if (data->src_session != NULL) + xfree(data->src_session); + xfree(data); +} + +size_t +cmd_copy_buffer_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_copy_buffer_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->src_idx != -1) { + off += xsnprintf(buf + off, len - off, " -a %d", + data->src_idx); + } + if (off < len && data->dst_idx != -1) { + off += xsnprintf(buf + off, len - off, " -b %d", + data->dst_idx); + } + if (off < len && data->src_session != NULL) { + off += cmd_prarg(buf + off, len - off, " -s ", + data->src_session); + } + if (off < len && data->dst_session != NULL) { + off += cmd_prarg(buf + off, len - off, " -t ", + data->dst_session); + } + return (off); +} diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c new file mode 100644 index 00000000..af83e10c --- /dev/null +++ b/cmd-copy-mode.c @@ -0,0 +1,56 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Enter copy mode. + */ + +int cmd_copy_mode_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_copy_mode_entry = { + "copy-mode", NULL, + CMD_TARGET_WINDOW_USAGE, + CMD_UFLAG, + cmd_target_init, + cmd_target_parse, + cmd_copy_mode_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + NULL +}; + +int +cmd_copy_mode_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + window_pane_set_mode(wl->window->active, &window_copy_mode); + if (data->flags & CMD_UFLAG) + window_copy_pageup(wl->window->active); + + return (0); +} diff --git a/cmd-delete-buffer.c b/cmd-delete-buffer.c new file mode 100644 index 00000000..7351cf2c --- /dev/null +++ b/cmd-delete-buffer.c @@ -0,0 +1,61 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Delete a paste buffer. + */ + +int cmd_delete_buffer_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_delete_buffer_entry = { + "delete-buffer", "deleteb", + CMD_BUFFER_SESSION_USAGE, + 0, + cmd_buffer_init, + cmd_buffer_parse, + cmd_delete_buffer_exec, + cmd_buffer_send, + cmd_buffer_recv, + cmd_buffer_free, + cmd_buffer_print +}; + +int +cmd_delete_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_buffer_data *data = self->data; + struct session *s; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (data->buffer == -1) + paste_free_top(&s->buffers); + else if (paste_free_index(&s->buffers, data->buffer) != 0) { + ctx->error(ctx, "no buffer %d", data->buffer); + return (-1); + } + + return (0); +} diff --git a/cmd-detach-client.c b/cmd-detach-client.c new file mode 100644 index 00000000..b900d84a --- /dev/null +++ b/cmd-detach-client.c @@ -0,0 +1,54 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Detach a client. + */ + +int cmd_detach_client_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_detach_client_entry = { + "detach-client", "detach", + CMD_TARGET_CLIENT_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_detach_client_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_detach_client_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct client *c; + + if ((c = cmd_find_client(ctx, data->target)) == NULL) + return (-1); + + server_write_client(c, MSG_DETACH, NULL, 0); + + return (0); +} diff --git a/cmd-down-pane.c b/cmd-down-pane.c new file mode 100644 index 00000000..ddfe412a --- /dev/null +++ b/cmd-down-pane.c @@ -0,0 +1,61 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Move down a pane. + */ + +int cmd_down_pane_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_down_pane_entry = { + "down-pane", "downp", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_down_pane_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_down_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct window *w; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + w = wl->window; + + do { + w->active = TAILQ_NEXT(w->active, entry); + if (w->active == NULL) + w->active = TAILQ_FIRST(&w->panes); + layout_refresh(w, 1); + } while (w->active->flags & PANE_HIDDEN); + + return (0); +} diff --git a/cmd-find-window.c b/cmd-find-window.c new file mode 100644 index 00000000..42637168 --- /dev/null +++ b/cmd-find-window.c @@ -0,0 +1,160 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Find window containing text. + */ + +int cmd_find_window_exec(struct cmd *, struct cmd_ctx *); + +void cmd_find_window_callback(void *, int); + +const struct cmd_entry cmd_find_window_entry = { + "find-window", "findw", + CMD_TARGET_WINDOW_USAGE " match-string", + CMD_ARG1, + cmd_target_init, + cmd_target_parse, + cmd_find_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +struct cmd_find_window_data { + u_int session; +}; + +int +cmd_find_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct cmd_find_window_data *cdata; + struct session *s; + struct winlink *wl, *wm; + struct window *w; + struct window_pane *wp; + ARRAY_DECL(, u_int) list_idx; + ARRAY_DECL(, char *) list_ctx; + char *sres, *sctx; + u_int i; + + if (ctx->curclient == NULL) { + ctx->error(ctx, "must be run interactively"); + return (-1); + } + s = ctx->curclient->session; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + ARRAY_INIT(&list_idx); + ARRAY_INIT(&list_ctx); + + RB_FOREACH(wm, winlinks, &s->windows) { + i = 0; + TAILQ_FOREACH(wp, &wm->window->panes, entry) { + i++; + + if (strstr(wm->window->name, data->arg) != NULL) + sctx = xstrdup(""); + else { + sres = window_pane_search(wp, data->arg); + if (sres == NULL && + strstr(wp->base.title, data->arg) == NULL) + continue; + + if (sres == NULL) { + xasprintf(&sctx, + "pane %u title: \"%s\"", i - 1, + wp->base.title); + } else { + xasprintf(&sctx, "\"%s\"", sres); + xfree(sres); + } + } + + ARRAY_ADD(&list_idx, wm->idx); + ARRAY_ADD(&list_ctx, sctx); + } + } + + if (ARRAY_LENGTH(&list_idx) == 0) { + ctx->error(ctx, "no windows matching: %s", data->arg); + ARRAY_FREE(&list_idx); + ARRAY_FREE(&list_ctx); + return (-1); + } + + if (ARRAY_LENGTH(&list_idx) == 1) { + if (session_select(s, ARRAY_FIRST(&list_idx)) == 0) + server_redraw_session(s); + recalculate_sizes(); + goto out; + } + + if (window_pane_set_mode(wl->window->active, &window_choose_mode) != 0) + goto out; + + for (i = 0; i < ARRAY_LENGTH(&list_idx); i++) { + wm = winlink_find_by_index( + &s->windows, ARRAY_ITEM(&list_idx, i)); + w = wm->window; + + sctx = ARRAY_ITEM(&list_ctx, i); + window_choose_add(wl->window->active, + wm->idx, "%3d: %s [%ux%u] (%u panes) %s", wm->idx, w->name, + w->sx, w->sy, window_count_panes(w), sctx); + xfree(sctx); + } + + cdata = xmalloc(sizeof *cdata); + if (session_index(s, &cdata->session) != 0) + fatalx("session not found"); + + window_choose_ready( + wl->window->active, 0, cmd_find_window_callback, cdata); + +out: + ARRAY_FREE(&list_idx); + ARRAY_FREE(&list_ctx); + + return (0); +} + +void +cmd_find_window_callback(void *data, int idx) +{ + struct cmd_find_window_data *cdata = data; + struct session *s; + + if (idx != -1 && cdata->session <= ARRAY_LENGTH(&sessions) - 1) { + s = ARRAY_ITEM(&sessions, cdata->session); + if (s != NULL && session_select(s, idx) == 0) + server_redraw_session(s); + recalculate_sizes(); + } + xfree(cdata); +} diff --git a/cmd-generic.c b/cmd-generic.c new file mode 100644 index 00000000..01254a06 --- /dev/null +++ b/cmd-generic.c @@ -0,0 +1,694 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +#define CMD_FLAGS "adDgkuU" +#define CMD_FLAGMASK (CMD_AFLAG|CMD_DFLAG|CMD_BIGDFLAG|CMD_GFLAG|CMD_KFLAG| \ + CMD_UFLAG|CMD_BIGUFLAG) + +int cmd_do_flags(int, int, int *); +size_t cmd_print_flags(char *, size_t, size_t, int); +int cmd_fill_argument(int, char **, int, char **); + +size_t +cmd_prarg(char *buf, size_t len, const char *prefix, char *arg) +{ + if (strchr(arg, ' ') != NULL) + return (xsnprintf(buf, len, "%s\"%s\"", prefix, arg)); + return (xsnprintf(buf, len, "%s%s", prefix, arg)); +} + +int +cmd_do_flags(int opt, int iflags, int *oflags) +{ + switch (opt) { + case 'a': + if (iflags & CMD_AFLAG) { + (*oflags) |= CMD_AFLAG; + return (0); + } + return (-1); + case 'd': + if (iflags & CMD_DFLAG) { + (*oflags) |= CMD_DFLAG; + return (0); + } + return (-1); + case 'D': + if (iflags & CMD_BIGDFLAG) { + (*oflags) |= CMD_BIGDFLAG; + return (0); + } + return (-1); + case 'g': + if (iflags & CMD_GFLAG) { + (*oflags) |= CMD_GFLAG; + return (0); + } + return (-1); + case 'k': + if (iflags & CMD_KFLAG) { + (*oflags) |= CMD_KFLAG; + return (0); + } + return (-1); + case 'u': + if (iflags & CMD_UFLAG) { + (*oflags) |= CMD_UFLAG; + return (0); + } + return (-1); + case 'U': + if (iflags & CMD_BIGUFLAG) { + (*oflags) |= CMD_BIGUFLAG; + return (0); + } + return (-1); + } + return (1); +} + +size_t +cmd_print_flags(char *buf, size_t len, size_t off, int flags) +{ + size_t boff = off; + + if ((flags & CMD_FLAGMASK) == 0) + return (0); + off += xsnprintf(buf + off, len - off, " -"); + if (off < len && flags & CMD_AFLAG) + off += xsnprintf(buf + off, len - off, "a"); + if (off < len && flags & CMD_BIGDFLAG) + off += xsnprintf(buf + off, len - off, "D"); + if (off < len && flags & CMD_DFLAG) + off += xsnprintf(buf + off, len - off, "d"); + if (off < len && flags & CMD_GFLAG) + off += xsnprintf(buf + off, len - off, "g"); + if (off < len && flags & CMD_KFLAG) + off += xsnprintf(buf + off, len - off, "k"); + if (off < len && flags & CMD_UFLAG) + off += xsnprintf(buf + off, len - off, "u"); + if (off < len && flags & CMD_BIGUFLAG) + off += xsnprintf(buf + off, len - off, "U"); + return (off - boff); +} + +int +cmd_fill_argument(int flags, char **arg, int argc, char **argv) +{ + *arg = NULL; + + if (flags & CMD_ARG1) { + if (argc != 1) + return (-1); + *arg = xstrdup(argv[0]); + return (0); + } + + if (flags & CMD_ARG01) { + if (argc != 0 && argc != 1) + return (-1); + if (argc == 1) + *arg = xstrdup(argv[0]); + return (0); + } + + if (argc != 0) + return (-1); + return (0); +} + +void +cmd_target_init(struct cmd *self, unused int key) +{ + struct cmd_target_data *data; + + self->data = data = xmalloc(sizeof *data); + data->flags = 0; + data->target = NULL; + data->arg = NULL; +} + +int +cmd_target_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_target_data *data; + int opt; + + /* Don't use the entry version since it may be dependent on key. */ + cmd_target_init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, CMD_FLAGS "t:")) != -1) { + switch (cmd_do_flags(opt, self->entry->flags, &data->flags)) { + case -1: + goto usage; + case 0: + continue; + } + switch (opt) { + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + + if (cmd_fill_argument(self->entry->flags, &data->arg, argc, argv) != 0) + goto usage; + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +void +cmd_target_send(struct cmd *self, struct buffer *b) +{ + struct cmd_target_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + cmd_send_string(b, data->arg); +} + +void +cmd_target_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_target_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->arg = cmd_recv_string(b); +} + +void +cmd_target_free(struct cmd *self) +{ + struct cmd_target_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + if (data->arg != NULL) + xfree(data->arg); + xfree(data); +} + +size_t +cmd_target_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_target_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + off += cmd_print_flags(buf, len, off, data->flags); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->arg != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->arg); + return (off); +} + +void +cmd_srcdst_init(struct cmd *self, unused int key) +{ + struct cmd_srcdst_data *data; + + self->data = data = xmalloc(sizeof *data); + data->flags = 0; + data->src = NULL; + data->dst = NULL; + data->arg = NULL; +} + +int +cmd_srcdst_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_srcdst_data *data; + int opt; + + cmd_srcdst_init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, CMD_FLAGS "s:t:")) != -1) { + switch (cmd_do_flags(opt, self->entry->flags, &data->flags)) { + case -1: + goto usage; + case 0: + continue; + } + switch (opt) { + case 's': + if (data->src == NULL) + data->src = xstrdup(optarg); + break; + case 't': + if (data->dst == NULL) + data->dst = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + + if (cmd_fill_argument(self->entry->flags, &data->arg, argc, argv) != 0) + goto usage; + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +void +cmd_srcdst_send(struct cmd *self, struct buffer *b) +{ + struct cmd_srcdst_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->src); + cmd_send_string(b, data->dst); + cmd_send_string(b, data->arg); +} + +void +cmd_srcdst_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_srcdst_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->src = cmd_recv_string(b); + data->dst = cmd_recv_string(b); + data->arg = cmd_recv_string(b); +} + +void +cmd_srcdst_free(struct cmd *self) +{ + struct cmd_srcdst_data *data = self->data; + + if (data->src != NULL) + xfree(data->src); + if (data->dst != NULL) + xfree(data->dst); + if (data->arg != NULL) + xfree(data->arg); + xfree(data); +} + +size_t +cmd_srcdst_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_srcdst_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + off += cmd_print_flags(buf, len, off, data->flags); + if (off < len && data->src != NULL) + off += xsnprintf(buf + off, len - off, " -s %s", data->src); + if (off < len && data->dst != NULL) + off += xsnprintf(buf + off, len - off, " -t %s", data->dst); + if (off < len && data->arg != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->arg); + return (off); +} + +void +cmd_buffer_init(struct cmd *self, unused int key) +{ + struct cmd_buffer_data *data; + + self->data = data = xmalloc(sizeof *data); + data->flags = 0; + data->target = NULL; + data->buffer = -1; + data->arg = NULL; +} + +int +cmd_buffer_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_buffer_data *data; + int opt, n; + const char *errstr; + + cmd_buffer_init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, CMD_FLAGS "b:t:")) != -1) { + switch (cmd_do_flags(opt, self->entry->flags, &data->flags)) { + case -1: + goto usage; + case 0: + continue; + } + switch (opt) { + case 'b': + if (data->buffer == -1) { + n = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "buffer %s", errstr); + goto error; + } + data->buffer = n; + } + break; + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + + if (cmd_fill_argument(self->entry->flags, &data->arg, argc, argv) != 0) + goto usage; + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + self->entry->free(self); + return (-1); +} + +void +cmd_buffer_send(struct cmd *self, struct buffer *b) +{ + struct cmd_buffer_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + cmd_send_string(b, data->arg); +} + +void +cmd_buffer_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_buffer_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->arg = cmd_recv_string(b); +} + +void +cmd_buffer_free(struct cmd *self) +{ + struct cmd_buffer_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + if (data->arg != NULL) + xfree(data->arg); + xfree(data); +} + +size_t +cmd_buffer_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_buffer_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + off += cmd_print_flags(buf, len, off, data->flags); + if (off < len && data->buffer != -1) + off += xsnprintf(buf + off, len - off, " -b %d", data->buffer); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->arg != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->arg); + return (off); +} + +void +cmd_option_init(struct cmd *self, unused int key) +{ + struct cmd_option_data *data; + + self->data = data = xmalloc(sizeof *data); + data->flags = 0; + data->target = NULL; + data->option = NULL; + data->value = NULL; +} + +int +cmd_option_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_option_data *data; + int opt; + + /* Don't use the entry version since it may be dependent on key. */ + cmd_option_init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, CMD_FLAGS "t:")) != -1) { + switch (cmd_do_flags(opt, self->entry->flags, &data->flags)) { + case -1: + goto usage; + case 0: + continue; + } + switch (opt) { + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + + if (argc == 2) { + data->option = xstrdup(argv[0]); + data->value = xstrdup(argv[1]); + } else if (argc == 1) + data->option = xstrdup(argv[0]); + else + goto usage; + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +void +cmd_option_send(struct cmd *self, struct buffer *b) +{ + struct cmd_option_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + cmd_send_string(b, data->option); + cmd_send_string(b, data->value); +} + +void +cmd_option_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_option_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->option = cmd_recv_string(b); + data->value = cmd_recv_string(b); +} + +void +cmd_option_free(struct cmd *self) +{ + struct cmd_option_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + if (data->option != NULL) + xfree(data->option); + if (data->value != NULL) + xfree(data->value); + xfree(data); +} + +size_t +cmd_option_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_option_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + off += cmd_print_flags(buf, len, off, data->flags); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->option != NULL) + off += xsnprintf(buf + off, len - off, " %s", data->option); + if (off < len && data->value != NULL) + off += xsnprintf(buf + off, len - off, " %s", data->value); + return (off); +} + +void +cmd_pane_init(struct cmd *self, unused int key) +{ + struct cmd_pane_data *data; + + self->data = data = xmalloc(sizeof *data); + data->flags = 0; + data->target = NULL; + data->arg = NULL; + data->pane = -1; +} + +int +cmd_pane_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_pane_data *data; + int opt, n; + const char *errstr; + + /* Don't use the entry version since it may be dependent on key. */ + cmd_pane_init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, CMD_FLAGS "p:t:")) != -1) { + switch (cmd_do_flags(opt, self->entry->flags, &data->flags)) { + case -1: + goto usage; + case 0: + continue; + } + switch (opt) { + case 'p': + if (data->pane == -1) { + n = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "pane %s", errstr); + goto error; + } + data->pane = n; + } + break; + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + + if (cmd_fill_argument(self->entry->flags, &data->arg, argc, argv) != 0) + goto usage; + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + self->entry->free(self); + return (-1); +} + +void +cmd_pane_send(struct cmd *self, struct buffer *b) +{ + struct cmd_pane_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + cmd_send_string(b, data->arg); +} + +void +cmd_pane_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_pane_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->arg = cmd_recv_string(b); +} + +void +cmd_pane_free(struct cmd *self) +{ + struct cmd_pane_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + if (data->arg != NULL) + xfree(data->arg); + xfree(data); +} + +size_t +cmd_pane_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_pane_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + off += cmd_print_flags(buf, len, off, data->flags); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->arg != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->arg); + return (off); +} diff --git a/cmd-has-session.c b/cmd-has-session.c new file mode 100644 index 00000000..c0620e58 --- /dev/null +++ b/cmd-has-session.c @@ -0,0 +1,51 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Cause client to report an error and exit with 1 if session doesn't exist. + */ + +int cmd_has_session_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_has_session_entry = { + "has-session", "has", + CMD_TARGET_SESSION_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_has_session_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_has_session_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + + if (cmd_find_session(ctx, data->target) == NULL) + return (-1); + + return (0); +} diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c new file mode 100644 index 00000000..b0e1dc7e --- /dev/null +++ b/cmd-kill-pane.c @@ -0,0 +1,72 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Kill pane. + */ + +int cmd_kill_pane_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_kill_pane_entry = { + "kill-pane", "killp", + CMD_PANE_WINDOW_USAGE, + 0, + cmd_pane_init, + cmd_pane_parse, + cmd_kill_pane_exec, + cmd_pane_send, + cmd_pane_recv, + cmd_pane_free, + cmd_pane_print +}; + +int +cmd_kill_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_pane_data *data = self->data; + struct winlink *wl; + struct window_pane *wp; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + if (data->pane == -1) + wp = wl->window->active; + else { + wp = window_pane_at_index(wl->window, data->pane); + if (wp == NULL) { + ctx->error(ctx, "no pane: %d", data->pane); + return (-1); + } + } + + if (window_count_panes(wl->window) == 1) { + ctx->error(ctx, "can't kill pane: %d", data->pane); + return (-1); + } + + window_remove_pane(wl->window, wp); + server_redraw_window(wl->window); + layout_refresh(wl->window, 0); + return (0); +} diff --git a/cmd-kill-server.c b/cmd-kill-server.c new file mode 100644 index 00000000..b2627a0a --- /dev/null +++ b/cmd-kill-server.c @@ -0,0 +1,51 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Kill the server and do nothing else. + */ + +int cmd_kill_server_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_kill_server_entry = { + "kill-server", NULL, + "", + 0, + NULL, + NULL, + cmd_kill_server_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_kill_server_exec(unused struct cmd *self, unused struct cmd_ctx *ctx) +{ + sigterm = 1; + + return (0); +} diff --git a/cmd-kill-session.c b/cmd-kill-session.c new file mode 100644 index 00000000..0ef9e4f1 --- /dev/null +++ b/cmd-kill-session.c @@ -0,0 +1,68 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Destroy session, detaching all clients attached to it and destroying any + * windows linked only to this session. + * + * Note this deliberately has no alias to make it hard to hit by accident. + */ + +int cmd_kill_session_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_kill_session_entry = { + "kill-session", NULL, + CMD_TARGET_SESSION_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_kill_session_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_kill_session_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + struct client *c; + u_int i; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c->session == s) { + c->session = NULL; + server_write_client(c, MSG_EXIT, NULL, 0); + } + } + recalculate_sizes(); + + session_destroy(s); + + return (0); +} diff --git a/cmd-kill-window.c b/cmd-kill-window.c new file mode 100644 index 00000000..155968d0 --- /dev/null +++ b/cmd-kill-window.c @@ -0,0 +1,69 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Destroy window. + */ + +int cmd_kill_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_kill_window_entry = { + "kill-window", "killw", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_kill_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_kill_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct session *s; + struct client *c; + u_int i; + int destroyed; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + + destroyed = session_detach(s, wl); + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session != s) + continue; + if (destroyed) { + c->session = NULL; + server_write_client(c, MSG_EXIT, NULL, 0); + } else + server_redraw_client(c); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-last-window.c b/cmd-last-window.c new file mode 100644 index 00000000..48c7fe0e --- /dev/null +++ b/cmd-last-window.c @@ -0,0 +1,60 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Move to last window. + */ + +int cmd_last_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_last_window_entry = { + "last-window", "last", + CMD_TARGET_SESSION_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_last_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_last_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (session_last(s) == 0) + server_redraw_session(s); + else { + ctx->error(ctx, "no last window"); + return (-1); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-link-window.c b/cmd-link-window.c new file mode 100644 index 00000000..143e3cf0 --- /dev/null +++ b/cmd-link-window.c @@ -0,0 +1,109 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Link a window into another session. + */ + +int cmd_link_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_link_window_entry = { + "link-window", "linkw", + "[-dk] " CMD_SRCDST_WINDOW_USAGE, + CMD_DFLAG|CMD_KFLAG, + cmd_srcdst_init, + cmd_srcdst_parse, + cmd_link_window_exec, + cmd_srcdst_send, + cmd_srcdst_recv, + cmd_srcdst_free, + cmd_srcdst_print +}; + +int +cmd_link_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_srcdst_data *data = self->data; + struct session *dst; + struct winlink *wl_src, *wl_dst; + char *cause; + int idx; + + if ((wl_src = cmd_find_window(ctx, data->src, NULL)) == NULL) + return (-1); + + if (arg_parse_window(data->dst, &dst, &idx) != 0) { + ctx->error(ctx, "bad window: %s", data->dst); + return (-1); + } + if (dst == NULL) + dst = ctx->cursession; + if (dst == NULL) + dst = cmd_current_session(ctx); + if (dst == NULL) { + ctx->error(ctx, "session not found: %s", data->dst); + return (-1); + } + + wl_dst = NULL; + if (idx != -1) + wl_dst = winlink_find_by_index(&dst->windows, idx); + if (wl_dst != NULL) { + if (wl_dst->window == wl_src->window) + return (0); + + if (data->flags & CMD_KFLAG) { + /* + * Can't use session_detach as it will destroy session + * if this makes it empty. + */ + session_alert_cancel(dst, wl_dst); + winlink_stack_remove(&dst->lastw, wl_dst); + winlink_remove(&dst->windows, wl_dst); + + /* Force select/redraw if current. */ + if (wl_dst == dst->curw) { + data->flags &= ~CMD_DFLAG; + dst->curw = NULL; + } + } + } + + wl_dst = session_attach(dst, wl_src->window, idx, &cause); + if (wl_dst == NULL) { + ctx->error(ctx, "create session failed: %s", cause); + xfree(cause); + return (-1); + } + + if (data->flags & CMD_DFLAG) + server_status_session(dst); + else { + session_select(dst, wl_dst->idx); + server_redraw_session(dst); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c new file mode 100644 index 00000000..4edc38c3 --- /dev/null +++ b/cmd-list-buffers.c @@ -0,0 +1,91 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * List paste buffers. + */ + +int cmd_list_buffers_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_list_buffers_entry = { + "list-buffers", "lsb", + CMD_TARGET_SESSION_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_list_buffers_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_list_buffers_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + struct paste_buffer *pb; + u_int idx; + char *tmp; + size_t size, in, out; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (s->sx > 35) { /* leave three for ... */ + size = s->sx - 32; + tmp = xmalloc(size + 1); + } else { + size = 0; + tmp = NULL; + } + + idx = 0; + while ((pb = paste_walk_stack(&s->buffers, &idx)) != NULL) { + if (tmp != NULL) { + in = out = 0; + while (out < size && pb->data[in] != '\0') { + if (pb->data[in] > 31 && pb->data[in] != 127) + tmp[out++] = pb->data[in]; + in++; + } + tmp[out] = '\0'; + if (out == size) { + tmp[out - 1] = '.'; + tmp[out - 2] = '.'; + tmp[out - 3] = '.'; + } + + ctx->print(ctx, "%d: %zu bytes: \"%s\"", + idx - 1, strlen(pb->data), tmp); + } else + ctx->print(ctx, "%d: %zu bytes", idx, strlen(pb->data)); + } + + if (tmp != NULL) + xfree(tmp); + + return (0); +} diff --git a/cmd-list-clients.c b/cmd-list-clients.c new file mode 100644 index 00000000..343afc20 --- /dev/null +++ b/cmd-list-clients.c @@ -0,0 +1,67 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * List all clients. + */ + +int cmd_list_clients_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_list_clients_entry = { + "list-clients", "lsc", + "", + 0, + NULL, + NULL, + cmd_list_clients_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_list_clients_exec(unused struct cmd *self, struct cmd_ctx *ctx) +{ + struct client *c; + u_int i; + const char *s_utf8; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + + if (c->tty.flags & TTY_UTF8) + s_utf8 = " (utf8)"; + else + s_utf8 = ""; + ctx->print(ctx, "%s: %s [%ux%u %s]%s", c->tty.path, + c->session->name, c->tty.sx, c->tty.sy, + c->tty.termname, s_utf8); + } + + return (0); +} diff --git a/cmd-list-commands.c b/cmd-list-commands.c new file mode 100644 index 00000000..59938ae2 --- /dev/null +++ b/cmd-list-commands.c @@ -0,0 +1,51 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * List all commands with usages. + */ + +int cmd_list_commands_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_list_commands_entry = { + "list-commands", "lscm", + "", + 0, + NULL, + NULL, + cmd_list_commands_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_list_commands_exec(unused struct cmd *self, struct cmd_ctx *ctx) +{ + const struct cmd_entry **entryp; + + for (entryp = cmd_table; *entryp != NULL; entryp++) + ctx->print(ctx, "%s %s", (*entryp)->name, (*entryp)->usage); + + return (0); +} diff --git a/cmd-list-keys.c b/cmd-list-keys.c new file mode 100644 index 00000000..1b22b4ab --- /dev/null +++ b/cmd-list-keys.c @@ -0,0 +1,59 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * List key bindings. + */ + +int cmd_list_keys_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_list_keys_entry = { + "list-keys", "lsk", + "", + 0, + NULL, + NULL, + cmd_list_keys_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_list_keys_exec(unused struct cmd *self, struct cmd_ctx *ctx) +{ + struct key_binding *bd; + const char *key; + char tmp[BUFSIZ]; + + SPLAY_FOREACH(bd, key_bindings, &key_bindings) { + if ((key = key_string_lookup_key(bd->key)) == NULL) + continue; + + *tmp = '\0'; + cmd_list_print(bd->cmdlist, tmp, sizeof tmp); + ctx->print(ctx, "%11s: %s", key, tmp); + } + + return (0); +} diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c new file mode 100644 index 00000000..01f86524 --- /dev/null +++ b/cmd-list-sessions.c @@ -0,0 +1,67 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * List all sessions. + */ + +int cmd_list_sessions_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_list_sessions_entry = { + "list-sessions", "ls", "", + 0, + NULL, + NULL, + cmd_list_sessions_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_list_sessions_exec(unused struct cmd *self, struct cmd_ctx *ctx) +{ + struct session *s; + char *tim; + u_int i; + time_t t; + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL) + continue; + + t = s->tv.tv_sec; + tim = ctime(&t); + *strchr(tim, '\n') = '\0'; + + ctx->print(ctx, "%s: %u windows (created %s) [%ux%u]%s", + s->name, winlink_count(&s->windows), tim, s->sx, s->sy, + s->flags & SESSION_UNATTACHED ? "" : " (attached)"); + } + + return (0); +} diff --git a/cmd-list-windows.c b/cmd-list-windows.c new file mode 100644 index 00000000..c9c3ad8c --- /dev/null +++ b/cmd-list-windows.c @@ -0,0 +1,88 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * List windows on given session. + */ + +int cmd_list_windows_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_list_windows_entry = { + "list-windows", "lsw", + CMD_TARGET_SESSION_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_list_windows_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_list_windows_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + struct winlink *wl; + struct window *w; + struct window_pane *wp; + struct grid *gd; + u_int i; + unsigned long long size; + const char *name; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + RB_FOREACH(wl, winlinks, &s->windows) { + w = wl->window; + ctx->print(ctx, + "%3d: %s [%ux%u]", wl->idx, w->name, w->sx, w->sy); + + TAILQ_FOREACH(wp, &w->panes, entry) { + gd = wp->base.grid; + + size = 0; + for (i = 0; i < gd->hsize; i++) { + size += gd->size[i] * sizeof **gd->data; + size += gd->usize[i] * sizeof **gd->udata; + } + size += gd->hsize * (sizeof *gd->data); + size += gd->hsize * (sizeof *gd->size); + + if (wp->fd != -1) + name = ttyname(wp->fd); + else + name = "unknown"; + ctx->print(ctx, + " %s [%ux%u %s] [history %u/%u, %llu bytes]", + name, wp->sx, wp->sy, layout_name(w), gd->hsize, + gd->hlimit, size); + } + } + + return (0); +} diff --git a/cmd-list.c b/cmd-list.c new file mode 100644 index 00000000..ffc8f1ee --- /dev/null +++ b/cmd-list.c @@ -0,0 +1,154 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +struct cmd_list * +cmd_list_parse(int argc, char **argv, char **cause) +{ + struct cmd_list *cmdlist; + struct cmd *cmd; + int i, lastsplit; + size_t arglen, new_argc; + char **new_argv; + + cmdlist = xmalloc(sizeof *cmdlist); + TAILQ_INIT(cmdlist); + + lastsplit = 0; + for (i = 0; i < argc; i++) { + arglen = strlen(argv[i]); + if (arglen == 0 || argv[i][arglen - 1] != ';') + continue; + argv[i][arglen - 1] = '\0'; + + if (arglen > 1 && argv[i][arglen - 2] == '\\') { + argv[i][arglen - 2] = ';'; + continue; + } + + new_argc = i - lastsplit; + new_argv = argv + lastsplit; + if (arglen != 1) + new_argc++; + + cmd = cmd_parse(new_argc, new_argv, cause); + if (cmd == NULL) + goto bad; + TAILQ_INSERT_TAIL(cmdlist, cmd, qentry); + + lastsplit = i + 1; + } + + if (lastsplit != argc) { + cmd = cmd_parse(argc - lastsplit, argv + lastsplit, cause); + if (cmd == NULL) + goto bad; + TAILQ_INSERT_TAIL(cmdlist, cmd, qentry); + } + + return (cmdlist); + +bad: + cmd_list_free(cmdlist); + return (NULL); +} + +int +cmd_list_exec(struct cmd_list *cmdlist, struct cmd_ctx *ctx) +{ + struct cmd *cmd; + int n; + + TAILQ_FOREACH(cmd, cmdlist, qentry) { + if ((n = cmd_exec(cmd, ctx)) != 0) + return (n); + } + return (0); +} + +void +cmd_list_send(struct cmd_list *cmdlist, struct buffer *b) +{ + struct cmd *cmd; + u_int n; + + n = 0; + TAILQ_FOREACH(cmd, cmdlist, qentry) + n++; + + buffer_write(b, &n, sizeof n); + TAILQ_FOREACH(cmd, cmdlist, qentry) + cmd_send(cmd, b); +} + +struct cmd_list * +cmd_list_recv(struct buffer *b) +{ + struct cmd_list *cmdlist; + struct cmd *cmd; + u_int n; + + buffer_read(b, &n, sizeof n); + + cmdlist = xmalloc(sizeof *cmdlist); + TAILQ_INIT(cmdlist); + + while (n-- > 0) { + cmd = cmd_recv(b); + TAILQ_INSERT_TAIL(cmdlist, cmd, qentry); + } + + return (cmdlist); +} + +void +cmd_list_free(struct cmd_list *cmdlist) +{ + struct cmd *cmd; + + while (!TAILQ_EMPTY(cmdlist)) { + cmd = TAILQ_FIRST(cmdlist); + TAILQ_REMOVE(cmdlist, cmd, qentry); + cmd_free(cmd); + } + xfree(cmdlist); +} + +size_t +cmd_list_print(struct cmd_list *cmdlist, char *buf, size_t len) +{ + struct cmd *cmd; + size_t off; + + off = 0; + TAILQ_FOREACH(cmd, cmdlist, qentry) { + if (off >= len) + break; + off += cmd_print(cmd, buf + off, len - off); + if (off >= len) + break; + if (TAILQ_NEXT(cmd, qentry) != NULL) + off += xsnprintf(buf + off, len - off, " ; "); + } + return (off); +} diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c new file mode 100644 index 00000000..23adfec2 --- /dev/null +++ b/cmd-load-buffer.c @@ -0,0 +1,106 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Tiago Cunha + * + * 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 +#include + +#include +#include +#include +#include + +#include "tmux.h" + +/* + * Loads a session paste buffer from a file. + */ + +int cmd_load_buffer_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_load_buffer_entry = { + "load-buffer", "loadb", + CMD_BUFFER_SESSION_USAGE " path", + CMD_ARG1, + cmd_buffer_init, + cmd_buffer_parse, + cmd_load_buffer_exec, + cmd_buffer_send, + cmd_buffer_recv, + cmd_buffer_free, + cmd_buffer_print +}; + +int +cmd_load_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_buffer_data *data = self->data; + struct session *s; + struct stat statbuf; + FILE *f; + char *buf; + u_int limit; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (stat(data->arg, &statbuf) < 0) { + ctx->error(ctx, "%s: %s", data->arg, strerror(errno)); + return (-1); + } + if (!S_ISREG(statbuf.st_mode)) { + ctx->error(ctx, "%s: not a regular file", data->arg); + return (-1); + } + + if ((f = fopen(data->arg, "rb")) == NULL) { + ctx->error(ctx, "%s: %s", data->arg, strerror(errno)); + return (-1); + } + + /* + * We don't want to die due to memory exhaustion, hence xmalloc can't + * be used here. + */ + if ((buf = malloc(statbuf.st_size + 1)) == NULL) { + ctx->error(ctx, "malloc error: %s", strerror(errno)); + return (-1); + } + + if (fread(buf, 1, statbuf.st_size, f) != (size_t) statbuf.st_size) { + ctx->error(ctx, "%s: fread error", data->arg); + xfree(buf); + fclose(f); + return (-1); + } + + buf[statbuf.st_size] = '\0'; + fclose(f); + + limit = options_get_number(&s->options, "buffer-limit"); + if (data->buffer == -1) { + paste_add(&s->buffers, buf, limit); + return (0); + } + if (paste_replace(&s->buffers, data->buffer, buf) != 0) { + ctx->error(ctx, "no buffer %d", data->buffer); + xfree(buf); + return (-1); + } + + return (0); +} diff --git a/cmd-lock-server.c b/cmd-lock-server.c new file mode 100644 index 00000000..5dac3292 --- /dev/null +++ b/cmd-lock-server.c @@ -0,0 +1,54 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#include "tmux.h" + +/* + * Lock server. + */ + +int cmd_lock_server_exec(struct cmd *, struct cmd_ctx *); + +int cmd_lock_server_callback(void *, const char *); + +const struct cmd_entry cmd_lock_server_entry = { + "lock-server", "lock", + "", + 0, + NULL, + NULL, + cmd_lock_server_exec, + NULL, + NULL, + NULL, + NULL, +}; + +int +cmd_lock_server_exec(unused struct cmd *self, unused struct cmd_ctx *ctx) +{ + server_lock(); + + return (0); +} diff --git a/cmd-move-window.c b/cmd-move-window.c new file mode 100644 index 00000000..9f356a64 --- /dev/null +++ b/cmd-move-window.c @@ -0,0 +1,123 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Move a window. + */ + +int cmd_move_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_move_window_entry = { + "move-window", "movew", + "[-dk] " CMD_SRCDST_WINDOW_USAGE, + CMD_DFLAG|CMD_KFLAG, + cmd_srcdst_init, + cmd_srcdst_parse, + cmd_move_window_exec, + cmd_srcdst_send, + cmd_srcdst_recv, + cmd_srcdst_free, + cmd_srcdst_print +}; + +int +cmd_move_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_srcdst_data *data = self->data; + struct session *src, *dst; + struct winlink *wl_src, *wl_dst; + struct client *c; + u_int i; + int destroyed, idx; + char *cause; + + if ((wl_src = cmd_find_window(ctx, data->src, &src)) == NULL) + return (-1); + + if (arg_parse_window(data->dst, &dst, &idx) != 0) { + ctx->error(ctx, "bad window: %s", data->dst); + return (-1); + } + if (dst == NULL) + dst = ctx->cursession; + if (dst == NULL) + dst = cmd_current_session(ctx); + if (dst == NULL) { + ctx->error(ctx, "session not found: %s", data->dst); + return (-1); + } + + wl_dst = NULL; + if (idx != -1) + wl_dst = winlink_find_by_index(&dst->windows, idx); + if (wl_dst != NULL) { + if (wl_dst->window == wl_src->window) + return (0); + + if (data->flags & CMD_KFLAG) { + /* + * Can't use session_detach as it will destroy session + * if this makes it empty. + */ + session_alert_cancel(dst, wl_dst); + winlink_stack_remove(&dst->lastw, wl_dst); + winlink_remove(&dst->windows, wl_dst); + + /* Force select/redraw if current. */ + if (wl_dst == dst->curw) { + data->flags &= ~CMD_DFLAG; + dst->curw = NULL; + } + } + } + + wl_dst = session_attach(dst, wl_src->window, idx, &cause); + if (wl_dst == NULL) { + ctx->error(ctx, "attach window failed: %s", cause); + xfree(cause); + return (-1); + } + + destroyed = session_detach(src, wl_src); + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session != src) + continue; + if (destroyed) { + c->session = NULL; + server_write_client(c, MSG_EXIT, NULL, 0); + } else + server_redraw_client(c); + } + + if (data->flags & CMD_DFLAG) + server_status_session(dst); + else { + session_select(dst, wl_dst->idx); + server_redraw_session(dst); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-new-session.c b/cmd-new-session.c new file mode 100644 index 00000000..7c2c664b --- /dev/null +++ b/cmd-new-session.c @@ -0,0 +1,248 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Create a new session and attach to the current terminal unless -d is given. + */ + +int cmd_new_session_parse(struct cmd *, int, char **, char **); +int cmd_new_session_exec(struct cmd *, struct cmd_ctx *); +void cmd_new_session_send(struct cmd *, struct buffer *); +void cmd_new_session_recv(struct cmd *, struct buffer *); +void cmd_new_session_free(struct cmd *); +void cmd_new_session_init(struct cmd *, int); +size_t cmd_new_session_print(struct cmd *, char *, size_t); + +struct cmd_new_session_data { + char *newname; + char *winname; + char *cmd; + int flag_detached; +}; + +const struct cmd_entry cmd_new_session_entry = { + "new-session", "new", + "[-d] [-n window-name] [-s session-name] [command]", + CMD_STARTSERVER|CMD_CANTNEST, + cmd_new_session_init, + cmd_new_session_parse, + cmd_new_session_exec, + cmd_new_session_send, + cmd_new_session_recv, + cmd_new_session_free, + cmd_new_session_print +}; + +void +cmd_new_session_init(struct cmd *self, unused int arg) +{ + struct cmd_new_session_data *data; + + self->data = data = xmalloc(sizeof *data); + data->flag_detached = 0; + data->newname = NULL; + data->winname = NULL; + data->cmd = NULL; +} + +int +cmd_new_session_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_new_session_data *data; + int opt; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "ds:n:")) != -1) { + switch (opt) { + case 'd': + data->flag_detached = 1; + break; + case 's': + if (data->newname == NULL) + data->newname = xstrdup(optarg); + break; + case 'n': + if (data->winname == NULL) + data->winname = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 0 && argc != 1) + goto usage; + + if (argc == 1) + data->cmd = xstrdup(argv[0]); + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +int +cmd_new_session_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_new_session_data *data = self->data; + struct client *c = ctx->cmdclient; + struct session *s; + char *cmd, *cwd, *cause; + u_int sx, sy; + + if (ctx->curclient != NULL) + return (0); + + if (!data->flag_detached) { + if (c == NULL) { + ctx->error(ctx, "no client to attach to"); + return (-1); + } + if (!(c->flags & CLIENT_TERMINAL)) { + ctx->error(ctx, "not a terminal"); + return (-1); + } + } + + if (data->newname != NULL && session_find(data->newname) != NULL) { + ctx->error(ctx, "duplicate session: %s", data->newname); + return (-1); + } + + cmd = data->cmd; + if (cmd == NULL) + cmd = options_get_string(&global_options, "default-command"); + if (c == NULL || c->cwd == NULL) + cwd = options_get_string(&global_options, "default-path"); + else + cwd = c->cwd; + + sx = 80; + sy = 25; + if (!data->flag_detached) { + sx = c->tty.sx; + sy = c->tty.sy; + } + + if (options_get_number(&global_options, "status")) { + if (sy == 0) + sy = 1; + else + sy--; + } + + if (!data->flag_detached && tty_open(&c->tty, &cause) != 0) { + ctx->error(ctx, "open terminal failed: %s", cause); + xfree(cause); + return (-1); + } + + + s = session_create(data->newname, cmd, cwd, sx, sy, &cause); + if (s == NULL) { + ctx->error(ctx, "create session failed: %s", cause); + xfree(cause); + return (-1); + } + if (data->winname != NULL) { + xfree(s->curw->window->name); + s->curw->window->name = xstrdup(data->winname); + options_set_number( + &s->curw->window->options, "automatic-rename", 0); + } + + if (data->flag_detached) { + if (c != NULL) + server_write_client(c, MSG_EXIT, NULL, 0); + } else { + c->session = s; + server_write_client(c, MSG_READY, NULL, 0); + server_redraw_client(c); + } + recalculate_sizes(); + + return (1); +} + +void +cmd_new_session_send(struct cmd *self, struct buffer *b) +{ + struct cmd_new_session_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->newname); + cmd_send_string(b, data->winname); + cmd_send_string(b, data->cmd); +} + +void +cmd_new_session_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_new_session_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->newname = cmd_recv_string(b); + data->winname = cmd_recv_string(b); + data->cmd = cmd_recv_string(b); +} + +void +cmd_new_session_free(struct cmd *self) +{ + struct cmd_new_session_data *data = self->data; + + if (data->newname != NULL) + xfree(data->newname); + if (data->winname != NULL) + xfree(data->winname); + if (data->cmd != NULL) + xfree(data->cmd); + xfree(data); +} + +size_t +cmd_new_session_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_new_session_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->flag_detached) + off += xsnprintf(buf + off, len - off, " -d"); + if (off < len && data->newname != NULL) + off += cmd_prarg(buf + off, len - off, " -s ", data->newname); + if (off < len && data->winname != NULL) + off += cmd_prarg(buf + off, len - off, " -n ", data->winname); + if (off < len && data->cmd != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->cmd); + return (off); +} diff --git a/cmd-new-window.c b/cmd-new-window.c new file mode 100644 index 00000000..7d3eeb45 --- /dev/null +++ b/cmd-new-window.c @@ -0,0 +1,241 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Create a new window. + */ + +int cmd_new_window_parse(struct cmd *, int, char **, char **); +int cmd_new_window_exec(struct cmd *, struct cmd_ctx *); +void cmd_new_window_send(struct cmd *, struct buffer *); +void cmd_new_window_recv(struct cmd *, struct buffer *); +void cmd_new_window_free(struct cmd *); +void cmd_new_window_init(struct cmd *, int); +size_t cmd_new_window_print(struct cmd *, char *, size_t); + +struct cmd_new_window_data { + char *target; + char *name; + char *cmd; + int flag_detached; + int flag_kill; +}; + +const struct cmd_entry cmd_new_window_entry = { + "new-window", "neww", + "[-dk] [-n window-name] [-t target-window] [command]", + 0, + cmd_new_window_init, + cmd_new_window_parse, + cmd_new_window_exec, + cmd_new_window_send, + cmd_new_window_recv, + cmd_new_window_free, + cmd_new_window_print +}; + +void +cmd_new_window_init(struct cmd *self, unused int arg) +{ + struct cmd_new_window_data *data; + + self->data = data = xmalloc(sizeof *data); + data->target = NULL; + data->name = NULL; + data->cmd = NULL; + data->flag_detached = 0; + data->flag_kill = 0; +} + +int +cmd_new_window_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_new_window_data *data; + int opt; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "dkt:n:")) != -1) { + switch (opt) { + case 'd': + data->flag_detached = 1; + break; + case 'k': + data->flag_kill = 1; + break; + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + case 'n': + if (data->name == NULL) + data->name = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 0 && argc != 1) + goto usage; + + if (argc == 1) + data->cmd = xstrdup(argv[0]); + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +int +cmd_new_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_new_window_data *data = self->data; + struct session *s; + struct winlink *wl; + char *cmd, *cwd, *cause; + int idx; + + if (data == NULL) + return (0); + + if (arg_parse_window(data->target, &s, &idx) != 0) { + ctx->error(ctx, "bad window: %s", data->target); + return (-1); + } + if (s == NULL) + s = ctx->cursession; + if (s == NULL) + s = cmd_current_session(ctx); + if (s == NULL) { + ctx->error(ctx, "session not found: %s", data->target); + return (-1); + } + + wl = NULL; + if (idx != -1) + wl = winlink_find_by_index(&s->windows, idx); + if (wl != NULL) { + if (data->flag_kill) { + /* + * Can't use session_detach as it will destroy session + * if this makes it empty. + */ + session_alert_cancel(s, wl); + winlink_stack_remove(&s->lastw, wl); + winlink_remove(&s->windows, wl); + + /* Force select/redraw if current. */ + if (wl == s->curw) { + data->flag_detached = 0; + s->curw = NULL; + } + } + } + + cmd = data->cmd; + if (cmd == NULL) + cmd = options_get_string(&s->options, "default-command"); + if (ctx->cmdclient == NULL || ctx->cmdclient->cwd == NULL) + cwd = options_get_string(&global_options, "default-path"); + else + cwd = ctx->cmdclient->cwd; + + wl = session_new(s, data->name, cmd, cwd, idx, &cause); + if (wl == NULL) { + ctx->error(ctx, "create window failed: %s", cause); + xfree(cause); + return (-1); + } + if (!data->flag_detached) { + session_select(s, wl->idx); + server_redraw_session(s); + } else + server_status_session(s); + + return (0); +} + +void +cmd_new_window_send(struct cmd *self, struct buffer *b) +{ + struct cmd_new_window_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + cmd_send_string(b, data->name); + cmd_send_string(b, data->cmd); +} + +void +cmd_new_window_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_new_window_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->name = cmd_recv_string(b); + data->cmd = cmd_recv_string(b); +} + +void +cmd_new_window_free(struct cmd *self) +{ + struct cmd_new_window_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + if (data->name != NULL) + xfree(data->name); + if (data->cmd != NULL) + xfree(data->cmd); + xfree(data); +} + +size_t +cmd_new_window_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_new_window_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->flag_detached) + off += xsnprintf(buf + off, len - off, " -d"); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->name != NULL) + off += cmd_prarg(buf + off, len - off, " -n ", data->name); + if (off < len && data->cmd != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->cmd); + return (off); +} diff --git a/cmd-next-layout.c b/cmd-next-layout.c new file mode 100644 index 00000000..85fd8d35 --- /dev/null +++ b/cmd-next-layout.c @@ -0,0 +1,55 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Switch window to next layout. + */ + +int cmd_next_layout_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_next_layout_entry = { + "next-layout", "nextl", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_next_layout_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_next_layout_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + layout_next(wl->window); + ctx->info(ctx, "layout now: %s", layout_name(wl->window)); + + return (0); +} diff --git a/cmd-next-window.c b/cmd-next-window.c new file mode 100644 index 00000000..71346764 --- /dev/null +++ b/cmd-next-window.c @@ -0,0 +1,78 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Move to next window. + */ + +void cmd_next_window_init(struct cmd *, int); +int cmd_next_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_next_window_entry = { + "next-window", "next", + CMD_TARGET_SESSION_USAGE, + CMD_AFLAG, + cmd_next_window_init, + cmd_target_parse, + cmd_next_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_next_window_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + if (key == KEYC_ADDESC('n')) + data->flags |= CMD_AFLAG; +} + +int +cmd_next_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + int activity; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + activity = 0; + if (data->flags & CMD_AFLAG) + activity = 1; + + if (session_next(s, activity) == 0) + server_redraw_session(s); + else { + ctx->error(ctx, "no next window"); + return (-1); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c new file mode 100644 index 00000000..35472d3d --- /dev/null +++ b/cmd-paste-buffer.c @@ -0,0 +1,78 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Paste paste buffer if present. + */ + +int cmd_paste_buffer_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_paste_buffer_entry = { + "paste-buffer", "pasteb", + "[-d] " CMD_BUFFER_WINDOW_USAGE, + CMD_DFLAG, + cmd_buffer_init, + cmd_buffer_parse, + cmd_paste_buffer_exec, + cmd_buffer_send, + cmd_buffer_recv, + cmd_buffer_free, + cmd_buffer_print +}; + +int +cmd_paste_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_buffer_data *data = self->data; + struct winlink *wl; + struct window *w; + struct session *s; + struct paste_buffer *pb; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + w = wl->window; + + if (data->buffer == -1) + pb = paste_get_top(&s->buffers); + else { + if ((pb = paste_get_index(&s->buffers, data->buffer)) == NULL) { + ctx->error(ctx, "no buffer %d", data->buffer); + return (-1); + } + } + + if (pb != NULL) + buffer_write(w->active->out, pb->data, strlen(pb->data)); + + /* Delete the buffer if -d. */ + if (data->flags & CMD_DFLAG) { + if (data->buffer == -1) + paste_free_top(&s->buffers); + else + paste_free_index(&s->buffers, data->buffer); + } + + return (0); +} diff --git a/cmd-previous-layout.c b/cmd-previous-layout.c new file mode 100644 index 00000000..5b662ede --- /dev/null +++ b/cmd-previous-layout.c @@ -0,0 +1,55 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Switch window to previous layout. + */ + +int cmd_previous_layout_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_previous_layout_entry = { + "previous-layout", "prevl", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_previous_layout_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_previous_layout_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + layout_previous(wl->window); + ctx->info(ctx, "layout now: %s", layout_name(wl->window)); + + return (0); +} diff --git a/cmd-previous-window.c b/cmd-previous-window.c new file mode 100644 index 00000000..7b880347 --- /dev/null +++ b/cmd-previous-window.c @@ -0,0 +1,78 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Move to previous window. + */ + +void cmd_previous_window_init(struct cmd *, int); +int cmd_previous_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_previous_window_entry = { + "previous-window", "prev", + CMD_TARGET_SESSION_USAGE, + CMD_AFLAG, + cmd_previous_window_init, + cmd_target_parse, + cmd_previous_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_previous_window_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + if (key == KEYC_ADDESC('p')) + data->flags |= CMD_AFLAG; +} + +int +cmd_previous_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + int activity; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + activity = 0; + if (data->flags & CMD_AFLAG) + activity = 1; + + if (session_previous(s, activity) == 0) + server_redraw_session(s); + else { + ctx->error(ctx, "no previous window"); + return (-1); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c new file mode 100644 index 00000000..9193a953 --- /dev/null +++ b/cmd-refresh-client.c @@ -0,0 +1,54 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Refresh client. + */ + +int cmd_refresh_client_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_refresh_client_entry = { + "refresh-client", "refresh", + CMD_TARGET_CLIENT_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_refresh_client_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_refresh_client_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct client *c; + + if ((c = cmd_find_client(ctx, data->target)) == NULL) + return (-1); + + server_redraw_client(c); + + return (0); +} diff --git a/cmd-rename-session.c b/cmd-rename-session.c new file mode 100644 index 00000000..b05835f1 --- /dev/null +++ b/cmd-rename-session.c @@ -0,0 +1,57 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Change session name. + */ + +int cmd_rename_session_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_rename_session_entry = { + "rename-session", "rename", + CMD_TARGET_SESSION_USAGE " new-name", + CMD_ARG1, + cmd_target_init, + cmd_target_parse, + cmd_rename_session_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_rename_session_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + xfree(s->name); + s->name = xstrdup(data->arg); + + return (0); +} diff --git a/cmd-rename-window.c b/cmd-rename-window.c new file mode 100644 index 00000000..985a7b58 --- /dev/null +++ b/cmd-rename-window.c @@ -0,0 +1,61 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Rename a window. + */ + +int cmd_rename_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_rename_window_entry = { + "rename-window", "renamew", + CMD_TARGET_WINDOW_USAGE " new-name", + CMD_ARG1, + cmd_target_init, + cmd_target_parse, + cmd_rename_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_rename_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + struct winlink *wl; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + + xfree(wl->window->name); + wl->window->name = xstrdup(data->arg); + options_set_number(&wl->window->options, "automatic-rename", 0); + + server_status_session(s); + + return (0); +} diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c new file mode 100644 index 00000000..2f1c3ad7 --- /dev/null +++ b/cmd-resize-pane.c @@ -0,0 +1,105 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Increase or decrease pane size. + */ + +void cmd_resize_pane_init(struct cmd *, int); +int cmd_resize_pane_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_resize_pane_entry = { + "resize-pane", "resizep", + CMD_PANE_WINDOW_USAGE "[-DU] [adjustment]", + CMD_ARG01|CMD_BIGUFLAG|CMD_BIGDFLAG, + cmd_resize_pane_init, + cmd_pane_parse, + cmd_resize_pane_exec, + cmd_pane_send, + cmd_pane_recv, + cmd_pane_free, + cmd_pane_print +}; + +void +cmd_resize_pane_init(struct cmd *self, int key) +{ + struct cmd_pane_data *data; + + cmd_pane_init(self, key); + data = self->data; + + if (key == KEYC_ADDCTL(KEYC_DOWN)) + data->flags |= CMD_BIGDFLAG; + + if (key == KEYC_ADDESC(KEYC_UP)) + data->arg = xstrdup("5"); + if (key == KEYC_ADDESC(KEYC_DOWN)) { + data->flags |= CMD_BIGDFLAG; + data->arg = xstrdup("5"); + } +} + +int +cmd_resize_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_pane_data *data = self->data; + struct winlink *wl; + const char *errstr; + struct window_pane *wp; + u_int adjust; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + if (data->pane == -1) + wp = wl->window->active; + else { + wp = window_pane_at_index(wl->window, data->pane); + if (wp == NULL) { + ctx->error(ctx, "no pane: %d", data->pane); + return (-1); + } + } + + if (data->arg == NULL) + adjust = 1; + else { + adjust = strtonum(data->arg, 1, INT_MAX, &errstr); + if (errstr != NULL) { + ctx->error(ctx, "adjustment %s: %s", errstr, data->arg); + return (-1); + } + } + + if (!(data->flags & CMD_BIGDFLAG)) + adjust = -adjust; + if (layout_resize(wp, adjust) != 0) { + ctx->error(ctx, "layout %s " + "does not support resizing", layout_name(wp->window)); + return (-1); + } + server_redraw_window(wl->window); + + return (0); +} diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c new file mode 100644 index 00000000..7c5b2fd5 --- /dev/null +++ b/cmd-respawn-window.c @@ -0,0 +1,87 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Respawn a window (restart the command). Kill existing if -k given. + */ + +int cmd_respawn_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_respawn_window_entry = { + "respawn-window", "respawnw", + "[-k] " CMD_TARGET_WINDOW_USAGE " [command]", + CMD_ARG01|CMD_KFLAG, + cmd_target_init, + cmd_target_parse, + cmd_respawn_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_respawn_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct window *w; + struct window_pane *wp; + struct session *s; + const char **env; + char *cause; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + w = wl->window; + + if (!(data->flags & CMD_KFLAG)) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->fd == -1) + continue; + ctx->error(ctx, + "window still active: %s:%d", s->name, wl->idx); + return (-1); + } + } + + env = server_fill_environ(s); + + wp = TAILQ_FIRST(&w->panes); + TAILQ_REMOVE(&w->panes, wp, entry); + window_destroy_panes(w); + TAILQ_INSERT_HEAD(&w->panes, wp, entry); + window_pane_resize(wp, w->sx, w->sy); + if (window_pane_spawn(wp, data->arg, NULL, env, &cause) != 0) { + ctx->error(ctx, "respawn window failed: %s", cause); + xfree(cause); + return (-1); + } + screen_reinit(&wp->base); + + recalculate_sizes(); + server_redraw_window(w); + + return (0); +} diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c new file mode 100644 index 00000000..354e6942 --- /dev/null +++ b/cmd-rotate-window.c @@ -0,0 +1,111 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Rotate the panes in a window. + */ + +void cmd_rotate_window_init(struct cmd *, int); +int cmd_rotate_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_rotate_window_entry = { + "rotate-window", "rotatew", + "[-DU] " CMD_TARGET_WINDOW_USAGE, + CMD_BIGUFLAG|CMD_BIGDFLAG, + cmd_rotate_window_init, + cmd_target_parse, + cmd_rotate_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_rotate_window_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + if (key == KEYC_ADDESC('o')) + data->flags |= CMD_BIGDFLAG; +} + +int +cmd_rotate_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct window *w; + struct window_pane *wp, *wp2; + u_int sx, sy, xoff, yoff; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + w = wl->window; + + if (data->flags & CMD_BIGDFLAG) { + wp = TAILQ_LAST(&w->panes, window_panes); + TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_INSERT_HEAD(&w->panes, wp, entry); + + xoff = wp->xoff; yoff = wp->yoff; + sx = wp->sx; sy = wp->sy; + TAILQ_FOREACH(wp, &w->panes, entry) { + if ((wp2 = TAILQ_NEXT(wp, entry)) == NULL) + break; + wp->xoff = wp2->xoff; wp->yoff = wp2->yoff; + window_pane_resize(wp, wp2->sx, wp2->sy); + } + wp->xoff = xoff; wp->yoff = yoff; + window_pane_resize(wp, sx, sy); + + if ((wp = TAILQ_PREV(w->active, window_panes, entry)) == NULL) + wp = TAILQ_LAST(&w->panes, window_panes); + window_set_active_pane(w, wp); + } else { + wp = TAILQ_FIRST(&w->panes); + TAILQ_REMOVE(&w->panes, wp, entry); + TAILQ_INSERT_TAIL(&w->panes, wp, entry); + + xoff = wp->xoff; yoff = wp->yoff; + sx = wp->sx; sy = wp->sy; + TAILQ_FOREACH_REVERSE(wp, &w->panes, window_panes, entry) { + if ((wp2 = TAILQ_PREV(wp, window_panes, entry)) == NULL) + break; + wp->xoff = wp2->xoff; wp->yoff = wp2->yoff; + window_pane_resize(wp, wp2->sx, wp2->sy); + } + wp->xoff = xoff; wp->yoff = yoff; + window_pane_resize(wp, sx, sy); + + if ((wp = TAILQ_NEXT(w->active, entry)) == NULL) + wp = TAILQ_FIRST(&w->panes); + window_set_active_pane(w, wp); + } + + layout_refresh(w, 0); + + return (0); +} diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c new file mode 100644 index 00000000..7c5af7a4 --- /dev/null +++ b/cmd-save-buffer.c @@ -0,0 +1,90 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Tiago Cunha + * + * 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 +#include + +#include +#include + +#include "tmux.h" + +/* + * Saves a session paste buffer to a file. + */ + +int cmd_save_buffer_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_save_buffer_entry = { + "save-buffer", "saveb", + "[-a] " CMD_BUFFER_SESSION_USAGE " path", + CMD_AFLAG|CMD_ARG1, + cmd_buffer_init, + cmd_buffer_parse, + cmd_save_buffer_exec, + cmd_buffer_send, + cmd_buffer_recv, + cmd_buffer_free, + cmd_buffer_print +}; + +int +cmd_save_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_buffer_data *data = self->data; + struct session *s; + struct paste_buffer *pb; + mode_t mask; + FILE *f; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (data->buffer == -1) { + if ((pb = paste_get_top(&s->buffers)) == NULL) { + ctx->error(ctx, "no buffers"); + return (-1); + } + } else { + if ((pb = paste_get_index(&s->buffers, data->buffer)) == NULL) { + ctx->error(ctx, "no buffer %d", data->buffer); + return (-1); + } + } + + mask = umask(S_IRWXG | S_IRWXO); + if (data->flags & CMD_AFLAG) + f = fopen(data->arg, "ab"); + else + f = fopen(data->arg, "wb"); + if (f == NULL) { + ctx->error(ctx, "%s: %s", data->arg, strerror(errno)); + return (-1); + } + + if (fwrite(pb->data, 1, strlen(pb->data), f) != strlen(pb->data)) { + ctx->error(ctx, "%s: fwrite error", data->arg); + fclose(f); + return (-1); + } + + fclose(f); + umask(mask); + + return (0); +} diff --git a/cmd-scroll-mode.c b/cmd-scroll-mode.c new file mode 100644 index 00000000..c34f9bd6 --- /dev/null +++ b/cmd-scroll-mode.c @@ -0,0 +1,72 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Enter scroll mode. + */ + +void cmd_scroll_mode_init(struct cmd *, int); +int cmd_scroll_mode_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_scroll_mode_entry = { + "scroll-mode", NULL, + CMD_TARGET_WINDOW_USAGE, + CMD_UFLAG, + cmd_scroll_mode_init, + cmd_target_parse, + cmd_scroll_mode_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_scroll_mode_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + switch (key) { + case KEYC_PPAGE: + data->flags |= CMD_UFLAG; + break; + } +} + +int +cmd_scroll_mode_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + window_pane_set_mode(wl->window->active, &window_scroll_mode); + if (data->flags & CMD_UFLAG) + window_scroll_pageup(wl->window->active); + + return (0); +} diff --git a/cmd-select-layout.c b/cmd-select-layout.c new file mode 100644 index 00000000..ef921660 --- /dev/null +++ b/cmd-select-layout.c @@ -0,0 +1,86 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Switch window to selected layout. + */ + +void cmd_select_layout_init(struct cmd *, int); +int cmd_select_layout_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_select_layout_entry = { + "select-layout", "selectl", + CMD_TARGET_WINDOW_USAGE " layout-name", + CMD_ARG1, + cmd_select_layout_init, + cmd_target_parse, + cmd_select_layout_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_select_layout_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + switch (key) { + case KEYC_ADDESC('0'): + data->arg = xstrdup("manual-vertical"); + break; + case KEYC_ADDESC('1'): + data->arg = xstrdup("even-horizontal"); + break; + case KEYC_ADDESC('2'): + data->arg = xstrdup("even-vertical"); + break; + case KEYC_ADDESC('9'): + data->arg = xstrdup("active-only"); + break; + } +} + +int +cmd_select_layout_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + int layout; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + if ((layout = layout_lookup(data->arg)) == -1) { + ctx->error(ctx, "unknown or ambiguous layout: %s", data->arg); + return (-1); + } + + if (layout_select(wl->window, layout) == 0) + ctx->info(ctx, "layout now: %s", layout_name(wl->window)); + + return (0); +} diff --git a/cmd-select-pane.c b/cmd-select-pane.c new file mode 100644 index 00000000..33c94a33 --- /dev/null +++ b/cmd-select-pane.c @@ -0,0 +1,69 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Select pane. + */ + +int cmd_select_pane_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_select_pane_entry = { + "select-pane", "selectp", + CMD_PANE_WINDOW_USAGE, + 0, + cmd_pane_init, + cmd_pane_parse, + cmd_select_pane_exec, + cmd_pane_send, + cmd_pane_recv, + cmd_pane_free, + cmd_pane_print +}; + +int +cmd_select_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_pane_data *data = self->data; + struct winlink *wl; + struct window_pane *wp; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + if (data->pane == -1) + wp = wl->window->active; + else { + wp = window_pane_at_index(wl->window, data->pane); + if (wp == NULL) { + ctx->error(ctx, "no pane: %d", data->pane); + return (-1); + } + } + + if (wp->flags & PANE_HIDDEN) { + ctx->error(ctx, "pane %d is hidden", data->pane); + return (-1); + } + window_set_active_pane(wl->window, wp); + layout_refresh(wl->window, 1); + + return (0); +} diff --git a/cmd-select-prompt.c b/cmd-select-prompt.c new file mode 100644 index 00000000..81da9dbb --- /dev/null +++ b/cmd-select-prompt.c @@ -0,0 +1,93 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Prompt for window index and select it. + */ + +int cmd_select_prompt_exec(struct cmd *, struct cmd_ctx *); + +int cmd_select_prompt_callback(void *, const char *); + +const struct cmd_entry cmd_select_prompt_entry = { + "select-prompt", NULL, + CMD_TARGET_CLIENT_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_select_prompt_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_select_prompt_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct client *c; + + if ((c = cmd_find_client(ctx, data->target)) == NULL) + return (-1); + + if (c->prompt_string != NULL) + return (0); + + status_prompt_set(c, "index ", cmd_select_prompt_callback, c, 0); + + return (0); +} + +int +cmd_select_prompt_callback(void *data, const char *s) +{ + struct client *c = data; + const char *errstr; + char msg[128]; + u_int idx; + + if (s == NULL) + return (0); + + idx = strtonum(s, 0, UINT_MAX, &errstr); + if (errstr != NULL) { + xsnprintf(msg, sizeof msg, "Index %s: %s", errstr, s); + status_message_set(c, msg); + return (0); + } + + if (winlink_find_by_index(&c->session->windows, idx) == NULL) { + xsnprintf(msg, sizeof msg, + "Window not found: %s:%d", c->session->name, idx); + status_message_set(c, msg); + return (0); + } + + if (session_select(c->session, idx) == 0) + server_redraw_session(c->session); + recalculate_sizes(); + + return (0); +} diff --git a/cmd-select-window.c b/cmd-select-window.c new file mode 100644 index 00000000..55f8318c --- /dev/null +++ b/cmd-select-window.c @@ -0,0 +1,71 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Select window by index. + */ + +void cmd_select_window_init(struct cmd *, int); +int cmd_select_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_select_window_entry = { + "select-window", "selectw", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_select_window_init, + cmd_target_parse, + cmd_select_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +void +cmd_select_window_init(struct cmd *self, int key) +{ + struct cmd_target_data *data; + + cmd_target_init(self, key); + data = self->data; + + xasprintf(&data->target, ":%d", key - '0'); +} + +int +cmd_select_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct session *s; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + + if (session_select(s, wl->idx) == 0) + server_redraw_session(s); + recalculate_sizes(); + + return (0); +} diff --git a/cmd-send-keys.c b/cmd-send-keys.c new file mode 100644 index 00000000..849fe7be --- /dev/null +++ b/cmd-send-keys.c @@ -0,0 +1,184 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Send keys to client. + */ + +int cmd_send_keys_parse(struct cmd *, int, char **, char **); +int cmd_send_keys_exec(struct cmd *, struct cmd_ctx *); +void cmd_send_keys_send(struct cmd *, struct buffer *); +void cmd_send_keys_recv(struct cmd *, struct buffer *); +void cmd_send_keys_free(struct cmd *); +size_t cmd_send_keys_print(struct cmd *, char *, size_t); + +struct cmd_send_keys_data { + char *target; + int idx; + u_int nkeys; + int *keys; +}; + +const struct cmd_entry cmd_send_keys_entry = { + "send-keys", "send", + "[-t target-window] key ...", + 0, + NULL, + cmd_send_keys_parse, + cmd_send_keys_exec, + cmd_send_keys_send, + cmd_send_keys_recv, + cmd_send_keys_free, + cmd_send_keys_print +}; + +int +cmd_send_keys_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_send_keys_data *data; + int opt, key; + char *s; + + self->data = data = xmalloc(sizeof *data); + data->target = NULL; + data->idx = -1; + data->nkeys = 0; + data->keys = NULL; + + while ((opt = getopt(argc, argv, "t:")) != -1) { + switch (opt) { + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc == 0) + goto usage; + + while (argc-- != 0) { + if ((key = key_string_lookup_string(*argv)) != KEYC_NONE) { + data->keys = xrealloc( + data->keys, data->nkeys + 1, sizeof *data->keys); + data->keys[data->nkeys++] = key; + } else { + for (s = *argv; *s != '\0'; s++) { + data->keys = xrealloc(data->keys, + data->nkeys + 1, sizeof *data->keys); + data->keys[data->nkeys++] = *s; + } + } + + argv++; + } + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +int +cmd_send_keys_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_send_keys_data *data = self->data; + struct winlink *wl; + u_int i; + + if (data == NULL) + return (-1); + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + + for (i = 0; i < data->nkeys; i++) { + window_pane_key( + wl->window->active, ctx->curclient, data->keys[i]); + } + + return (0); +} + +void +cmd_send_keys_send(struct cmd *self, struct buffer *b) +{ + struct cmd_send_keys_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + buffer_write(b, data->keys, data->nkeys * sizeof *data->keys); +} + +void +cmd_send_keys_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_send_keys_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->keys = xcalloc(data->nkeys, sizeof *data->keys); + buffer_read(b, data->keys, data->nkeys * sizeof *data->keys); +} + +void +cmd_send_keys_free(struct cmd *self) +{ + struct cmd_send_keys_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + xfree(data); +} + +size_t +cmd_send_keys_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_send_keys_data *data = self->data; + size_t off = 0; + u_int i; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->idx != -1) + off += xsnprintf(buf + off, len - off, " -i %d", data->idx); + + for (i = 0; i < data->nkeys; i++) { + if (off >= len) + break; + off += xsnprintf(buf + off, + len - off, " %s", key_string_lookup_key(data->keys[i])); + } + return (off); +} diff --git a/cmd-send-prefix.c b/cmd-send-prefix.c new file mode 100644 index 00000000..d45ca0c1 --- /dev/null +++ b/cmd-send-prefix.c @@ -0,0 +1,57 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Send prefix key as a key. + */ + +int cmd_send_prefix_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_send_prefix_entry = { + "send-prefix", NULL, + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_send_prefix_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_send_prefix_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + struct winlink *wl; + int key; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + + key = options_get_number(&s->options, "prefix"); + window_pane_key(wl->window->active, ctx->curclient, key); + + return (0); +} diff --git a/cmd-server-info.c b/cmd-server-info.c new file mode 100644 index 00000000..94373f01 --- /dev/null +++ b/cmd-server-info.c @@ -0,0 +1,179 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include +#include + +#include "tmux.h" + +/* + * Show various information about server. + */ + +int cmd_server_info_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_server_info_entry = { + "server-info", "info", + "", + 0, + NULL, + NULL, + cmd_server_info_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_server_info_exec(unused struct cmd *self, struct cmd_ctx *ctx) +{ + struct tty_term *term; + struct client *c; + struct session *s; + struct winlink *wl; + struct window *w; + struct window_pane *wp; + struct tty_code *code; + struct tty_term_code_entry *ent; + struct utsname un; + struct grid *gd; + u_int i, j, k; + char out[80]; + char *tim; + time_t t; + u_int lines, ulines; + size_t size, usize; + + tim = ctime(&start_time); + *strchr(tim, '\n') = '\0'; + ctx->print(ctx, "pid %ld, started %s", (long) getpid(), tim); + ctx->print(ctx, "socket path %s, debug level %d%s", + socket_path, debug_level, be_quiet ? ", quiet" : ""); + if (uname(&un) == 0) { + ctx->print(ctx, "system is %s %s %s %s", + un.sysname, un.release, un.version, un.machine); + } + if (cfg_file != NULL) + ctx->print(ctx, "configuration file is %s", cfg_file); + else + ctx->print(ctx, "configuration file not specified"); + ctx->print(ctx, "protocol version is %d", PROTOCOL_VERSION); + ctx->print(ctx, "%u clients, %u sessions", + ARRAY_LENGTH(&clients), ARRAY_LENGTH(&sessions)); + ctx->print(ctx, ""); + + ctx->print(ctx, "Clients:"); + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + + ctx->print(ctx, "%2d: %s (%d, %d): %s [%ux%u %s] " + "[flags=0x%x/0x%x]", i, c->tty.path, c->fd, c->tty.fd, + c->session->name, c->tty.sx, c->tty.sy, c->tty.termname, + c->flags, c->tty.flags); + } + ctx->print(ctx, ""); + + ctx->print(ctx, "Sessions: [%zu/%zu]", + sizeof (struct grid_cell), sizeof (struct grid_utf8)); + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL) + continue; + + t = s->tv.tv_sec; + tim = ctime(&t); + *strchr(tim, '\n') = '\0'; + + ctx->print(ctx, "%2u: %s: %u windows (created %s) [%ux%u] " + "[flags=0x%x]", i, s->name, winlink_count(&s->windows), + tim, s->sx, s->sy, s->flags); + RB_FOREACH(wl, winlinks, &s->windows) { + w = wl->window; + ctx->print(ctx, "%4u: %s [%ux%u] [flags=0x%x, " + "references=%u, layout=%u]", wl->idx, w->name, + w->sx, w->sy, w->flags, w->references, + w->layout); + j = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + lines = ulines = size = usize = 0; + gd = wp->base.grid; + for (k = 0; k < gd->hsize + gd->sy; k++) { + if (gd->data[k] != NULL) { + lines++; + size += gd->size[k] * + sizeof (**gd->data); + } + if (gd->udata[k] != NULL) { + ulines++; + usize += gd->usize[k] * + sizeof (**gd->udata); + } + } + ctx->print(ctx, "%6u: %s %lu %d %u/%u, %zu " + "bytes; UTF-8 %u/%u, %zu bytes", j, + wp->tty, (u_long) wp->pid, wp->fd, lines, + gd->hsize + gd->sy, size, ulines, + gd->hsize + gd->sy, usize); + j++; + } + } + } + ctx->print(ctx, ""); + + ctx->print(ctx, "Terminals:"); + SLIST_FOREACH(term, &tty_terms, entry) { + ctx->print(ctx, "%s [references=%u, flags=0x%x]:", + term->name, term->references, term->flags); + for (i = 0; i < NTTYCODE; i++) { + ent = &tty_term_codes[i]; + code = &term->codes[ent->code]; + switch (code->type) { + case TTYCODE_NONE: + ctx->print(ctx, "%2u: %s: [missing]", + ent->code, ent->name); + break; + case TTYCODE_STRING: + clean_string( + code->value.string, out, sizeof out); + ctx->print(ctx, "%2u: %s: (string) %s", + ent->code, ent->name, out); + break; + case TTYCODE_NUMBER: + ctx->print(ctx, "%2u: %s: (number) %d", + ent->code, ent->name, code->value.number); + break; + case TTYCODE_FLAG: + ctx->print(ctx, "%2u: %s: (flag) %s", + ent->code, ent->name, + code->value.flag ? "true" : "false"); + break; + } + } + } + ctx->print(ctx, ""); + + return (0); +} diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c new file mode 100644 index 00000000..3e1aa0a5 --- /dev/null +++ b/cmd-set-buffer.c @@ -0,0 +1,64 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Add or set a session paste buffer. + */ + +int cmd_set_buffer_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_set_buffer_entry = { + "set-buffer", "setb", + CMD_BUFFER_SESSION_USAGE " data", + CMD_ARG1, + cmd_buffer_init, + cmd_buffer_parse, + cmd_set_buffer_exec, + cmd_buffer_send, + cmd_buffer_recv, + cmd_buffer_free, + cmd_buffer_print +}; + +int +cmd_set_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_buffer_data *data = self->data; + struct session *s; + u_int limit; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + limit = options_get_number(&s->options, "buffer-limit"); + if (data->buffer == -1) { + paste_add(&s->buffers, xstrdup(data->arg), limit); + return (0); + } + if (paste_replace(&s->buffers, data->buffer, xstrdup(data->arg)) != 0) { + ctx->error(ctx, "no buffer %d", data->buffer); + return (-1); + } + return (0); +} diff --git a/cmd-set-option.c b/cmd-set-option.c new file mode 100644 index 00000000..d4e90957 --- /dev/null +++ b/cmd-set-option.c @@ -0,0 +1,169 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Set an option. + */ + +int cmd_set_option_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_set_option_entry = { + "set-option", "set", + CMD_OPTION_SESSION_USAGE, + CMD_GFLAG|CMD_UFLAG, + NULL, + cmd_option_parse, + cmd_set_option_exec, + cmd_option_send, + cmd_option_recv, + cmd_option_free, + cmd_option_print +}; + +const char *set_option_status_keys_list[] = { + "emacs", "vi", NULL +}; +const char *set_option_bell_action_list[] = { + "none", "any", "current", NULL +}; +const struct set_option_entry set_option_table[NSETOPTION] = { + { "bell-action", SET_OPTION_CHOICE, 0, 0, set_option_bell_action_list }, + { "buffer-limit", SET_OPTION_NUMBER, 1, INT_MAX, NULL }, + { "default-command", SET_OPTION_STRING, 0, 0, NULL }, + { "default-path", SET_OPTION_STRING, 0, 0, NULL }, + { "display-time", SET_OPTION_NUMBER, 1, INT_MAX, NULL }, + { "history-limit", SET_OPTION_NUMBER, 0, INT_MAX, NULL }, + { "lock-after-time", SET_OPTION_NUMBER, 0, INT_MAX, NULL }, + { "message-attr", SET_OPTION_ATTRIBUTES, 0, 0, NULL }, + { "message-bg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "message-fg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "prefix", SET_OPTION_KEY, 0, 0, NULL }, + { "repeat-time", SET_OPTION_NUMBER, 0, SHRT_MAX, NULL }, + { "set-remain-on-exit", SET_OPTION_FLAG, 0, 0, NULL }, + { "set-titles", SET_OPTION_FLAG, 0, 0, NULL }, + { "status", SET_OPTION_FLAG, 0, 0, NULL }, + { "status-attr", SET_OPTION_ATTRIBUTES, 0, 0, NULL }, + { "status-bg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "status-fg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "status-interval", SET_OPTION_NUMBER, 0, INT_MAX, NULL }, + { "status-keys", SET_OPTION_CHOICE, 0, 0, set_option_status_keys_list }, + { "status-left", SET_OPTION_STRING, 0, 0, NULL }, + { "status-left-length", SET_OPTION_NUMBER, 0, SHRT_MAX, NULL }, + { "status-right", SET_OPTION_STRING, 0, 0, NULL }, + { "status-right-length", SET_OPTION_NUMBER, 0, SHRT_MAX, NULL }, +}; + +int +cmd_set_option_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_option_data *data = self->data; + struct session *s; + struct client *c; + struct options *oo; + const struct set_option_entry *entry; + u_int i; + + if (data->flags & CMD_GFLAG) + oo = &global_options; + else { + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + oo = &s->options; + } + + if (*data->option == '\0') { + ctx->error(ctx, "invalid option"); + return (-1); + } + + entry = NULL; + for (i = 0; i < NSETOPTION; i++) { + if (strncmp(set_option_table[i].name, + data->option, strlen(data->option)) != 0) + continue; + if (entry != NULL) { + ctx->error(ctx, "ambiguous option: %s", data->option); + return (-1); + } + entry = &set_option_table[i]; + + /* Bail now if an exact match. */ + if (strcmp(entry->name, data->option) == 0) + break; + } + if (entry == NULL) { + ctx->error(ctx, "unknown option: %s", data->option); + return (-1); + } + + if (data->flags & CMD_UFLAG) { + if (data->flags & CMD_GFLAG) { + ctx->error(ctx, + "can't unset global option: %s", entry->name); + return (-1); + } + if (data->value != NULL) { + ctx->error(ctx, + "value passed to unset option: %s", entry->name); + return (-1); + } + + options_remove(oo, entry->name); + ctx->info(ctx, "unset option: %s", entry->name); + } else { + switch (entry->type) { + case SET_OPTION_STRING: + set_option_string(ctx, oo, entry, data->value); + break; + case SET_OPTION_NUMBER: + set_option_number(ctx, oo, entry, data->value); + break; + case SET_OPTION_KEY: + set_option_key(ctx, oo, entry, data->value); + break; + case SET_OPTION_COLOUR: + set_option_colour(ctx, oo, entry, data->value); + break; + case SET_OPTION_ATTRIBUTES: + set_option_attributes(ctx, oo, entry, data->value); + break; + case SET_OPTION_FLAG: + set_option_flag(ctx, oo, entry, data->value); + break; + case SET_OPTION_CHOICE: + set_option_choice(ctx, oo, entry, data->value); + break; + } + } + + recalculate_sizes(); + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c != NULL && c->session != NULL) + server_redraw_client(c); + } + + return (0); +} diff --git a/cmd-set-password.c b/cmd-set-password.c new file mode 100644 index 00000000..93705cab --- /dev/null +++ b/cmd-set-password.c @@ -0,0 +1,169 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Set server password. + */ + +int cmd_set_password_parse(struct cmd *, int, char **, char **); +int cmd_set_password_exec(struct cmd *, struct cmd_ctx *); +void cmd_set_password_send(struct cmd *, struct buffer *); +void cmd_set_password_recv(struct cmd *, struct buffer *); +void cmd_set_password_free(struct cmd *); +void cmd_set_password_init(struct cmd *, int); +size_t cmd_set_password_print(struct cmd *, char *, size_t); + +struct cmd_set_password_data { + char *password; + int flag_encrypted; +}; + +const struct cmd_entry cmd_set_password_entry = { + "set-password", "pass", + "[-c] password", + 0, + cmd_set_password_init, + cmd_set_password_parse, + cmd_set_password_exec, + cmd_set_password_send, + cmd_set_password_recv, + cmd_set_password_free, + cmd_set_password_print +}; + +void +cmd_set_password_init(struct cmd *self, unused int arg) +{ + struct cmd_set_password_data *data; + + self->data = data = xmalloc(sizeof *data); + data->password = NULL; + data->flag_encrypted = 0; +} + +int +cmd_set_password_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_set_password_data *data; + int opt; + char *out; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "c")) != -1) { + switch (opt) { + case 'c': + data->flag_encrypted = 1; + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 1) + goto usage; + + if (!data->flag_encrypted) { + if ((out = crypt(argv[0], "$1")) != NULL) + data->password = xstrdup(out); + } else + data->password = xstrdup(argv[0]); + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +int +cmd_set_password_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_set_password_data *data = self->data; + + if (data->password == NULL) { + ctx->error(ctx, "failed to encrypt password"); + return (-1); + } + + if (server_password != NULL) + xfree(server_password); + if (*data->password == '\0') + server_password = NULL; + else + server_password = xstrdup(data->password); + log_debug("pw now %s", server_password); + + return (0); +} + +void +cmd_set_password_send(struct cmd *self, struct buffer *b) +{ + struct cmd_set_password_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->password); +} + +void +cmd_set_password_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_set_password_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->password = cmd_recv_string(b); +} + +void +cmd_set_password_free(struct cmd *self) +{ + struct cmd_set_password_data *data = self->data; + + if (data->password != NULL) + xfree(data->password); + xfree(data); +} + +size_t +cmd_set_password_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_set_password_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->flag_encrypted) + off += xsnprintf(buf + off, len - off, " -c"); + if (off < len && data->password != NULL) + off += xsnprintf(buf + off, len - off, " password"); + return (off); +} diff --git a/cmd-set-window-option.c b/cmd-set-window-option.c new file mode 100644 index 00000000..8d17498e --- /dev/null +++ b/cmd-set-window-option.c @@ -0,0 +1,170 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Set a window option. + */ + +int cmd_set_window_option_parse(struct cmd *, int, char **, char **); +int cmd_set_window_option_exec(struct cmd *, struct cmd_ctx *); +void cmd_set_window_option_send(struct cmd *, struct buffer *); +void cmd_set_window_option_recv(struct cmd *, struct buffer *); +void cmd_set_window_option_free(struct cmd *); +size_t cmd_set_window_option_print(struct cmd *, char *, size_t); + +const struct cmd_entry cmd_set_window_option_entry = { + "set-window-option", "setw", + CMD_OPTION_WINDOW_USAGE, + CMD_GFLAG|CMD_UFLAG, + NULL, + cmd_option_parse, + cmd_set_window_option_exec, + cmd_option_send, + cmd_option_recv, + cmd_option_free, + cmd_option_print +}; + +const char *set_option_mode_keys_list[] = { + "emacs", "vi", NULL +}; +const char *set_option_clock_mode_style_list[] = { + "12", "24", NULL +}; +const struct set_option_entry set_window_option_table[NSETWINDOWOPTION] = { + { "aggressive-resize", SET_OPTION_FLAG, 0, 0, NULL }, + { "automatic-rename", SET_OPTION_FLAG, 0, 0, NULL }, + { "clock-mode-colour", SET_OPTION_COLOUR, 0, 0, NULL }, + { "clock-mode-style", + SET_OPTION_CHOICE, 0, 0, set_option_clock_mode_style_list }, + { "force-height", SET_OPTION_NUMBER, 0, INT_MAX, NULL }, + { "force-width", SET_OPTION_NUMBER, 0, INT_MAX, NULL }, + { "main-pane-width", SET_OPTION_NUMBER, 1, INT_MAX, NULL }, + { "mode-attr", SET_OPTION_ATTRIBUTES, 0, 0, NULL }, + { "mode-bg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "mode-fg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "mode-keys", SET_OPTION_CHOICE, 0, 0, set_option_mode_keys_list }, + { "monitor-activity", SET_OPTION_FLAG, 0, 0, NULL }, + { "monitor-content", SET_OPTION_STRING, 0, 0, NULL }, + { "remain-on-exit", SET_OPTION_FLAG, 0, 0, NULL }, + { "utf8", SET_OPTION_FLAG, 0, 0, NULL }, + { "window-status-attr", SET_OPTION_ATTRIBUTES, 0, 0, NULL }, + { "window-status-bg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "window-status-fg", SET_OPTION_COLOUR, 0, 0, NULL }, + { "xterm-keys", SET_OPTION_FLAG, 0, 0, NULL }, +}; + +int +cmd_set_window_option_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_option_data *data = self->data; + struct winlink *wl; + struct client *c; + struct options *oo; + const struct set_option_entry *entry; + u_int i; + + if (data->flags & CMD_GFLAG) + oo = &global_window_options; + else { + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + oo = &wl->window->options; + } + + if (*data->option == '\0') { + ctx->error(ctx, "invalid option"); + return (-1); + } + + entry = NULL; + for (i = 0; i < NSETWINDOWOPTION; i++) { + if (strncmp(set_window_option_table[i].name, + data->option, strlen(data->option)) != 0) + continue; + if (entry != NULL) { + ctx->error(ctx, "ambiguous option: %s", data->option); + return (-1); + } + entry = &set_window_option_table[i]; + + /* Bail now if an exact match. */ + if (strcmp(entry->name, data->option) == 0) + break; + } + if (entry == NULL) { + ctx->error(ctx, "unknown option: %s", data->option); + return (-1); + } + + if (data->flags & CMD_UFLAG) { + if (data->flags & CMD_GFLAG) { + ctx->error(ctx, + "can't unset global option: %s", entry->name); + return (-1); + } + if (data->value != NULL) { + ctx->error(ctx, + "value passed to unset option: %s", entry->name); + return (-1); + } + + options_remove(oo, entry->name); + ctx->info(ctx, "unset option: %s", entry->name); + } else { + switch (entry->type) { + case SET_OPTION_STRING: + set_option_string(ctx, oo, entry, data->value); + break; + case SET_OPTION_NUMBER: + set_option_number(ctx, oo, entry, data->value); + break; + case SET_OPTION_KEY: + set_option_key(ctx, oo, entry, data->value); + break; + case SET_OPTION_COLOUR: + set_option_colour(ctx, oo, entry, data->value); + break; + case SET_OPTION_ATTRIBUTES: + set_option_attributes(ctx, oo, entry, data->value); + break; + case SET_OPTION_FLAG: + set_option_flag(ctx, oo, entry, data->value); + break; + case SET_OPTION_CHOICE: + set_option_choice(ctx, oo, entry, data->value); + break; + } + } + + recalculate_sizes(); + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c != NULL && c->session != NULL) + server_redraw_client(c); + } + + return (0); +} diff --git a/cmd-show-buffer.c b/cmd-show-buffer.c new file mode 100644 index 00000000..3e779f31 --- /dev/null +++ b/cmd-show-buffer.c @@ -0,0 +1,89 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Show a session paste buffer. + */ + +int cmd_show_buffer_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_show_buffer_entry = { + "show-buffer", "showb", + CMD_BUFFER_SESSION_USAGE, + 0, + cmd_buffer_init, + cmd_buffer_parse, + cmd_show_buffer_exec, + cmd_buffer_send, + cmd_buffer_recv, + cmd_buffer_free, + cmd_buffer_print +}; + +int +cmd_show_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_buffer_data *data = self->data; + struct session *s; + struct paste_buffer *pb; + u_int size; + char *buf, *ptr; + size_t len; + + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + if (data->buffer == -1) { + if ((pb = paste_get_top(&s->buffers)) == NULL) { + ctx->error(ctx, "no buffers"); + return (-1); + } + } else if ((pb = paste_get_index(&s->buffers, data->buffer)) == NULL) { + ctx->error(ctx, "no buffer %d", data->buffer); + return (-1); + } + + if (pb != NULL) { + size = s->sx; + + buf = xmalloc(size + 1); + len = 0; + + ptr = pb->data; + do { + buf[len++] = *ptr++; + + if (len == size) { + buf[len] = '\0'; + ctx->print(ctx, buf); + + len = 0; + } + } while (*ptr != '\0'); + buf[len] = '\0'; + ctx->print(ctx, buf); + } + + return (0); +} diff --git a/cmd-show-options.c b/cmd-show-options.c new file mode 100644 index 00000000..13482525 --- /dev/null +++ b/cmd-show-options.c @@ -0,0 +1,110 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Show options. + */ + +int cmd_show_options_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_show_options_entry = { + "show-options", "show", + "[-g] " CMD_TARGET_SESSION_USAGE, + CMD_GFLAG, + cmd_target_init, + cmd_target_parse, + cmd_show_options_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_show_options_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct session *s; + struct options *oo; + const struct set_option_entry *entry; + u_int i; + char *vs; + long long vn; + + if (data->flags & CMD_GFLAG) + oo = &global_options; + else { + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + oo = &s->options; + } + + for (i = 0; i < NSETOPTION; i++) { + entry = &set_option_table[i]; + + if (options_find1(oo, entry->name) == NULL) + continue; + + switch (entry->type) { + case SET_OPTION_STRING: + vs = options_get_string(oo, entry->name); + ctx->print(ctx, "%s \"%s\"", entry->name, vs); + break; + case SET_OPTION_NUMBER: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %lld", entry->name, vn); + break; + case SET_OPTION_KEY: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, key_string_lookup_key(vn)); + break; + case SET_OPTION_COLOUR: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, colour_tostring(vn)); + break; + case SET_OPTION_ATTRIBUTES: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, attributes_tostring(vn)); + break; + case SET_OPTION_FLAG: + vn = options_get_number(oo, entry->name); + if (vn) + ctx->print(ctx, "%s on", entry->name); + else + ctx->print(ctx, "%s off", entry->name); + break; + case SET_OPTION_CHOICE: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, entry->choices[vn]); + break; + } + } + + return (0); +} diff --git a/cmd-show-window-options.c b/cmd-show-window-options.c new file mode 100644 index 00000000..dd8914a1 --- /dev/null +++ b/cmd-show-window-options.c @@ -0,0 +1,110 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Show window options. + */ + +int cmd_show_window_options_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_show_window_options_entry = { + "show-window-options", "showw", + "[-g] " CMD_TARGET_WINDOW_USAGE, + CMD_GFLAG, + cmd_target_init, + cmd_target_parse, + cmd_show_window_options_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_show_window_options_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct options *oo; + const struct set_option_entry *entry; + u_int i; + char *vs; + long long vn; + + if (data->flags & CMD_GFLAG) + oo = &global_window_options; + else { + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + oo = &wl->window->options; + } + + for (i = 0; i < NSETWINDOWOPTION; i++) { + entry = &set_window_option_table[i]; + + if (options_find1(oo, entry->name) == NULL) + continue; + + switch (entry->type) { + case SET_OPTION_STRING: + vs = options_get_string(oo, entry->name); + ctx->print(ctx, "%s \"%s\"", entry->name, vs); + break; + case SET_OPTION_NUMBER: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %lld", entry->name, vn); + break; + case SET_OPTION_KEY: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, key_string_lookup_key(vn)); + break; + case SET_OPTION_COLOUR: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, colour_tostring(vn)); + break; + case SET_OPTION_ATTRIBUTES: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, attributes_tostring(vn)); + break; + case SET_OPTION_FLAG: + vn = options_get_number(oo, entry->name); + if (vn) + ctx->print(ctx, "%s on", entry->name); + else + ctx->print(ctx, "%s off", entry->name); + break; + case SET_OPTION_CHOICE: + vn = options_get_number(oo, entry->name); + ctx->print(ctx, "%s %s", + entry->name, entry->choices[vn]); + break; + } + } + + return (0); +} diff --git a/cmd-source-file.c b/cmd-source-file.c new file mode 100644 index 00000000..f22322ee --- /dev/null +++ b/cmd-source-file.c @@ -0,0 +1,147 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Tiago Cunha + * + * 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 + +#include "tmux.h" + +/* + * Sources a configuration file. + */ + +int cmd_source_file_parse(struct cmd *, int, char **, char **); +int cmd_source_file_exec(struct cmd *, struct cmd_ctx *); +void cmd_source_file_send(struct cmd *, struct buffer *); +void cmd_source_file_recv(struct cmd *, struct buffer *); +void cmd_source_file_free(struct cmd *); +void cmd_source_file_init(struct cmd *, int); +size_t cmd_source_file_print(struct cmd *, char *, size_t); + +struct cmd_source_file_data { + char *path; +}; + +const struct cmd_entry cmd_source_file_entry = { + "source-file", "source", + "path", + 0, + cmd_source_file_init, + cmd_source_file_parse, + cmd_source_file_exec, + cmd_source_file_send, + cmd_source_file_recv, + cmd_source_file_free, + cmd_source_file_print +}; + +void +cmd_source_file_init(struct cmd *self, unused int arg) +{ + struct cmd_source_file_data *data; + + self->data = data = xmalloc(sizeof *data); + data->path = NULL; +} + +int +cmd_source_file_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_source_file_data *data; + int opt; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "")) != -1) { + switch (opt) { + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 1) + goto usage; + + data->path = xstrdup(argv[0]); + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +int +cmd_source_file_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_source_file_data *data = self->data; + char *cause; + + if (load_cfg(data->path, &cause) != 0) { + ctx->error(ctx, "%s", cause); + xfree(cause); + return (-1); + } + + return (0); +} + +void +cmd_source_file_send(struct cmd *self, struct buffer *b) +{ + struct cmd_source_file_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->path); +} + +void +cmd_source_file_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_source_file_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->path = cmd_recv_string(b); +} + +void +cmd_source_file_free(struct cmd *self) +{ + struct cmd_source_file_data *data = self->data; + + if (data->path != NULL) + xfree(data->path); + xfree(data); +} + +size_t +cmd_source_file_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_source_file_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->path != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->path); + return (off); +} diff --git a/cmd-split-window.c b/cmd-split-window.c new file mode 100644 index 00000000..70822f7d --- /dev/null +++ b/cmd-split-window.c @@ -0,0 +1,235 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Split a window (add a new pane). + */ + +int cmd_split_window_parse(struct cmd *, int, char **, char **); +int cmd_split_window_exec(struct cmd *, struct cmd_ctx *); +void cmd_split_window_send(struct cmd *, struct buffer *); +void cmd_split_window_recv(struct cmd *, struct buffer *); +void cmd_split_window_free(struct cmd *); +void cmd_split_window_init(struct cmd *, int); +size_t cmd_split_window_print(struct cmd *, char *, size_t); + +struct cmd_split_window_data { + char *target; + char *cmd; + int flag_detached; + int percentage; + int lines; +}; + +const struct cmd_entry cmd_split_window_entry = { + "split-window", "splitw", + "[-d] [-p percentage|-l lines] [-t target-window] [command]", + 0, + cmd_split_window_init, + cmd_split_window_parse, + cmd_split_window_exec, + cmd_split_window_send, + cmd_split_window_recv, + cmd_split_window_free, + cmd_split_window_print +}; + +void +cmd_split_window_init(struct cmd *self, unused int arg) +{ + struct cmd_split_window_data *data; + + self->data = data = xmalloc(sizeof *data); + data->target = NULL; + data->cmd = NULL; + data->flag_detached = 0; + data->percentage = -1; + data->lines = -1; +} + +int +cmd_split_window_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_split_window_data *data; + int opt, n; + const char *errstr; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "dl:p:t:")) != -1) { + switch (opt) { + case 'd': + data->flag_detached = 1; + break; + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + case 'l': + if (data->percentage == -1 && data->lines == -1) { + n = strtonum(optarg, 1, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "lines %s", errstr); + goto error; + } + data->lines = n; + } + break; + case 'p': + if (data->lines == -1 && data->percentage == -1) { + n = strtonum(optarg, 1, 100, &errstr); + if (errstr != NULL) { + xasprintf( + cause, "percentage %s", errstr); + goto error; + } + data->percentage = n; + } + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 0 && argc != 1) + goto usage; + + if (argc == 1) + data->cmd = xstrdup(argv[0]); + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + self->entry->free(self); + return (-1); +} + +int +cmd_split_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_split_window_data *data = self->data; + struct session *s; + struct winlink *wl; + struct window *w; + struct window_pane *wp; + const char **env; + char *cmd, *cwd, *cause; + u_int hlimit, lines; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + w = wl->window; + + env = server_fill_environ(s); + + cmd = data->cmd; + if (cmd == NULL) + cmd = options_get_string(&s->options, "default-command"); + if (ctx->cmdclient == NULL || ctx->cmdclient->cwd == NULL) + cwd = options_get_string(&global_options, "default-path"); + else + cwd = ctx->cmdclient->cwd; + + lines = -1; + if (data->lines != -1) + lines = data->lines; + else if (data->percentage != -1) + lines = (w->active->sy * data->percentage) / 100; + + hlimit = options_get_number(&s->options, "history-limit"); + wp = window_add_pane(w, lines, cmd, cwd, env, hlimit, &cause); + if (wp == NULL) { + ctx->error(ctx, "create pane failed: %s", cause); + xfree(cause); + return (-1); + } + server_redraw_window(w); + + if (!data->flag_detached) { + window_set_active_pane(w, wp); + session_select(s, wl->idx); + server_redraw_session(s); + } else + server_status_session(s); + layout_refresh(w, 0); + + return (0); +} + +void +cmd_split_window_send(struct cmd *self, struct buffer *b) +{ + struct cmd_split_window_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); + cmd_send_string(b, data->cmd); +} + +void +cmd_split_window_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_split_window_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); + data->cmd = cmd_recv_string(b); +} + +void +cmd_split_window_free(struct cmd *self) +{ + struct cmd_split_window_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + if (data->cmd != NULL) + xfree(data->cmd); + xfree(data); +} + +size_t +cmd_split_window_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_split_window_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->flag_detached) + off += xsnprintf(buf + off, len - off, " -d"); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->cmd != NULL) + off += cmd_prarg(buf + off, len - off, " ", data->cmd); + return (off); +} diff --git a/cmd-start-server.c b/cmd-start-server.c new file mode 100644 index 00000000..21bc2395 --- /dev/null +++ b/cmd-start-server.c @@ -0,0 +1,46 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Start the server and do nothing else. + */ + +int cmd_start_server_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_start_server_entry = { + "start-server", "start", + "", + CMD_STARTSERVER, + NULL, + NULL, + cmd_start_server_exec, + NULL, + NULL, + NULL, + NULL +}; + +int +cmd_start_server_exec(unused struct cmd *self, unused struct cmd_ctx *ctx) +{ + return (0); +} diff --git a/cmd-string.c b/cmd-string.c new file mode 100644 index 00000000..1e471081 --- /dev/null +++ b/cmd-string.c @@ -0,0 +1,309 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include +#include +#include + +#include "tmux.h" + +/* + * Parse a command from a string. + */ + +int cmd_string_getc(const char *, size_t *); +void cmd_string_ungetc(const char *, size_t *); +char *cmd_string_string(const char *, size_t *, char, int); +char *cmd_string_variable(const char *, size_t *); + +int +cmd_string_getc(const char *s, size_t *p) +{ + if (s[*p] == '\0') + return (EOF); + return (s[(*p)++]); +} + +void +cmd_string_ungetc(unused const char *s, size_t *p) +{ + (*p)--; +} + +/* + * Parse command string. Returns -1 on error. If returning -1, cause is error + * string, or NULL for empty command. + */ +int +cmd_string_parse(const char *s, struct cmd_list **cmdlist, char **cause) +{ + size_t p; + int ch, argc, rval, have_arg; + char **argv, *buf, *t, *u; + size_t len; + + if ((t = strchr(s, ' ')) == NULL && (t = strchr(s, '\t')) == NULL) + t = strchr(s, '\0'); + if ((u = strchr(s, '=')) != NULL && u < t) { + if (putenv((char *) s) != 0) { + xasprintf(cause, "assignment failed: %s", s); + return (-1); + } + *cmdlist = NULL; + return (0); + } + + argv = NULL; + argc = 0; + + buf = NULL; + len = 0; + + have_arg = 0; + + *cause = NULL; + + *cmdlist = NULL; + rval = -1; + + p = 0; + for (;;) { + ch = cmd_string_getc(s, &p); + switch (ch) { + case '\'': + if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL) + goto error; + buf = xrealloc(buf, 1, len + strlen(t) + 1); + strlcpy(buf + len, t, strlen(t) + 1); + len += strlen(t); + xfree(t); + + have_arg = 1; + break; + case '"': + if ((t = cmd_string_string(s, &p, '"', 1)) == NULL) + goto error; + buf = xrealloc(buf, 1, len + strlen(t) + 1); + strlcpy(buf + len, t, strlen(t) + 1); + len += strlen(t); + xfree(t); + + have_arg = 1; + break; + case '$': + if ((t = cmd_string_variable(s, &p)) == NULL) + goto error; + buf = xrealloc(buf, 1, len + strlen(t) + 1); + strlcpy(buf + len, t, strlen(t) + 1); + len += strlen(t); + + have_arg = 1; + break; + case '#': + /* Comment: discard rest of line. */ + while ((ch = cmd_string_getc(s, &p)) != EOF) + ; + /* FALLTHROUGH */ + case EOF: + case ' ': + case '\t': + if (have_arg) { + buf = xrealloc(buf, 1, len + 1); + buf[len] = '\0'; + + argv = xrealloc(argv, argc + 1, sizeof *argv); + argv[argc++] = buf; + + buf = NULL; + len = 0; + + have_arg = 0; + } + + if (ch != EOF) + break; + if (argc == 0) + goto out; + + *cmdlist = cmd_list_parse(argc, argv, cause); + if (*cmdlist == NULL) + goto out; + + do + xfree(argv[argc - 1]); + while (--argc > 0); + + rval = 0; + goto out; + default: + if (len >= SIZE_MAX - 2) + goto error; + + buf = xrealloc(buf, 1, len + 1); + buf[len++] = ch; + + have_arg = 1; + break; + } + } + +error: + xasprintf(cause, "invalid or unknown command: %s", s); + +out: + if (buf != NULL) + xfree(buf); + + while (--argc >= 0) + xfree(argv[argc]); + if (argv != NULL) + xfree(argv); + + return (rval); +} + +char * +cmd_string_string(const char *s, size_t *p, char endch, int esc) +{ + int ch; + char *buf, *t; + size_t len; + + buf = NULL; + len = 0; + + while ((ch = cmd_string_getc(s, p)) != endch) { + switch (ch) { + case EOF: + goto error; + case '\\': + if (!esc) + break; + switch (ch = cmd_string_getc(s, p)) { + case EOF: + goto error; + case 'r': + ch = '\r'; + break; + case 'n': + ch = '\n'; + break; + case 't': + ch = '\t'; + break; + } + break; + case '$': + if (!esc) + break; + if ((t = cmd_string_variable(s, p)) == NULL) + goto error; + buf = xrealloc(buf, 1, len + strlen(t) + 1); + strlcpy(buf + len, t, strlen(t) + 1); + len += strlen(t); + continue; + } + + if (len >= SIZE_MAX - 2) + goto error; + buf = xrealloc(buf, 1, len + 1); + buf[len++] = ch; + } + + buf = xrealloc(buf, 1, len + 1); + buf[len] = '\0'; + return (buf); + +error: + if (buf != NULL) + xfree(buf); + return (NULL); +} + +char * +cmd_string_variable(const char *s, size_t *p) +{ + int ch, fch; + char *buf, *t; + size_t len; + +#define cmd_string_first(ch) ((ch) == '_' || \ + ((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z')) +#define cmd_string_other(ch) ((ch) == '_' || \ + ((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \ + ((ch) >= '0' && (ch) <= '9')) + + buf = NULL; + len = 0; + + fch = EOF; + switch (ch = cmd_string_getc(s, p)) { + case EOF: + goto error; + case '{': + fch = '{'; + + ch = cmd_string_getc(s, p); + if (!cmd_string_first(ch)) + goto error; + /* FALLTHROUGH */ + default: + if (!cmd_string_first(ch)) { + xasprintf(&t, "$%c", ch); + return (t); + } + + buf = xrealloc(buf, 1, len + 1); + buf[len++] = ch; + + for (;;) { + ch = cmd_string_getc(s, p); + if (ch == EOF || !cmd_string_other(ch)) + break; + else { + if (len >= SIZE_MAX - 3) + goto error; + buf = xrealloc(buf, 1, len + 1); + buf[len++] = ch; + } + } + } + + if (fch == '{' && ch != '}') + goto error; + if (ch != EOF && fch != '{') + cmd_string_ungetc(s, p); /* ch */ + + buf = xrealloc(buf, 1, len + 1); + buf[len] = '\0'; + + if ((t = getenv(buf)) == NULL) { + xfree(buf); + return (xstrdup("")); + } + xfree(buf); + return (xstrdup(t)); + +error: + if (buf != NULL) + xfree(buf); + return (NULL); +} diff --git a/cmd-suspend-client.c b/cmd-suspend-client.c new file mode 100644 index 00000000..00941f61 --- /dev/null +++ b/cmd-suspend-client.c @@ -0,0 +1,64 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Suspend client with SIGTSTP. + */ + +int cmd_suspend_client_exec(struct cmd *, struct cmd_ctx *); + +struct cmd_suspend_client_data { + char *name; + char *target; +}; + +const struct cmd_entry cmd_suspend_client_entry = { + "suspend-client", "suspendc", + "[-c target-client]", + 0, + cmd_target_init, + cmd_target_parse, + cmd_suspend_client_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_suspend_client_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct client *c; + + if ((c = cmd_find_client(ctx, data->target)) == NULL) + return (-1); + + tty_stop_tty(&c->tty); + c->flags |= CLIENT_SUSPENDED; + server_write_client(c, MSG_SUSPEND, NULL, 0); + + return (0); +} diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c new file mode 100644 index 00000000..0a34d14d --- /dev/null +++ b/cmd-swap-pane.c @@ -0,0 +1,285 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Swap two panes. + */ + +int cmd_swap_pane_parse(struct cmd *, int, char **, char **); +int cmd_swap_pane_exec(struct cmd *, struct cmd_ctx *); +void cmd_swap_pane_send(struct cmd *, struct buffer *); +void cmd_swap_pane_recv(struct cmd *, struct buffer *); +void cmd_swap_pane_free(struct cmd *); +void cmd_swap_pane_init(struct cmd *, int); +size_t cmd_swap_pane_print(struct cmd *, char *, size_t); + +struct cmd_swap_pane_data { + char *target; + int src; + int dst; + int flag_detached; + int flag_up; + int flag_down; +}; + +const struct cmd_entry cmd_swap_pane_entry = { + "swap-pane", "swapp", + "[-dDU] [-t target-window] [-p src-index] [-q dst-index]", + 0, + cmd_swap_pane_init, + cmd_swap_pane_parse, + cmd_swap_pane_exec, + cmd_swap_pane_send, + cmd_swap_pane_recv, + cmd_swap_pane_free, + cmd_swap_pane_print +}; + +void +cmd_swap_pane_init(struct cmd *self, int key) +{ + struct cmd_swap_pane_data *data; + + self->data = data = xmalloc(sizeof *data); + data->target = NULL; + data->src = -1; + data->dst = -1; + data->flag_detached = 0; + data->flag_up = 0; + data->flag_down = 0; + + switch (key) { + case '{': + data->flag_up = 1; + break; + case '}': + data->flag_down = 1; + break; + } +} + +int +cmd_swap_pane_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_swap_pane_data *data; + int opt, n; + const char *errstr; + + self->entry->init(self, 0); + data = self->data; + + while ((opt = getopt(argc, argv, "dDt:p:q:U")) != -1) { + switch (opt) { + case 'd': + data->flag_detached = 1; + break; + case 'D': + data->flag_up = 0; + data->flag_down = 1; + data->dst = -1; + break; + case 't': + if (data->target == NULL) + data->target = xstrdup(optarg); + break; + case 'p': + if (data->src == -1) { + n = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "src %s", errstr); + goto error; + } + data->src = n; + } + break; + case 'q': + if (data->dst == -1) { + n = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + xasprintf(cause, "dst %s", errstr); + goto error; + } + data->dst = n; + } + data->flag_up = 0; + data->flag_down = 0; + break; + case 'U': + data->flag_up = 1; + data->flag_down = 0; + data->dst = -1; + break; + + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 0) + goto usage; + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + self->entry->free(self); + return (-1); +} + +int +cmd_swap_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_swap_pane_data *data = self->data; + struct winlink *wl; + struct window *w; + struct window_pane *tmp_wp, *src_wp, *dst_wp; + u_int xx, yy; + + if (data == NULL) + return (0); + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + w = wl->window; + + if (data->src == -1) + src_wp = w->active; + else { + src_wp = window_pane_at_index(w, data->src); + if (src_wp == NULL) { + ctx->error(ctx, "no pane: %d", data->src); + return (-1); + } + } + if (data->dst == -1) + dst_wp = w->active; + else { + dst_wp = window_pane_at_index(w, data->dst); + if (dst_wp == NULL) { + ctx->error(ctx, "no pane: %d", data->dst); + return (-1); + } + } + + if (data->dst == -1 && data->flag_up) { + if ((dst_wp = TAILQ_PREV(src_wp, window_panes, entry)) == NULL) + dst_wp = TAILQ_LAST(&w->panes, window_panes); + } + if (data->dst == -1 && data->flag_down) { + if ((dst_wp = TAILQ_NEXT(src_wp, entry)) == NULL) + dst_wp = TAILQ_FIRST(&w->panes); + } + + if (src_wp == dst_wp) + return (0); + + tmp_wp = TAILQ_PREV(dst_wp, window_panes, entry); + TAILQ_REMOVE(&w->panes, dst_wp, entry); + TAILQ_REPLACE(&w->panes, src_wp, dst_wp, entry); + if (tmp_wp == src_wp) + tmp_wp = dst_wp; + if (tmp_wp == NULL) + TAILQ_INSERT_HEAD(&w->panes, src_wp, entry); + else + TAILQ_INSERT_AFTER(&w->panes, tmp_wp, src_wp, entry); + + xx = src_wp->xoff; + yy = src_wp->yoff; + src_wp->xoff = dst_wp->xoff; + src_wp->yoff = dst_wp->yoff; + dst_wp->xoff = xx; + dst_wp->yoff = yy; + + xx = src_wp->sx; + yy = src_wp->sy; + window_pane_resize(src_wp, dst_wp->sx, dst_wp->sy); + window_pane_resize(dst_wp, xx, yy); + + if (!data->flag_detached) { + window_set_active_pane(w, dst_wp); + layout_refresh(w, 0); + } + + return (0); +} + +void +cmd_swap_pane_send(struct cmd *self, struct buffer *b) +{ + struct cmd_swap_pane_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->target); +} + +void +cmd_swap_pane_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_swap_pane_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->target = cmd_recv_string(b); +} + +void +cmd_swap_pane_free(struct cmd *self) +{ + struct cmd_swap_pane_data *data = self->data; + + if (data->target != NULL) + xfree(data->target); + xfree(data); +} + +size_t +cmd_swap_pane_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_swap_pane_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && + (data->flag_down || data->flag_up || data->flag_detached)) { + off += xsnprintf(buf + off, len - off, " -"); + if (off < len && data->flag_detached) + off += xsnprintf(buf + off, len - off, "d"); + if (off < len && data->flag_up) + off += xsnprintf(buf + off, len - off, "D"); + if (off < len && data->flag_down) + off += xsnprintf(buf + off, len - off, "U"); + } + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + if (off < len && data->src != -1) + off += xsnprintf(buf + off, len - off, " -p %d", data->src); + if (off < len && data->dst != -1) + off += xsnprintf(buf + off, len - off, " -q %d", data->dst); + return (off); +} diff --git a/cmd-swap-window.c b/cmd-swap-window.c new file mode 100644 index 00000000..4a72a3e5 --- /dev/null +++ b/cmd-swap-window.c @@ -0,0 +1,75 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Swap one window with another. + */ + +int cmd_swap_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_swap_window_entry = { + "swap-window", "swapw", + "[-d] " CMD_SRCDST_WINDOW_USAGE, + CMD_DFLAG, + cmd_srcdst_init, + cmd_srcdst_parse, + cmd_swap_window_exec, + cmd_srcdst_send, + cmd_srcdst_recv, + cmd_srcdst_free, + cmd_srcdst_print +}; + +int +cmd_swap_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_srcdst_data *data = self->data; + struct session *src, *dst; + struct winlink *wl_src, *wl_dst; + struct window *w; + + if ((wl_src = cmd_find_window(ctx, data->src, &src)) == NULL) + return (-1); + if ((wl_dst = cmd_find_window(ctx, data->dst, &dst)) == NULL) + return (-1); + + if (wl_dst->window == wl_src->window) + return (0); + + w = wl_dst->window; + wl_dst->window = wl_src->window; + wl_src->window = w; + + if (!(data->flags & CMD_DFLAG)) { + session_select(dst, wl_dst->idx); + if (src != dst) + session_select(src, wl_src->idx); + } + server_redraw_session(src); + if (src != dst) + server_redraw_session(dst); + recalculate_sizes(); + + return (0); +} diff --git a/cmd-switch-client.c b/cmd-switch-client.c new file mode 100644 index 00000000..9df443db --- /dev/null +++ b/cmd-switch-client.c @@ -0,0 +1,161 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Switch client to a different session. + */ + +int cmd_switch_client_parse(struct cmd *, int, char **, char **); +int cmd_switch_client_exec(struct cmd *, struct cmd_ctx *); +void cmd_switch_client_send(struct cmd *, struct buffer *); +void cmd_switch_client_recv(struct cmd *, struct buffer *); +void cmd_switch_client_free(struct cmd *); +size_t cmd_switch_client_print(struct cmd *, char *, size_t); + +struct cmd_switch_client_data { + char *name; + char *target; +}; + +const struct cmd_entry cmd_switch_client_entry = { + "switch-client", "switchc", + "[-c target-client] [-t target-session]", + 0, + NULL, + cmd_switch_client_parse, + cmd_switch_client_exec, + cmd_switch_client_send, + cmd_switch_client_recv, + cmd_switch_client_free, + cmd_switch_client_print +}; + +int +cmd_switch_client_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_switch_client_data *data; + int opt; + + self->data = data = xmalloc(sizeof *data); + data->name = NULL; + data->target = NULL; + + while ((opt = getopt(argc, argv, "c:t:")) != -1) { + switch (opt) { + case 'c': + data->name = xstrdup(optarg); + break; + case 't': + data->target = xstrdup(optarg); + break; + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 0) + goto usage; + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + + self->entry->free(self); + return (-1); +} + +int +cmd_switch_client_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_switch_client_data *data = self->data; + struct client *c; + struct session *s; + + if (data == NULL) + return (0); + + if ((c = cmd_find_client(ctx, data->name)) == NULL) + return (-1); + if ((s = cmd_find_session(ctx, data->target)) == NULL) + return (-1); + + c->session = s; + + recalculate_sizes(); + server_redraw_client(c); + + return (0); +} + +void +cmd_switch_client_send(struct cmd *self, struct buffer *b) +{ + struct cmd_switch_client_data *data = self->data; + + buffer_write(b, data, sizeof *data); + cmd_send_string(b, data->name); + cmd_send_string(b, data->target); +} + +void +cmd_switch_client_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_switch_client_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); + data->name = cmd_recv_string(b); + data->target = cmd_recv_string(b); +} + +void +cmd_switch_client_free(struct cmd *self) +{ + struct cmd_switch_client_data *data = self->data; + + if (data->name != NULL) + xfree(data->name); + if (data->target != NULL) + xfree(data->target); + xfree(data); +} + +size_t +cmd_switch_client_print(struct cmd *self, char *buf, size_t len) +{ + struct cmd_switch_client_data *data = self->data; + size_t off = 0; + + off += xsnprintf(buf, len, "%s", self->entry->name); + if (data == NULL) + return (off); + if (off < len && data->name != NULL) + off += cmd_prarg(buf + off, len - off, " -c ", data->name); + if (off < len && data->target != NULL) + off += cmd_prarg(buf + off, len - off, " -t ", data->target); + return (off); +} diff --git a/cmd-unbind-key.c b/cmd-unbind-key.c new file mode 100644 index 00000000..d9389752 --- /dev/null +++ b/cmd-unbind-key.c @@ -0,0 +1,120 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Unbind key from command. + */ + +int cmd_unbind_key_parse(struct cmd *, int, char **, char **); +int cmd_unbind_key_exec(struct cmd *, struct cmd_ctx *); +void cmd_unbind_key_send(struct cmd *, struct buffer *); +void cmd_unbind_key_recv(struct cmd *, struct buffer *); +void cmd_unbind_key_free(struct cmd *); + +struct cmd_unbind_key_data { + int key; +}; + +const struct cmd_entry cmd_unbind_key_entry = { + "unbind-key", "unbind", + "key", + 0, + NULL, + cmd_unbind_key_parse, + cmd_unbind_key_exec, + cmd_unbind_key_send, + cmd_unbind_key_recv, + cmd_unbind_key_free, + NULL +}; + +int +cmd_unbind_key_parse(struct cmd *self, int argc, char **argv, char **cause) +{ + struct cmd_unbind_key_data *data; + int opt; + + self->data = data = xmalloc(sizeof *data); + + while ((opt = getopt(argc, argv, "")) != -1) { + switch (opt) { + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 1) + goto usage; + + if ((data->key = key_string_lookup_string(argv[0])) == KEYC_NONE) { + xasprintf(cause, "unknown key: %s", argv[0]); + goto error; + } + + return (0); + +usage: + xasprintf(cause, "usage: %s %s", self->entry->name, self->entry->usage); + +error: + xfree(data); + return (-1); +} + +int +cmd_unbind_key_exec(struct cmd *self, unused struct cmd_ctx *ctx) +{ + struct cmd_unbind_key_data *data = self->data; + + if (data == NULL) + return (0); + + key_bindings_remove(data->key); + + return (0); +} + +void +cmd_unbind_key_send(struct cmd *self, struct buffer *b) +{ + struct cmd_unbind_key_data *data = self->data; + + buffer_write(b, data, sizeof *data); +} + +void +cmd_unbind_key_recv(struct cmd *self, struct buffer *b) +{ + struct cmd_unbind_key_data *data; + + self->data = data = xmalloc(sizeof *data); + buffer_read(b, data, sizeof *data); +} + +void +cmd_unbind_key_free(struct cmd *self) +{ + struct cmd_unbind_key_data *data = self->data; + + xfree(data); +} diff --git a/cmd-unlink-window.c b/cmd-unlink-window.c new file mode 100644 index 00000000..5d9734b6 --- /dev/null +++ b/cmd-unlink-window.c @@ -0,0 +1,74 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Unlink a window, unless it would be destroyed by doing so (only one link). + */ + +int cmd_unlink_window_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_unlink_window_entry = { + "unlink-window", "unlinkw", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_unlink_window_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_unlink_window_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct session *s; + struct client *c; + u_int i; + int destroyed; + + if ((wl = cmd_find_window(ctx, data->target, &s)) == NULL) + return (-1); + + if (wl->window->references == 1) { + ctx->error(ctx, "window is only linked to one session"); + return (-1); + } + + destroyed = session_detach(s, wl); + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session != s) + continue; + if (destroyed) { + c->session = NULL; + server_write_client(c, MSG_EXIT, NULL, 0); + } else + server_redraw_client(c); + } + recalculate_sizes(); + + return (0); +} diff --git a/cmd-up-pane.c b/cmd-up-pane.c new file mode 100644 index 00000000..fc62173e --- /dev/null +++ b/cmd-up-pane.c @@ -0,0 +1,61 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* + * Move up a pane. + */ + +int cmd_up_pane_exec(struct cmd *, struct cmd_ctx *); + +const struct cmd_entry cmd_up_pane_entry = { + "up-pane", "upp", + CMD_TARGET_WINDOW_USAGE, + 0, + cmd_target_init, + cmd_target_parse, + cmd_up_pane_exec, + cmd_target_send, + cmd_target_recv, + cmd_target_free, + cmd_target_print +}; + +int +cmd_up_pane_exec(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + struct winlink *wl; + struct window *w; + + if ((wl = cmd_find_window(ctx, data->target, NULL)) == NULL) + return (-1); + w = wl->window; + + do { + w->active = TAILQ_PREV(w->active, window_panes, entry); + if (w->active == NULL) + w->active = TAILQ_LAST(&w->panes, window_panes); + layout_refresh(w, 1); + } while (w->active->flags & PANE_HIDDEN); + + return (0); +} diff --git a/cmd.c b/cmd.c new file mode 100644 index 00000000..fc87558d --- /dev/null +++ b/cmd.c @@ -0,0 +1,393 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include + +#include "tmux.h" + +const struct cmd_entry *cmd_table[] = { + &cmd_attach_session_entry, + &cmd_bind_key_entry, + &cmd_break_pane_entry, + &cmd_choose_session_entry, + &cmd_choose_window_entry, + &cmd_clear_history_entry, + &cmd_clock_mode_entry, + &cmd_command_prompt_entry, + &cmd_confirm_before_entry, + &cmd_copy_buffer_entry, + &cmd_copy_mode_entry, + &cmd_delete_buffer_entry, + &cmd_detach_client_entry, + &cmd_down_pane_entry, + &cmd_find_window_entry, + &cmd_has_session_entry, + &cmd_kill_pane_entry, + &cmd_kill_server_entry, + &cmd_kill_session_entry, + &cmd_kill_window_entry, + &cmd_last_window_entry, + &cmd_link_window_entry, + &cmd_list_buffers_entry, + &cmd_list_clients_entry, + &cmd_list_commands_entry, + &cmd_list_keys_entry, + &cmd_list_sessions_entry, + &cmd_list_windows_entry, + &cmd_load_buffer_entry, + &cmd_lock_server_entry, + &cmd_move_window_entry, + &cmd_new_session_entry, + &cmd_new_window_entry, + &cmd_next_layout_entry, + &cmd_next_window_entry, + &cmd_paste_buffer_entry, + &cmd_previous_layout_entry, + &cmd_previous_window_entry, + &cmd_refresh_client_entry, + &cmd_rename_session_entry, + &cmd_rename_window_entry, + &cmd_resize_pane_entry, + &cmd_respawn_window_entry, + &cmd_rotate_window_entry, + &cmd_save_buffer_entry, + &cmd_scroll_mode_entry, + &cmd_select_layout_entry, + &cmd_select_pane_entry, + &cmd_select_prompt_entry, + &cmd_select_window_entry, + &cmd_send_keys_entry, + &cmd_send_prefix_entry, + &cmd_server_info_entry, + &cmd_set_buffer_entry, + &cmd_set_option_entry, + &cmd_set_password_entry, + &cmd_set_window_option_entry, + &cmd_show_buffer_entry, + &cmd_show_options_entry, + &cmd_show_window_options_entry, + &cmd_source_file_entry, + &cmd_split_window_entry, + &cmd_start_server_entry, + &cmd_suspend_client_entry, + &cmd_swap_pane_entry, + &cmd_swap_window_entry, + &cmd_switch_client_entry, + &cmd_unbind_key_entry, + &cmd_unlink_window_entry, + &cmd_up_pane_entry, + NULL +}; + +struct cmd * +cmd_parse(int argc, char **argv, char **cause) +{ + const struct cmd_entry **entryp, *entry; + struct cmd *cmd; + char s[BUFSIZ]; + int opt; + + *cause = NULL; + if (argc == 0) + return (NULL); + + entry = NULL; + for (entryp = cmd_table; *entryp != NULL; entryp++) { + if ((*entryp)->alias != NULL && + strcmp((*entryp)->alias, argv[0]) == 0) { + entry = *entryp; + break; + } + + if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0) + continue; + if (entry != NULL) + goto ambiguous; + entry = *entryp; + + /* Bail now if an exact match. */ + if (strcmp(entry->name, argv[0]) == 0) + break; + } + if (entry == NULL) { + xasprintf(cause, "unknown command: %s", argv[0]); + return (NULL); + } + + optreset = 1; + optind = 1; + if (entry->parse == NULL) { + while ((opt = getopt(argc, argv, "")) != -1) { + switch (opt) { + default: + goto usage; + } + } + argc -= optind; + argv += optind; + if (argc != 0) + goto usage; + } + + cmd = xmalloc(sizeof *cmd); + cmd->entry = entry; + cmd->data = NULL; + if (entry->parse != NULL) { + if (entry->parse(cmd, argc, argv, cause) != 0) { + xfree(cmd); + return (NULL); + } + } + return (cmd); + +ambiguous: + *s = '\0'; + for (entryp = cmd_table; *entryp != NULL; entryp++) { + if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0) + continue; + if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s) + break; + if (strlcat(s, ", ", sizeof s) >= sizeof s) + break; + } + s[strlen(s) - 2] = '\0'; + xasprintf(cause, "ambiguous command: %s, could be: %s", argv[0], s); + return (NULL); + +usage: + xasprintf(cause, "usage: %s %s", entry->name, entry->usage); + return (NULL); +} + +int +cmd_exec(struct cmd *cmd, struct cmd_ctx *ctx) +{ + if (server_locked) { + ctx->error(ctx, "server is locked"); + return (-1); + } + return (cmd->entry->exec(cmd, ctx)); +} + +void +cmd_send(struct cmd *cmd, struct buffer *b) +{ + const struct cmd_entry **entryp; + u_int n; + + n = 0; + for (entryp = cmd_table; *entryp != NULL; entryp++) { + if (*entryp == cmd->entry) + break; + n++; + } + if (*entryp == NULL) + fatalx("command not found"); + + buffer_write(b, &n, sizeof n); + + if (cmd->entry->send != NULL) + cmd->entry->send(cmd, b); +} + +struct cmd * +cmd_recv(struct buffer *b) +{ + const struct cmd_entry **entryp; + struct cmd *cmd; + u_int m, n; + + buffer_read(b, &m, sizeof m); + + n = 0; + for (entryp = cmd_table; *entryp != NULL; entryp++) { + if (n == m) + break; + n++; + } + if (*entryp == NULL) + fatalx("command not found"); + + cmd = xmalloc(sizeof *cmd); + cmd->entry = *entryp; + + if (cmd->entry->recv != NULL) + cmd->entry->recv(cmd, b); + return (cmd); +} + +void +cmd_free(struct cmd *cmd) +{ + if (cmd->data != NULL && cmd->entry->free != NULL) + cmd->entry->free(cmd); + xfree(cmd); +} + +size_t +cmd_print(struct cmd *cmd, char *buf, size_t len) +{ + if (cmd->entry->print == NULL) { + return (xsnprintf(buf, len, "%s", cmd->entry->name)); + } + return (cmd->entry->print(cmd, buf, len)); +} + +void +cmd_send_string(struct buffer *b, const char *s) +{ + size_t n; + + if (s == NULL) { + n = 0; + buffer_write(b, &n, sizeof n); + return; + } + + n = strlen(s) + 1; + buffer_write(b, &n, sizeof n); + + buffer_write(b, s, n); +} + +char * +cmd_recv_string(struct buffer *b) +{ + char *s; + size_t n; + + buffer_read(b, &n, sizeof n); + + if (n == 0) + return (NULL); + + s = xmalloc(n); + buffer_read(b, s, n); + s[n - 1] = '\0'; + + return (s); +} + +struct session * +cmd_current_session(struct cmd_ctx *ctx) +{ + struct msg_command_data *data = ctx->msgdata; + struct timeval *tv; + struct session *s, *newest = NULL; + u_int i; + + if (ctx->cursession != NULL) + return (ctx->cursession); + + if (data != NULL && data->pid != -1) { + if (data->pid != getpid()) { + ctx->error(ctx, "wrong server: %ld", (long) data->pid); + return (NULL); + } + if (data->idx > ARRAY_LENGTH(&sessions)) { + ctx->error(ctx, "index out of range: %d", data->idx); + return (NULL); + } + if ((s = ARRAY_ITEM(&sessions, data->idx)) == NULL) { + ctx->error(ctx, "session doesn't exist: %u", data->idx); + return (NULL); + } + return (s); + } + + tv = NULL; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s != NULL && (tv == NULL || timercmp(&s->tv, tv, >))) { + newest = ARRAY_ITEM(&sessions, i); + tv = &s->tv; + } + } + return (newest); +} + +struct client * +cmd_find_client(struct cmd_ctx *ctx, const char *arg) +{ + struct client *c; + + if (arg == NULL) + c = ctx->curclient; + else { + if ((c = arg_parse_client(arg)) == NULL) { + if (arg != NULL) + ctx->error(ctx, "client not found: %s", arg); + else + ctx->error(ctx, "no client found"); + } + } + return (c); +} + +struct session * +cmd_find_session(struct cmd_ctx *ctx, const char *arg) +{ + struct session *s; + + if (arg == NULL) + s = cmd_current_session(ctx); + else { + if ((s = arg_parse_session(arg)) == NULL) { + if (arg != NULL) + ctx->error(ctx, "session not found: %s", arg); + else + ctx->error(ctx, "no session found"); + } + } + return (s); +} + +struct winlink * +cmd_find_window(struct cmd_ctx *ctx, const char *arg, struct session **sp) +{ + struct session *s; + struct winlink *wl; + int idx; + + wl = NULL; + if (arg_parse_window(arg, &s, &idx) != 0) { + ctx->error(ctx, "bad window: %s", arg); + return (NULL); + } + if (s == NULL) + s = ctx->cursession; + if (s == NULL) + s = cmd_current_session(ctx); + if (s == NULL) + return (NULL); + if (sp != NULL) + *sp = s; + + if (idx == -1) + wl = s->curw; + else + wl = winlink_find_by_index(&s->windows, idx); + if (wl == NULL) + ctx->error(ctx, "window not found: %s:%d", s->name, idx); + return (wl); +} diff --git a/colour.c b/colour.c new file mode 100644 index 00000000..2067d0dd --- /dev/null +++ b/colour.c @@ -0,0 +1,123 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +const char * +colour_tostring(u_char c) +{ + switch (c) { + case 0: + return ("black"); + case 1: + return ("red"); + case 2: + return ("green"); + case 3: + return ("yellow"); + case 4: + return ("blue"); + case 5: + return ("magenta"); + case 6: + return ("cyan"); + case 7: + return ("white"); + case 8: + return ("default"); + } + return (NULL); +} + +int +colour_fromstring(const char *s) +{ + if (strcasecmp(s, "black") == 0 || (s[0] == '0' && s[1] == '\0')) + return (0); + if (strcasecmp(s, "red") == 0 || (s[0] == '1' && s[1] == '\0')) + return (1); + if (strcasecmp(s, "green") == 0 || (s[0] == '2' && s[1] == '\0')) + return (2); + if (strcasecmp(s, "yellow") == 0 || (s[0] == '3' && s[1] == '\0')) + return (3); + if (strcasecmp(s, "blue") == 0 || (s[0] == '4' && s[1] == '\0')) + return (4); + if (strcasecmp(s, "magenta") == 0 || (s[0] == '5' && s[1] == '\0')) + return (5); + if (strcasecmp(s, "cyan") == 0 || (s[0] == '6' && s[1] == '\0')) + return (6); + if (strcasecmp(s, "white") == 0 || (s[0] == '7' && s[1] == '\0')) + return (7); + if (strcasecmp(s, "default") == 0 || (s[0] == '8' && s[1] == '\0')) + return (8); + return (-1); +} + +u_char +colour_256to16(u_char c) +{ + static const u_char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4, + 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, + 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12, + 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, + 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1, + 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12, + 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5, + 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3, + 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, + 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, + 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10, + 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, + 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, + 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8, + 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15 + }; + + return (table[c]); +} + +u_char +colour_256to88(u_char c) +{ + static const u_char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 17, 18, 18, 19, 20, 21, 21, 22, 22, 23, 20, 21, 21, 22, + 22, 23, 24, 25, 25, 26, 26, 27, 24, 25, 25, 26, 26, 27, 28, 29, + 29, 30, 30, 31, 32, 33, 33, 34, 34, 35, 36, 37, 37, 38, 38, 39, + 36, 37, 37, 38, 38, 39, 40, 41, 41, 42, 42, 43, 40, 41, 41, 42, + 42, 43, 44, 45, 45, 46, 46, 47, 32, 33, 33, 34, 34, 35, 36, 37, + 37, 38, 38, 39, 36, 37, 37, 38, 38, 39, 40, 41, 41, 42, 42, 43, + 40, 41, 41, 42, 42, 43, 44, 45, 45, 46, 46, 47, 48, 49, 49, 50, + 50, 51, 52, 53, 53, 54, 54, 55, 52, 53, 53, 54, 54, 55, 56, 57, + 57, 58, 58, 59, 56, 57, 57, 58, 58, 59, 60, 61, 61, 62, 62, 63, + 48, 49, 49, 50, 50, 51, 52, 53, 53, 54, 54, 55, 52, 53, 53, 54, + 54, 55, 56, 57, 57, 58, 58, 59, 56, 57, 57, 58, 58, 59, 60, 61, + 61, 62, 62, 63, 64, 65, 65, 66, 66, 67, 68, 69, 69, 70, 70, 71, + 68, 69, 69, 70, 70, 71, 72, 73, 73, 74, 74, 75, 72, 73, 73, 74, + 74, 75, 76, 77, 77, 78, 78, 79, 0, 0, 80, 80, 80, 81, 81, 81, + 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87 + }; + + return (table[c]); +} diff --git a/grid-view.c b/grid-view.c new file mode 100644 index 00000000..f1224bf6 --- /dev/null +++ b/grid-view.c @@ -0,0 +1,211 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Grid view functions. These work using coordinates relative to the visible + * screen area. + */ + +#define grid_view_x(gd, x) (x) +#define grid_view_y(gd, y) ((gd)->hsize + (y)) + +/* Get cell for reading. */ +const struct grid_cell * +grid_view_peek_cell(struct grid *gd, u_int px, u_int py) +{ + return (grid_peek_cell(gd, grid_view_x(gd, px), grid_view_y(gd, py))); +} + +/* Get cell for writing. */ +struct grid_cell * +grid_view_get_cell(struct grid *gd, u_int px, u_int py) +{ + return (grid_get_cell(gd, grid_view_x(gd, px), grid_view_y(gd, py))); +} + +/* Set cell. */ +void +grid_view_set_cell( + struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) +{ + grid_set_cell(gd, grid_view_x(gd, px), grid_view_y(gd, py), gc); +} + +/* Get UTF-8 for reading. */ +const struct grid_utf8 * +grid_view_peek_utf8(struct grid *gd, u_int px, u_int py) +{ + return (grid_peek_utf8(gd, grid_view_x(gd, px), grid_view_y(gd, py))); +} + +/* Get UTF-8 for writing. */ +struct grid_utf8 * +grid_view_get_utf8(struct grid *gd, u_int px, u_int py) +{ + return (grid_get_utf8(gd, grid_view_x(gd, px), grid_view_y(gd, py))); +} + +/* Set UTF-8. */ +void +grid_view_set_utf8( + struct grid *gd, u_int px, u_int py, const struct grid_utf8 *gu) +{ + grid_set_utf8(gd, grid_view_x(gd, px), grid_view_y(gd, py), gu); +} + +/* Clear area. */ +void +grid_view_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny) +{ + GRID_DEBUG(gd, "px=%u, py=%u, nx=%u, ny=%u", px, py, nx, ny); + + px = grid_view_x(gd, px); + py = grid_view_y(gd, py); + + grid_clear(gd, px, py, nx, ny); +} + +/* Scroll region up. */ +void +grid_view_scroll_region_up(struct grid *gd, u_int rupper, u_int rlower) +{ + GRID_DEBUG(gd, "rupper=%u, rlower=%u", rupper, rlower); + + if (rupper == 0 && rlower == gd->sy - 1) { + grid_scroll_line(gd); + return; + } + + rupper = grid_view_y(gd, rupper); + rlower = grid_view_y(gd, rlower); + + grid_move_lines(gd, rupper, rupper + 1, rlower - rupper); +} + +/* Scroll region down. */ +void +grid_view_scroll_region_down(struct grid *gd, u_int rupper, u_int rlower) +{ + GRID_DEBUG(gd, "rupper=%u, rlower=%u", rupper, rlower); + + rupper = grid_view_y(gd, rupper); + rlower = grid_view_y(gd, rlower); + + grid_move_lines(gd, rupper + 1, rupper, rlower - rupper); +} + +/* Insert lines. */ +void +grid_view_insert_lines(struct grid *gd, u_int py, u_int ny) +{ + u_int sy; + + GRID_DEBUG(gd, "py=%u, ny=%u", py, ny); + + py = grid_view_y(gd, py); + + sy = grid_view_y(gd, gd->sy); + + grid_move_lines(gd, py + ny, py, sy - py - ny); +} + +/* Insert lines in region. */ +void +grid_view_insert_lines_region( + struct grid *gd, unused u_int rupper, u_int rlower, u_int py, u_int ny) +{ + GRID_DEBUG( + gd, "rupper=%u, rlower=%u, py=%u, ny=%u", rupper, rlower, py, ny); + + rlower = grid_view_y(gd, rlower); + + py = grid_view_y(gd, py); + + grid_move_lines(gd, py + ny, py, (rlower + 1) - py - ny); +} + +/* Delete lines. */ +void +grid_view_delete_lines(struct grid *gd, u_int py, u_int ny) +{ + u_int sy; + + GRID_DEBUG(gd, "py=%u, ny=%u", py, ny); + + py = grid_view_y(gd, py); + + sy = grid_view_y(gd, gd->sy); + + grid_move_lines(gd, py, py + ny, sy - py - ny); +} + +/* Delete lines inside scroll region. */ +void +grid_view_delete_lines_region( + struct grid *gd, unused u_int rupper, u_int rlower, u_int py, u_int ny) +{ + GRID_DEBUG( + gd, "rupper=%u, rlower=%u, py=%u, ny=%u", rupper, rlower, py, ny); + + rlower = grid_view_y(gd, rlower); + + py = grid_view_y(gd, py); + + grid_move_lines(gd, py, py + ny, (rlower + 1) - py - ny); +} + +/* Insert characters. */ +void +grid_view_insert_cells(struct grid *gd, u_int px, u_int py, u_int nx) +{ + u_int sx; + + GRID_DEBUG(gd, "px=%u, py=%u, nx=%u", px, py, nx); + + px = grid_view_x(gd, px); + py = grid_view_y(gd, py); + + sx = grid_view_x(gd, gd->sx); + + if (px == sx - 1) + grid_clear(gd, px, py, 1, 1); + else + grid_move_cells(gd, px + nx, px, py, (sx - 1) - (px + nx)); +} + +/* Delete characters. */ +void +grid_view_delete_cells(struct grid *gd, u_int px, u_int py, u_int nx) +{ + u_int sx; + + GRID_DEBUG(gd, "px=%u, py=%u, nx=%u", px, py, nx); + + px = grid_view_x(gd, px); + py = grid_view_y(gd, py); + + sx = grid_view_x(gd, gd->sx); + + grid_move_cells(gd, px, px + nx, py, (sx - 1) - (px + nx)); +} diff --git a/grid.c b/grid.c new file mode 100644 index 00000000..461d92ea --- /dev/null +++ b/grid.c @@ -0,0 +1,497 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Grid data. This is the basic data structure that represents what is shown on + * screen. + * + * A grid is a grid of cells (struct grid_cell). Lines are not allocated until + * cells in that line are written to. The grid is split into history and + * viewable data with the history starting at row (line) 0 and extending to + * (hsize - 1); from hsize to hsize + (sy - 1) is the viewable data. All + * functions in this file work on absolute coordinates, grid-view.c has + * functions which work on the screen data. + */ + +/* Default grid cell data. */ +const struct grid_cell grid_default_cell = { 0, 0, 8, 8, ' ' }; + +#define grid_put_cell(gd, px, py, gc) do { \ + memcpy(&gd->data[py][px], gc, sizeof gd->data[py][px]); \ +} while (0) +#define grid_put_utf8(gd, px, py, gc) do { \ + memcpy(&gd->udata[py][px], gc, sizeof gd->udata[py][px]); \ +} while (0) + +int grid_check_x(struct grid *, u_int); +int grid_check_y(struct grid *, u_int); + +#ifdef DEBUG +int +grid_check_x(struct grid *gd, u_int px) +{ + if ((px) >= (gd)->sx) + log_fatalx("x out of range: %u", px); + return (0); +} + +int +grid_check_y(struct grid *gd, u_int py) +{ + if ((py) >= (gd)->hsize + (gd)->sy) + log_fatalx("y out of range: %u", py); + return (0); +} +#else +int +grid_check_x(struct grid *gd, u_int px) +{ + if ((px) >= (gd)->sx) { + log_debug("x out of range: %u", px); + return (-1); + } + return (0); +} + +int +grid_check_y(struct grid *gd, u_int py) +{ + if ((py) >= (gd)->hsize + (gd)->sy) { + log_debug("y out of range: %u", py); + return (-1); + } + return (0); +} +#endif + +/* Create a new grid. */ +struct grid * +grid_create(u_int sx, u_int sy, u_int hlimit) +{ + struct grid *gd; + + gd = xmalloc(sizeof *gd); + gd->sx = sx; + gd->sy = sy; + + gd->hsize = 0; + gd->hlimit = hlimit; + + gd->size = xcalloc(gd->sy, sizeof *gd->size); + gd->data = xcalloc(gd->sy, sizeof *gd->data); + + gd->usize = xcalloc(gd->sy, sizeof *gd->usize); + gd->udata = xcalloc(gd->sy, sizeof *gd->udata); + + return (gd); +} + +/* Destroy grid. */ +void +grid_destroy(struct grid *gd) +{ + u_int yy; + + for (yy = 0; yy < gd->hsize + gd->sy; yy++) { + if (gd->udata[yy] != NULL) + xfree(gd->udata[yy]); + if (gd->data[yy] != NULL) + xfree(gd->data[yy]); + } + + if (gd->udata != NULL) + xfree(gd->udata); + if (gd->usize != NULL) + xfree(gd->usize); + + if (gd->data != NULL) + xfree(gd->data); + if (gd->size != NULL) + xfree(gd->size); + + xfree(gd); +} + +/* Compare grids. */ +int +grid_compare(struct grid *ga, struct grid *gb) +{ + struct grid_cell *gca, *gcb; + struct grid_utf8 *gua, *gub; + u_int xx, yy; + + if (ga->sx != gb->sx || ga->sy != ga->sy) + return (1); + + for (yy = 0; yy < ga->sy; yy++) { + if (ga->size[yy] != gb->size[yy]) + return (1); + for (xx = 0; xx < ga->sx; xx++) { + gca = &ga->data[yy][xx]; + gcb = &gb->data[yy][xx]; + if (memcmp(gca, gcb, sizeof (struct grid_cell)) != 0) + return (1); + if (!(gca->flags & GRID_FLAG_UTF8)) + continue; + gua = &ga->udata[yy][xx]; + gub = &gb->udata[yy][xx]; + if (memcmp(gua, gub, sizeof (struct grid_utf8)) != 0) + return (1); + } + } + + return (0); +} + +/* Scroll a line into the history. */ +void +grid_scroll_line(struct grid *gd) +{ + u_int yy; + + GRID_DEBUG(gd, ""); + + if (gd->hsize >= gd->hlimit - 1) { + /* If the limit is hit, free the bottom 10% and shift up. */ + yy = gd->hlimit / 10; + if (yy < 1) + yy = 1; + + grid_move_lines(gd, 0, yy, gd->hsize + gd->sy - yy); + gd->hsize -= yy; + } + + yy = gd->hsize + gd->sy; + + gd->size = xrealloc(gd->size, yy + 1, sizeof *gd->size); + gd->size[yy] = 0; + gd->data = xrealloc(gd->data, yy + 1, sizeof *gd->data); + gd->data[yy] = NULL; + + gd->usize = xrealloc(gd->usize, yy + 1, sizeof *gd->usize); + gd->usize[yy] = 0; + gd->udata = xrealloc(gd->udata, yy + 1, sizeof *gd->udata); + gd->udata[yy] = NULL; + + gd->hsize++; +} + +/* Reduce line to fit to cell. */ +void +grid_reduce_line(struct grid *gd, u_int py, u_int sx) +{ + if (sx < gd->size[py]) { + gd->data[py] = xrealloc(gd->data[py], sx, sizeof **gd->data); + gd->size[py] = sx; + } + if (sx < gd->usize[py]) { + gd->udata[py] = xrealloc(gd->udata[py], sx, sizeof **gd->udata); + gd->usize[py] = sx; + } +} + +/* Expand line to fit to cell. */ +void +grid_expand_line(struct grid *gd, u_int py, u_int sx) +{ + u_int xx; + + if (sx <= gd->size[py]) + return; + + gd->data[py] = xrealloc(gd->data[py], sx, sizeof **gd->data); + for (xx = gd->size[py]; xx < sx; xx++) + grid_put_cell(gd, xx, py, &grid_default_cell); + gd->size[py] = sx; +} + +/* Expand line to fit to cell for UTF-8. */ +void +grid_expand_line_utf8(struct grid *gd, u_int py, u_int sx) +{ + if (sx <= gd->usize[py]) + return; + + gd->udata[py] = xrealloc(gd->udata[py], sx, sizeof **gd->udata); + gd->usize[py] = sx; +} + +/* Get cell for reading. */ +const struct grid_cell * +grid_peek_cell(struct grid *gd, u_int px, u_int py) +{ + if (grid_check_x(gd, px) != 0) + return (&grid_default_cell); + if (grid_check_y(gd, py) != 0) + return (&grid_default_cell); + + if (px >= gd->size[py]) + return (&grid_default_cell); + return (&gd->data[py][px]); +} + +/* Get cell at relative position (for writing). */ +struct grid_cell * +grid_get_cell(struct grid *gd, u_int px, u_int py) +{ + if (grid_check_x(gd, px) != 0) + return (NULL); + if (grid_check_y(gd, py) != 0) + return (NULL); + + grid_expand_line(gd, py, px + 1); + return (&gd->data[py][px]); +} + +/* Set cell at relative position. */ +void +grid_set_cell( + struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) +{ + if (grid_check_x(gd, px) != 0) + return; + if (grid_check_y(gd, py) != 0) + return; + + grid_expand_line(gd, py, px + 1); + grid_put_cell(gd, px, py, gc); +} + +/* Get UTF-8 for reading. */ +const struct grid_utf8 * +grid_peek_utf8(struct grid *gd, u_int px, u_int py) +{ + if (grid_check_x(gd, px) != 0) + return (NULL); + if (grid_check_y(gd, py) != 0) + return (NULL); + + if (px >= gd->usize[py]) + return (NULL); + return (&gd->udata[py][px]); +} + +/* Get utf8 at relative position (for writing). */ +struct grid_utf8 * +grid_get_utf8(struct grid *gd, u_int px, u_int py) +{ + if (grid_check_x(gd, px) != 0) + return (NULL); + if (grid_check_y(gd, py) != 0) + return (NULL); + + grid_expand_line_utf8(gd, py, px + 1); + return (&gd->udata[py][px]); +} + +/* Set utf8 at relative position. */ +void +grid_set_utf8( + struct grid *gd, u_int px, u_int py, const struct grid_utf8 *gc) +{ + if (grid_check_x(gd, px) != 0) + return; + if (grid_check_y(gd, py) != 0) + return; + + grid_expand_line_utf8(gd, py, px + 1); + grid_put_utf8(gd, px, py, gc); +} + +/* + * Clear area. Note this is different from a fill as it just omits unallocated + * cells. + */ +void +grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny) +{ + u_int xx, yy; + + GRID_DEBUG(gd, "px=%u, py=%u, nx=%u, ny=%u", px, py, nx, ny); + + if (nx == 0 || ny == 0) + return; + + if (px == 0 && nx == gd->sx) { + grid_clear_lines(gd, py, ny); + return; + } + + if (grid_check_x(gd, px) != 0) + return; + if (grid_check_x(gd, px + nx - 1) != 0) + return; + if (grid_check_y(gd, py) != 0) + return; + if (grid_check_y(gd, py + ny - 1) != 0) + return; + + for (yy = py; yy < py + ny; yy++) { + for (xx = px; xx < px + nx; xx++) { + if (xx >= gd->size[yy]) + break; + grid_put_cell(gd, xx, yy, &grid_default_cell); + } + } +} + +/* Clear lines. This just frees and truncates the lines. */ +void +grid_clear_lines(struct grid *gd, u_int py, u_int ny) +{ + u_int yy; + + GRID_DEBUG(gd, "py=%u, ny=%u", py, ny); + + if (ny == 0) + return; + + if (grid_check_y(gd, py) != 0) + return; + if (grid_check_y(gd, py + ny - 1) != 0) + return; + + for (yy = py; yy < py + ny; yy++) { + if (gd->data[yy] != NULL) { + xfree(gd->data[yy]); + gd->data[yy] = NULL; + gd->size[yy] = 0; + } + if (gd->udata[yy] != NULL) { + xfree(gd->udata[yy]); + gd->udata[yy] = NULL; + gd->usize[yy] = 0; + } + } +} + +/* Move a group of lines. */ +void +grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny) +{ + u_int yy; + + GRID_DEBUG(gd, "dy=%u, py=%u, ny=%u", dy, py, ny); + + if (ny == 0 || py == dy) + return; + + if (grid_check_y(gd, py) != 0) + return; + if (grid_check_y(gd, py + ny - 1) != 0) + return; + if (grid_check_y(gd, dy) != 0) + return; + if (grid_check_y(gd, dy + ny - 1) != 0) + return; + + /* Free any lines which are being replaced. */ + for (yy = dy; yy < dy + ny; yy++) { + if (yy >= py && yy < py + ny) + continue; + grid_clear_lines(gd, yy, 1); + } + + memmove(&gd->data[dy], &gd->data[py], ny * (sizeof *gd->data)); + memmove(&gd->size[dy], &gd->size[py], ny * (sizeof *gd->size)); + + memmove(&gd->udata[dy], &gd->udata[py], ny * (sizeof *gd->udata)); + memmove(&gd->usize[dy], &gd->usize[py], ny * (sizeof *gd->usize)); + + /* Wipe any lines that have been moved (without freeing them). */ + for (yy = py; yy < py + ny; yy++) { + if (yy >= dy && yy < dy + ny) + continue; + gd->data[yy] = NULL; + gd->size[yy] = 0; + gd->udata[yy] = NULL; + gd->usize[yy] = 0; + } +} + +/* Clear a group of cells. */ +void +grid_clear_cells(struct grid *gd, u_int px, u_int py, u_int nx) +{ + u_int xx; + + GRID_DEBUG(gd, "px=%u, py=%u, nx=%u", px, py, nx); + + if (nx == 0) + return; + + if (grid_check_x(gd, px) != 0) + return; + if (grid_check_x(gd, px + nx - 1) != 0) + return; + if (grid_check_y(gd, py) != 0) + return; + + for (xx = px; xx < px + nx; xx++) { + if (xx >= gd->size[py]) + break; + grid_put_cell(gd, xx, py, &grid_default_cell); + } +} + +/* Move a group of cells. */ +void +grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx) +{ + u_int xx; + + GRID_DEBUG(gd, "dx=%u, px=%u, py=%u, nx=%u", dx, px, py, nx); + + if (nx == 0 || px == dx) + return; + + if (grid_check_x(gd, px) != 0) + return; + if (grid_check_x(gd, px + nx - 1) != 0) + return; + if (grid_check_x(gd, dx + nx - 1) != 0) + return; + if (grid_check_y(gd, py) != 0) + return; + + grid_expand_line(gd, py, px + nx); + grid_expand_line(gd, py, dx + nx); + memmove(&gd->data[py][dx], &gd->data[py][px], nx * (sizeof **gd->data)); + + if (gd->udata[py] != NULL) { + grid_expand_line_utf8(gd, py, px + nx); + grid_expand_line_utf8(gd, py, dx + nx); + memmove(&gd->udata[py][dx], + &gd->udata[py][px], nx * (sizeof **gd->udata)); + } + + /* Wipe any cells that have been moved. */ + for (xx = px; xx < px + nx; xx++) { + if (xx >= dx && xx < dx + nx) + continue; + grid_put_cell(gd, xx, py, &grid_default_cell); + } +} + + diff --git a/input-keys.c b/input-keys.c new file mode 100644 index 00000000..24c0bbaa --- /dev/null +++ b/input-keys.c @@ -0,0 +1,227 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#include "tmux.h" + +struct input_key_ent { + int key; + const char *data; + + int flags; +#define INPUTKEY_KEYPAD 0x1 /* keypad key */ +#define INPUTKEY_CURSOR 0x2 /* cursor key */ +#define INPUTKEY_CTRL 0x4 /* may be modified with ctrl */ +#define INPUTKEY_XTERM 0x4 /* may have xterm argument appended */ +}; + +struct input_key_ent input_keys[] = { + /* Function keys. */ + { KEYC_F1, "\033OP", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F2, "\033OQ", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F3, "\033OR", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F4, "\033OS", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F5, "\033[15~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F6, "\033[17~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F7, "\033[18~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F8, "\033[19~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F9, "\033[20~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F10, "\033[21~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F11, "\033[23~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F12, "\033[24~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F13, "\033[25~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F14, "\033[26~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F15, "\033[28~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F16, "\033[29~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F17, "\033[31~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F18, "\033[32~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F19, "\033[33~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_F20, "\033[34~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_IC, "\033[2~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_DC, "\033[3~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_HOME, "\033[1~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_END, "\033[4~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_NPAGE, "\033[6~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_PPAGE, "\033[5~", INPUTKEY_CTRL|INPUTKEY_XTERM }, + { KEYC_BTAB, "\033[Z", INPUTKEY_CTRL }, + + /* Arrow keys. Cursor versions must come first. */ + { KEYC_ADDCTL(KEYC_UP), "\033Oa", 0 }, + { KEYC_ADDCTL(KEYC_DOWN), "\033Ob", 0 }, + { KEYC_ADDCTL(KEYC_RIGHT), "\033Oc", 0 }, + { KEYC_ADDCTL(KEYC_LEFT), "\033Od", 0 }, + + { KEYC_ADDSFT(KEYC_UP), "\033[a", 0 }, + { KEYC_ADDSFT(KEYC_DOWN), "\033[b", 0 }, + { KEYC_ADDSFT(KEYC_RIGHT), "\033[c", 0 }, + { KEYC_ADDSFT(KEYC_LEFT), "\033[d", 0 }, + + { KEYC_UP, "\033OA", INPUTKEY_CURSOR }, + { KEYC_DOWN, "\033OB", INPUTKEY_CURSOR }, + { KEYC_RIGHT, "\033OC", INPUTKEY_CURSOR }, + { KEYC_LEFT, "\033OD", INPUTKEY_CURSOR }, + + { KEYC_UP, "\033[A", 0 }, + { KEYC_DOWN, "\033[B", 0 }, + { KEYC_RIGHT, "\033[C", 0 }, + { KEYC_LEFT, "\033[D", 0 }, + + /* Keypad keys. Keypad versions must come first. */ + { KEYC_KP0_1, "/", INPUTKEY_KEYPAD }, + { KEYC_KP0_2, "*", INPUTKEY_KEYPAD }, + { KEYC_KP0_3, "-", INPUTKEY_KEYPAD }, + { KEYC_KP1_0, "7", INPUTKEY_KEYPAD }, + { KEYC_KP1_1, "8", INPUTKEY_KEYPAD }, + { KEYC_KP1_2, "9", INPUTKEY_KEYPAD }, + { KEYC_KP1_3, "+", INPUTKEY_KEYPAD }, + { KEYC_KP2_0, "4", INPUTKEY_KEYPAD }, + { KEYC_KP2_1, "5", INPUTKEY_KEYPAD }, + { KEYC_KP2_2, "6", INPUTKEY_KEYPAD }, + { KEYC_KP3_0, "1", INPUTKEY_KEYPAD }, + { KEYC_KP3_1, "2", INPUTKEY_KEYPAD }, + { KEYC_KP3_2, "3", INPUTKEY_KEYPAD }, + { KEYC_KP3_3, "\n", INPUTKEY_KEYPAD }, /* this can be CRLF too? */ + { KEYC_KP4_0, "0", INPUTKEY_KEYPAD }, + { KEYC_KP4_2, ".", INPUTKEY_KEYPAD }, + { KEYC_KP0_1, "\033Oo", 0 }, + { KEYC_KP0_2, "\033Oj", 0 }, + { KEYC_KP0_3, "\033Om", 0 }, + { KEYC_KP1_0, "\033Ow", 0 }, + { KEYC_KP1_1, "\033Ox", 0 }, + { KEYC_KP1_2, "\033Oy", 0 }, + { KEYC_KP1_3, "\033Ok", 0 }, + { KEYC_KP2_0, "\033Ot", 0 }, + { KEYC_KP2_1, "\033Ou", 0 }, + { KEYC_KP2_2, "\033Ov", 0 }, + { KEYC_KP3_0, "\033Oq", 0 }, + { KEYC_KP3_1, "\033Or", 0 }, + { KEYC_KP3_2, "\033Os", 0 }, + { KEYC_KP3_3, "\033OM", 0 }, + { KEYC_KP4_0, "\033Op", 0 }, + { KEYC_KP4_2, "\033On", 0 }, +}; + +/* Translate a key code from client into an output key sequence. */ +void +input_key(struct window_pane *wp, int key) +{ + struct input_key_ent *ike; + u_int i; + char ch; + size_t dlen; + int xterm_keys; + + log_debug2("writing key 0x%x", key); + + if (key != KEYC_NONE && KEYC_REMOVEESC(key) < KEYC_OFFSET) { + if (KEYC_ISESC(key)) + buffer_write8(wp->out, '\033'); + buffer_write8(wp->out, (uint8_t) KEYC_REMOVEESC(key)); + return; + } + + for (i = 0; i < nitems(input_keys); i++) { + ike = &input_keys[i]; + + if ((ike->flags & INPUTKEY_KEYPAD) && + !(wp->screen->mode & MODE_KKEYPAD)) + continue; + if ((ike->flags & INPUTKEY_CURSOR) && + !(wp->screen->mode & MODE_KCURSOR)) + continue; + + if (KEYC_ISESC(key) && KEYC_ADDESC(ike->key) == key) + break; + if (KEYC_ISSFT(key) && KEYC_ADDSFT(ike->key) == key) + break; + if (KEYC_ISCTL(key) && KEYC_ADDCTL(ike->key) == key) { + if (ike->flags & INPUTKEY_CTRL) + break; + } + if (ike->key == key) + break; + } + if (i == nitems(input_keys)) { + log_debug2("key 0x%x missing", key); + return; + } + dlen = strlen(ike->data); + + log_debug2("found key 0x%x: \"%s\"", key, ike->data); + + /* + * If in xterm keys mode, work out and append the modifier as an + * argument. + */ + xterm_keys = options_get_number(&wp->window->options, "xterm-keys"); + if (xterm_keys && ike->flags & INPUTKEY_XTERM) { + ch = '\0'; + if (KEYC_ISSFT(key) && KEYC_ISESC(key) && KEYC_ISCTL(key)) + ch = '8'; + else if (KEYC_ISESC(key) && KEYC_ISCTL(key)) + ch = '7'; + else if (KEYC_ISSFT(key) && KEYC_ISCTL(key)) + ch = '6'; + else if (KEYC_ISCTL(key)) + ch = '5'; + else if (KEYC_ISSFT(key) && KEYC_ISESC(key)) + ch = '4'; + else if (KEYC_ISESC(key)) + ch = '3'; + else if (KEYC_ISSFT(key)) + ch = '2'; + if (ch != '\0') { + buffer_write(wp->out, ike->data, dlen - 1); + buffer_write8(wp->out, ';'); + buffer_write8(wp->out, ch); + buffer_write8(wp->out, ike->data[dlen - 1]); + } else + buffer_write(wp->out, ike->data, dlen); + return; + } + + /* + * Not in xterm mode. Prefix a \033 for escape, and set bit 5 of the + * last byte for ctrl. + */ + if (KEYC_ISESC(key)) + buffer_write8(wp->out, '\033'); + if (KEYC_ISCTL(key) && ike->flags & INPUTKEY_CTRL) { + buffer_write(wp->out, ike->data, dlen - 1); + buffer_write8(wp->out, ike->data[dlen - 1] ^ 0x20); + return; + } + buffer_write(wp->out, ike->data, dlen); +} + +/* Handle input mouse. */ +void +input_mouse(struct window_pane *wp, u_char b, u_char x, u_char y) +{ + if (wp->screen->mode & MODE_MOUSE) { + buffer_write(wp->out, "\033[M", 3); + buffer_write8(wp->out, b + 32); + buffer_write8(wp->out, x + 33); + buffer_write8(wp->out, y + 33); + } +} diff --git a/input.c b/input.c new file mode 100644 index 00000000..9483bae1 --- /dev/null +++ b/input.c @@ -0,0 +1,1276 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#include "tmux.h" + +#define INPUT_C0CONTROL(ch) (ch <= 0x1f) +#define INPUT_INTERMEDIATE(ch) (ch == 0xa0 || (ch >= 0x20 && ch <= 0x2f)) +#define INPUT_PARAMETER(ch) (ch >= 0x30 && ch <= 0x3f) +#define INPUT_UPPERCASE(ch) (ch >= 0x40 && ch <= 0x5f) +#define INPUT_LOWERCASE(ch) (ch >= 0x60 && ch <= 0x7e) +#define INPUT_DELETE(ch) (ch == 0x7f) +#define INPUT_C1CONTROL(ch) (ch >= 0x80 && ch <= 0x9f) +#define INPUT_G1DISPLAYABLE(ch) (ch >= 0xa1 && ch <= 0xfe) +#define INPUT_SPECIAL(ch) (ch == 0xff) + +int input_get_argument(struct input_ctx *, u_int, uint16_t *, uint16_t); +int input_new_argument(struct input_ctx *); +int input_add_argument(struct input_ctx *, u_char); + +void input_start_string(struct input_ctx *, int); +void input_abort_string(struct input_ctx *); +int input_add_string(struct input_ctx *, u_char); +char *input_get_string(struct input_ctx *); + +void input_state(struct input_ctx *, void *); + +void input_state_first(u_char, struct input_ctx *); +void input_state_escape(u_char, struct input_ctx *); +void input_state_intermediate(u_char, struct input_ctx *); +void input_state_sequence_first(u_char, struct input_ctx *); +void input_state_sequence_next(u_char, struct input_ctx *); +void input_state_sequence_intermediate(u_char, struct input_ctx *); +void input_state_string_next(u_char, struct input_ctx *); +void input_state_string_escape(u_char, struct input_ctx *); +void input_state_utf8(u_char, struct input_ctx *); + +void input_handle_character(u_char, struct input_ctx *); +void input_handle_c0_control(u_char, struct input_ctx *); +void input_handle_c1_control(u_char, struct input_ctx *); +void input_handle_private_two(u_char, struct input_ctx *); +void input_handle_standard_two(u_char, struct input_ctx *); +void input_handle_sequence(u_char, struct input_ctx *); + +void input_handle_sequence_cuu(struct input_ctx *); +void input_handle_sequence_cud(struct input_ctx *); +void input_handle_sequence_cuf(struct input_ctx *); +void input_handle_sequence_cub(struct input_ctx *); +void input_handle_sequence_dch(struct input_ctx *); +void input_handle_sequence_dl(struct input_ctx *); +void input_handle_sequence_ich(struct input_ctx *); +void input_handle_sequence_il(struct input_ctx *); +void input_handle_sequence_vpa(struct input_ctx *); +void input_handle_sequence_hpa(struct input_ctx *); +void input_handle_sequence_cup(struct input_ctx *); +void input_handle_sequence_cup(struct input_ctx *); +void input_handle_sequence_ed(struct input_ctx *); +void input_handle_sequence_el(struct input_ctx *); +void input_handle_sequence_sm(struct input_ctx *); +void input_handle_sequence_rm(struct input_ctx *); +void input_handle_sequence_decstbm(struct input_ctx *); +void input_handle_sequence_sgr(struct input_ctx *); +void input_handle_sequence_dsr(struct input_ctx *); + +int input_sequence_cmp(const void *, const void *); + +struct input_sequence_entry { + u_char ch; + void (*fn)(struct input_ctx *); +}; +const struct input_sequence_entry input_sequence_table[] = { + { '@', input_handle_sequence_ich }, + { 'A', input_handle_sequence_cuu }, + { 'B', input_handle_sequence_cud }, + { 'C', input_handle_sequence_cuf }, + { 'D', input_handle_sequence_cub }, + { 'G', input_handle_sequence_hpa }, + { 'H', input_handle_sequence_cup }, + { 'J', input_handle_sequence_ed }, + { 'K', input_handle_sequence_el }, + { 'L', input_handle_sequence_il }, + { 'M', input_handle_sequence_dl }, + { 'P', input_handle_sequence_dch }, + { 'd', input_handle_sequence_vpa }, + { 'f', input_handle_sequence_cup }, + { 'h', input_handle_sequence_sm }, + { 'l', input_handle_sequence_rm }, + { 'm', input_handle_sequence_sgr }, + { 'n', input_handle_sequence_dsr }, + { 'r', input_handle_sequence_decstbm }, +}; + +int +input_sequence_cmp(const void *a, const void *b) +{ + int ai = ((const struct input_sequence_entry *) a)->ch; + int bi = ((const struct input_sequence_entry *) b)->ch; + + return (ai - bi); +} + +int +input_new_argument(struct input_ctx *ictx) +{ + struct input_arg *arg; + + ARRAY_EXPAND(&ictx->args, 1); + + arg = &ARRAY_LAST(&ictx->args); + arg->used = 0; + + return (0); +} + +int +input_add_argument(struct input_ctx *ictx, u_char ch) +{ + struct input_arg *arg; + + if (ARRAY_LENGTH(&ictx->args) == 0) + return (0); + + arg = &ARRAY_LAST(&ictx->args); + if (arg->used > (sizeof arg->data) - 1) + return (-1); + arg->data[arg->used++] = ch; + + return (0); +} + +int +input_get_argument(struct input_ctx *ictx, u_int i, uint16_t *n, uint16_t d) +{ + struct input_arg *arg; + const char *errstr; + + *n = d; + if (i >= ARRAY_LENGTH(&ictx->args)) + return (0); + + arg = &ARRAY_ITEM(&ictx->args, i); + if (*arg->data == '\0') + return (0); + + *n = strtonum(arg->data, 0, UINT16_MAX, &errstr); + if (errstr != NULL) + return (-1); + return (0); +} + +void +input_start_string(struct input_ctx *ictx, int type) +{ + ictx->string_type = type; + ictx->string_len = 0; +} + +void +input_abort_string(struct input_ctx *ictx) +{ + if (ictx->string_buf != NULL) + xfree(ictx->string_buf); + ictx->string_buf = NULL; +} + +int +input_add_string(struct input_ctx *ictx, u_char ch) +{ + ictx->string_buf = xrealloc(ictx->string_buf, 1, ictx->string_len + 1); + ictx->string_buf[ictx->string_len++] = ch; + + if (ictx->string_len >= MAXSTRINGLEN) { + input_abort_string(ictx); + return (1); + } + + return (0); +} + +char * +input_get_string(struct input_ctx *ictx) +{ + char *s; + + if (ictx->string_buf == NULL || input_add_string(ictx, '\0') != 0) + return (xstrdup("")); + + s = ictx->string_buf; + ictx->string_buf = NULL; + return (s); +} + +void +input_state(struct input_ctx *ictx, void *state) +{ + ictx->state = state; +} + +void +input_init(struct window_pane *wp) +{ + struct input_ctx *ictx = &wp->ictx; + + ARRAY_INIT(&ictx->args); + + ictx->string_len = 0; + ictx->string_buf = NULL; + + memcpy(&ictx->cell, &grid_default_cell, sizeof ictx->cell); + + memcpy(&ictx->saved_cell, &grid_default_cell, sizeof ictx->saved_cell); + ictx->saved_cx = 0; + ictx->saved_cy = 0; + + input_state(ictx, input_state_first); +} + +void +input_free(struct window_pane *wp) +{ + if (wp->ictx.string_buf != NULL) + xfree(wp->ictx.string_buf); + + ARRAY_FREE(&wp->ictx.args); +} + +void +input_parse(struct window_pane *wp) +{ + struct input_ctx *ictx = &wp->ictx; + u_char ch; + + if (BUFFER_USED(wp->in) == 0) + return; + + ictx->buf = BUFFER_OUT(wp->in); + ictx->len = BUFFER_USED(wp->in); + ictx->off = 0; + + ictx->wp = wp; + + log_debug2("entry; buffer=%zu", ictx->len); + + if (wp->mode == NULL) + screen_write_start(&ictx->ctx, wp, &wp->base); + else + screen_write_start(&ictx->ctx, NULL, &wp->base); + + if (ictx->off != ictx->len) + wp->window->flags |= WINDOW_ACTIVITY; + while (ictx->off < ictx->len) { + ch = ictx->buf[ictx->off++]; + ictx->state(ch, ictx); + } + + screen_write_stop(&ictx->ctx); + + buffer_remove(wp->in, ictx->len); +} + +void +input_state_first(u_char ch, struct input_ctx *ictx) +{ + ictx->intermediate = '\0'; + + if (INPUT_C0CONTROL(ch)) { + if (ch == 0x1b) + input_state(ictx, input_state_escape); + else + input_handle_c0_control(ch, ictx); + return; + } + +#if 0 + if (INPUT_C1CONTROL(ch)) { + ch -= 0x40; + if (ch == '[') + input_state(ictx, input_state_sequence_first); + else if (ch == ']') { + input_start_string(ictx, STRING_SYSTEM); + input_state(ictx, input_state_string_next); + } else if (ch == '_') { + input_start_string(ictx, STRING_APPLICATION); + input_state(ictx, input_state_string_next); + } else + input_handle_c1_control(ch, ictx); + return; + } +#endif + + if (INPUT_DELETE(ch)) + return; + + input_handle_character(ch, ictx); +} + +void +input_state_escape(u_char ch, struct input_ctx *ictx) +{ + /* Treat C1 control and G1 displayable as 7-bit equivalent. */ + if (INPUT_C1CONTROL(ch) || INPUT_G1DISPLAYABLE(ch)) + ch &= 0x7f; + + if (INPUT_C0CONTROL(ch)) { + input_handle_c0_control(ch, ictx); + return; + } + + if (INPUT_INTERMEDIATE(ch)) { + log_debug2(":: in1 %zu: %hhu (%c)", ictx->off, ch, ch); + ictx->intermediate = ch; + input_state(ictx, input_state_intermediate); + return; + } + + if (INPUT_PARAMETER(ch)) { + input_state(ictx, input_state_first); + input_handle_private_two(ch, ictx); + return; + } + + if (INPUT_UPPERCASE(ch)) { + if (ch == '[') + input_state(ictx, input_state_sequence_first); + else if (ch == ']') { + input_start_string(ictx, STRING_SYSTEM); + input_state(ictx, input_state_string_next); + } else if (ch == '_') { + input_start_string(ictx, STRING_APPLICATION); + input_state(ictx, input_state_string_next); + } else { + input_state(ictx, input_state_first); + input_handle_c1_control(ch, ictx); + } + return; + } + + if (INPUT_LOWERCASE(ch)) { + input_state(ictx, input_state_first); + input_handle_standard_two(ch, ictx); + return; + } + + input_state(ictx, input_state_first); +} + +void +input_state_intermediate(u_char ch, struct input_ctx *ictx) +{ + if (INPUT_INTERMEDIATE(ch)) { + /* Multiple intermediates currently ignored. */ + log_debug2(":: in2 %zu: %hhu (%c)", ictx->off, ch, ch); + return; + } + + if (INPUT_PARAMETER(ch)) { + input_state(ictx, input_state_first); + input_handle_private_two(ch, ictx); + return; + } + + if (INPUT_UPPERCASE(ch) || INPUT_LOWERCASE(ch)) { + input_state(ictx, input_state_first); + input_handle_standard_two(ch, ictx); + return; + } + + input_state(ictx, input_state_first); +} + +void +input_state_sequence_first(u_char ch, struct input_ctx *ictx) +{ + ictx->private = '\0'; + ARRAY_CLEAR(&ictx->args); + + input_state(ictx, input_state_sequence_next); + + if (INPUT_PARAMETER(ch)) { + input_new_argument(ictx); + if (ch >= 0x3c && ch <= 0x3f) { + /* Private control sequence. */ + ictx->private = ch; + return; + } + } + + /* Pass character on directly. */ + input_state_sequence_next(ch, ictx); +} + +void +input_state_sequence_next(u_char ch, struct input_ctx *ictx) +{ + if (INPUT_INTERMEDIATE(ch)) { + if (input_add_argument(ictx, '\0') != 0) + input_state(ictx, input_state_first); + else { + log_debug2(":: si1 %zu: %hhu (%c)", ictx->off, ch, ch); + input_state(ictx, input_state_sequence_intermediate); + } + return; + } + + if (INPUT_PARAMETER(ch)) { + if (ch == ';') { + if (input_add_argument(ictx, '\0') != 0) + input_state(ictx, input_state_first); + else + input_new_argument(ictx); + } else if (input_add_argument(ictx, ch) != 0) + input_state(ictx, input_state_first); + return; + } + + if (INPUT_UPPERCASE(ch) || INPUT_LOWERCASE(ch)) { + if (input_add_argument(ictx, '\0') != 0) + input_state(ictx, input_state_first); + else { + input_state(ictx, input_state_first); + input_handle_sequence(ch, ictx); + } + return; + } + + input_state(ictx, input_state_first); +} + +void +input_state_sequence_intermediate(u_char ch, struct input_ctx *ictx) +{ + if (INPUT_INTERMEDIATE(ch)) { + log_debug2(":: si2 %zu: %hhu (%c)", ictx->off, ch, ch); + return; + } + + if (INPUT_UPPERCASE(ch) || INPUT_LOWERCASE(ch)) { + input_state(ictx, input_state_first); + input_handle_sequence(ch, ictx); + return; + } + + input_state(ictx, input_state_first); +} + +void +input_state_string_next(u_char ch, struct input_ctx *ictx) +{ + if (ch == 0x1b) { + input_state(ictx, input_state_string_escape); + return; + } + if (ch == 0x07) { + input_state_string_escape(ch, ictx); + return; + } + + if (ch >= 0x20 && ch != 0x7f) { + if (input_add_string(ictx, ch) != 0) + input_state(ictx, input_state_first); + return; + } +} + +void +input_state_string_escape(u_char ch, struct input_ctx *ictx) +{ + char *s; + + if (ch == '\007' || ch == '\\') { + input_state(ictx, input_state_first); + switch (ictx->string_type) { + case STRING_SYSTEM: + if (ch != '\007') + return; + s = input_get_string(ictx); + if ((s[0] != '0' && s[0] != '2') || s[1] != ';') { + xfree(s); + return; + } + screen_set_title(ictx->ctx.s, s + 2); + server_status_window(ictx->wp->window); + xfree(s); + break; + case STRING_APPLICATION: + if (ch != '\\') + return; + s = input_get_string(ictx); + screen_set_title(ictx->ctx.s, s); + server_status_window(ictx->wp->window); + xfree(s); + break; + case STRING_NAME: + if (ch != '\\') + return; + xfree(ictx->wp->window->name); + ictx->wp->window->name = input_get_string(ictx); + server_status_window(ictx->wp->window); + break; + } + return; + } + + input_state(ictx, input_state_string_next); + input_state_string_next(ch, ictx); +} + +void +input_state_utf8(u_char ch, struct input_ctx *ictx) +{ + log_debug2("-- un %zu: %hhu (%c)", ictx->off, ch, ch); + + ictx->utf8_buf[ictx->utf8_off++] = ch; + if (--ictx->utf8_len != 0) + return; + input_state(ictx, input_state_first); + + ictx->cell.flags |= GRID_FLAG_UTF8; + screen_write_cell(&ictx->ctx, &ictx->cell, ictx->utf8_buf); + ictx->cell.flags &= ~GRID_FLAG_UTF8; +} + +void +input_handle_character(u_char ch, struct input_ctx *ictx) +{ + struct window_pane *wp = ictx->wp; + + if (ch > 0x7f && options_get_number(&wp->window->options, "utf8")) { + /* + * UTF-8 sequence. + * + * 11000010-11011111 C2-DF start of 2-byte sequence + * 11100000-11101111 E0-EF start of 3-byte sequence + * 11110000-11110100 F0-F4 start of 4-byte sequence + */ + memset(ictx->utf8_buf, 0xff, sizeof ictx->utf8_buf); + ictx->utf8_buf[0] = ch; + ictx->utf8_off = 1; + + if (ch >= 0xc2 && ch <= 0xdf) { + log_debug2("-- u2 %zu: %hhu (%c)", ictx->off, ch, ch); + input_state(ictx, input_state_utf8); + ictx->utf8_len = 1; + return; + } + if (ch >= 0xe0 && ch <= 0xef) { + log_debug2("-- u3 %zu: %hhu (%c)", ictx->off, ch, ch); + input_state(ictx, input_state_utf8); + ictx->utf8_len = 2; + return; + } + if (ch >= 0xf0 && ch <= 0xf4) { + log_debug2("-- u4 %zu: %hhu (%c)", ictx->off, ch, ch); + input_state(ictx, input_state_utf8); + ictx->utf8_len = 3; + return; + } + } + log_debug2("-- ch %zu: %hhu (%c)", ictx->off, ch, ch); + + ictx->cell.data = ch; + screen_write_cell(&ictx->ctx, &ictx->cell, ictx->utf8_buf); +} + +void +input_handle_c0_control(u_char ch, struct input_ctx *ictx) +{ + struct screen *s = ictx->ctx.s; + + log_debug2("-- c0 %zu: %hhu", ictx->off, ch); + + switch (ch) { + case '\0': /* NUL */ + break; + case '\n': /* LF */ + screen_write_linefeed(&ictx->ctx); + break; + case '\r': /* CR */ + screen_write_carriagereturn(&ictx->ctx); + break; + case '\007': /* BELL */ + ictx->wp->window->flags |= WINDOW_BELL; + break; + case '\010': /* BS */ + screen_write_cursorleft(&ictx->ctx, 1); + break; + case '\011': /* TAB */ + s->cx = ((s->cx / 8) * 8) + 8; + if (s->cx > screen_size_x(s) - 1) { + s->cx = 0; + screen_write_cursordown(&ictx->ctx, 1); + } + screen_write_cursormove(&ictx->ctx, s->cx, s->cy); + break; + case '\016': /* SO */ + ictx->cell.attr |= GRID_ATTR_CHARSET; + break; + case '\017': /* SI */ + ictx->cell.attr &= ~GRID_ATTR_CHARSET; + break; + default: + log_debug("unknown c0: %hhu", ch); + break; + } +} + +void +input_handle_c1_control(u_char ch, struct input_ctx *ictx) +{ + log_debug2("-- c1 %zu: %hhu (%c)", ictx->off, ch, ch); + + switch (ch) { + case 'E': /* NEL */ + screen_write_carriagereturn(&ictx->ctx); + screen_write_linefeed(&ictx->ctx); + break; + case 'M': /* RI */ + screen_write_reverseindex(&ictx->ctx); + break; + default: + log_debug("unknown c1: %hhu", ch); + break; + } +} + +void +input_handle_private_two(u_char ch, struct input_ctx *ictx) +{ + struct screen *s = ictx->ctx.s; + + log_debug2( + "-- p2 %zu: %hhu (%c) %hhu", ictx->off, ch, ch, ictx->intermediate); + + switch (ch) { + case '0': /* Dscs (graphics) */ + /* + * Not really supported, but fake it up enough for those that + * use it to switch character sets (by redefining G0 to + * graphics set, rather than switching to G1). + */ + switch (ictx->intermediate) { + case '(': /* G0 */ + ictx->cell.attr |= GRID_ATTR_CHARSET; + break; + } + break; + case '=': /* DECKPAM */ + screen_write_kkeypadmode(&ictx->ctx, 1); + log_debug("kkeypad on (application mode)"); + break; + case '>': /* DECKPNM */ + screen_write_kkeypadmode(&ictx->ctx, 0); + log_debug("kkeypad off (number mode)"); + break; + case '7': /* DECSC */ + memcpy(&ictx->saved_cell, &ictx->cell, sizeof ictx->saved_cell); + ictx->saved_cx = s->cx; + ictx->saved_cy = s->cy; + break; + case '8': /* DECRC */ + memcpy(&ictx->cell, &ictx->saved_cell, sizeof ictx->cell); + screen_write_cursormove( + &ictx->ctx, ictx->saved_cx, ictx->saved_cy); + break; + default: + log_debug("unknown p2: %hhu", ch); + break; + } +} + +void +input_handle_standard_two(u_char ch, struct input_ctx *ictx) +{ + log_debug2( + "-- s2 %zu: %hhu (%c) %hhu", ictx->off, ch, ch, ictx->intermediate); + + switch (ch) { + case 'B': /* Dscs (ASCII) */ + /* + * Not really supported, but fake it up enough for those that + * use it to switch character sets (by redefining G0 to + * graphics set, rather than switching to G1). + */ + switch (ictx->intermediate) { + case '(': /* G0 */ + ictx->cell.attr &= ~GRID_ATTR_CHARSET; + break; + } + break; + case 'c': /* RIS */ + memcpy(&ictx->cell, &grid_default_cell, sizeof ictx->cell); + + memcpy(&ictx->saved_cell, &ictx->cell, sizeof ictx->saved_cell); + ictx->saved_cx = 0; + ictx->saved_cy = 0; + + screen_write_scrollregion( + &ictx->ctx, 0, screen_size_y(ictx->ctx.s) - 1); + + screen_write_insertmode(&ictx->ctx, 0); + screen_write_kcursormode(&ictx->ctx, 0); + screen_write_kkeypadmode(&ictx->ctx, 0); + screen_write_mousemode(&ictx->ctx, 0); + + screen_write_clearscreen(&ictx->ctx); + screen_write_cursormove(&ictx->ctx, 0, 0); + break; + case 'k': + input_start_string(ictx, STRING_NAME); + input_state(ictx, input_state_string_next); + break; + default: + log_debug("unknown s2: %hhu", ch); + break; + } +} + +void +input_handle_sequence(u_char ch, struct input_ctx *ictx) +{ + struct input_sequence_entry *entry, find; + struct screen *s = ictx->ctx.s; + u_int i; + struct input_arg *iarg; + + log_debug2("-- sq %zu: %hhu (%c): %u [sx=%u, sy=%u, cx=%u, cy=%u, " + "ru=%u, rl=%u]", ictx->off, ch, ch, ARRAY_LENGTH(&ictx->args), + screen_size_x(s), screen_size_y(s), s->cx, s->cy, s->rupper, + s->rlower); + for (i = 0; i < ARRAY_LENGTH(&ictx->args); i++) { + iarg = &ARRAY_ITEM(&ictx->args, i); + if (*iarg->data != '\0') + log_debug2(" ++ %u: %s", i, iarg->data); + } + + find.ch = ch; + entry = bsearch(&find, + input_sequence_table, nitems(input_sequence_table), + sizeof input_sequence_table[0], input_sequence_cmp); + if (entry != NULL) + entry->fn(ictx); + else + log_debug("unknown sq: %c (%hhu %hhu)", ch, ch, ictx->private); +} + +void +input_handle_sequence_cuu(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_cursorup(&ictx->ctx, n); +} + +void +input_handle_sequence_cud(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_cursordown(&ictx->ctx, n); +} + +void +input_handle_sequence_cuf(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_cursorright(&ictx->ctx, n); +} + +void +input_handle_sequence_cub(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_cursorleft(&ictx->ctx, n); +} + +void +input_handle_sequence_dch(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_deletecharacter(&ictx->ctx, n); +} + +void +input_handle_sequence_dl(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_deleteline(&ictx->ctx, n); +} + +void +input_handle_sequence_ich(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_insertcharacter(&ictx->ctx, n); +} + +void +input_handle_sequence_il(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_insertline(&ictx->ctx, n); +} + +void +input_handle_sequence_vpa(struct input_ctx *ictx) +{ + struct screen *s = ictx->ctx.s; + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_cursormove(&ictx->ctx, s->cx, n - 1); +} + +void +input_handle_sequence_hpa(struct input_ctx *ictx) +{ + struct screen *s = ictx->ctx.s; + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (n == 0) + n = 1; + + screen_write_cursormove(&ictx->ctx, n - 1, s->cy); +} + +void +input_handle_sequence_cup(struct input_ctx *ictx) +{ + uint16_t n, m; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 2) + return; + if (input_get_argument(ictx, 0, &n, 1) != 0) + return; + if (input_get_argument(ictx, 1, &m, 1) != 0) + return; + if (n == 0) + n = 1; + if (m == 0) + m = 1; + + screen_write_cursormove(&ictx->ctx, m - 1, n - 1); +} + +void +input_handle_sequence_ed(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 0) != 0) + return; + if (n > 2) + return; + + switch (n) { + case 0: + screen_write_clearendofscreen(&ictx->ctx); + break; + case 1: + screen_write_clearstartofscreen(&ictx->ctx); + break; + case 2: + screen_write_clearscreen(&ictx->ctx); + break; + } +} + +void +input_handle_sequence_el(struct input_ctx *ictx) +{ + uint16_t n; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 0) != 0) + return; + if (n > 2) + return; + + switch (n) { + case 0: + screen_write_clearendofline(&ictx->ctx); + break; + case 1: + screen_write_clearstartofline(&ictx->ctx); + break; + case 2: + screen_write_clearline(&ictx->ctx); + break; + } +} + +void +input_handle_sequence_sm(struct input_ctx *ictx) +{ + uint16_t n; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 0) != 0) + return; + + if (ictx->private == '?') { + switch (n) { + case 1: /* GATM */ + screen_write_kcursormode(&ictx->ctx, 1); + log_debug("kcursor on"); + break; + case 25: /* TCEM */ + screen_write_cursormode(&ictx->ctx, 1); + log_debug("cursor on"); + break; + case 1000: + screen_write_mousemode(&ictx->ctx, 1); + log_debug("mouse on"); + break; + default: + log_debug("unknown SM [%hhu]: %u", ictx->private, n); + break; + } + } else { + switch (n) { + case 4: /* IRM */ + screen_write_insertmode(&ictx->ctx, 1); + log_debug("insert on"); + break; + case 34: + /* Cursor high visibility not supported. */ + break; + default: + log_debug("unknown SM [%hhu]: %u", ictx->private, n); + break; + } + } +} + +void +input_handle_sequence_rm(struct input_ctx *ictx) +{ + uint16_t n; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 0) != 0) + return; + + if (ictx->private == '?') { + switch (n) { + case 1: /* GATM */ + screen_write_kcursormode(&ictx->ctx, 0); + log_debug("kcursor off"); + break; + case 25: /* TCEM */ + screen_write_cursormode(&ictx->ctx, 0); + log_debug("cursor off"); + break; + case 1000: + screen_write_mousemode(&ictx->ctx, 0); + log_debug("mouse off"); + break; + default: + log_debug("unknown RM [%hhu]: %u", ictx->private, n); + break; + } + } else if (ictx->private == '\0') { + switch (n) { + case 4: /* IRM */ + screen_write_insertmode(&ictx->ctx, 0); + log_debug("insert off"); + break; + case 34: + /* Cursor high visibility not supported. */ + break; + default: + log_debug("unknown RM [%hhu]: %u", ictx->private, n); + break; + } + } +} + +void +input_handle_sequence_dsr(struct input_ctx *ictx) +{ + struct screen *s = ictx->ctx.s; + uint16_t n; + char reply[32]; + + if (ARRAY_LENGTH(&ictx->args) > 1) + return; + if (input_get_argument(ictx, 0, &n, 0) != 0) + return; + + if (ictx->private == '\0') { + switch (n) { + case 6: /* cursor position */ + xsnprintf(reply, sizeof reply, + "\033[%u;%uR", s->cy + 1, s->cx + 1); + log_debug("cursor request, reply: %s", reply); + buffer_write(ictx->wp->out, reply, strlen(reply)); + break; + } + } + +} + +void +input_handle_sequence_decstbm(struct input_ctx *ictx) +{ + struct screen *s = ictx->ctx.s; + uint16_t n, m; + + if (ictx->private != '\0') + return; + + if (ARRAY_LENGTH(&ictx->args) > 2) + return; + if (input_get_argument(ictx, 0, &n, 0) != 0) + return; + if (input_get_argument(ictx, 1, &m, 0) != 0) + return; + if (n == 0) + n = 1; + if (m == 0) + m = screen_size_y(s); + + screen_write_scrollregion(&ictx->ctx, n - 1, m - 1); +} + +void +input_handle_sequence_sgr(struct input_ctx *ictx) +{ + struct grid_cell *gc = &ictx->cell; + u_int i; + uint16_t m, o; + u_char attr; + + if (ARRAY_LENGTH(&ictx->args) == 0) { + attr = gc->attr; + memcpy(gc, &grid_default_cell, sizeof *gc); + gc->attr |= (attr & GRID_ATTR_CHARSET); + return; + } + + for (i = 0; i < ARRAY_LENGTH(&ictx->args); i++) { + if (input_get_argument(ictx, i, &m, 0) != 0) + return; + + if (m == 38 || m == 48) { + i++; + if (input_get_argument(ictx, i, &o, 0) != 0) + return; + if (o != 5) + continue; + + i++; + if (input_get_argument(ictx, i, &o, 0) != 0) + return; + if (m == 38) { + gc->flags |= GRID_FLAG_FG256; + gc->fg = o; + } else if (m == 48) { + gc->flags |= GRID_FLAG_BG256; + gc->bg = o; + } + continue; + } + + switch (m) { + case 0: + case 10: + attr = gc->attr; + memcpy(gc, &grid_default_cell, sizeof *gc); + gc->attr |= (attr & GRID_ATTR_CHARSET); + break; + case 1: + gc->attr |= GRID_ATTR_BRIGHT; + break; + case 2: + gc->attr |= GRID_ATTR_DIM; + break; + case 3: + gc->attr |= GRID_ATTR_ITALICS; + break; + case 4: + gc->attr |= GRID_ATTR_UNDERSCORE; + break; + case 5: + gc->attr |= GRID_ATTR_BLINK; + break; + case 7: + gc->attr |= GRID_ATTR_REVERSE; + break; + case 8: + gc->attr |= GRID_ATTR_HIDDEN; + break; + case 22: + gc->attr &= ~(GRID_ATTR_BRIGHT|GRID_ATTR_DIM); + break; + case 23: + gc->attr &= ~GRID_ATTR_ITALICS; + break; + case 24: + gc->attr &= ~GRID_ATTR_UNDERSCORE; + break; + case 25: + gc->attr &= ~GRID_ATTR_BLINK; + break; + case 27: + gc->attr &= ~GRID_ATTR_REVERSE; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + gc->flags &= ~GRID_FLAG_FG256; + gc->fg = m - 30; + break; + case 39: + gc->flags &= ~GRID_FLAG_FG256; + gc->fg = 8; + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + gc->flags &= ~GRID_FLAG_BG256; + gc->bg = m - 40; + break; + case 49: + gc->flags &= ~GRID_FLAG_BG256; + gc->bg = 8; + break; + } + } +} diff --git a/key-bindings.c b/key-bindings.c new file mode 100644 index 00000000..fc232928 --- /dev/null +++ b/key-bindings.c @@ -0,0 +1,237 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#include "tmux.h" + +SPLAY_GENERATE(key_bindings, key_binding, entry, key_bindings_cmp); + +struct key_bindings key_bindings; + +int +key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) +{ + return (bd1->key - bd2->key); +} + +struct key_binding * +key_bindings_lookup(int key) +{ + struct key_binding bd; + + bd.key = key; + return (SPLAY_FIND(key_bindings, &key_bindings, &bd)); +} + +void +key_bindings_add(int key, int can_repeat, struct cmd_list *cmdlist) +{ + struct key_binding *bd; + + if ((bd = key_bindings_lookup(key)) == NULL) { + bd = xmalloc(sizeof *bd); + bd->key = key; + SPLAY_INSERT(key_bindings, &key_bindings, bd); + } else + cmd_list_free(bd->cmdlist); + bd->can_repeat = can_repeat; + bd->cmdlist = cmdlist; +} + +void +key_bindings_remove(int key) +{ + struct key_binding *bd; + + if ((bd = key_bindings_lookup(key)) == NULL) + return; + SPLAY_REMOVE(key_bindings, &key_bindings, bd); + + cmd_list_free(bd->cmdlist); + xfree(bd); +} + +void +key_bindings_init(void) +{ + static const struct { + int key; + int can_repeat; + const struct cmd_entry *entry; + } table[] = { + { ' ', 0, &cmd_next_layout_entry }, + { '!', 0, &cmd_break_pane_entry }, + { '"', 0, &cmd_split_window_entry }, + { '#', 0, &cmd_list_buffers_entry }, + { '&', 0, &cmd_confirm_before_entry }, + { ',', 0, &cmd_command_prompt_entry }, + { '-', 0, &cmd_delete_buffer_entry }, + { '.', 0, &cmd_command_prompt_entry }, + { '0', 0, &cmd_select_window_entry }, + { '1', 0, &cmd_select_window_entry }, + { '2', 0, &cmd_select_window_entry }, + { '3', 0, &cmd_select_window_entry }, + { '4', 0, &cmd_select_window_entry }, + { '5', 0, &cmd_select_window_entry }, + { '6', 0, &cmd_select_window_entry }, + { '7', 0, &cmd_select_window_entry }, + { '8', 0, &cmd_select_window_entry }, + { '9', 0, &cmd_select_window_entry }, + { ':', 0, &cmd_command_prompt_entry }, + { '=', 0, &cmd_scroll_mode_entry }, + { '?', 0, &cmd_list_keys_entry }, + { '[', 0, &cmd_copy_mode_entry }, + { '\'', 0, &cmd_select_prompt_entry }, + { '\032', /* C-z */ 0, &cmd_suspend_client_entry }, + { ']', 0, &cmd_paste_buffer_entry }, + { 'c', 0, &cmd_new_window_entry }, + { 'd', 0, &cmd_detach_client_entry }, + { 'f', 0, &cmd_command_prompt_entry }, + { 'l', 0, &cmd_last_window_entry }, + { 'n', 0, &cmd_next_window_entry }, + { 'o', 0, &cmd_down_pane_entry }, + { 'p', 0, &cmd_previous_window_entry }, + { 'r', 0, &cmd_refresh_client_entry }, + { 's', 0, &cmd_choose_session_entry }, + { 't', 0, &cmd_clock_mode_entry }, + { 'w', 0, &cmd_choose_window_entry }, + { 'x', 0, &cmd_confirm_before_entry }, + { '{', 0, &cmd_swap_pane_entry }, + { '}', 0, &cmd_swap_pane_entry }, + { '\002', 0, &cmd_send_prefix_entry }, + { KEYC_ADDESC('0'), 0, &cmd_select_layout_entry }, + { KEYC_ADDESC('1'), 0, &cmd_select_layout_entry }, + { KEYC_ADDESC('2'), 0, &cmd_select_layout_entry }, + { KEYC_ADDESC('9'), 0, &cmd_select_layout_entry }, + { KEYC_ADDCTL(KEYC_DOWN), 1, &cmd_resize_pane_entry }, + { KEYC_PPAGE, 0, &cmd_scroll_mode_entry }, + { KEYC_ADDESC('n'), 0, &cmd_next_window_entry }, + { KEYC_ADDESC('p'), 0, &cmd_previous_window_entry }, + { KEYC_UP, 1, &cmd_up_pane_entry }, + { KEYC_DOWN, 1, &cmd_down_pane_entry }, + { KEYC_ADDESC(KEYC_UP), 1, &cmd_resize_pane_entry }, + { KEYC_ADDESC(KEYC_DOWN), 1, &cmd_resize_pane_entry }, + { KEYC_ADDCTL(KEYC_UP), 1, &cmd_resize_pane_entry }, + { KEYC_ADDCTL(KEYC_DOWN), 1, &cmd_resize_pane_entry }, + { KEYC_ADDESC('o'), 0, &cmd_rotate_window_entry }, + { '\017', 0, &cmd_rotate_window_entry }, + }; + u_int i; + struct cmd *cmd; + struct cmd_list *cmdlist; + + SPLAY_INIT(&key_bindings); + + for (i = 0; i < nitems(table); i++) { + cmdlist = xmalloc(sizeof *cmdlist); + TAILQ_INIT(cmdlist); + + cmd = xmalloc(sizeof *cmd); + cmd->entry = table[i].entry; + cmd->data = NULL; + if (cmd->entry->init != NULL) + cmd->entry->init(cmd, table[i].key); + TAILQ_INSERT_HEAD(cmdlist, cmd, qentry); + + key_bindings_add(table[i].key, table[i].can_repeat, cmdlist); + } +} + +void +key_bindings_free(void) +{ + struct key_binding *bd; + + while (!SPLAY_EMPTY(&key_bindings)) { + bd = SPLAY_ROOT(&key_bindings); + SPLAY_REMOVE(key_bindings, &key_bindings, bd); + cmd_list_free(bd->cmdlist); + xfree(bd); + } +} + +void printflike2 +key_bindings_error(struct cmd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + *msg = toupper((u_char) *msg); + status_message_set(ctx->curclient, msg); + xfree(msg); +} + +void printflike2 +key_bindings_print(struct cmd_ctx *ctx, const char *fmt, ...) +{ + struct winlink *wl = ctx->cursession->curw; + va_list ap; + + if (wl->window->active->mode != &window_more_mode) + window_pane_reset_mode(wl->window->active); + window_pane_set_mode(wl->window->active, &window_more_mode); + + va_start(ap, fmt); + window_more_vadd(wl->window->active, fmt, ap); + va_end(ap); +} + +void printflike2 +key_bindings_info(struct cmd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + char *msg; + + if (be_quiet) + return; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + *msg = toupper((u_char) *msg); + status_message_set(ctx->curclient, msg); + xfree(msg); +} + +void +key_bindings_dispatch(struct key_binding *bd, struct client *c) +{ + struct cmd_ctx ctx; + + ctx.msgdata = NULL; + ctx.cursession = c->session; + ctx.curclient = c; + + ctx.error = key_bindings_error; + ctx.print = key_bindings_print; + ctx.info = key_bindings_info; + + ctx.cmdclient = NULL; + + cmd_list_exec(bd->cmdlist, &ctx); +} diff --git a/key-string.c b/key-string.c new file mode 100644 index 00000000..b2f1fad2 --- /dev/null +++ b/key-string.c @@ -0,0 +1,198 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +int key_string_search_table(const char *); + +struct { + const char *string; + int key; +} key_string_table[] = { + /* Function keys. */ + { "F1", KEYC_F1 }, + { "F2", KEYC_F2 }, + { "F3", KEYC_F3 }, + { "F4", KEYC_F4 }, + { "F5", KEYC_F5 }, + { "F6", KEYC_F6 }, + { "F7", KEYC_F7 }, + { "F8", KEYC_F8 }, + { "F9", KEYC_F9 }, + { "F10", KEYC_F10 }, + { "F11", KEYC_F11 }, + { "F12", KEYC_F12 }, + { "F13", KEYC_F13 }, + { "F14", KEYC_F14 }, + { "F15", KEYC_F15 }, + { "F16", KEYC_F16 }, + { "F17", KEYC_F17 }, + { "F18", KEYC_F18 }, + { "F19", KEYC_F19 }, + { "F20", KEYC_F20 }, + { "IC", KEYC_IC }, + { "DC", KEYC_DC }, + { "Home", KEYC_HOME }, + { "End", KEYC_END }, + { "NPage", KEYC_NPAGE }, + { "PPage", KEYC_PPAGE }, + { "Tab", '\011' }, + { "BTab", KEYC_BTAB }, + + /* Arrow keys. */ + { "Up", KEYC_UP }, + { "Down", KEYC_DOWN }, + { "Left", KEYC_LEFT }, + { "Right", KEYC_RIGHT }, + + /* Numeric keypad. */ + { "KP/", KEYC_KP0_1 }, + { "KP*", KEYC_KP0_2 }, + { "KP-", KEYC_KP0_3 }, + { "KP7", KEYC_KP1_0 }, + { "KP8", KEYC_KP1_1 }, + { "KP9", KEYC_KP1_2 }, + { "KP+", KEYC_KP1_3 }, + { "KP4", KEYC_KP2_0 }, + { "KP5", KEYC_KP2_1 }, + { "KP6", KEYC_KP2_2 }, + { "KP1", KEYC_KP3_0 }, + { "KP2", KEYC_KP3_1 }, + { "KP3", KEYC_KP3_2 }, + { "KPEnter", KEYC_KP3_3 }, + { "KP0", KEYC_KP4_0 }, + { "KP.", KEYC_KP4_2 }, +}; + +int +key_string_search_table(const char *string) +{ + u_int i; + + for (i = 0; i < nitems(key_string_table); i++) { + if (strcasecmp(string, key_string_table[i].string) == 0) + return (key_string_table[i].key); + } + return (KEYC_NONE); +} + +int +key_string_lookup_string(const char *string) +{ + int key; + const u_char *ptr; + + if (string[0] == '\0') + return (KEYC_NONE); + if (string[1] == '\0') + return (string[0]); + + ptr = NULL; + if (string[0] == 'C' && string[1] == '-') + ptr = string + 2; + else if (string[0] == '^') + ptr = string + 1; + if (ptr != NULL) { + if (ptr[0] == '\0') + return (KEYC_NONE); + if (ptr[1] == '\0') { + if (ptr[0] == 32) + return (0); + if (ptr[0] >= 64 && ptr[0] <= 95) + return (ptr[0] - 64); + if (ptr[0] >= 97 && ptr[0] <= 122) + return (ptr[0] - 96); + return (KEYC_NONE); + } + key = key_string_search_table(ptr); + if (key != KEYC_NONE) + return (KEYC_ADDCTL(key)); + return (KEYC_NONE); + } + + if (string[0] == 'M' && string[1] == '-') { + ptr = string + 2; + if (ptr[0] == '\0') + return (KEYC_NONE); + if (ptr[1] == '\0') { + if (ptr[0] < 32 || ptr[0] > 127) + return (KEYC_NONE); + return (KEYC_ADDESC(ptr[0])); + } + key = key_string_lookup_string(ptr); + if (key != KEYC_NONE) + return (KEYC_ADDESC(key)); + return (KEYC_NONE); + } + + return (key_string_search_table(string)); +} + +const char * +key_string_lookup_key(int key) +{ + static char tmp[24], tmp2[24]; + const char *s; + u_int i; + + if (key == 127) + return (NULL); + + if (KEYC_ISESC(key)) { + if ((s = key_string_lookup_key(KEYC_REMOVEESC(key))) == NULL) + return (NULL); + xsnprintf(tmp2, sizeof tmp2, "M-%s", s); + return (tmp2); + } + if (KEYC_ISCTL(key)) { + if ((s = key_string_lookup_key(KEYC_REMOVECTL(key))) == NULL) + return (NULL); + xsnprintf(tmp2, sizeof tmp2, "C-%s", s); + return (tmp2); + } + if (KEYC_ISSFT(key)) { + if ((s = key_string_lookup_key(KEYC_REMOVESFT(key))) == NULL) + return (NULL); + xsnprintf(tmp2, sizeof tmp2, "S-%s", s); + return (tmp2); + } + + if (key >= 32 && key <= 255) { + tmp[0] = key; + tmp[1] = '\0'; + return (tmp); + } + + if (key >= 0 && key <= 32) { + if (key == 0 || key > 26) + xsnprintf(tmp, sizeof tmp, "C-%c", 64 + key); + else + xsnprintf(tmp, sizeof tmp, "C-%c", 96 + key); + return (tmp); + } + + for (i = 0; i < nitems(key_string_table); i++) { + if (key == key_string_table[i].key) + return (key_string_table[i].string); + } + return (NULL); +} diff --git a/layout-manual.c b/layout-manual.c new file mode 100644 index 00000000..eee21d41 --- /dev/null +++ b/layout-manual.c @@ -0,0 +1,183 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +void layout_manual_v_update_offsets(struct window *); + +void +layout_manual_v_refresh(struct window *w, unused int active_only) +{ + struct window_pane *wp; + u_int npanes, canfit, total; + int left; + + if (active_only) + return; + + if (TAILQ_EMPTY(&w->panes)) + return; + + /* Clear hidden flags. */ + TAILQ_FOREACH(wp, &w->panes, entry) + wp->flags &= ~PANE_HIDDEN; + + /* Check the new size. */ + npanes = window_count_panes(w); + if (w->sy <= PANE_MINIMUM * npanes) { + /* How many can we fit? */ + canfit = w->sy / PANE_MINIMUM; + if (canfit == 0) { + /* None. Just use this size for the first. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) + wp->sy = w->sy; + else + wp->flags |= PANE_HIDDEN; + } + } else { + /* >=1, set minimum for them all. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (canfit-- > 0) + wp->sy = PANE_MINIMUM - 1; + else + wp->flags |= PANE_HIDDEN; + } + /* And increase the first by the rest. */ + TAILQ_FIRST(&w->panes)->sy += 1 + w->sy % PANE_MINIMUM; + } + } else { + /* In theory they will all fit. Find the current total. */ + total = 0; + TAILQ_FOREACH(wp, &w->panes, entry) + total += wp->sy; + total += npanes - 1; + + /* Growing or shrinking? */ + left = w->sy - total; + if (left > 0) { + /* Growing. Expand evenly. */ + while (left > 0) { + TAILQ_FOREACH(wp, &w->panes, entry) { + wp->sy++; + if (--left == 0) + break; + } + } + } else { + /* Shrinking. Reduce evenly down to minimum. */ + while (left < 0) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->sy <= PANE_MINIMUM - 1) + continue; + wp->sy--; + if (++left == 0) + break; + } + } + } + } + + /* Now do the resize. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + wp->sy--; + window_pane_resize(wp, w->sx, wp->sy + 1); + } + + /* Fill in the offsets. */ + layout_manual_v_update_offsets(w); + + /* Switch the active window if necessary. */ + window_set_active_pane(w, w->active); +} + +void +layout_manual_v_resize(struct window_pane *wp, int adjust) +{ + struct window *w = wp->window; + struct window_pane *wq; + + if (adjust > 0) { + /* + * If this is not the last pane, keep trying to increase size + * and remove it from the next panes. If it is the last, do + * so on the previous pane. + */ + if (TAILQ_NEXT(wp, entry) == NULL) { + if (wp == TAILQ_FIRST(&w->panes)) { + /* Only one pane. */ + return; + } + wp = TAILQ_PREV(wp, window_panes, entry); + } + while (adjust-- > 0) { + wq = wp; + while ((wq = TAILQ_NEXT(wq, entry)) != NULL) { + if (wq->sy <= PANE_MINIMUM) + continue; + window_pane_resize(wq, wq->sx, wq->sy - 1); + break; + } + if (wq == NULL) + break; + window_pane_resize(wp, wp->sx, wp->sy + 1); + } + } else { + adjust = -adjust; + /* + * If this is not the last pane, keep trying to reduce size + * and add to the following pane. If it is the last, do so on + * the previous pane. + */ + wq = TAILQ_NEXT(wp, entry); + if (wq == NULL) { + if (wp == TAILQ_FIRST(&w->panes)) { + /* Only one pane. */ + return; + } + wq = wp; + wp = TAILQ_PREV(wq, window_panes, entry); + } + while (adjust-- > 0) { + if (wp->sy <= PANE_MINIMUM) + break; + window_pane_resize(wq, wq->sx, wq->sy + 1); + window_pane_resize(wp, wp->sx, wp->sy - 1); + } + } + + layout_manual_v_update_offsets(w); +} + +void +layout_manual_v_update_offsets(struct window *w) +{ + struct window_pane *wp; + u_int yoff; + + yoff = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_HIDDEN) + continue; + wp->xoff = 0; + wp->yoff = yoff; + yoff += wp->sy + 1; + } +} diff --git a/layout.c b/layout.c new file mode 100644 index 00000000..1ae946e5 --- /dev/null +++ b/layout.c @@ -0,0 +1,373 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Each layout has two functions, _refresh to relayout the panes and _resize to + * resize a single pane. + * + * Second argument (int) to _refresh is 1 if the only change has been that the + * active pane has changed. If 0 then panes, active pane or both may have + * changed. + */ + +void layout_active_only_refresh(struct window *, int); +void layout_even_h_refresh(struct window *, int); +void layout_even_v_refresh(struct window *, int); +void layout_main_h_refresh(struct window *, int); +void layout_main_v_refresh(struct window *, int); + +const struct { + const char *name; + void (*refresh)(struct window *, int); + void (*resize)(struct window_pane *, int); +} layouts[] = { + { "manual-vertical", layout_manual_v_refresh, layout_manual_v_resize }, + { "active-only", layout_active_only_refresh, NULL }, + { "even-horizontal", layout_even_h_refresh, NULL }, + { "even-vertical", layout_even_v_refresh, NULL }, + { "main-horizontal", layout_main_h_refresh, NULL }, + { "main-vertical", layout_main_v_refresh, NULL }, +}; + +const char * +layout_name(struct window *w) +{ + return (layouts[w->layout].name); +} + +int +layout_lookup(const char *name) +{ + u_int i; + int matched = -1; + + for (i = 0; i < nitems(layouts); i++) { + if (strncmp(layouts[i].name, name, strlen(name)) == 0) { + if (matched != -1) /* ambiguous */ + return (-1); + matched = i; + } + } + + return (matched); +} + +int +layout_select(struct window *w, u_int layout) +{ + if (layout > nitems(layouts) - 1 || layout == w->layout) + return (-1); + w->layout = layout; + + layout_refresh(w, 0); + return (0); +} + +void +layout_next(struct window *w) +{ + w->layout++; + if (w->layout > nitems(layouts) - 1) + w->layout = 0; + layout_refresh(w, 0); +} + +void +layout_previous(struct window *w) +{ + if (w->layout == 0) + w->layout = nitems(layouts) - 1; + else + w->layout--; + layout_refresh(w, 0); +} + +void +layout_refresh(struct window *w, int active_only) +{ + layouts[w->layout].refresh(w, active_only); + server_redraw_window(w); +} + +int +layout_resize(struct window_pane *wp, int adjust) +{ + struct window *w = wp->window; + + if (layouts[w->layout].resize == NULL) + return (-1); + layouts[w->layout].resize(wp, adjust); + return (0); +} + +void +layout_active_only_refresh(struct window *w, unused int active_only) +{ + struct window_pane *wp; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == w->active) { + wp->flags &= ~PANE_HIDDEN; + wp->xoff = wp->yoff = 0; + window_pane_resize(wp, w->sx, w->sy); + } else + wp->flags |= PANE_HIDDEN; + } +} + +void +layout_even_h_refresh(struct window *w, int active_only) +{ + struct window_pane *wp; + u_int i, n, width, xoff; + + if (active_only) + return; + + /* Get number of panes. */ + n = window_count_panes(w); + if (n == 0) + return; + + /* How many can we fit? */ + if (w->sx / n < PANE_MINIMUM) { + width = PANE_MINIMUM; + n = w->sx / PANE_MINIMUM; + } else + width = w->sx / n; + + /* Fit the panes. */ + i = xoff = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (i > n) { + wp->flags |= PANE_HIDDEN; + continue; + } + wp->flags &= ~PANE_HIDDEN; + + wp->xoff = xoff; + wp->yoff = 0; + if (i != n - 1) + window_pane_resize(wp, width - 1, w->sy); + else + window_pane_resize(wp, width, w->sy); + + i++; + xoff += width; + } + + /* Any space left? */ + while (xoff++ < w->sx) { + wp = TAILQ_LAST(&w->panes, window_panes); + window_pane_resize(wp, wp->sx + 1, wp->sy); + } +} + +void +layout_even_v_refresh(struct window *w, int active_only) +{ + struct window_pane *wp; + u_int i, n, height, yoff; + + if (active_only) + return; + + /* Get number of panes. */ + n = window_count_panes(w); + if (n == 0) + return; + + /* How many can we fit? */ + if (w->sy / n < PANE_MINIMUM) { + height = PANE_MINIMUM; + n = w->sy / PANE_MINIMUM; + } else + height = w->sy / n; + + /* Fit the panes. */ + i = yoff = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (i > n) { + wp->flags |= PANE_HIDDEN; + continue; + } + wp->flags &= ~PANE_HIDDEN; + + wp->xoff = 0; + wp->yoff = yoff; + if (i != n - 1) + window_pane_resize(wp, w->sx, height - 1); + else + window_pane_resize(wp, w->sx, height); + + i++; + yoff += height; + } + + /* Any space left? */ + while (yoff++ < w->sy) { + wp = TAILQ_LAST(&w->panes, window_panes); + window_pane_resize(wp, wp->sx, wp->sy + 1); + } +} + +void +layout_main_v_refresh(struct window *w, int active_only) +{ + struct window_pane *wp; + u_int i, n, mainwidth, height, yoff; + + if (active_only) + return; + + /* Get number of panes. */ + n = window_count_panes(w); + if (n == 0) + return; + + /* Get the main pane width and add one for separator line. */ + mainwidth = options_get_number(&w->options, "main-pane-width") + 1; + + /* Need >1 pane and minimum columns; if fewer, display active only. */ + if (n == 1 || w->sx < mainwidth + PANE_MINIMUM) { + layout_active_only_refresh(w, active_only); + return; + } + n--; + + /* How many can we fit, not including first? */ + if (w->sy / n < PANE_MINIMUM) { + height = PANE_MINIMUM; + n = w->sy / PANE_MINIMUM; + } else + height = w->sy / n; + + /* Fit the panes. */ + i = yoff = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) { + wp->xoff = 0; + wp->yoff = 0; + window_pane_resize(wp, mainwidth - 1, w->sy); + wp->flags &= ~PANE_HIDDEN; + continue; + } + + if (i > n) { + wp->flags |= PANE_HIDDEN; + continue; + } + wp->flags &= ~PANE_HIDDEN; + + wp->xoff = mainwidth; + wp->yoff = yoff; + if (i != n - 1) + window_pane_resize(wp, w->sx - mainwidth, height - 1); + else + window_pane_resize(wp, w->sx - mainwidth, height); + + i++; + yoff += height; + } + + /* Any space left? */ + while (yoff++ < w->sy) { + wp = TAILQ_LAST(&w->panes, window_panes); + while (wp != NULL && wp == TAILQ_FIRST(&w->panes)) + wp = TAILQ_PREV(wp, window_panes, entry); + if (wp == NULL) + break; + window_pane_resize(wp, wp->sx, wp->sy + 1); + } +} + +void +layout_main_h_refresh(struct window *w, int active_only) +{ + struct window_pane *wp; + u_int i, n, mainheight, width, xoff; + + if (active_only) + return; + + /* Get number of panes. */ + n = window_count_panes(w); + if (n == 0) + return; + + /* Get the main pane height and add one for separator line. */ + mainheight = options_get_number(&w->options, "main-pane-height") + 1; + + /* Need >1 pane and minimum rows; if fewer, display active only. */ + if (n == 1 || w->sy < mainheight + PANE_MINIMUM) { + layout_active_only_refresh(w, active_only); + return; + } + n--; + + /* How many can we fit, not including first? */ + if (w->sx / n < PANE_MINIMUM) { + width = PANE_MINIMUM; + n = w->sx / PANE_MINIMUM; + } else + width = w->sx / n; + + /* Fit the panes. */ + i = xoff = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) { + wp->xoff = 0; + wp->yoff = 0; + window_pane_resize(wp, w->sx, mainheight - 1); + wp->flags &= ~PANE_HIDDEN; + continue; + } + + if (i > n) { + wp->flags |= PANE_HIDDEN; + continue; + } + wp->flags &= ~PANE_HIDDEN; + + wp->xoff = xoff; + wp->yoff = mainheight; + if (i != n - 1) + window_pane_resize(wp, width - 1, w->sy - mainheight); + else + window_pane_resize(wp, width - 1, w->sy - mainheight); + + i++; + xoff += width; + } + + /* Any space left? */ + while (xoff++ < w->sx + 1) { + wp = TAILQ_LAST(&w->panes, window_panes); + while (wp != NULL && wp == TAILQ_FIRST(&w->panes)) + wp = TAILQ_PREV(wp, window_panes, entry); + if (wp == NULL) + break; + window_pane_resize(wp, wp->sx + 1, wp->sy); + } +} diff --git a/log.c b/log.c new file mode 100644 index 00000000..b4a612d5 --- /dev/null +++ b/log.c @@ -0,0 +1,250 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +/* Logging type. */ +#define LOG_TYPE_OFF 0 +#define LOG_TYPE_SYSLOG 1 +#define LOG_TYPE_TTY 2 +#define LOG_TYPE_FILE 3 +int log_type = LOG_TYPE_OFF; + +/* Log file, if needed. */ +FILE *log_file; + +/* Debug level. */ +int log_level; + +/* Open logging to syslog. */ +void +log_open_syslog(int level) +{ + log_type = LOG_TYPE_SYSLOG; + log_level = level; + + openlog(__progname, LOG_PID|LOG_NDELAY, LOG_FACILITY); + + tzset(); +} + +/* Open logging to tty. */ +void +log_open_tty(int level) +{ + log_type = LOG_TYPE_TTY; + log_level = level; + + setlinebuf(stderr); + setlinebuf(stdout); + + tzset(); +} + +/* Open logging to file. */ +void +log_open_file(int level, const char *path) +{ + log_file = fopen(path, "w"); + if (log_file == NULL) + return; + + log_type = LOG_TYPE_FILE; + log_level = level; + + setlinebuf(log_file); + + tzset(); +} + +/* Close logging. */ +void +log_close(void) +{ + if (log_type == LOG_TYPE_FILE) + fclose(log_file); + + log_type = LOG_TYPE_OFF; +} + +/* Write a log message. */ +void +log_write(int pri, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + log_vwrite(pri, msg, ap); + va_end(ap); +} + +/* Write a log message. */ +void +log_vwrite(int pri, const char *msg, va_list ap) +{ + char *fmt; + FILE *f = log_file; + + switch (log_type) { + case LOG_TYPE_SYSLOG: + vsyslog(pri, msg, ap); + break; + case LOG_TYPE_TTY: + if (pri == LOG_INFO) + f = stdout; + else + f = stderr; + /* FALLTHROUGH */ + case LOG_TYPE_FILE: + if (asprintf(&fmt, "%s\n", msg) == -1) + exit(1); + if (vfprintf(f, fmt, ap) == -1) + exit(1); + fflush(f); + free(fmt); + break; + } +} + +/* Log a warning with error string. */ +void printflike1 +log_warn(const char *msg, ...) +{ + va_list ap; + char *fmt; + + va_start(ap, msg); + if (asprintf(&fmt, "%s: %s", msg, strerror(errno)) == -1) + exit(1); + log_vwrite(LOG_CRIT, fmt, ap); + free(fmt); + va_end(ap); +} + +/* Log a warning. */ +void printflike1 +log_warnx(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + log_vwrite(LOG_CRIT, msg, ap); + va_end(ap); +} + +/* Log an informational message. */ +void printflike1 +log_info(const char *msg, ...) +{ + va_list ap; + + if (log_level > -1) { + va_start(ap, msg); + log_vwrite(LOG_INFO, msg, ap); + va_end(ap); + } +} + +/* Log a debug message. */ +void printflike1 +log_debug(const char *msg, ...) +{ + va_list ap; + + if (log_level > 0) { + va_start(ap, msg); + log_vwrite(LOG_DEBUG, msg, ap); + va_end(ap); + } +} + +/* Log a debug message at level 2. */ +void printflike1 +log_debug2(const char *msg, ...) +{ + va_list ap; + + if (log_level > 1) { + va_start(ap, msg); + log_vwrite(LOG_DEBUG, msg, ap); + va_end(ap); + } +} + +/* Log a debug message at level 3. */ +void printflike1 +log_debug3(const char *msg, ...) +{ + va_list ap; + + if (log_level > 2) { + va_start(ap, msg); + log_vwrite(LOG_DEBUG, msg, ap); + va_end(ap); + } +} + +/* Log a critical error, with error string if necessary, and die. */ +__dead void +log_vfatal(const char *msg, va_list ap) +{ + char *fmt; + + if (errno != 0) { + if (asprintf(&fmt, "fatal: %s: %s", msg, strerror(errno)) == -1) + exit(1); + log_vwrite(LOG_CRIT, fmt, ap); + } else { + if (asprintf(&fmt, "fatal: %s", msg) == -1) + exit(1); + log_vwrite(LOG_CRIT, fmt, ap); + } + free(fmt); + + exit(1); +} + +/* Log a critical error, with error string, and die. */ +__dead void printflike1 +log_fatal(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + log_vfatal(msg, ap); +} + +/* Log a critical error and die. */ +__dead void printflike1 +log_fatalx(const char *msg, ...) +{ + va_list ap; + + errno = 0; + va_start(ap, msg); + log_vfatal(msg, ap); +} diff --git a/mode-key.c b/mode-key.c new file mode 100644 index 00000000..ba1d12d1 --- /dev/null +++ b/mode-key.c @@ -0,0 +1,210 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +enum mode_key_cmd mode_key_lookup_vi(struct mode_key_data *, int); +enum mode_key_cmd mode_key_lookup_emacs(struct mode_key_data *, int); + +void +mode_key_init(struct mode_key_data *mdata, int type, int flags) +{ + mdata->type = type; + + if (flags & MODEKEY_CANEDIT) + flags |= MODEKEY_EDITMODE; + mdata->flags = flags; +} + +void +mode_key_free(unused struct mode_key_data *mdata) +{ +} + +enum mode_key_cmd +mode_key_lookup(struct mode_key_data *mdata, int key) +{ + switch (mdata->type) { + case MODEKEY_VI: + return (mode_key_lookup_vi(mdata, key)); + case MODEKEY_EMACS: + return (mode_key_lookup_emacs(mdata, key)); + default: + fatalx("unknown mode key type"); + } +} + +enum mode_key_cmd +mode_key_lookup_vi(struct mode_key_data *mdata, int key) +{ + if (KEYC_ISESC(key)) { + key = KEYC_REMOVEESC(key); + if (mdata->flags & MODEKEY_CANEDIT) + mdata->flags ^= MODEKEY_EDITMODE; + } + + + if (mdata->flags & MODEKEY_EDITMODE) { + switch (key) { + case '\003': + return (MODEKEYCMD_QUIT); + case '\033': + if (mdata->flags & MODEKEY_CANEDIT) + mdata->flags &= ~MODEKEY_EDITMODE; + return (MODEKEYCMD_NONE); + case '\010': + case '\177': + return (MODEKEYCMD_BACKSPACE); + case '\011': + return (MODEKEYCMD_COMPLETE); + case KEYC_DC: + return (MODEKEYCMD_DELETE); + case '\r': + return (MODEKEYCMD_CHOOSE); + } + return (MODEKEYCMD_OTHERKEY); + } + + switch (key) { + case '\010': + case '\177': + return (MODEKEYCMD_LEFT); + case KEYC_DC: + return (MODEKEYCMD_DELETE); + case '\011': + return (MODEKEYCMD_COMPLETE); + case 'i': + if (mdata->flags & MODEKEY_CANEDIT) + mdata->flags |= MODEKEY_EDITMODE; + break; + case 'a': + if (mdata->flags & MODEKEY_CANEDIT) { + mdata->flags |= MODEKEY_EDITMODE; + return (MODEKEYCMD_RIGHT); + } + break; + case '\r': + if (mdata->flags & (MODEKEY_CANEDIT|MODEKEY_CHOOSEMODE)) + return (MODEKEYCMD_CHOOSE); + return (MODEKEYCMD_COPYSELECTION); + case '0': + case '^': + return (MODEKEYCMD_STARTOFLINE); + case '\033': + return (MODEKEYCMD_CLEARSELECTION); + case 'j': + case KEYC_DOWN: + return (MODEKEYCMD_DOWN); + case '$': + return (MODEKEYCMD_ENDOFLINE); + case 'h': + case KEYC_LEFT: + return (MODEKEYCMD_LEFT); + case '\006': + case KEYC_NPAGE: + return (MODEKEYCMD_NEXTPAGE); + case 'w': + return (MODEKEYCMD_NEXTWORD); + case '\025': + case KEYC_PPAGE: + return (MODEKEYCMD_PREVIOUSPAGE); + case 'b': + return (MODEKEYCMD_PREVIOUSWORD); + case 'q': + case '\003': + return (MODEKEYCMD_QUIT); + case 'l': + case KEYC_RIGHT: + return (MODEKEYCMD_RIGHT); + case ' ': + return (MODEKEYCMD_STARTSELECTION); + case 'k': + case KEYC_UP: + return (MODEKEYCMD_UP); + case 'p': + return (MODEKEYCMD_PASTE); + } + + return (MODEKEYCMD_NONE); +} + +enum mode_key_cmd +mode_key_lookup_emacs(struct mode_key_data *mdata, int key) +{ + switch (key) { + case '\010': + case '\177': + return (MODEKEYCMD_BACKSPACE); + case KEYC_DC: + return (MODEKEYCMD_DELETE); + case '\011': + return (MODEKEYCMD_COMPLETE); + case '\r': + return (MODEKEYCMD_CHOOSE); + case '\001': + return (MODEKEYCMD_STARTOFLINE); + case '\007': + return (MODEKEYCMD_CLEARSELECTION); + case '\027': + case KEYC_ADDESC('w'): + return (MODEKEYCMD_COPYSELECTION); + case '\016': + case KEYC_DOWN: + return (MODEKEYCMD_DOWN); + case '\005': + return (MODEKEYCMD_ENDOFLINE); + case '\002': + case KEYC_LEFT: + return (MODEKEYCMD_LEFT); + case ' ': + if (mdata->flags & MODEKEY_CANEDIT) + break; + /* FALLTHROUGH */ + case '\026': + case KEYC_NPAGE: + return (MODEKEYCMD_NEXTPAGE); + case KEYC_ADDESC('f'): + return (MODEKEYCMD_NEXTWORD); + case '\031': + return (MODEKEYCMD_PASTE); + case KEYC_ADDESC('v'): + case KEYC_PPAGE: + return (MODEKEYCMD_PREVIOUSPAGE); + case KEYC_ADDESC('b'): + return (MODEKEYCMD_PREVIOUSWORD); + case '\006': + case KEYC_RIGHT: + return (MODEKEYCMD_RIGHT); + case '\000': + return (MODEKEYCMD_STARTSELECTION); + case '\020': + case KEYC_UP: + return (MODEKEYCMD_UP); + case 'q': + if (mdata->flags & MODEKEY_CANEDIT) + break; + /* FALLTHROUGH */ + case '\003': + case '\033': + return (MODEKEYCMD_QUIT); + } + + return (MODEKEYCMD_OTHERKEY); +} diff --git a/names.c b/names.c new file mode 100644 index 00000000..d5182d88 --- /dev/null +++ b/names.c @@ -0,0 +1,110 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include +#include +#include +#include + +#include "tmux.h" + +char *parse_window_name(const char *); + +void +set_window_names(void) +{ + struct window *w; + u_int i; + char *name, *wname; + struct timeval tv, tv2; + + if (gettimeofday(&tv, NULL) != 0) + fatal("gettimeofday"); + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL || w->active == NULL) + continue; + if (!options_get_number(&w->options, "automatic-rename")) + continue; + + if (timercmp(&tv, &w->name_timer, <)) + continue; + memcpy(&w->name_timer, &tv, sizeof w->name_timer); + tv2.tv_sec = 0; + tv2.tv_usec = NAME_INTERVAL * 1000L; + timeradd(&w->name_timer, &tv2, &w->name_timer); + + if (w->active->screen != &w->active->base) + name = NULL; + else + name = get_proc_name(w->active->fd, w->active->tty); + if (name == NULL) + wname = default_window_name(w); + else { + wname = parse_window_name(name); + xfree(name); + } + + if (strcmp(wname, w->name) == 0) + xfree(wname); + else { + xfree(w->name); + w->name = wname; + server_status_window(w); + } + } +} + +char * +default_window_name(struct window *w) +{ + if (w->active->screen != &w->active->base) + return (xstrdup("[tmux]")); + return (parse_window_name(w->active->cmd)); +} + +char * +parse_window_name(const char *in) +{ + char *copy, *name, *ptr; + + name = copy = xstrdup(in); + if (strncmp(name, "exec ", (sizeof "exec ") - 1) == 0) + name = name + (sizeof "exec ") - 1; + + while (*name == ' ') + name++; + if ((ptr = strchr(name, ' ')) != NULL) + *ptr = '\0'; + + if (*name != '\0') { + ptr = name + strlen(name) - 1; + while (ptr > name && !isalnum(*ptr)) + *ptr-- = '\0'; + } + + if (*name == '/') + name = basename(name); + name = xstrdup(name); + xfree(copy); + return (name); +} + diff --git a/options-cmd.c b/options-cmd.c new file mode 100644 index 00000000..6b7090bd --- /dev/null +++ b/options-cmd.c @@ -0,0 +1,182 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +void +set_option_string(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + if (value == NULL) { + ctx->error(ctx, "empty value"); + return; + } + + options_set_string(oo, entry->name, "%s", value); + ctx->info(ctx, "set option: %s -> %s", entry->name, value); +} + +void +set_option_number(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + long long number; + const char *errstr; + + if (value == NULL) { + ctx->error(ctx, "empty value"); + return; + } + + number = strtonum(value, entry->minimum, entry->maximum, &errstr); + if (errstr != NULL) { + ctx->error(ctx, "value is %s: %s", errstr, value); + return; + } + options_set_number(oo, entry->name, number); + ctx->info(ctx, "set option: %s -> %lld", entry->name, number); +} + +void +set_option_key(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + int key; + + if (value == NULL) { + ctx->error(ctx, "empty value"); + return; + } + + if ((key = key_string_lookup_string(value)) == KEYC_NONE) { + ctx->error(ctx, "unknown key: %s", value); + return; + } + options_set_number(oo, entry->name, key); + ctx->info(ctx, + "set option: %s -> %s", entry->name, key_string_lookup_key(key)); +} + +void +set_option_colour(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + int colour; + + if (value == NULL) { + ctx->error(ctx, "empty value"); + return; + } + + if ((colour = colour_fromstring(value)) == -1) { + ctx->error(ctx, "bad colour: %s", value); + return; + } + + options_set_number(oo, entry->name, colour); + ctx->info(ctx, + "set option: %s -> %s", entry->name, colour_tostring(colour)); +} + +void +set_option_attributes(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + int attr; + + if (value == NULL) { + ctx->error(ctx, "empty value"); + return; + } + + if ((attr = attributes_fromstring(value)) == -1) { + ctx->error(ctx, "bad attributes: %s", value); + return; + } + + options_set_number(oo, entry->name, attr); + ctx->info(ctx, + "set option: %s -> %s", entry->name, attributes_tostring(attr)); +} + +void +set_option_flag(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + int flag; + + if (value == NULL || *value == '\0') + flag = !options_get_number(oo, entry->name); + else { + if ((value[0] == '1' && value[1] == '\0') || + strcasecmp(value, "on") == 0 || + strcasecmp(value, "yes") == 0) + flag = 1; + else if ((value[0] == '0' && value[1] == '\0') || + strcasecmp(value, "off") == 0 || + strcasecmp(value, "no") == 0) + flag = 0; + else { + ctx->error(ctx, "bad value: %s", value); + return; + } + } + + options_set_number(oo, entry->name, flag); + ctx->info(ctx, + "set option: %s -> %s", entry->name, flag ? "on" : "off"); +} + +void +set_option_choice(struct cmd_ctx *ctx, struct options *oo, + const struct set_option_entry *entry, char *value) +{ + const char **choicep; + int n, choice = -1; + + if (value == NULL) { + ctx->error(ctx, "empty value"); + return; + } + + n = 0; + for (choicep = entry->choices; *choicep != NULL; choicep++) { + n++; + if (strncmp(*choicep, value, strlen(value)) != 0) + continue; + + if (choice != -1) { + ctx->error(ctx, "ambiguous option: %s", value); + return; + } + choice = n - 1; + } + if (choice == -1) { + ctx->error(ctx, "unknown option: %s", value); + return; + } + + options_set_number(oo, entry->name, choice); + ctx->info(ctx, + "set option: %s -> %s", entry->name, entry->choices[choice]); +} diff --git a/options.c b/options.c new file mode 100644 index 00000000..c742e092 --- /dev/null +++ b/options.c @@ -0,0 +1,160 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* + * Option handling; each option has a name, type and value and is stored in + * a splay tree. + */ + +SPLAY_GENERATE(options_tree, options_entry, entry, options_cmp); + +int +options_cmp(struct options_entry *o1, struct options_entry *o2) +{ + return (strcmp(o1->name, o2->name)); +} + +void +options_init(struct options *oo, struct options *parent) +{ + SPLAY_INIT(&oo->tree); + oo->parent = parent; +} + +void +options_free(struct options *oo) +{ + struct options_entry *o; + + while (!SPLAY_EMPTY(&oo->tree)) { + o = SPLAY_ROOT(&oo->tree); + SPLAY_REMOVE(options_tree, &oo->tree, o); + xfree(o->name); + if (o->type == OPTIONS_STRING) + xfree(o->value.string); + xfree(o); + } +} + +struct options_entry * +options_find1(struct options *oo, const char *name) +{ + struct options_entry p; + + p.name = (char *) name; + return (SPLAY_FIND(options_tree, &oo->tree, &p)); +} + +struct options_entry * +options_find(struct options *oo, const char *name) +{ + struct options_entry *o, p; + + p.name = (char *) name; + o = SPLAY_FIND(options_tree, &oo->tree, &p); + while (o == NULL) { + oo = oo->parent; + if (oo == NULL) + break; + o = SPLAY_FIND(options_tree, &oo->tree, &p); + } + return (o); +} + +int +options_remove(struct options *oo, const char *name) +{ + struct options_entry *o; + + if ((o = options_find1(oo, name)) == NULL) + return (-1); + + SPLAY_REMOVE(options_tree, &oo->tree, o); + xfree(o->name); + if (o->type == OPTIONS_STRING) + xfree(o->value.string); + xfree(o); + return (0); +} + +void printflike3 +options_set_string(struct options *oo, const char *name, const char *fmt, ...) +{ + struct options_entry *o; + va_list ap; + + if ((o = options_find1(oo, name)) == NULL) { + o = xmalloc(sizeof *o); + o->name = xstrdup(name); + SPLAY_INSERT(options_tree, &oo->tree, o); + } else if (o->type == OPTIONS_STRING) + xfree(o->value.string); + + va_start(ap, fmt); + o->type = OPTIONS_STRING; + xvasprintf(&o->value.string, fmt, ap); + va_end(ap); +} + +char * +options_get_string(struct options *oo, const char *name) +{ + struct options_entry *o; + + if ((o = options_find(oo, name)) == NULL) + fatalx("missing option"); + if (o->type != OPTIONS_STRING) + fatalx("option not a string"); + return (o->value.string); +} + +void +options_set_number(struct options *oo, const char *name, long long value) +{ + struct options_entry *o; + + if ((o = options_find1(oo, name)) == NULL) { + o = xmalloc(sizeof *o); + o->name = xstrdup(name); + SPLAY_INSERT(options_tree, &oo->tree, o); + } else if (o->type == OPTIONS_STRING) + xfree(o->value.string); + + o->type = OPTIONS_NUMBER; + o->value.number = value; + +} + +long long +options_get_number(struct options *oo, const char *name) +{ + struct options_entry *o; + + if ((o = options_find(oo, name)) == NULL) + fatalx("missing option"); + if (o->type != OPTIONS_NUMBER) + fatalx("option not a number"); + return (o->value.number); +} diff --git a/paste.c b/paste.c new file mode 100644 index 00000000..3644381c --- /dev/null +++ b/paste.c @@ -0,0 +1,131 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include + +#include "tmux.h" + +void +paste_init_stack(struct paste_stack *ps) +{ + ARRAY_INIT(ps); +} + +void +paste_free_stack(struct paste_stack *ps) +{ + while (paste_free_top(ps) == 0) + ; +} + +struct paste_buffer * +paste_walk_stack(struct paste_stack *ps, uint *idx) +{ + struct paste_buffer *pb; + + pb = paste_get_index(ps, *idx); + (*idx)++; + return (pb); +} + +struct paste_buffer * +paste_get_top(struct paste_stack *ps) +{ + if (ARRAY_LENGTH(ps) == 0) + return (NULL); + return (ARRAY_FIRST(ps)); +} + +struct paste_buffer * +paste_get_index(struct paste_stack *ps, u_int idx) +{ + if (idx >= ARRAY_LENGTH(ps)) + return (NULL); + return (ARRAY_ITEM(ps, idx)); +} + +int +paste_free_top(struct paste_stack *ps) +{ + struct paste_buffer *pb; + + if (ARRAY_LENGTH(ps) == 0) + return (-1); + + pb = ARRAY_FIRST(ps); + ARRAY_REMOVE(ps, 0); + + xfree(pb->data); + xfree(pb); + + return (0); +} + +int +paste_free_index(struct paste_stack *ps, u_int idx) +{ + struct paste_buffer *pb; + + if (idx >= ARRAY_LENGTH(ps)) + return (-1); + + pb = ARRAY_ITEM(ps, idx); + ARRAY_REMOVE(ps, idx); + + xfree(pb->data); + xfree(pb); + + return (0); +} + +void +paste_add(struct paste_stack *ps, char *data, u_int limit) +{ + struct paste_buffer *pb; + + while (ARRAY_LENGTH(ps) >= limit) + ARRAY_TRUNC(ps, 1); + + pb = xmalloc(sizeof *pb); + ARRAY_INSERT(ps, 0, pb); + + pb->data = data; + if (gettimeofday(&pb->tv, NULL) != 0) + fatal("gettimeofday"); +} + +int +paste_replace(struct paste_stack *ps, u_int idx, char *data) +{ + struct paste_buffer *pb; + + if (idx >= ARRAY_LENGTH(ps)) + return (-1); + + pb = ARRAY_ITEM(ps, idx); + xfree(pb->data); + + pb->data = data; + if (gettimeofday(&pb->tv, NULL) != 0) + fatal("gettimeofday"); + + return (0); +} diff --git a/procname.c b/procname.c new file mode 100644 index 00000000..262d63a4 --- /dev/null +++ b/procname.c @@ -0,0 +1,130 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 +#include +#include + +#include +#include +#include +#include + +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) + +#define is_runnable(p) \ + ((p)->p_stat == SRUN || (p)->p_stat == SIDL || (p)->p_stat == SONPROC) +#define is_stopped(p) \ + ((p)->p_stat == SSTOP || (p)->p_stat == SZOMB || (p)->p_stat == SDEAD) + +char *get_proc_name(int, char *); + +char * +get_proc_name(int fd, char *tty) +{ + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PGRP, 0 }; + struct stat sb; + size_t len; + struct kinfo_proc *buf, *newbuf; + struct proc *p, *bestp; + u_int i; + char *name; + + buf = NULL; + + if (stat(tty, &sb) == -1) + return (NULL); + if ((mib[3] = tcgetpgrp(fd)) == -1) + return (NULL); + +retry: + if (sysctl(mib, nitems(mib), NULL, &len, NULL, 0) == -1) + return (NULL); + len = (len * 5) / 4; + + if ((newbuf = realloc(buf, len)) == NULL) { + free(buf); + return (NULL); + } + buf = newbuf; + + if (sysctl(mib, nitems(mib), buf, &len, NULL, 0) == -1) { + if (errno == ENOMEM) + goto retry; + free(buf); + return (NULL); + } + + bestp = NULL; + for (i = 0; i < len / sizeof (struct kinfo_proc); i++) { + if (buf[i].kp_eproc.e_tdev != sb.st_rdev) + continue; + p = &buf[i].kp_proc; + if (bestp == NULL) { + bestp = p; + continue; + } + + if (is_runnable(p) && !is_runnable(bestp)) + bestp = p; + else if (!is_runnable(p) && is_runnable(bestp)) + continue; + + if (!is_stopped(p) && is_stopped(bestp)) + bestp = p; + else if (is_stopped(p) && !is_stopped(bestp)) + continue; + + if (p->p_estcpu > bestp->p_estcpu) + bestp = p; + else if (p->p_estcpu < bestp->p_estcpu) + continue; + + if (p->p_slptime < bestp->p_slptime) + bestp = p; + else if (p->p_slptime > bestp->p_slptime) + continue; + + if (p->p_flag & P_SINTR && !(bestp->p_flag & P_SINTR)) + bestp = p; + else if (!(p->p_flag & P_SINTR) && bestp->p_flag & P_SINTR) + continue; + + if (LIST_FIRST(&p->p_children) == NULL && + LIST_FIRST(&bestp->p_children) != NULL) /* XXX ugh */ + bestp = p; + else if (LIST_FIRST(&p->p_children) != NULL && + LIST_FIRST(&bestp->p_children) == NULL) + continue; + + if (strcmp(p->p_comm, p->p_comm) < 0) + bestp = p; + else if (strcmp(p->p_comm, p->p_comm) > 0) + continue; + + if (p->p_pid > bestp->p_pid) + bestp = p; + } + + name = NULL; + if (bestp != NULL) + name = strdup(bestp->p_comm); + + free(buf); + return (name); +} diff --git a/resize.c b/resize.c new file mode 100644 index 00000000..50e12fb0 --- /dev/null +++ b/resize.c @@ -0,0 +1,138 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +/* + * Recalculate window and session sizes. + * + * Every session has the size of the smallest client it is attached to and + * every window the size of the smallest session it is attached to. + * + * So, when a client is resized or a session attached to or detached from a + * client, the window sizes must be recalculated. For each session, find the + * smallest client it is attached to, and resize it to that size. Then for + * every window, find the smallest session it is attached to, resize it to that + * size and clear and redraw every client with it as the current window. + * + * This is quite inefficient - better/additional data structures are needed + * to make it better. + * + * As a side effect, this function updates the SESSION_UNATTACHED flag. This + * flag is necessary to make sure unattached sessions do not limit the size of + * windows that are attached both to them and to other (attached) sessions. + */ + +void +recalculate_sizes(void) +{ + struct session *s; + struct client *c; + struct window *w; + u_int i, j, ssx, ssy, has, limit; + int flag; + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL) + continue; + + ssx = ssy = UINT_MAX; + for (j = 0; j < ARRAY_LENGTH(&clients); j++) { + c = ARRAY_ITEM(&clients, j); + if (c == NULL) + continue; + if (c->session == s) { + if (c->tty.sx < ssx) + ssx = c->tty.sx; + if (c->tty.sy < ssy) + ssy = c->tty.sy; + } + } + if (ssx == UINT_MAX || ssy == UINT_MAX) { + s->flags |= SESSION_UNATTACHED; + continue; + } + s->flags &= ~SESSION_UNATTACHED; + + if (options_get_number(&s->options, "status")) { + if (ssy == 0) + ssy = 1; + else + ssy--; + } + if (s->sx == ssx && s->sy == ssy) + continue; + + log_debug( + "session size %u,%u (was %u,%u)", ssx, ssy, s->sx, s->sy); + + s->sx = ssx; + s->sy = ssy; + } + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL) + continue; + flag = options_get_number(&w->options, "aggressive-resize"); + + ssx = ssy = UINT_MAX; + for (j = 0; j < ARRAY_LENGTH(&sessions); j++) { + s = ARRAY_ITEM(&sessions, j); + if (s == NULL || s->flags & SESSION_UNATTACHED) + continue; + if (flag) + has = s->curw->window == w; + else + has = session_has(s, w); + if (has) { + if (s->sx < ssx) + ssx = s->sx; + if (s->sy < ssy) + ssy = s->sy; + } + } + if (ssx == UINT_MAX || ssy == UINT_MAX) { + w->flags |= WINDOW_HIDDEN; + continue; + } + w->flags &= ~WINDOW_HIDDEN; + + limit = options_get_number(&w->options, "force-width"); + if (limit != 0 && ssx > limit) + ssx = limit; + limit = options_get_number(&w->options, "force-height"); + if (limit != 0 && ssy > limit) + ssy = limit; + + if (w->sx == ssx && w->sy == ssy) + continue; + + log_debug( + "window size %u,%u (was %u,%u)", ssx, ssy, w->sx, w->sy); + + window_resize(w, ssx, ssy); + server_redraw_window(w); + layout_refresh(w, 0); + } +} diff --git a/screen-redraw.c b/screen-redraw.c new file mode 100644 index 00000000..06033436 --- /dev/null +++ b/screen-redraw.c @@ -0,0 +1,171 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +int screen_redraw_check_cell(struct client *, u_int, u_int); + +/* Check if cell inside a pane. */ +int +screen_redraw_check_cell(struct client *c, u_int px, u_int py) +{ + struct window *w = c->session->curw->window; + struct window_pane *wp; + + if (px > w->sx || py > w->sy) + return (0); + + TAILQ_FOREACH(wp, &w->panes, entry) { + /* Inside pane. */ + if (px >= wp->xoff && px < wp->xoff + wp->sx && + py >= wp->yoff && py < wp->yoff + wp->sy) + return (1); + + /* Left/right borders. */ + if (py >= wp->yoff && py < wp->yoff + wp->sy) { + if (wp->xoff != 0 && px == wp->xoff - 1) + return (1); + if (px == wp->xoff + wp->sx) + return (1); + } + + /* Top/bottom borders. */ + if (px >= wp->xoff && px < wp->xoff + wp->sx) { + if (wp->yoff != 0 && py == wp->yoff - 1) + return (1); + if (py == wp->yoff + wp->sy) + return (1); + } + } + + return (0); +} + +/* Redraw entire screen.. */ +void +screen_redraw_screen(struct client *c) +{ + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct window_pane *wp; + struct screen *s; + u_int i, j, sx, sy; + int status, has_acs; + u_char choriz, cvert, cbackg; + + /* Get status line, er, status. */ + status = options_get_number(&c->session->options, "status"); + + /* Work out ACS characters. */ + if (tty_term_has(tty->term, TTYC_ACSC)) { + has_acs = 1; + choriz = tty_get_acs(tty, 'q'); + cvert = tty_get_acs(tty, 'x'); + cbackg = tty_get_acs(tty, '~'); + } else { + has_acs = 0; + choriz = '-'; + cvert = '|'; + cbackg = '.'; + } + + /* Clear the screen. */ + tty_reset(tty); + if (has_acs) + tty_putcode(tty, TTYC_SMACS); + for (j = 0; j < tty->sy - status; j++) { + for (i = 0; i < tty->sx; i++) { + if (!screen_redraw_check_cell(c, i, j)) { + tty_cursor(tty, i, j, 0, 0); + tty_putc(tty, cbackg); + } + } + } + if (has_acs) + tty_putcode(tty, TTYC_RMACS); + + /* Draw the panes. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_HIDDEN) + continue; + + tty_reset(tty); + + s = wp->screen; + sx = wp->sx; + sy = wp->sy; + + /* Draw left and right borders. */ + if (has_acs) + tty_putcode(tty, TTYC_SMACS); + if (wp->xoff > 0) { + for (i = wp->yoff; i < wp->yoff + sy; i++) { + tty_cursor(tty, wp->xoff - 1, i, 0, 0); + tty_putc(tty, cvert); + } + } + if (wp->xoff + sx < tty->sx) { + for (i = wp->yoff; i < wp->yoff + sy; i++) { + tty_cursor(tty, wp->xoff + sx, i, 0, 0); + tty_putc(&c->tty, cvert); + } + } + + /* Draw top and bottom borders. */ + if (wp->yoff > 0) { + tty_cursor(tty, wp->xoff, wp->yoff - 1, 0, 0); + for (i = 0; i < sx; i++) + tty_putc(tty, choriz); + } + if (wp->yoff + sy < tty->sy - status) { + tty_cursor(tty, wp->xoff, wp->yoff + sy, 0, 0); + for (i = 0; i < sx; i++) + tty_putc(tty, choriz); + } + if (has_acs) + tty_putcode(tty, TTYC_RMACS); + + /* Draw the pane. */ + screen_redraw_pane(c, wp); + } + + /* Draw the status line. */ + screen_redraw_status(c); +} + +/* Draw a single pane. */ +void +screen_redraw_pane(struct client *c, struct window_pane *wp) +{ + u_int i; + + for (i = 0; i < wp->sy; i++) + tty_draw_line(&c->tty, wp->screen, i, wp->xoff, wp->yoff); +} + + +/* Draw the status line. */ +void +screen_redraw_status(struct client *c) +{ + tty_draw_line(&c->tty, &c->status, 0, 0, c->tty.sy - 1); +} diff --git a/screen-write.c b/screen-write.c new file mode 100644 index 00000000..ac31fc6f --- /dev/null +++ b/screen-write.c @@ -0,0 +1,684 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +void screen_write_save(struct screen_write_ctx *); +void screen_write_overwrite(struct screen_write_ctx *); + +/* Initialise writing with a window. */ +void +screen_write_start( + struct screen_write_ctx *ctx, struct window_pane *wp, struct screen *s) +{ + ctx->wp = wp; + if (wp != NULL && s == NULL) + ctx->s = wp->screen; + else + ctx->s = s; +} + +/* Finish writing. */ +void +screen_write_stop(unused struct screen_write_ctx *ctx) +{ +} + +/* Write character. */ +void +screen_write_putc( + struct screen_write_ctx *ctx, struct grid_cell *gc, u_char ch) +{ + gc->data = ch; + screen_write_cell(ctx, gc, NULL); +} + +/* Write string. */ +void printflike3 +screen_write_puts( + struct screen_write_ctx *ctx, struct grid_cell *gc, const char *fmt, ...) +{ + va_list ap; + char *msg, *ptr; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + for (ptr = msg; *ptr != '\0'; ptr++) + screen_write_putc(ctx, gc, (u_char) *ptr); + + xfree(msg); +} + +/* Copy from another screen. */ +void +screen_write_copy(struct screen_write_ctx *ctx, + struct screen *src, u_int px, u_int py, u_int nx, u_int ny) +{ + struct screen *s = ctx->s; + struct grid *gd = src->grid; + const struct grid_cell *gc; + struct grid_utf8 *gu; + u_char *udata; + u_int xx, yy, cx, cy; + + cx = s->cx; + cy = s->cy; + for (yy = py; yy < py + ny; yy++) { + for (xx = px; xx < px + nx; xx++) { + if (xx >= gd->sx || yy >= gd->hsize + gd->sy) + gc = &grid_default_cell; + else + gc = grid_peek_cell(gd, xx, yy); + + udata = NULL; + if (gc->flags & GRID_FLAG_UTF8) { + gu = grid_get_utf8(gd, xx, yy); + udata = gu->data; + } + + screen_write_cell(ctx, gc, udata); + } + cy++; + screen_write_cursormove(ctx, cx, cy); + } +} + +/* Save cursor and region positions. */ +void +screen_write_save(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + + s->old_cx = s->cx; + s->old_cy = s->cy; + + s->old_rlower = s->rlower; + s->old_rupper = s->rupper; +} + +/* Cursor up by ny. */ +void +screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny) +{ + struct screen *s = ctx->s; + + if (ny == 0) + ny = 1; + + if (ny > s->cy) + ny = s->cy; + if (ny == 0) + return; + + s->cy -= ny; +} + +/* Cursor down by ny. */ +void +screen_write_cursordown(struct screen_write_ctx *ctx, u_int ny) +{ + struct screen *s = ctx->s; + + if (ny == 0) + ny = 1; + + if (ny > screen_size_y(s) - 1 - s->cy) + ny = screen_size_y(s) - 1 - s->cy; + if (ny == 0) + return; + + s->cy += ny; +} + +/* Cursor right by nx. */ +void +screen_write_cursorright(struct screen_write_ctx *ctx, u_int nx) +{ + struct screen *s = ctx->s; + + if (nx == 0) + nx = 1; + + if (nx > screen_size_x(s) - 1 - s->cx) + nx = screen_size_x(s) - 1 - s->cx; + if (nx == 0) + return; + + s->cx += nx; +} + +/* Cursor left by nx. */ +void +screen_write_cursorleft(struct screen_write_ctx *ctx, u_int nx) +{ + struct screen *s = ctx->s; + + if (nx == 0) + nx = 1; + + if (nx > s->cx) + nx = s->cx; + if (nx == 0) + return; + + s->cx -= nx; +} + +/* Insert nx characters. */ +void +screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx) +{ + struct screen *s = ctx->s; + + if (nx == 0) + nx = 1; + + if (nx > screen_size_x(s) - 1 - s->cx) + nx = screen_size_x(s) - 1 - s->cx; + if (nx == 0) + return; + + screen_write_save(ctx); + + if (s->cx <= screen_size_x(s) - 1) + grid_view_insert_cells(s->grid, s->cx, s->cy, nx); + + tty_write_cmd(ctx->wp, TTY_INSERTCHARACTER, nx); +} + +/* Delete nx characters. */ +void +screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx) +{ + struct screen *s = ctx->s; + + if (nx == 0) + nx = 1; + + if (nx > screen_size_x(s) - 1 - s->cx) + nx = screen_size_x(s) - 1 - s->cx; + if (nx == 0) + return; + + screen_write_save(ctx); + + if (s->cx <= screen_size_x(s) - 1) + grid_view_delete_cells(s->grid, s->cx, s->cy, nx); + + tty_write_cmd(ctx->wp, TTY_DELETECHARACTER, nx); +} + +/* Insert ny lines. */ +void +screen_write_insertline(struct screen_write_ctx *ctx, u_int ny) +{ + struct screen *s = ctx->s; + + if (ny == 0) + ny = 1; + + if (ny > screen_size_y(s) - 1 - s->cy) + ny = screen_size_y(s) - 1 - s->cy; + if (ny == 0) + return; + + screen_write_save(ctx); + + if (s->cy < s->rupper || s->cy > s->rlower) + grid_view_insert_lines(s->grid, s->cy, ny); + else { + grid_view_insert_lines_region( + s->grid, s->rupper, s->rlower, s->cy, ny); + } + + tty_write_cmd(ctx->wp, TTY_INSERTLINE, ny); +} + +/* Delete ny lines. */ +void +screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny) +{ + struct screen *s = ctx->s; + + if (ny == 0) + ny = 1; + + if (ny > screen_size_y(s) - 1 - s->cy) + ny = screen_size_y(s) - 1 - s->cy; + if (ny == 0) + return; + + screen_write_save(ctx); + + if (s->cy < s->rupper || s->cy > s->rlower) + grid_view_delete_lines(s->grid, s->cy, ny); + else { + grid_view_delete_lines_region( + s->grid, s->rupper, s->rlower, s->cy, ny); + } + + tty_write_cmd(ctx->wp, TTY_DELETELINE, ny); +} + +/* Clear line at cursor. */ +void +screen_write_clearline(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + + screen_write_save(ctx); + + grid_view_clear(s->grid, 0, s->cy, screen_size_x(s), 1); + + tty_write_cmd(ctx->wp, TTY_CLEARLINE); +} + +/* Clear to end of line from cursor. */ +void +screen_write_clearendofline(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + u_int sx; + + screen_write_save(ctx); + + sx = screen_size_x(s); + + if (s->cx <= sx - 1) + grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1); + + tty_write_cmd(ctx->wp, TTY_CLEARENDOFLINE); +} + +/* Clear to start of line from cursor. */ +void +screen_write_clearstartofline(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + u_int sx; + + screen_write_save(ctx); + + sx = screen_size_x(s); + + if (s->cx > sx - 1) + grid_view_clear(s->grid, 0, s->cy, sx, 1); + else + grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1); + + tty_write_cmd(ctx->wp, TTY_CLEARSTARTOFLINE); +} + +/* Move cursor to px,py. */ +void +screen_write_cursormove(struct screen_write_ctx *ctx, u_int px, u_int py) +{ + struct screen *s = ctx->s; + + if (px > screen_size_x(s) - 1) + px = screen_size_x(s) - 1; + if (py > screen_size_y(s) - 1) + py = screen_size_y(s) - 1; + + s->cx = px; + s->cy = py; +} + +/* Set cursor mode. */ +void +screen_write_cursormode(struct screen_write_ctx *ctx, int state) +{ + struct screen *s = ctx->s; + + if (state) + s->mode |= MODE_CURSOR; + else + s->mode &= ~MODE_CURSOR; +} + +/* Reverse index (up with scroll). */ +void +screen_write_reverseindex(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + + screen_write_save(ctx); + + if (s->cy == s->rupper) + grid_view_scroll_region_down(s->grid, s->rupper, s->rlower); + else if (s->cy > 0) + s->cy--; + + tty_write_cmd(ctx->wp, TTY_REVERSEINDEX); +} + +/* Set scroll region. */ +void +screen_write_scrollregion( + struct screen_write_ctx *ctx, u_int rupper, u_int rlower) +{ + struct screen *s = ctx->s; + + if (rupper > screen_size_y(s) - 1) + rupper = screen_size_y(s) - 1; + if (rlower > screen_size_y(s) - 1) + rlower = screen_size_y(s) - 1; + if (rupper > rlower) + return; + + /* Cursor moves to top-left. */ + s->cx = 0; + s->cy = 0; + + s->rupper = rupper; + s->rlower = rlower; +} + +/* Set insert mode. */ +void +screen_write_insertmode(struct screen_write_ctx *ctx, int state) +{ + struct screen *s = ctx->s; + + if (state) + s->mode |= MODE_INSERT; + else + s->mode &= ~MODE_INSERT; +} + +/* Set mouse mode. */ +void +screen_write_mousemode(struct screen_write_ctx *ctx, int state) +{ + struct screen *s = ctx->s; + + if (state) + s->mode |= MODE_MOUSE; + else + s->mode &= ~MODE_MOUSE; +} + +/* Line feed (down with scroll). */ +void +screen_write_linefeed(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + + screen_write_save(ctx); + + if (s->cy == s->rlower) + grid_view_scroll_region_up(s->grid, s->rupper, s->rlower); + else if (s->cy < screen_size_y(s) - 1) + s->cy++; + + tty_write_cmd(ctx->wp, TTY_LINEFEED); +} + +/* Carriage return (cursor to start of line). */ +void +screen_write_carriagereturn(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + + s->cx = 0; +} + +/* Set keypad cursor keys mode. */ +void +screen_write_kcursormode(struct screen_write_ctx *ctx, int state) +{ + struct screen *s = ctx->s; + + if (state) + s->mode |= MODE_KCURSOR; + else + s->mode &= ~MODE_KCURSOR; +} + +/* Set keypad number keys mode. */ +void +screen_write_kkeypadmode(struct screen_write_ctx *ctx, int state) +{ + struct screen *s = ctx->s; + + if (state) + s->mode |= MODE_KKEYPAD; + else + s->mode &= ~MODE_KKEYPAD; +} + +/* Clear to end of screen from cursor. */ +void +screen_write_clearendofscreen(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + u_int sx, sy; + + screen_write_save(ctx); + + sx = screen_size_x(s); + sy = screen_size_y(s); + + if (s->cx <= sx - 1) + grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1); + grid_view_clear(s->grid, 0, s->cy + 1, sx, sy - (s->cy + 1)); + + tty_write_cmd(ctx->wp, TTY_CLEARENDOFSCREEN); +} + +/* Clear to start of screen. */ +void +screen_write_clearstartofscreen(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + u_int sx; + + screen_write_save(ctx); + + sx = screen_size_x(s); + + if (s->cy > 0) + grid_view_clear(s->grid, 0, 0, sx, s->cy - 1); + if (s->cx > sx - 1) + grid_view_clear(s->grid, 0, s->cy, sx, 1); + else + grid_view_clear(s->grid, 0, s->cy, s->cx, 1); + + tty_write_cmd(ctx->wp, TTY_CLEARSTARTOFSCREEN); +} + +/* Clear entire screen. */ +void +screen_write_clearscreen(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + + screen_write_save(ctx); + + grid_view_clear(s->grid, 0, 0, screen_size_x(s), screen_size_y(s)); + + tty_write_cmd(ctx->wp, TTY_CLEARSCREEN); +} + +/* Write cell data. */ +void +screen_write_cell( + struct screen_write_ctx *ctx, const struct grid_cell *gc, u_char *udata) +{ + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct grid_utf8 gu, *tmp_gu; + u_int width, xx, i; + struct grid_cell tmp_gc, *tmp_gc2; + size_t size; + + /* Ignore padding. */ + if (gc->flags & GRID_FLAG_PADDING) + return; + + /* Find character width. */ + if (gc->flags & GRID_FLAG_UTF8) { + width = utf8_width(udata); + + gu.width = width; + memcpy(&gu.data, udata, sizeof gu.data); + } else + width = 1; + + /* If the width is zero, combine onto the previous character. */ + if (width == 0) { + if (s->cx == 0) + return; + tmp_gc2 = grid_view_get_cell(gd, s->cx - 1, s->cy); + if (!(tmp_gc2->flags & GRID_FLAG_UTF8)) { + tmp_gc2->flags |= GRID_FLAG_UTF8; + memset(&gu.data, 0xff, sizeof gu.data); + *gu.data = tmp_gc2->data; + gu.width = 1; + grid_view_set_utf8(gd, s->cx - 1, s->cy, &gu); + } + tmp_gu = grid_view_get_utf8(gd, s->cx - 1, s->cy); + + for (i = 0; i < UTF8_SIZE; i++) { + if (tmp_gu->data[i] == 0xff) + break; + } + memcpy(tmp_gu->data + i, udata, UTF8_SIZE - i); + + /* Assume the previous character has just been input. */ + for (size = 0; size < UTF8_SIZE; size++) { + if (udata[size] == 0xff) + break; + } + tty_write_cmd(ctx->wp, TTY_RAW, udata, size); + return; + } + + /* If the character is wider than the screen, don't print it. */ + if (width > screen_size_x(s)) { + memcpy(&tmp_gc, gc, sizeof tmp_gc); + tmp_gc.data = '_'; + width = 1; + gc = &tmp_gc; + } + + /* Check this will fit on the current line; scroll if not. */ + if (s->cx > screen_size_x(s) - width) { + screen_write_carriagereturn(ctx); + screen_write_linefeed(ctx); + } + + /* Sanity checks. */ + if (s->cx > screen_size_x(s) - 1 || s->cy > screen_size_y(s) - 1) + return; + + /* Handle overwriting of UTF-8 characters. */ + screen_write_overwrite(ctx); + + /* + * If the new character is UTF-8 wide, fill in padding cells. Have + * already ensured there is enough room. + */ + for (xx = s->cx + 1; xx < s->cx + width; xx++) { + tmp_gc2 = grid_view_get_cell(gd, xx, s->cy); + if (tmp_gc2 != NULL) + tmp_gc2->flags |= GRID_FLAG_PADDING; + } + + /* Set the cell. */ + grid_view_set_cell(gd, s->cx, s->cy, gc); + if (gc->flags & GRID_FLAG_UTF8) + grid_view_set_utf8(gd, s->cx, s->cy, &gu); + + /* Move the cursor. */ + screen_write_save(ctx); + s->cx += width; + + /* Draw to the screen if necessary. */ + if (screen_check_selection(s, s->cx - width, s->cy)) { + s->sel.cell.data = gc->data; + tty_write_cmd(ctx->wp, TTY_CELL, &s->sel.cell, &gu); + } else + tty_write_cmd(ctx->wp, TTY_CELL, gc, &gu); +} + +/* + * UTF-8 wide characters are a bit of an annoyance. They take up more than one + * cell on the screen, so following cells must not be drawn by marking them as + * padding. + * + * So far, so good. The problem is, when overwriting a padding cell, or a UTF-8 + * character, it is necessary to also overwrite any other cells which covered + * by the same character. + */ +void +screen_write_overwrite(struct screen_write_ctx *ctx) +{ + struct screen *s = ctx->s; + struct grid *gd = s->grid; + const struct grid_cell *gc; + const struct grid_utf8 *gu; + u_int xx; + + gc = grid_view_peek_cell(gd, s->cx, s->cy); + gu = grid_view_peek_utf8(gd, s->cx, s->cy); + + if (gc->flags & GRID_FLAG_PADDING) { + /* + * A padding cell, so clear any following and leading padding + * cells back to the character. Don't overwrite the current + * cell as that happens later anyway. + */ + xx = s->cx + 1; + while (--xx > 0) { + gc = grid_view_peek_cell(gd, xx, s->cy); + if (!(gc->flags & GRID_FLAG_PADDING)) + break; + grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); + } + + /* Overwrite the character at the start of this padding. */ + grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); + + /* Overwrite following padding cells. */ + xx = s->cx; + while (++xx < screen_size_x(s)) { + gc = grid_view_peek_cell(gd, xx, s->cy); + if (!(gc->flags & GRID_FLAG_PADDING)) + break; + grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); + } + } else if (gc->flags & GRID_FLAG_UTF8 && gu->width > 1) { + /* + * An UTF-8 wide cell; overwrite following padding cells only. + */ + xx = s->cx; + while (++xx < screen_size_x(s)) { + gc = grid_view_peek_cell(gd, xx, s->cy); + if (!(gc->flags & GRID_FLAG_PADDING)) + break; + grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); + } + } +} diff --git a/screen.c b/screen.c new file mode 100644 index 00000000..4b355115 --- /dev/null +++ b/screen.c @@ -0,0 +1,240 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +void screen_resize_x(struct screen *, u_int); +void screen_resize_y(struct screen *, u_int); + +/* Create a new screen. */ +void +screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) +{ + s->grid = grid_create(sx, sy, hlimit); + + s->title = xstrdup(""); + + screen_reinit(s); +} + +/* Reinitialise screen. */ +void +screen_reinit(struct screen *s) +{ + s->cx = 0; + s->cy = 0; + + s->rupper = 0; + s->rlower = screen_size_y(s) - 1; + + s->mode = MODE_CURSOR; + + grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy - 1); + + screen_clear_selection(s); +} + +/* Destroy a screen. */ +void +screen_free(struct screen *s) +{ + xfree(s->title); + grid_destroy(s->grid); +} + +/* Set screen title. */ +void +screen_set_title(struct screen *s, const char *title) +{ + xfree(s->title); + s->title = xstrdup(title); +} + +/* Resize screen. */ +void +screen_resize(struct screen *s, u_int sx, u_int sy) +{ + if (sx < 1) + sx = 1; + if (sy < 1) + sy = 1; + + if (sx != screen_size_x(s)) + screen_resize_x(s, sx); + if (sy != screen_size_y(s)) + screen_resize_y(s, sy); +} + +void +screen_resize_x(struct screen *s, u_int sx) +{ + struct grid *gd = s->grid; + const struct grid_cell *gc; + const struct grid_utf8 *gu; + u_int xx, yy; + + if (sx == 0) + fatalx("zero size"); + + /* If getting larger, not much to do. */ + if (sx > screen_size_x(s)) { + gd->sx = sx; + return; + } + + /* If getting smaller, nuke any data in lines over the new size. */ + for (yy = gd->hsize; yy < gd->hsize + screen_size_y(s); yy++) { + /* + * If the character after the last is wide or padding, remove + * it and any leading padding. + */ + gc = &grid_default_cell; + for (xx = sx; xx > 0; xx--) { + gc = grid_peek_cell(gd, xx - 1, yy); + if (!(gc->flags & GRID_FLAG_PADDING)) + break; + grid_set_cell(gd, xx - 1, yy, &grid_default_cell); + } + if (xx > 0 && xx != sx && gc->flags & GRID_FLAG_UTF8) { + gu = grid_peek_utf8(gd, xx - 1, yy); + if (gu->width > 1) { + grid_set_cell( + gd, xx - 1, yy, &grid_default_cell); + } + } + + /* Reduce the line size. */ + grid_reduce_line(gd, yy, sx); + } + + if (s->cx >= sx) + s->cx = sx - 1; + gd->sx = sx; +} + +void +screen_resize_y(struct screen *s, u_int sy) +{ + struct grid *gd = s->grid; + u_int oy, yy, ny; + + if (sy == 0) + fatalx("zero size"); + + /* Size decreasing. */ + if (sy < screen_size_y(s)) { + oy = screen_size_y(s); + + if (s->cy != 0) { + /* + * The cursor is not at the start. Try to remove as + * many lines as possible from the top. (Up to the + * cursor line.) + */ + ny = s->cy; + if (ny > oy - sy) + ny = oy - sy; + + grid_view_delete_lines(gd, 0, ny); + + s->cy -= ny; + oy -= ny; + } + + if (sy < oy) { + /* Remove any remaining lines from the bottom. */ + grid_view_delete_lines(gd, sy, oy - sy); + if (s->cy >= sy) + s->cy = sy - 1; + } + } + + /* Resize line arrays. */ + gd->size = xrealloc(gd->size, gd->hsize + sy, sizeof *gd->size); + gd->data = xrealloc(gd->data, gd->hsize + sy, sizeof *gd->data); + gd->usize = xrealloc(gd->usize, gd->hsize + sy, sizeof *gd->usize); + gd->udata = xrealloc(gd->udata, gd->hsize + sy, sizeof *gd->udata); + + /* Size increasing. */ + if (sy > screen_size_y(s)) { + oy = screen_size_y(s); + for (yy = gd->hsize + oy; yy < gd->hsize + sy; yy++) { + gd->size[yy] = 0; + gd->data[yy] = NULL; + gd->usize[yy] = 0; + gd->udata[yy] = NULL; + } + } + + gd->sy = sy; + + s->rupper = 0; + s->rlower = screen_size_y(s) - 1; +} + +/* Set selection. */ +void +screen_set_selection(struct screen *s, + u_int sx, u_int sy, u_int ex, u_int ey, struct grid_cell *gc) +{ + struct screen_sel *sel = &s->sel; + + memcpy(&sel->cell, gc, sizeof sel->cell); + + sel->flag = 1; + if (ey < sy || (sy == ey && ex < sx)) { + sel->sx = ex; sel->sy = ey; + sel->ex = sx; sel->ey = sy; + } else { + sel->sx = sx; sel->sy = sy; + sel->ex = ex; sel->ey = ey; + } +} + +/* Clear selection. */ +void +screen_clear_selection(struct screen *s) +{ + struct screen_sel *sel = &s->sel; + + sel->flag = 0; +} + +/* Check if cell in selection. */ +int +screen_check_selection(struct screen *s, u_int px, u_int py) +{ + struct screen_sel *sel = &s->sel; + + if (!sel->flag || py < sel->sy || py > sel->ey) + return (0); + + if (py == sel->sy && py == sel->ey) { + if (px < sel->sx || px > sel->ex) + return (0); + return (1); + } + + if ((py == sel->sy && px < sel->sx) || (py == sel->ey && px > sel->ex)) + return (0); + return (1); +} diff --git a/server-fn.c b/server-fn.c new file mode 100644 index 00000000..69926d8e --- /dev/null +++ b/server-fn.c @@ -0,0 +1,242 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include + +#include "tmux.h" + +int server_lock_callback(void *, const char *); + +const char ** +server_fill_environ(struct session *s) +{ + static const char *env[] = { NULL /* TMUX= */, "TERM=screen", NULL }; + static char tmuxvar[MAXPATHLEN + 256]; + u_int idx; + + if (session_index(s, &idx) != 0) + fatalx("session not found"); + + xsnprintf(tmuxvar, sizeof tmuxvar, + "TMUX=%s,%ld,%u", socket_path, (long) getpid(), idx); + env[0] = tmuxvar; + + return (env); +} + +void +server_write_client( + struct client *c, enum hdrtype type, const void *buf, size_t len) +{ + struct hdr hdr; + + log_debug("writing %d to client %d", type, c->fd); + + hdr.type = type; + hdr.size = len; + + buffer_write(c->out, &hdr, sizeof hdr); + if (buf != NULL && len > 0) + buffer_write(c->out, buf, len); +} + +void +server_write_session( + struct session *s, enum hdrtype type, const void *buf, size_t len) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->session == s) + server_write_client(c, type, buf, len); + } +} + +void +server_write_window( + struct window *w, enum hdrtype type, const void *buf, size_t len) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->session->curw->window == w) + server_write_client(c, type, buf, len); + } +} + +void +server_redraw_client(struct client *c) +{ + c->flags |= CLIENT_REDRAW; +} + +void +server_status_client(struct client *c) +{ + c->flags |= CLIENT_STATUS; +} + +void +server_redraw_session(struct session *s) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->session == s) + server_redraw_client(c); + } +} + +void +server_status_session(struct session *s) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->session == s) + server_status_client(c); + } +} + +void +server_redraw_window(struct window *w) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->session->curw->window == w) + server_redraw_client(c); + } + w->flags |= WINDOW_REDRAW; +} + +void +server_status_window(struct window *w) +{ + struct session *s; + u_int i; + + /* + * This is slightly different. We want to redraw the status line of any + * clients containing this window rather than any where it is the + * current window. + */ + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s != NULL && session_has(s, w)) + server_status_session(s); + } +} + +void +server_lock(void) +{ + struct client *c; + u_int i; + + if (server_locked) + return; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + + status_prompt_clear(c); + status_prompt_set( + c, "Password: ", server_lock_callback, c, PROMPT_HIDDEN); + server_redraw_client(c); + } + server_locked = 1; +} + +int +server_lock_callback(unused void *data, const char *s) +{ + return (server_unlock(s)); +} + +int +server_unlock(const char *s) +{ + struct client *c; + u_int i; + char *out; + + if (!server_locked) + return (0); + server_activity = time(NULL); + + if (server_password != NULL) { + if (s == NULL) + return (-1); + out = crypt(s, server_password); + if (strcmp(out, server_password) != 0) + goto wrong; + } + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL) + continue; + + status_prompt_clear(c); + server_redraw_client(c); + } + + server_locked = 0; + return (0); + +wrong: + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL) + continue; + + *c->prompt_buffer = '\0'; + c->prompt_index = 0; + server_status_client(c); + } + + return (-1); +} diff --git a/server-msg.c b/server-msg.c new file mode 100644 index 00000000..cff23cd5 --- /dev/null +++ b/server-msg.c @@ -0,0 +1,304 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include +#include +#include +#include +#include + +#include "tmux.h" + +int server_msg_fn_command(struct hdr *, struct client *); +int server_msg_fn_identify(struct hdr *, struct client *); +int server_msg_fn_resize(struct hdr *, struct client *); +int server_msg_fn_exiting(struct hdr *, struct client *); +int server_msg_fn_unlock(struct hdr *, struct client *); +int server_msg_fn_wakeup(struct hdr *, struct client *); + +void printflike2 server_msg_fn_command_error( + struct cmd_ctx *, const char *, ...); +void printflike2 server_msg_fn_command_print( + struct cmd_ctx *, const char *, ...); +void printflike2 server_msg_fn_command_info( + struct cmd_ctx *, const char *, ...); + +struct server_msg { + enum hdrtype type; + int (*fn)(struct hdr *, struct client *); +}; +const struct server_msg server_msg_table[] = { + { MSG_IDENTIFY, server_msg_fn_identify }, + { MSG_COMMAND, server_msg_fn_command }, + { MSG_RESIZE, server_msg_fn_resize }, + { MSG_EXITING, server_msg_fn_exiting }, + { MSG_UNLOCK, server_msg_fn_unlock }, + { MSG_WAKEUP, server_msg_fn_wakeup }, +}; + +int +server_msg_dispatch(struct client *c) +{ + struct hdr hdr; + const struct server_msg *msg; + u_int i; + int n; + + for (;;) { + if (BUFFER_USED(c->in) < sizeof hdr) + return (0); + memcpy(&hdr, BUFFER_OUT(c->in), sizeof hdr); + if (BUFFER_USED(c->in) < (sizeof hdr) + hdr.size) + return (0); + buffer_remove(c->in, sizeof hdr); + + for (i = 0; i < nitems(server_msg_table); i++) { + msg = server_msg_table + i; + if (msg->type == hdr.type) { + if ((n = msg->fn(&hdr, c)) != 0) + return (n); + break; + } + } + if (i == nitems(server_msg_table)) + fatalx("unexpected message"); + } +} + +void printflike2 +server_msg_fn_command_error(struct cmd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + server_write_client(ctx->cmdclient, MSG_ERROR, msg, strlen(msg)); + xfree(msg); +} + +void printflike2 +server_msg_fn_command_print(struct cmd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + server_write_client(ctx->cmdclient, MSG_PRINT, msg, strlen(msg)); + xfree(msg); +} + +void printflike2 +server_msg_fn_command_info(struct cmd_ctx *ctx, const char *fmt, ...) +{ + va_list ap; + char *msg; + + if (be_quiet) + return; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + server_write_client(ctx->cmdclient, MSG_PRINT, msg, strlen(msg)); + xfree(msg); +} + +int +server_msg_fn_command(struct hdr *hdr, struct client *c) +{ + struct msg_command_data data; + struct cmd_ctx ctx; + struct cmd_list *cmdlist; + struct cmd *cmd; + + if (hdr->size < sizeof data) + fatalx("bad MSG_COMMAND size"); + buffer_read(c->in, &data, sizeof data); + + cmdlist = cmd_list_recv(c->in); + server_activity = time(NULL); + + ctx.error = server_msg_fn_command_error; + ctx.print = server_msg_fn_command_print; + ctx.info = server_msg_fn_command_info; + + ctx.msgdata = &data; + ctx.curclient = NULL; + ctx.cursession = NULL; + + ctx.cmdclient = c; + + if (data.pid != -1) { + TAILQ_FOREACH(cmd, cmdlist, qentry) { + if (cmd->entry->flags & CMD_CANTNEST) { + server_msg_fn_command_error(&ctx, + "sessions should be nested with care. " + "unset $TMUX to force"); + cmd_list_free(cmdlist); + server_write_client(c, MSG_EXIT, NULL, 0); + return (0); + } + } + } + + if (cmd_list_exec(cmdlist, &ctx) != 1) + server_write_client(c, MSG_EXIT, NULL, 0); + cmd_list_free(cmdlist); + return (0); +} + +int +server_msg_fn_identify(struct hdr *hdr, struct client *c) +{ + struct msg_identify_data data; + char *term; + + if (hdr->size < sizeof data) + fatalx("bad MSG_IDENTIFY size"); + buffer_read(c->in, &data, sizeof data); + term = cmd_recv_string(c->in); + + log_debug("identify msg from client: %u,%u (%d)", + data.sx, data.sy, data.version); + + if (data.version != PROTOCOL_VERSION) { +#define MSG "protocol version mismatch" + server_write_client(c, MSG_ERROR, MSG, (sizeof MSG) - 1); +#undef MSG + return (0); + } + + c->tty.sx = data.sx; + c->tty.sy = data.sy; + + c->cwd = NULL; + if (*data.cwd != '\0') + c->cwd = xstrdup(data.cwd); + + data.tty[(sizeof data.tty) - 1] = '\0'; + tty_init(&c->tty, data.tty, term); + if (data.flags & IDENTIFY_UTF8) + c->tty.flags |= TTY_UTF8; + if (data.flags & IDENTIFY_256COLOURS) + c->tty.term_flags |= TERM_256COLOURS; + else if (data.flags & IDENTIFY_88COLOURS) + c->tty.term_flags |= TERM_88COLOURS; + if (data.flags & IDENTIFY_HASDEFAULTS) + c->tty.term_flags |= TERM_HASDEFAULTS; + xfree(term); + + c->flags |= CLIENT_TERMINAL; + + return (0); +} + +int +server_msg_fn_resize(struct hdr *hdr, struct client *c) +{ + struct msg_resize_data data; + + if (hdr->size != sizeof data) + fatalx("bad MSG_RESIZE size"); + buffer_read(c->in, &data, sizeof data); + + log_debug("resize msg from client: %u,%u", data.sx, data.sy); + + c->tty.sx = data.sx; + if (c->tty.sx == 0) + c->tty.sx = 80; + c->tty.sy = data.sy; + if (c->tty.sy == 0) + c->tty.sy = 25; + + c->tty.cx = UINT_MAX; + c->tty.cy = UINT_MAX; + c->tty.rupper = UINT_MAX; + c->tty.rlower = UINT_MAX; + + recalculate_sizes(); + + /* Always redraw this client. */ + server_redraw_client(c); + + return (0); +} + +int +server_msg_fn_exiting(struct hdr *hdr, struct client *c) +{ + if (hdr->size != 0) + fatalx("bad MSG_EXITING size"); + + log_debug("exiting msg from client"); + + c->session = NULL; + + tty_close(&c->tty, c->flags & CLIENT_SUSPENDED); + + server_write_client(c, MSG_EXITED, NULL, 0); + + return (0); +} + +int +server_msg_fn_unlock(struct hdr *hdr, struct client *c) +{ + char *pass; + + if (hdr->size == 0) + fatalx("bad MSG_UNLOCK size"); + pass = cmd_recv_string(c->in); + + log_debug("unlock msg from client"); + + if (server_unlock(pass) != 0) { +#define MSG "bad password" + server_write_client(c, MSG_ERROR, MSG, (sizeof MSG) - 1); + server_write_client(c, MSG_EXIT, NULL, 0); + return (0); +#undef MSG + } + + server_write_client(c, MSG_EXIT, NULL, 0); + + return (0); +} + +int +server_msg_fn_wakeup(struct hdr *hdr, struct client *c) +{ + if (hdr->size != 0) + fatalx("bad MSG_WAKEUP size"); + + log_debug("wakeup msg from client"); + + c->flags &= ~CLIENT_SUSPENDED; + tty_start_tty(&c->tty); + server_redraw_client(c); + + return (0); +} diff --git a/server.c b/server.c new file mode 100644 index 00000000..67931c99 --- /dev/null +++ b/server.c @@ -0,0 +1,1105 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +/* + * Main server functions. + */ + +/* Client list. */ +struct clients clients; + +int server_create_socket(void); +int server_main(int); +void server_shutdown(void); +void server_child_signal(void); +void server_fill_windows(struct pollfd **); +void server_handle_windows(struct pollfd **); +void server_fill_clients(struct pollfd **); +void server_handle_clients(struct pollfd **); +struct client *server_accept_client(int); +void server_handle_client(struct client *); +void server_handle_window(struct window *, struct window_pane *); +int server_check_window_bell(struct session *, struct window *, + struct window_pane *); +int server_check_window_activity(struct session *, + struct window *); +int server_check_window_content(struct session *, struct window *, + struct window_pane *); +void server_lost_client(struct client *); +void server_check_window(struct window *); +void server_check_redraw(struct client *); +void server_redraw_locked(struct client *); +void server_check_timers(struct client *); +void server_second_timers(void); +int server_update_socket(void); + +/* Create a new client. */ +struct client * +server_create_client(int fd) +{ + struct client *c; + int mode; + u_int i; + + if ((mode = fcntl(fd, F_GETFL)) == -1) + fatal("fcntl failed"); + if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) + fatal("fcntl failed"); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + fatal("fcntl failed"); + + c = xcalloc(1, sizeof *c); + c->fd = fd; + c->in = buffer_create(BUFSIZ); + c->out = buffer_create(BUFSIZ); + + ARRAY_INIT(&c->prompt_hdata); + + c->tty.fd = -1; + c->title = NULL; + + c->session = NULL; + c->tty.sx = 80; + c->tty.sy = 25; + screen_init(&c->status, c->tty.sx, 1, 0); + + c->message_string = NULL; + + c->prompt_string = NULL; + c->prompt_buffer = NULL; + c->prompt_index = 0; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + if (ARRAY_ITEM(&clients, i) == NULL) { + ARRAY_SET(&clients, i, c); + return (c); + } + } + ARRAY_ADD(&clients, c); + return (c); +} + +/* Find client index. */ +int +server_client_index(struct client *c) +{ + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + if (c == ARRAY_ITEM(&clients, i)) + return (i); + } + return (-1); +} + +/* Fork new server. */ +int +server_start(char *path) +{ + int pair[2], srv_fd; + char *cause; + char rpathbuf[MAXPATHLEN]; + + /* The first client is special and gets a socketpair; create it. */ + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) + fatal("socketpair failed"); + + switch (fork()) { + case -1: + fatal("fork failed"); + case 0: + break; + default: + close(pair[1]); + return (pair[0]); + } + close(pair[0]); + + /* + * Must daemonise before loading configuration as the PID changes so + * $TMUX would be wrong for sessions created in the config file. + */ + if (daemon(1, 1) != 0) + fatal("daemon failed"); + + ARRAY_INIT(&windows); + ARRAY_INIT(&clients); + ARRAY_INIT(&sessions); + key_bindings_init(); + utf8_build(); + + server_locked = 0; + server_password = NULL; + server_activity = time(NULL); + + start_time = time(NULL); + socket_path = path; + + if (cfg_file != NULL && load_cfg(cfg_file, &cause) != 0) { + log_warnx("%s", cause); + exit(1); + } + logfile("server"); + + log_debug("server started, pid %ld", (long) getpid()); + log_debug("socket path %s", socket_path); + + if (realpath(socket_path, rpathbuf) == NULL) + strlcpy(rpathbuf, socket_path, sizeof rpathbuf); + setproctitle("server (%s)", rpathbuf); + + srv_fd = server_create_socket(); + server_create_client(pair[1]); + + exit(server_main(srv_fd)); +} + +/* Create server socket. */ +int +server_create_socket(void) +{ + struct sockaddr_un sa; + size_t size; + mode_t mask; + int fd, mode; + + memset(&sa, 0, sizeof sa); + sa.sun_family = AF_UNIX; + size = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path); + if (size >= sizeof sa.sun_path) { + errno = ENAMETOOLONG; + fatal("socket failed"); + } + unlink(sa.sun_path); + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + fatal("socket failed"); + + mask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + if (bind(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1) + fatal("bind failed"); + umask(mask); + + if (listen(fd, 16) == -1) + fatal("listen failed"); + + if ((mode = fcntl(fd, F_GETFL)) == -1) + fatal("fcntl failed"); + if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) + fatal("fcntl failed"); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + fatal("fcntl failed"); + + return (fd); +} + +/* Main server loop. */ +int +server_main(int srv_fd) +{ + struct window *w; + struct pollfd *pfds, *pfd; + int nfds, xtimeout; + u_int i, n; + time_t now, last; + + siginit(); + + last = time(NULL); + + pfds = NULL; + for (;;) { + /* If sigterm, kill all windows and clients. */ + if (sigterm) + server_shutdown(); + + /* Handle child exit. */ + if (sigchld) { + server_child_signal(); + sigchld = 0; + } + + /* Recreate socket on SIGUSR1. */ + if (sigusr1) { + close(srv_fd); + srv_fd = server_create_socket(); + sigusr1 = 0; + } + + /* Initialise pollfd array. */ + nfds = 1; + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w != NULL) + nfds += window_count_panes(w); + } + nfds += ARRAY_LENGTH(&clients) * 2; + pfds = xrealloc(pfds, nfds, sizeof *pfds); + memset(pfds, 0, nfds * sizeof *pfds); + pfd = pfds; + + /* Fill server socket. */ + pfd->fd = srv_fd; + pfd->events = POLLIN; + pfd++; + + /* Fill window and client sockets. */ + server_fill_windows(&pfd); + server_fill_clients(&pfd); + + /* Update socket permissions. */ + xtimeout = INFTIM; + if (sigterm || server_update_socket() != 0) + xtimeout = POLL_TIMEOUT; + + /* Do the poll. */ + if ((nfds = poll(pfds, nfds, xtimeout)) == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + fatal("poll failed"); + } + pfd = pfds; + + /* Handle server socket. */ + if (pfd->revents & (POLLERR|POLLNVAL|POLLHUP)) + fatalx("lost server socket"); + if (pfd->revents & POLLIN) { + server_accept_client(srv_fd); + continue; + } + pfd++; + + /* Call second-based timers. */ + now = time(NULL); + if (now != last) { + last = now; + server_second_timers(); + } + + /* Set window names. */ + set_window_names(); + + /* + * Handle window and client sockets. Clients can create + * windows, so windows must come first to avoid messing up by + * increasing the array size. + */ + server_handle_windows(&pfd); + server_handle_clients(&pfd); + + /* + * If we have no sessions and clients left, let's get out + * of here... + */ + n = 0; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + if (ARRAY_ITEM(&sessions, i) != NULL) + n++; + } + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + if (ARRAY_ITEM(&clients, i) != NULL) + n++; + } + if (n == 0) + break; + } + if (pfds != NULL) + xfree(pfds); + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + if (ARRAY_ITEM(&sessions, i) != NULL) + session_destroy(ARRAY_ITEM(&sessions, i)); + } + ARRAY_FREE(&sessions); + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + if (ARRAY_ITEM(&clients, i) != NULL) + server_lost_client(ARRAY_ITEM(&clients, i)); + } + ARRAY_FREE(&clients); + + key_bindings_free(); + + close(srv_fd); + + unlink(socket_path); + xfree(socket_path); + + options_free(&global_options); + options_free(&global_window_options); + if (server_password != NULL) + xfree(server_password); + + return (0); +} + +/* Kill all clients. */ +void +server_shutdown(void) +{ + struct session *s; + struct client *c; + u_int i, j; + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + for (j = 0; j < ARRAY_LENGTH(&clients); j++) { + c = ARRAY_ITEM(&clients, j); + if (c != NULL && c->session == s) { + s = NULL; + break; + } + } + if (s != NULL) + session_destroy(s); + } + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c != NULL) + server_write_client(c, MSG_SHUTDOWN, NULL, 0); + } +} + +/* Handle SIGCHLD. */ +void +server_child_signal(void) +{ + struct window *w; + struct window_pane *wp; + int status; + pid_t pid; + u_int i; + + for (;;) { + switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) { + case -1: + if (errno == ECHILD) + return; + fatal("waitpid"); + case 0: + return; + } + if (!WIFSTOPPED(status)) + continue; + if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) + continue; + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL) + continue; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->pid == pid) { + if (killpg(pid, SIGCONT) != 0) + kill(pid, SIGCONT); + } + } + } + } +} + +/* Fill window pollfds. */ +void +server_fill_windows(struct pollfd **pfd) +{ + struct window *w; + struct window_pane *wp; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL) + continue; + + TAILQ_FOREACH(wp, &w->panes, entry) { + (*pfd)->fd = wp->fd; + if (wp->fd != -1) { + (*pfd)->events = POLLIN; + if (BUFFER_USED(wp->out) > 0) + (*pfd)->events |= POLLOUT; + } + (*pfd)++; + } + } +} + +/* Handle window pollfds. */ +void +server_handle_windows(struct pollfd **pfd) +{ + struct window *w; + struct window_pane *wp; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL) + continue; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->fd != -1) { + if (buffer_poll(*pfd, wp->in, wp->out) != 0) { + close(wp->fd); + wp->fd = -1; + } else + server_handle_window(w, wp); + } + (*pfd)++; + } + + server_check_window(w); + } +} + +/* Check for general redraw on client. */ +void +server_check_redraw(struct client *c) +{ + struct session *s; + struct window_pane *wp; + char title[512]; + int flags, redraw; + + if (c == NULL || c->session == NULL) + return; + s = c->session; + + flags = c->tty.flags & TTY_FREEZE; + c->tty.flags &= ~TTY_FREEZE; + + if (options_get_number(&s->options, "set-titles")) { + xsnprintf(title, sizeof title, "%s:%u:%s - \"%s\"", + s->name, s->curw->idx, s->curw->window->name, + s->curw->window->active->screen->title); + if (c->title == NULL || strcmp(title, c->title) != 0) { + if (c->title != NULL) + xfree(c->title); + c->title = xstrdup(title); + tty_set_title(&c->tty, c->title); + } + } + + if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) { + if (c->message_string != NULL) + redraw = status_message_redraw(c); + else if (c->prompt_string != NULL) + redraw = status_prompt_redraw(c); + else + redraw = status_redraw(c); + if (!redraw) + c->flags &= ~CLIENT_STATUS; + } + + if (c->flags & CLIENT_REDRAW) { + if (server_locked) + server_redraw_locked(c); + else + screen_redraw_screen(c); + c->flags &= ~CLIENT_STATUS; + } else { + TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { + if (wp->flags & PANE_REDRAW) + screen_redraw_pane(c, wp); + } + } + + if (c->flags & CLIENT_STATUS) + screen_redraw_status(c); + + c->tty.flags |= flags; + + c->flags &= ~(CLIENT_REDRAW|CLIENT_STATUS); +} + +/* Redraw client when locked. */ +void +server_redraw_locked(struct client *c) +{ + struct screen_write_ctx ctx; + struct screen screen; + u_int colour, xx, yy, i; + int style; + + xx = c->tty.sx; + yy = c->tty.sy - 1; + if (xx == 0 || yy == 0) + return; + colour = options_get_number( + &global_window_options, "clock-mode-colour"); + style = options_get_number( + &global_window_options, "clock-mode-style"); + + screen_init(&screen, xx, yy, 0); + + screen_write_start(&ctx, NULL, &screen); + clock_draw(&ctx, colour, style); + screen_write_stop(&ctx); + + for (i = 0; i < screen_size_y(&screen); i++) + tty_draw_line(&c->tty, &screen, i, 0, 0); + screen_redraw_status(c); + + screen_free(&screen); +} + +/* Check for timers on client. */ +void +server_check_timers(struct client *c) +{ + struct session *s; + struct timeval tv; + u_int interval; + + if (c == NULL || c->session == NULL) + return; + s = c->session; + + if (gettimeofday(&tv, NULL) != 0) + fatal("gettimeofday"); + + if (c->message_string != NULL && timercmp(&tv, &c->message_timer, >)) + status_message_clear(c); + + if (c->message_string != NULL || c->prompt_string != NULL) { + /* + * Don't need timed redraw for messages/prompts so bail now. + * The status timer isn't reset when they are redrawn anyway. + */ + return; + } + if (!options_get_number(&s->options, "status")) + return; + + /* Check timer; resolution is only a second so don't be too clever. */ + interval = options_get_number(&s->options, "status-interval"); + if (interval == 0) + return; + if (tv.tv_sec < c->status_timer.tv_sec || + ((u_int) tv.tv_sec) - c->status_timer.tv_sec >= interval) + c->flags |= CLIENT_STATUS; +} + +/* Fill client pollfds. */ +void +server_fill_clients(struct pollfd **pfd) +{ + struct client *c; + struct window *w; + struct window_pane *wp; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + + server_check_timers(c); + server_check_redraw(c); + + if (c == NULL) + (*pfd)->fd = -1; + else { + (*pfd)->fd = c->fd; + (*pfd)->events = POLLIN; + if (BUFFER_USED(c->out) > 0) + (*pfd)->events |= POLLOUT; + } + (*pfd)++; + + if (c == NULL || c->flags & CLIENT_SUSPENDED || + c->tty.fd == -1 || c->session == NULL) + (*pfd)->fd = -1; + else { + (*pfd)->fd = c->tty.fd; + (*pfd)->events = POLLIN; + if (BUFFER_USED(c->tty.out) > 0) + (*pfd)->events |= POLLOUT; + } + (*pfd)++; + } + + /* + * Clear any window redraw flags (will have been redrawn as part of + * client). + */ + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL) + continue; + + w->flags &= ~WINDOW_REDRAW; + TAILQ_FOREACH(wp, &w->panes, entry) + wp->flags &= ~PANE_REDRAW; + } +} + +/* Handle client pollfds. */ +void +server_handle_clients(struct pollfd **pfd) +{ + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + + if (c != NULL) { + if (buffer_poll(*pfd, c->in, c->out) != 0) { + server_lost_client(c); + (*pfd) += 2; + continue; + } else + server_msg_dispatch(c); + } + (*pfd)++; + + if (c != NULL && !(c->flags & CLIENT_SUSPENDED) && + c->tty.fd != -1 && c->session != NULL) { + if (buffer_poll(*pfd, c->tty.in, c->tty.out) != 0) + server_lost_client(c); + else + server_handle_client(c); + } + (*pfd)++; + } +} + +/* accept(2) and create new client. */ +struct client * +server_accept_client(int srv_fd) +{ + struct sockaddr_storage sa; + socklen_t slen = sizeof sa; + int fd; + + fd = accept(srv_fd, (struct sockaddr *) &sa, &slen); + if (fd == -1) { + if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED) + return (NULL); + fatal("accept failed"); + } + if (sigterm) { + close(fd); + return (NULL); + } + return (server_create_client(fd)); +} + +/* Input data from client. */ +void +server_handle_client(struct client *c) +{ + struct window_pane *wp; + struct screen *s; + struct timeval tv; + struct key_binding *bd; + int key, prefix, status, xtimeout; + int mode; + u_char mouse[3]; + + xtimeout = options_get_number(&c->session->options, "repeat-time"); + if (xtimeout != 0 && c->flags & CLIENT_REPEAT) { + if (gettimeofday(&tv, NULL) != 0) + fatal("gettimeofday"); + if (timercmp(&tv, &c->repeat_timer, >)) + c->flags &= ~(CLIENT_PREFIX|CLIENT_REPEAT); + } + + /* Process keys. */ + prefix = options_get_number(&c->session->options, "prefix"); + while (tty_keys_next(&c->tty, &key, mouse) == 0) { + server_activity = time(NULL); + + if (c->session == NULL) + return; + wp = c->session->curw->window->active; /* could die */ + + status_message_clear(c); + if (c->prompt_string != NULL) { + status_prompt_key(c, key); + continue; + } + if (server_locked) + continue; + + /* Check for mouse keys. */ + if (key == KEYC_MOUSE) { + window_pane_mouse(wp, c, mouse[0], mouse[1], mouse[2]); + continue; + } + + /* No previous prefix key. */ + if (!(c->flags & CLIENT_PREFIX)) { + if (key == prefix) + c->flags |= CLIENT_PREFIX; + else + window_pane_key(wp, c, key); + continue; + } + + /* Prefix key already pressed. Reset prefix and lookup key. */ + c->flags &= ~CLIENT_PREFIX; + if ((bd = key_bindings_lookup(key)) == NULL) { + /* If repeating, treat this as a key, else ignore. */ + if (c->flags & CLIENT_REPEAT) { + c->flags &= ~CLIENT_REPEAT; + if (key == prefix) + 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 (key == prefix) + 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; + if (gettimeofday(&c->repeat_timer, NULL) != 0) + fatal("gettimeofday"); + timeradd(&c->repeat_timer, &tv, &c->repeat_timer); + } + + /* Dispatch the command. */ + key_bindings_dispatch(bd, c); + } + if (c->session == NULL) + return; + wp = c->session->curw->window->active; /* could die - do each loop */ + s = wp->screen; + + /* Ensure cursor position and mode settings. */ + status = options_get_number(&c->session->options, "status"); + if (wp->yoff + s->cy < c->tty.sy - status) + tty_cursor(&c->tty, s->cx, s->cy, wp->xoff, wp->yoff); + + mode = s->mode; + if (server_locked) + mode &= ~TTY_NOCURSOR; + tty_update_mode(&c->tty, mode); +} + +/* Lost a client. */ +void +server_lost_client(struct client *c) +{ + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + if (ARRAY_ITEM(&clients, i) == c) + ARRAY_SET(&clients, i, NULL); + } + + tty_free(&c->tty, c->flags & CLIENT_SUSPENDED); + + screen_free(&c->status); + + if (c->title != NULL) + xfree(c->title); + + if (c->message_string != NULL) + xfree(c->message_string); + + if (c->prompt_string != NULL) + xfree(c->prompt_string); + if (c->prompt_buffer != NULL) + xfree(c->prompt_buffer); + for (i = 0; i < ARRAY_LENGTH(&c->prompt_hdata); i++) + xfree(ARRAY_ITEM(&c->prompt_hdata, i)); + ARRAY_FREE(&c->prompt_hdata); + + if (c->cwd != NULL) + xfree(c->cwd); + + close(c->fd); + buffer_destroy(c->in); + buffer_destroy(c->out); + xfree(c); + + recalculate_sizes(); +} + +/* Handle window data. */ +void +server_handle_window(struct window *w, struct window_pane *wp) +{ + struct session *s; + u_int i; + int update; + + window_pane_parse(wp); + + if ((w->flags & (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_CONTENT)) == 0) + return; + + update = 0; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL || !session_has(s, w)) + continue; + + update += server_check_window_bell(s, w, wp); + update += server_check_window_activity(s, w); + update += server_check_window_content(s, w, wp); + } + if (update) + server_status_window(w); + + w->flags &= ~(WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_CONTENT); +} + +int +server_check_window_bell( + struct session *s, struct window *w, struct window_pane *wp) +{ + struct client *c; + u_int i; + int action; + + if (!(w->flags & WINDOW_BELL)) + return (0); + if (session_alert_has_window(s, w, WINDOW_BELL)) + return (0); + session_alert_add(s, w, WINDOW_BELL); + + action = options_get_number(&s->options, "bell-action"); + switch (action) { + case BELL_ANY: + if (s->flags & SESSION_UNATTACHED) + break; + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c != NULL && c->session == s) + tty_putcode(&c->tty, TTYC_BEL); + } + break; + case BELL_CURRENT: + if (w->active != wp) + break; + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c != NULL && c->session == s) + tty_putcode(&c->tty, TTYC_BEL); + } + break; + } + return (1); +} + +int +server_check_window_activity(struct session *s, struct window *w) +{ + if (!(w->flags & WINDOW_ACTIVITY)) + return (0); + if (!options_get_number(&w->options, "monitor-activity")) + return (0); + if (session_alert_has_window(s, w, WINDOW_ACTIVITY)) + return (0); + session_alert_add(s, w, WINDOW_ACTIVITY); + return (1); +} + +int +server_check_window_content( + struct session *s, struct window *w, struct window_pane *wp) +{ + char *found, *ptr; + + if (!(w->flags & WINDOW_CONTENT)) + return (0); + if ((ptr = options_get_string(&w->options, "monitor-content")) == NULL) + return (0); + if (*ptr == '\0') + return (0); + if (session_alert_has_window(s, w, WINDOW_CONTENT)) + return (0); + if ((found = window_pane_search(wp, ptr)) == NULL) + return (0); + session_alert_add(s, w, WINDOW_CONTENT); + xfree(found); + return (1); +} + +/* Check if window still exists.. */ +void +server_check_window(struct window *w) +{ + struct window_pane *wp, *wq; + struct client *c; + struct session *s; + struct winlink *wl; + u_int i, j; + int destroyed, flag; + + flag = options_get_number(&w->options, "remain-on-exit"); + + destroyed = 1; + + wp = TAILQ_FIRST(&w->panes); + while (wp != NULL) { + wq = TAILQ_NEXT(wp, entry); + if (wp->fd != -1) + destroyed = 0; + else if (!flag) { + window_remove_pane(w, wp); + server_redraw_window(w); + layout_refresh(w, 0); + } + wp = wq; + } + + if (!destroyed) + return; + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s == NULL) + continue; + if (!session_has(s, w)) + continue; + + restart: + /* Detach window and either redraw or kill clients. */ + RB_FOREACH(wl, winlinks, &s->windows) { + if (wl->window != w) + continue; + destroyed = session_detach(s, wl); + for (j = 0; j < ARRAY_LENGTH(&clients); j++) { + c = ARRAY_ITEM(&clients, j); + if (c == NULL || c->session != s) + continue; + if (!destroyed) { + server_redraw_client(c); + continue; + } + c->session = NULL; + server_write_client(c, MSG_EXIT, NULL, 0); + } + /* If the session was destroyed, bail now. */ + if (destroyed) + break; + goto restart; + } + } + + recalculate_sizes(); +} + +/* Call any once-per-second timers. */ +void +server_second_timers(void) +{ + struct window *w; + struct window_pane *wp; + u_int i; + int xtimeout; + struct tm now, then; + static time_t last_t = 0; + time_t t; + + t = time(NULL); + xtimeout = options_get_number(&global_options, "lock-after-time"); + if (xtimeout > 0 && t > server_activity + xtimeout) + server_lock(); + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + w = ARRAY_ITEM(&windows, i); + if (w == NULL) + continue; + + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->mode != NULL && wp->mode->timer != NULL) + wp->mode->timer(wp); + } + } + + /* Check for a minute having passed. */ + gmtime_r(&t, &now); + gmtime_r(&last_t, &then); + if (now.tm_min == then.tm_min) + return; + last_t = t; + + /* If locked, redraw all clients. */ + if (server_locked) { + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + if (ARRAY_ITEM(&clients, i) != NULL) + server_redraw_client(ARRAY_ITEM(&clients, i)); + } + } +} + +/* Update socket execute permissions based on whether sessions are attached. */ +int +server_update_socket(void) +{ + struct session *s; + u_int i; + static int last = -1; + int n; + + n = 0; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s != NULL && !(s->flags & SESSION_UNATTACHED)) { + n++; + break; + } + } + + if (n != last) { + last = n; + if (n != 0) + chmod(socket_path, S_IRWXU); + else + chmod(socket_path, S_IRUSR|S_IWUSR); + } + + return (n); +} diff --git a/session.c b/session.c new file mode 100644 index 00000000..3b65127c --- /dev/null +++ b/session.c @@ -0,0 +1,378 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include + +#include "tmux.h" + +/* Global session list. */ +struct sessions sessions; + +struct winlink *session_next_activity(struct session *, struct winlink *); +struct winlink *session_previous_activity(struct session *, struct winlink *); + +void +session_alert_cancel(struct session *s, struct winlink *wl) +{ + struct session_alert *sa, *sb; + + sa = SLIST_FIRST(&s->alerts); + while (sa != NULL) { + sb = sa; + sa = SLIST_NEXT(sa, entry); + + if (wl == NULL || sb->wl == wl) { + SLIST_REMOVE(&s->alerts, sb, session_alert, entry); + xfree(sb); + } + } +} + +void +session_alert_add(struct session *s, struct window *w, int type) +{ + struct session_alert *sa; + struct winlink *wl; + + RB_FOREACH(wl, winlinks, &s->windows) { + if (wl == s->curw) + continue; + + if (wl->window == w && + !session_alert_has(s, wl, type)) { + sa = xmalloc(sizeof *sa); + sa->wl = wl; + sa->type = type; + SLIST_INSERT_HEAD(&s->alerts, sa, entry); + } + } +} + +int +session_alert_has(struct session *s, struct winlink *wl, int type) +{ + struct session_alert *sa; + + SLIST_FOREACH(sa, &s->alerts, entry) { + if (sa->wl == wl && sa->type == type) + return (1); + } + + return (0); +} + +int +session_alert_has_window(struct session *s, struct window *w, int type) +{ + struct session_alert *sa; + + SLIST_FOREACH(sa, &s->alerts, entry) { + if (sa->wl->window == w && sa->type == type) + return (1); + } + + return (0); +} + +/* Find session by name. */ +struct session * +session_find(const char *name) +{ + struct session *s; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + s = ARRAY_ITEM(&sessions, i); + if (s != NULL && strcmp(s->name, name) == 0) + return (s); + } + + return (NULL); +} + +/* Create a new session. */ +struct session * +session_create(const char *name, + const char *cmd, const char *cwd, u_int sx, u_int sy, char **cause) +{ + struct session *s; + u_int i; + + s = xmalloc(sizeof *s); + s->flags = 0; + if (gettimeofday(&s->tv, NULL) != 0) + fatal("gettimeofday"); + s->curw = NULL; + SLIST_INIT(&s->lastw); + RB_INIT(&s->windows); + SLIST_INIT(&s->alerts); + paste_init_stack(&s->buffers); + options_init(&s->options, &global_options); + + s->sx = sx; + s->sy = sy; + + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { + if (ARRAY_ITEM(&sessions, i) == NULL) { + ARRAY_SET(&sessions, i, s); + break; + } + } + if (i == ARRAY_LENGTH(&sessions)) + ARRAY_ADD(&sessions, s); + + if (name != NULL) + s->name = xstrdup(name); + else + xasprintf(&s->name, "%u", i); + if (session_new(s, NULL, cmd, cwd, -1, cause) == NULL) { + session_destroy(s); + return (NULL); + } + session_select(s, 0); + + log_debug("session %s created", s->name); + + return (s); +} + +/* Destroy a session. */ +void +session_destroy(struct session *s) +{ + u_int i; + + log_debug("session %s destroyed", s->name); + + if (session_index(s, &i) != 0) + fatalx("session not found"); + ARRAY_SET(&sessions, i, NULL); + while (!ARRAY_EMPTY(&sessions) && ARRAY_LAST(&sessions) == NULL) + ARRAY_TRUNC(&sessions, 1); + + session_alert_cancel(s, NULL); + options_free(&s->options); + paste_free_stack(&s->buffers); + + while (!SLIST_EMPTY(&s->lastw)) + winlink_stack_remove(&s->lastw, SLIST_FIRST(&s->lastw)); + while (!RB_EMPTY(&s->windows)) + winlink_remove(&s->windows, RB_ROOT(&s->windows)); + + xfree(s->name); + xfree(s); +} + +/* Find session index. */ +int +session_index(struct session *s, u_int *i) +{ + for (*i = 0; *i < ARRAY_LENGTH(&sessions); (*i)++) { + if (s == ARRAY_ITEM(&sessions, *i)) + return (0); + } + return (-1); +} + +/* Create a new window on a session. */ +struct winlink * +session_new(struct session *s, + const char *name, const char *cmd, const char *cwd, int idx, char **cause) +{ + struct window *w; + const char **env; + u_int hlimit; + + env = server_fill_environ(s); + + hlimit = options_get_number(&s->options, "history-limit"); + w = window_create(name, cmd, cwd, env, s->sx, s->sy, hlimit, cause); + if (w == NULL) + return (NULL); + + if (options_get_number(&s->options, "set-remain-on-exit")) + options_set_number(&w->options, "remain-on-exit", 1); + + return (session_attach(s, w, idx, cause)); +} + +/* Attach a window to a session. */ +struct winlink * +session_attach(struct session *s, struct window *w, int idx, char **cause) +{ + struct winlink *wl; + + if ((wl = winlink_add(&s->windows, w, idx)) == NULL) + xasprintf(cause, "index in use: %d", idx); + return (wl); +} + +/* Detach a window from a session. */ +int +session_detach(struct session *s, struct winlink *wl) +{ + if (s->curw == wl && + session_last(s) != 0 && session_previous(s, 0) != 0) + session_next(s, 0); + + session_alert_cancel(s, wl); + winlink_stack_remove(&s->lastw, wl); + winlink_remove(&s->windows, wl); + if (RB_EMPTY(&s->windows)) { + session_destroy(s); + return (1); + } + return (0); +} + +/* Return if session has window. */ +int +session_has(struct session *s, struct window *w) +{ + struct winlink *wl; + + RB_FOREACH(wl, winlinks, &s->windows) { + if (wl->window == w) + return (1); + } + return (0); +} + +struct winlink * +session_next_activity(struct session *s, struct winlink *wl) +{ + while (wl != NULL) { + if (session_alert_has(s, wl, WINDOW_BELL)) + break; + if (session_alert_has(s, wl, WINDOW_ACTIVITY)) + break; + if (session_alert_has(s, wl, WINDOW_CONTENT)) + break; + wl = winlink_next(&s->windows, wl); + } + return (wl); +} + +/* Move session to next window. */ +int +session_next(struct session *s, int activity) +{ + struct winlink *wl; + + if (s->curw == NULL) + return (-1); + + wl = winlink_next(&s->windows, s->curw); + if (activity) + wl = session_next_activity(s, wl); + if (wl == NULL) { + wl = RB_MIN(winlinks, &s->windows); + if (activity && ((wl = session_next_activity(s, wl)) == NULL)) + return (-1); + } + if (wl == s->curw) + return (1); + winlink_stack_remove(&s->lastw, wl); + winlink_stack_push(&s->lastw, s->curw); + s->curw = wl; + session_alert_cancel(s, wl); + return (0); +} + +struct winlink * +session_previous_activity(struct session *s, struct winlink *wl) +{ + while (wl != NULL) { + if (session_alert_has(s, wl, WINDOW_BELL)) + break; + if (session_alert_has(s, wl, WINDOW_ACTIVITY)) + break; + if (session_alert_has(s, wl, WINDOW_CONTENT)) + break; + wl = winlink_previous(&s->windows, wl); + } + return (wl); +} + +/* Move session to previous window. */ +int +session_previous(struct session *s, int activity) +{ + struct winlink *wl; + + if (s->curw == NULL) + return (-1); + + wl = winlink_previous(&s->windows, s->curw); + if (activity) + wl = session_previous_activity(s, wl); + if (wl == NULL) { + wl = RB_MAX(winlinks, &s->windows); + if (activity && (wl = session_previous_activity(s, wl)) == NULL) + return (-1); + } + if (wl == s->curw) + return (1); + winlink_stack_remove(&s->lastw, wl); + winlink_stack_push(&s->lastw, s->curw); + s->curw = wl; + session_alert_cancel(s, wl); + return (0); +} + +/* Move session to specific window. */ +int +session_select(struct session *s, int idx) +{ + struct winlink *wl; + + wl = winlink_find_by_index(&s->windows, idx); + if (wl == NULL) + return (-1); + if (wl == s->curw) + return (1); + winlink_stack_remove(&s->lastw, wl); + winlink_stack_push(&s->lastw, s->curw); + s->curw = wl; + session_alert_cancel(s, wl); + return (0); +} + +/* Move session to last used window. */ +int +session_last(struct session *s) +{ + struct winlink *wl; + + wl = SLIST_FIRST(&s->lastw); + if (wl == NULL) + return (-1); + if (wl == s->curw) + return (1); + + winlink_stack_remove(&s->lastw, wl); + winlink_stack_push(&s->lastw, s->curw); + s->curw = wl; + session_alert_cancel(s, wl); + return (0); +} diff --git a/status.c b/status.c new file mode 100644 index 00000000..a72e5a26 --- /dev/null +++ b/status.c @@ -0,0 +1,960 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +char *status_replace(struct session *, char *, time_t); +char *status_replace_popen(char **); +size_t status_width(struct winlink *); +char *status_print(struct session *, struct winlink *, struct grid_cell *); + +void status_prompt_add_history(struct client *); +char *status_prompt_complete(const char *); + +/* Draw status for client on the last lines of given context. */ +int +status_redraw(struct client *c) +{ + struct screen_write_ctx ctx; + struct session *s = c->session; + struct winlink *wl; + struct window_pane *wp; + struct screen *sc = NULL, old_status; + char *left, *right, *text, *ptr; + size_t llen, rlen, offset, xx, yy, sy; + size_t size, start, width; + struct grid_cell stdgc, gc; + int larrow, rarrow; + + left = right = NULL; + + /* Create the target screen. */ + memcpy(&old_status, &c->status, sizeof old_status); + screen_init(&c->status, c->tty.sx, 1, 0); + + /* No status line? */ + if (c->tty.sy == 0 || !options_get_number(&s->options, "status")) + goto off; + larrow = rarrow = 0; + + if (gettimeofday(&c->status_timer, NULL) != 0) + fatal("gettimeofday"); + memcpy(&stdgc, &grid_default_cell, sizeof gc); + stdgc.bg = options_get_number(&s->options, "status-fg"); + stdgc.fg = options_get_number(&s->options, "status-bg"); + stdgc.attr |= options_get_number(&s->options, "status-attr"); + + yy = c->tty.sy - 1; + if (yy == 0) + goto blank; + + /* Work out the left and right strings. */ + left = status_replace(s, options_get_string( + &s->options, "status-left"), c->status_timer.tv_sec); + llen = options_get_number(&s->options, "status-left-length"); + if (strlen(left) < llen) + llen = strlen(left); + left[llen] = '\0'; + + right = status_replace(s, options_get_string( + &s->options, "status-right"), c->status_timer.tv_sec); + rlen = options_get_number(&s->options, "status-right-length"); + if (strlen(right) < rlen) + rlen = strlen(right); + right[rlen] = '\0'; + + /* + * Figure out how much space we have for the window list. If there isn't + * enough space, just wimp out. + */ + xx = 0; + if (llen != 0) + xx += llen + 1; + if (rlen != 0) + xx += rlen + 1; + if (c->tty.sx == 0 || c->tty.sx <= xx) + goto blank; + xx = c->tty.sx - xx; + + /* + * Right. We have xx characters to fill. Find out how much is to go in + * them and the offset of the current window (it must be on screen). + */ + width = offset = 0; + RB_FOREACH(wl, winlinks, &s->windows) { + size = status_width(wl) + 1; + if (wl == s->curw) + offset = width; + width += size; + } + start = 0; + + /* If there is enough space for the total width, all is gravy. */ + if (width <= xx) + goto draw; + + /* Find size of current window text. */ + size = status_width(s->curw); + + /* + * If the offset is already on screen, we're good to draw from the + * start and just leave off the end. + */ + if (offset + size < xx) { + if (xx > 0) { + rarrow = 1; + xx--; + } + + width = xx; + goto draw; + } + + /* + * Work out how many characters we need to omit from the start. There + * are xx characters to fill, and offset + size must be the last. So, + * the start character is offset + size - xx. + */ + if (xx > 0) { + larrow = 1; + xx--; + } + + start = offset + size - xx; + if (xx > 0 && width > start + xx + 1) { /* + 1, eh? */ + rarrow = 1; + start++; + xx--; + } + width = xx; + +draw: + /* Bail here if anything is too small too. XXX. */ + if (width == 0 || xx == 0) + goto blank; + + /* Begin drawing and move to the starting position. */ + screen_write_start(&ctx, NULL, &c->status); + if (llen != 0) { + screen_write_cursormove(&ctx, 0, yy); + screen_write_puts(&ctx, &stdgc, "%s ", left); + if (larrow) + screen_write_putc(&ctx, &stdgc, ' '); + } else { + if (larrow) + screen_write_cursormove(&ctx, 1, yy); + else + screen_write_cursormove(&ctx, 0, yy); + } + + /* Draw each character in succession. */ + offset = 0; + RB_FOREACH(wl, winlinks, &s->windows) { + memcpy(&gc, &stdgc, sizeof gc); + text = status_print(s, wl, &gc); + + if (larrow == 1 && offset < start) { + if (session_alert_has(s, wl, WINDOW_ACTIVITY)) + larrow = -1; + else if (session_alert_has(s, wl, WINDOW_BELL)) + larrow = -1; + else if (session_alert_has(s, wl, WINDOW_CONTENT)) + larrow = -1; + } + + for (ptr = text; *ptr != '\0'; ptr++) { + if (offset >= start && offset < start + width) + screen_write_putc(&ctx, &gc, *ptr); + offset++; + } + + if (rarrow == 1 && offset > start + width) { + if (session_alert_has(s, wl, WINDOW_ACTIVITY)) + rarrow = -1; + else if (session_alert_has(s, wl, WINDOW_BELL)) + rarrow = -1; + else if (session_alert_has(s, wl, WINDOW_CONTENT)) + rarrow = -1; + } + + if (offset < start + width) { + if (offset >= start) { + screen_write_putc(&ctx, &stdgc, ' '); + } + offset++; + } + + xfree(text); + } + + /* Fill the remaining space if any. */ + while (offset++ < xx) + screen_write_putc(&ctx, &stdgc, ' '); + + /* Draw the last item. */ + if (rlen != 0) { + screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, yy); + screen_write_puts(&ctx, &stdgc, " %s", right); + } + + /* Draw the arrows. */ + if (larrow != 0) { + memcpy(&gc, &stdgc, sizeof gc); + if (larrow == -1) + gc.attr ^= GRID_ATTR_REVERSE; + if (llen != 0) + screen_write_cursormove(&ctx, llen + 1, yy); + else + screen_write_cursormove(&ctx, 0, yy); + screen_write_putc(&ctx, &gc, '<'); + } + if (rarrow != 0) { + memcpy(&gc, &stdgc, sizeof gc); + if (rarrow == -1) + gc.attr ^= GRID_ATTR_REVERSE; + if (rlen != 0) + screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, yy); + else + screen_write_cursormove(&ctx, c->tty.sx - 1, yy); + screen_write_putc(&ctx, &gc, '>'); + } + + goto out; + +blank: + /* Just draw the whole line as blank. */ + screen_write_start(&ctx, NULL, &c->status); + screen_write_cursormove(&ctx, 0, yy); + for (offset = 0; offset < c->tty.sx; offset++) + screen_write_putc(&ctx, &stdgc, ' '); + + goto out; + +off: + /* + * Draw the real window last line. Necessary to wipe over message if + * status is off. Not sure this is the right place for this. + */ + memcpy(&stdgc, &grid_default_cell, sizeof stdgc); + screen_write_start(&ctx, NULL, &c->status); + + sy = 0; + TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { + sy += wp->sy + 1; + sc = wp->screen; + } + + screen_write_cursormove(&ctx, 0, 0); + if (sy < c->tty.sy) { + /* If the screen is too small, use blank. */ + for (offset = 0; offset < c->tty.sx; offset++) + screen_write_putc(&ctx, &stdgc, ' '); + } else { + screen_write_copy(&ctx, + sc, 0, sc->grid->hsize + screen_size_y(sc) - 1, c->tty.sx, 1); + } + +out: + screen_write_stop(&ctx); + + if (left != NULL) + xfree(left); + if (right != NULL) + xfree(right); + + if (grid_compare(c->status.grid, old_status.grid) == 0) { + screen_free(&old_status); + return (0); + } + screen_free(&old_status); + return (1); +} + +char * +status_replace(struct session *s, char *fmt, time_t t) +{ + struct winlink *wl = s->curw; + static char out[BUFSIZ]; + char in[BUFSIZ], tmp[256], ch, *iptr, *optr, *ptr, *endptr; + char *savedptr; + size_t len; + long n; + + strftime(in, sizeof in, fmt, localtime(&t)); + in[(sizeof in) - 1] = '\0'; + + iptr = in; + optr = out; + savedptr = NULL; + + while (*iptr != '\0') { + if (optr >= out + (sizeof out) - 1) + break; + switch (ch = *iptr++) { + case '#': + errno = 0; + n = strtol(iptr, &endptr, 10); + if ((n == 0 && errno != EINVAL) || + (n == LONG_MIN && errno != ERANGE) || + (n == LONG_MAX && errno != ERANGE) || + n != 0) + iptr = endptr; + if (n <= 0) + n = LONG_MAX; + + ptr = NULL; + switch (*iptr++) { + case '(': + if (ptr == NULL) { + ptr = status_replace_popen(&iptr); + if (ptr == NULL) + break; + savedptr = ptr; + } + /* FALLTHROUGH */ + case 'H': + if (ptr == NULL) { + if (gethostname(tmp, sizeof tmp) != 0) + fatal("gethostname"); + ptr = tmp; + } + /* FALLTHROUGH */ + case 'S': + if (ptr == NULL) + ptr = s->name; + /* FALLTHROUGH */ + case 'T': + if (ptr == NULL) + ptr = wl->window->active->base.title; + len = strlen(ptr); + if ((size_t) n < len) + len = n; + if (optr + len >= out + (sizeof out) - 1) + break; + while (len > 0 && *ptr != '\0') { + *optr++ = *ptr++; + len--; + } + break; + case '#': + *optr++ = '#'; + break; + } + if (savedptr != NULL) { + xfree(savedptr); + savedptr = NULL; + } + break; + default: + *optr++ = ch; + break; + } + } + *optr = '\0'; + + return (xstrdup(out)); +} + +char * +status_replace_popen(char **iptr) +{ + FILE *f; + char *buf, *cmd, *ptr; + int lastesc; + size_t len; + + if (**iptr == '\0') + return (NULL); + if (**iptr == ')') { /* no command given */ + (*iptr)++; + return (NULL); + } + + buf = NULL; + + cmd = xmalloc(strlen(*iptr) + 1); + len = 0; + + lastesc = 0; + for (; **iptr != '\0'; (*iptr)++) { + if (!lastesc && **iptr == ')') + break; /* unescaped ) is the end */ + if (!lastesc && **iptr == '\\') { + lastesc = 1; + continue; /* skip \ if not escaped */ + } + lastesc = 0; + cmd[len++] = **iptr; + } + if (**iptr == '\0') /* no terminating ) */ + goto out; + (*iptr)++; /* skip final ) */ + cmd[len] = '\0'; + + if ((f = popen(cmd, "r")) == NULL) + goto out; + + if ((buf = fgetln(f, &len)) == NULL) { + pclose(f); + goto out; + } + if (buf[len - 1] == '\n') { + buf[len - 1] = '\0'; + buf = xstrdup(buf); + } else { + ptr = xmalloc(len + 1); + memcpy(ptr, buf, len); + ptr[len] = '\0'; + buf = ptr; + } + pclose(f); + +out: + xfree(cmd); + return (buf); +} + +size_t +status_width(struct winlink *wl) +{ + return (xsnprintf(NULL, 0, "%d:%s ", wl->idx, wl->window->name)); +} + +char * +status_print(struct session *s, struct winlink *wl, struct grid_cell *gc) +{ + char *text, flag; + u_char fg, bg, attr; + + fg = options_get_number(&wl->window->options, "window-status-fg"); + if (fg != 8) + gc->fg = fg; + bg = options_get_number(&wl->window->options, "window-status-bg"); + if (bg != 8) + gc->bg = bg; + attr = options_get_number(&wl->window->options, "window-status-attr"); + if (attr != 0) + gc->attr = attr; + + flag = ' '; + if (wl == SLIST_FIRST(&s->lastw)) + flag = '-'; + if (wl == s->curw) + flag = '*'; + + if (session_alert_has(s, wl, WINDOW_ACTIVITY)) { + flag = '#'; + gc->attr ^= GRID_ATTR_REVERSE; + } else if (session_alert_has(s, wl, WINDOW_BELL)) { + flag = '!'; + gc->attr ^= GRID_ATTR_REVERSE; + } else if (session_alert_has(s, wl, WINDOW_CONTENT)) { + flag = '+'; + gc->attr ^= GRID_ATTR_REVERSE; + } + + xasprintf(&text, "%d:%s%c", wl->idx, wl->window->name, flag); + return (text); +} + +void +status_message_set(struct client *c, const char *msg) +{ + struct timeval tv; + int delay; + + delay = options_get_number(&c->session->options, "display-time"); + tv.tv_sec = delay / 1000; + tv.tv_usec = (delay % 1000) * 1000L; + + c->message_string = xstrdup(msg); + if (gettimeofday(&c->message_timer, NULL) != 0) + fatal("gettimeofday"); + timeradd(&c->message_timer, &tv, &c->message_timer); + + c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); + c->flags |= CLIENT_STATUS; +} + +void +status_message_clear(struct client *c) +{ + if (c->message_string == NULL) + return; + + xfree(c->message_string); + c->message_string = NULL; + + c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); + c->flags |= CLIENT_REDRAW; +} + +/* Draw client message on status line of present else on last line. */ +int +status_message_redraw(struct client *c) +{ + struct screen_write_ctx ctx; + struct session *s = c->session; + struct screen old_status; + size_t len; + struct grid_cell gc; + + if (c->tty.sx == 0 || c->tty.sy == 0) + return (0); + memcpy(&old_status, &c->status, sizeof old_status); + screen_init(&c->status, c->tty.sx, 1, 0); + + len = strlen(c->message_string); + if (len > c->tty.sx) + len = c->tty.sx; + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.bg = options_get_number(&s->options, "message-fg"); + gc.fg = options_get_number(&s->options, "message-bg"); + gc.attr |= options_get_number(&s->options, "message-attr"); + + screen_write_start(&ctx, NULL, &c->status); + + screen_write_cursormove(&ctx, 0, 0); + screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string); + for (; len < c->tty.sx; len++) + screen_write_putc(&ctx, &gc, ' '); + + screen_write_stop(&ctx); + + if (grid_compare(c->status.grid, old_status.grid) == 0) { + screen_free(&old_status); + return (0); + } + screen_free(&old_status); + return (1); +} + +void +status_prompt_set(struct client *c, + const char *msg, int (*fn)(void *, const char *), void *data, int flags) +{ + c->prompt_string = xstrdup(msg); + + c->prompt_buffer = xstrdup(""); + c->prompt_index = 0; + + c->prompt_callback = fn; + c->prompt_data = data; + + c->prompt_hindex = 0; + + c->prompt_flags = flags; + + mode_key_init(&c->prompt_mdata, + options_get_number(&c->session->options, "status-keys"), + MODEKEY_CANEDIT); + + c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); + c->flags |= CLIENT_STATUS; +} + +void +status_prompt_clear(struct client *c) +{ + if (c->prompt_string == NULL) + return; + + mode_key_free(&c->prompt_mdata); + + xfree(c->prompt_string); + c->prompt_string = NULL; + + xfree(c->prompt_buffer); + c->prompt_buffer = NULL; + + c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); + c->flags |= CLIENT_REDRAW; +} + +/* Draw client prompt on status line of present else on last line. */ +int +status_prompt_redraw(struct client *c) +{ + struct screen_write_ctx ctx; + struct session *s = c->session; + struct screen old_status; + size_t i, size, left, len, offset, n; + char ch; + struct grid_cell gc; + + if (c->tty.sx == 0 || c->tty.sy == 0) + return (0); + memcpy(&old_status, &c->status, sizeof old_status); + screen_init(&c->status, c->tty.sx, 1, 0); + offset = 0; + + len = strlen(c->prompt_string); + if (len > c->tty.sx) + len = c->tty.sx; + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.bg = options_get_number(&s->options, "message-fg"); + gc.fg = options_get_number(&s->options, "message-bg"); + gc.attr |= options_get_number(&s->options, "message-attr"); + + screen_write_start(&ctx, NULL, &c->status); + + screen_write_cursormove(&ctx, 0, 0); + screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string); + + left = c->tty.sx - len; + if (left != 0) { + if (c->prompt_index < left) + size = strlen(c->prompt_buffer); + else { + offset = c->prompt_index - left - 1; + if (c->prompt_index == strlen(c->prompt_buffer)) + left--; + size = left; + } + if (c->prompt_flags & PROMPT_HIDDEN) { + n = strlen(c->prompt_buffer); + if (n > left) + n = left; + for (i = 0; i < n; i++) + screen_write_putc(&ctx, &gc, '*'); + } else { + screen_write_puts(&ctx, &gc, + "%.*s", (int) left, c->prompt_buffer + offset); + } + + for (i = len + size; i < c->tty.sx; i++) + screen_write_putc(&ctx, &gc, ' '); + } + + /* Draw a fake cursor. */ + screen_write_cursormove(&ctx, len + c->prompt_index - offset, 0); + if (c->prompt_index == strlen(c->prompt_buffer)) + ch = ' '; + else { + if (c->prompt_flags & PROMPT_HIDDEN) + ch = '*'; + else + ch = c->prompt_buffer[c->prompt_index]; + } + if (ch == '\0') + ch = ' '; + gc.attr ^= GRID_ATTR_REVERSE; + screen_write_putc(&ctx, &gc, ch); + + screen_write_stop(&ctx); + + if (grid_compare(c->status.grid, old_status.grid) == 0) { + screen_free(&old_status); + return (0); + } + screen_free(&old_status); + return (1); +} + +/* Handle keys in prompt. */ +void +status_prompt_key(struct client *c, int key) +{ + struct paste_buffer *pb; + char *s, *first, *last, word[64]; + size_t size, n, off, idx; + + size = strlen(c->prompt_buffer); + switch (mode_key_lookup(&c->prompt_mdata, key)) { + case MODEKEYCMD_LEFT: + if (c->prompt_index > 0) { + c->prompt_index--; + c->flags |= CLIENT_STATUS; + } + break; + case MODEKEYCMD_RIGHT: + if (c->prompt_index < size) { + c->prompt_index++; + c->flags |= CLIENT_STATUS; + } + break; + case MODEKEYCMD_STARTOFLINE: + if (c->prompt_index != 0) { + c->prompt_index = 0; + c->flags |= CLIENT_STATUS; + } + break; + case MODEKEYCMD_ENDOFLINE: + if (c->prompt_index != size) { + c->prompt_index = size; + c->flags |= CLIENT_STATUS; + } + break; + case MODEKEYCMD_COMPLETE: + if (*c->prompt_buffer == '\0') + break; + + idx = c->prompt_index; + if (idx != 0) + idx--; + + /* Find the word we are in. */ + first = c->prompt_buffer + idx; + while (first > c->prompt_buffer && *first != ' ') + first--; + while (*first == ' ') + first++; + last = c->prompt_buffer + idx; + while (*last != '\0' && *last != ' ') + last++; + while (*last == ' ') + last--; + if (*last != '\0') + last++; + if (last <= first || + ((size_t) (last - first)) > (sizeof word) - 1) + break; + memcpy(word, first, last - first); + word[last - first] = '\0'; + + /* And try to complete it. */ + if ((s = status_prompt_complete(word)) == NULL) + break; + + /* Trim out word. */ + n = size - (last - c->prompt_buffer) + 1; /* with \0 */ + memmove(first, last, n); + size -= last - first; + + /* Insert the new word. */ + size += strlen(s); + off = first - c->prompt_buffer; + c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1); + first = c->prompt_buffer + off; + memmove(first + strlen(s), first, n); + memcpy(first, s, strlen(s)); + + c->prompt_index = (first - c->prompt_buffer) + strlen(s); + + c->flags |= CLIENT_STATUS; + break; + case MODEKEYCMD_BACKSPACE: + if (c->prompt_index != 0) { + if (c->prompt_index == size) + c->prompt_buffer[--c->prompt_index] = '\0'; + else { + memmove(c->prompt_buffer + c->prompt_index - 1, + c->prompt_buffer + c->prompt_index, + size + 1 - c->prompt_index); + c->prompt_index--; + } + c->flags |= CLIENT_STATUS; + } + break; + case MODEKEYCMD_DELETE: + if (c->prompt_index != size) { + memmove(c->prompt_buffer + c->prompt_index, + c->prompt_buffer + c->prompt_index + 1, + size + 1 - c->prompt_index); + c->flags |= CLIENT_STATUS; + } + break; + case MODEKEYCMD_UP: + if (server_locked) + break; + + if (ARRAY_LENGTH(&c->prompt_hdata) == 0) + break; + xfree(c->prompt_buffer); + + c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata, + ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex)); + if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1) + c->prompt_hindex++; + + c->prompt_index = strlen(c->prompt_buffer); + c->flags |= CLIENT_STATUS; + break; + case MODEKEYCMD_DOWN: + if (server_locked) + break; + + xfree(c->prompt_buffer); + + if (c->prompt_hindex != 0) { + c->prompt_hindex--; + c->prompt_buffer = xstrdup(ARRAY_ITEM( + &c->prompt_hdata, ARRAY_LENGTH( + &c->prompt_hdata) - 1 - c->prompt_hindex)); + } else + c->prompt_buffer = xstrdup(""); + + c->prompt_index = strlen(c->prompt_buffer); + c->flags |= CLIENT_STATUS; + break; + case MODEKEYCMD_PASTE: + if ((pb = paste_get_top(&c->session->buffers)) == NULL) + break; + if ((last = strchr(pb->data, '\n')) == NULL) + last = strchr(pb->data, '\0'); + n = last - pb->data; + + c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1); + if (c->prompt_index == size) { + memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); + c->prompt_index += n; + c->prompt_buffer[c->prompt_index] = '\0'; + } else { + memmove(c->prompt_buffer + c->prompt_index + n, + c->prompt_buffer + c->prompt_index, + size + 1 - c->prompt_index); + memcpy(c->prompt_buffer + c->prompt_index, pb->data, n); + c->prompt_index += n; + } + + c->flags |= CLIENT_STATUS; + break; + case MODEKEYCMD_CHOOSE: + if (*c->prompt_buffer != '\0') { + status_prompt_add_history(c); + if (c->prompt_callback( + c->prompt_data, c->prompt_buffer) == 0) + status_prompt_clear(c); + break; + } + /* FALLTHROUGH */ + case MODEKEYCMD_QUIT: + if (c->prompt_callback(c->prompt_data, NULL) == 0) + status_prompt_clear(c); + break; + case MODEKEYCMD_OTHERKEY: + if (key < 32 || key > 126) + break; + c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2); + + if (c->prompt_index == size) { + c->prompt_buffer[c->prompt_index++] = key; + c->prompt_buffer[c->prompt_index] = '\0'; + } else { + memmove(c->prompt_buffer + c->prompt_index + 1, + c->prompt_buffer + c->prompt_index, + size + 1 - c->prompt_index); + c->prompt_buffer[c->prompt_index++] = key; + } + + if (c->prompt_flags & PROMPT_SINGLE) { + if (c->prompt_callback( + c->prompt_data, c->prompt_buffer) == 0) + status_prompt_clear(c); + } + + c->flags |= CLIENT_STATUS; + break; + default: + break; + } +} + +/* Add line to the history. */ +void +status_prompt_add_history(struct client *c) +{ + if (server_locked) + return; + + if (ARRAY_LENGTH(&c->prompt_hdata) > 0 && + strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0) + return; + + if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) { + xfree(ARRAY_FIRST(&c->prompt_hdata)); + ARRAY_REMOVE(&c->prompt_hdata, 0); + } + + ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer)); +} + +/* Complete word. */ +char * +status_prompt_complete(const char *s) +{ + const struct cmd_entry **cmdent; + const struct set_option_entry *optent; + ARRAY_DECL(, const char *) list; + char *prefix, *s2; + u_int i; + size_t j; + + if (*s == '\0') + return (NULL); + + /* First, build a list of all the possible matches. */ + ARRAY_INIT(&list); + for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { + if (strncmp((*cmdent)->name, s, strlen(s)) == 0) + ARRAY_ADD(&list, (*cmdent)->name); + } + for (i = 0; i < NSETOPTION; i++) { + optent = &set_option_table[i]; + if (strncmp(optent->name, s, strlen(s)) == 0) + ARRAY_ADD(&list, optent->name); + } + for (i = 0; i < NSETWINDOWOPTION; i++) { + optent = &set_window_option_table[i]; + if (strncmp(optent->name, s, strlen(s)) == 0) + ARRAY_ADD(&list, optent->name); + } + + /* If none, bail now. */ + if (ARRAY_LENGTH(&list) == 0) { + ARRAY_FREE(&list); + return (NULL); + } + + /* If an exact match, return it, with a trailing space. */ + if (ARRAY_LENGTH(&list) == 1) { + xasprintf(&s2, "%s ", ARRAY_FIRST(&list)); + ARRAY_FREE(&list); + return (s2); + } + + /* Now loop through the list and find the longest common prefix. */ + prefix = xstrdup(ARRAY_FIRST(&list)); + for (i = 1; i < ARRAY_LENGTH(&list); i++) { + s = ARRAY_ITEM(&list, i); + + j = strlen(s); + if (j > strlen(prefix)) + j = strlen(prefix); + for (; j > 0; j--) { + if (prefix[j - 1] != s[j - 1]) + prefix[j - 1] = '\0'; + } + } + + ARRAY_FREE(&list); + return (prefix); +} diff --git a/tmux.1 b/tmux.1 new file mode 100644 index 00000000..4b96ad47 --- /dev/null +++ b/tmux.1 @@ -0,0 +1,1329 @@ +.\" $OpenBSD$ +.\" +.\" Copyright (c) 2007 Nicholas Marriott +.\" +.\" 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. +.\" +.Dd April 20, 2009 +.Dt TMUX 1 +.Os +.Sh NAME +.Nm tmux +.Nd "terminal multiplexer" +.Sh SYNOPSIS +.Nm tmux +.Bk -words +.Op Fl 28dqUuv +.Op Fl f Ar file +.Op Fl L Ar socket-name +.Op Fl S Ar socket-path +.Op Ar command Op Ar flags +.Ek +.Sh DESCRIPTION +.Nm +is a terminal multiplexer; it enables a number of terminals to be accessed and +controlled from a single terminal. +.Pp +.Nm +runs as a server-client system. +A server is created automatically when necessary and holds a number of +.Em sessions , +each of which may have a number of +.Em windows +linked to it. +A window may be split on screen into one or more +.Em panes , +each of which is a separate terminal. +Any number of +.Em clients +may connect to a session, or the server +may be controlled by issuing commands with +.Nm . +Communication takes place through a socket, by default placed in +.Pa /tmp . +.Pp +The options are as follows: +.Bl -tag -width "XXXXXXXXXXXX" +.It Fl 2 +Force +.Nm +to assume the terminal supports 256 colours. +.It Fl 8 +Like +.Fl 2 , +indicates the terminal supports 88 colours. +.It Fl d +Force +.Nm +to assume the terminal supports default colours. +.It Fl f Ar file +Specify an alternative configuration file. +By default, +.Nm +will look for a config file at +.Pa ~/.tmux.conf . +The configuration file is a set of +.Nm +commands which are executed in sequence when the server is first started. +.It Fl q +Prevent the server sending various information messages, for example when +window flags are altered. +.It Fl L Ar socket-name +.Nm +stores the server socket in a directory under +.Pa /tmp ; +the default socket is named +.Em default . +This option allows a different socket name to be specified, allowing several +independent +.Nm +servers to be run. +Unlike +.Fl S +a full path is not necessary: the sockets are all created in the same +directory. +.It Fl S Ar socket-path +Specify a full alternative path to the server socket. +If +.Fl S +is specified, the default socket directory is not used and any +.Fl L +flag is ignored. +.It Fl U +Unlock the server. +.It Fl u +Instruct +.Nm +that the terminal support UTF-8. +.It Fl v +Request verbose logging. +This option may be specified multiple times for increasing verbosity. +Log messages will be saved into +.Pa tmux-client-PID.log +and +.Pa tmux-server-PID.log +files in the current directory, where +.Em PID +is the pid of the server or client process. +.It Ar command Op Ar flags +This specifies one of a set of commands used to control +.Nm , +and described in the following sections. +If no command and flags is specified, the +.Ic new-session +command is assumed. +.Pp +.El +.Sh QUICK START +To create a new tmux session running +.Xr vi 1 : +.Pp +.Dl $ tmux new-session vi +.Pp +Most commands have a shorter form, known as an alias. +For new-session, this is +.Ic new : +.Pp +.Dl $ tmux new vi +.Pp +Alternatively, the shortest unambiguous form of a command is accepted. +If there are several options, they are listed: +.Bd -literal -offset indent +$ tmux n +ambiguous command: n, could be: new-session, new-window, next-window +$ +.Ed +.Pp +Within an active session, a new window may be created by typing +.Ql C-b +(ctrl-b, known as the prefix key) +followed by the +.Ql c +key. +.Pp +Windows may be navigated with: +.Ql C-b 0 +(to select window 0), +.Ql C-b 1 +(to select window 1), and so on; +.Ql C-b n +to select the next window; and +.Ql C-b p +to select the previous window. +.Pp +A session may be detached using +.Ql C-b d +and reattached with: +.Pp +.Dl $ tmux attach-session +.Pp +Typing +.Ql C-b \&? +lists the current key bindings in the current window; up and down may be used +to navigate the list or +.Ql Q +to exit from it. +.Sh KEY BINDINGS +.Nm +may be controlled from an attached client by using a key combination of a +prefix key, +.Ql C-b +(ctrl-b) by default, followed by a command key. +.Pp +Some of the default key bindings include: +.Pp +.Bl -tag -width Ds -compact +.It Ql d +Detach current client. +.It Ql c +Create new window. +.It Ql n +Change to next window in the current session. +.It Ql p +Change to previous window in the current session. +.It Ql l +Move to last (previously selected) window in the current session. +.It Ql t +Display a large clock. +.It Ql \&? +List current key bindings. +.El +.Pp +A complete list may be obtained with the +.Ic list-keys +command (bound to +.Ql \&? +by default). +Key bindings may be changed with the +.Ic bind-key +and +.Ic unbind-key +commands. +.Sh HISTORY +.Nm +maintains a configurable history buffer for each window. +By default, up to 2000 lines are kept, this can be altered with the +.Ic history-limit +option (see the +.Ic set-option +command below). +.Sh MODES +A +.Nm +window may be in one of several modes. +The default permits direct access to the terminal attached to the window. +The others are: +.Bl -tag -width Ds +.It Em output mode +This is entered when a command which produces output, such as +.Ic list-keys , +is executed from a key binding. +.It Em scroll mode +This is entered with the +.Ic scroll-mode +command (bound to +.Ql = +by default) and permits the window history buffer to be inspected. +.It Em copy mode +This permits a section of a window or its history to be copied to a +.Em paste buffer +for later insertion into another window. +This mode is entered with the +.Ic copy-mode +command, bound to +.Ql [ +by default. +.El +.Pp +The keys available depend on whether +.Xr emacs 1 +or +.Xr vi 1 +mode is selected (see the +.Ic mode-keys +option). +The following keys are supported as appropriate for the mode: +.Bl -column "FunctionXXXXXXXXXXXX" "viXXXXXX" "emacs" -offset indent +.It Sy "Function" Ta Sy "vi" Ta Sy "emacs" +.It Li "Start of line" Ta "0 or ^" Ta "C-a" +.It Li "Clear selection" Ta "Escape" Ta "C-g" +.It Li "Copy selection" Ta "Enter" Ta "M-w" +.It Li "Cursor down" Ta "j" Ta "Down" +.It Li "End of line" Ta "$" Ta "C-e" +.It Li "Cursor left" Ta "h" Ta "Left" +.It Li "Next page" Ta "C-f" Ta "Page down" +.It Li "Next word" Ta "w" Ta "M-f" +.It Li "Previous page" Ta "C-u" Ta "Page up" +.It Li "Previous word" Ta "b" Ta "M-b" +.It Li "Quit mode" Ta "q" Ta "Escape" +.It Li "Cursor right" Ta "l" Ta "Right" +.It Li "Start selection" Ta "Space" Ta "C-Space" +.It Li "Cursor up" Ta "k" Ta "Up" +.El +.Pp +.Sh BUFFERS +.Nm +maintains a stack of +.Em paste buffers +for each session. +Up to the value of the +.Ic buffer-limit +option are kept; when a new buffer is added, the buffer at the bottom of the +stack is removed. +Buffers may be added using +.Ic copy-mode +or the +.Ic set-buffer +command, and pasted into a window using the +.Ic paste-buffer +command. +.Sh PANES AND LAYOUTS +Each window displayed by +.Nm +may be split into one or more +.Em panes ; +each pane takes up a certain area of the display and is a separate terminal. +A window may be split into panes using the +.Ic split-window +command. +.Pp +Panes are numbered beginning from zero; in horizontal layouts zero is the +leftmost pane and in vertical the topmost. +.Pp +Panes may be arranged using several layouts. +The layout may be cycled with the +.Ic next-layout +command (bound to +.Ql C-space +by default), the current pane may be changed with the +.Ic up-pane +and +.Ic down-pane +commands and the +.Ic rotate-window +and +.Ic swap-pane +commands may be used to swap panes without changing the window layout. +.Pp +The following layouts are supported: +.Bl -tag -width Ds +.It Ic manual +Manual layout splits windows vertically (running across); only with this layout +may panes be resized using the +.Ic resize-pane +command. +.It Ic active-only +Only the active pane is shown - all other panes are hidden. +.It Ic even-horizontal +Panes are spread out evenly from left to right across the window. +.It Ic even-vertical +Panes are spread evenly from top to bottom. +.It Ic main-vertical +A large (81 column) pane is shown on the left of the window and the remaining +panes are spread from top to bottom in the leftover space to the right. +.El +.Sh COMMANDS +This section contains a list of the commands supported by +.Nm . +Most commands accept the optional +.Fl t +argument with one of +.Ar target-client , +.Ar target-session +or +.Ar target-window . +These specify the client, session or window which a command should affect. +.Ar target-client +is the name of the +.Xr pty 4 +file to which the client is connected, for example +.Pa /dev/ttyp1 . +Clients may be listed with the +.Ic list-clients +command. +.Pp +.Ar target-session +is either the name of a session (as listed by the +.Ic list-sessions +command); or the name of a client as for +.Ar target-client , +in this case, the session attached to the client is used. +An +.Xr fnmatch 3 +pattern may be used to match the session name. +If a session is omitted when required, +.Nm tmux +attempts to use the current session; if no current session is available, the +most recently created is chosen. +If no client is specified, the current client is chosen, if possible, or an +error is reported. +.Pp +.Ar target-window +specifies a window in the form +.Em session Ns \&: Ns Em index , +for example mysession:1. +The session is in the same form as for +.Ar target-session . +.Em session , +.Em index +or both may be omitted. +If +.Em session +is omitted, the same rules as for +.Ar target-session +are followed; if +.Em index +is not present, the current window for the given session is used. +When the argument does not contain a colon (:), +.Nm +first attempts to parse it as window index; if that fails, an attempt is made +to match a session or client name. +.Pp +Multiple commands may be specified together as part of a +.Em command sequence . +Each command should be separated by spaces and a semicolon +.Eo ( Ql \& \&; \& Ec ) ; +commands are executed sequentially from left to right. +A literal semicolon may be included by escaping it with a backslash (for +example, when specifying a command sequence to +.Ic bind-key ) . +.Pp +Examples include: +.Pp +.Bd -literal -offset indent +refresh-client -t/dev/ttyp2 + +rename-session -tfirst newname + +set-window-option -t:0 monitor-activity on + +new-window ; split-window -d + +bind-key D detach-client \e\; lock-server +.Ed +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Xo Ic attach-session +.Op Fl d +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic attach ) +Create a new client in the current terminal and attach it to a session. +If +.Fl d +is specified, any other clients attached to the session are detached. +.Pp +If no server is started, +.Ic attach-session +will attempt to start it; this will fail unless sessions are created in the +configuration file. +.It Xo Ic bind-key +.Op Fl r +.Ar key Ar command Op Ar arguments +.Xc +.D1 (alias: Ic bind ) +Bind key +.Ar key +to +.Ar command . +Keys may be specified prefixed with +.Ql C- +or +.Ql ^ +for ctrl keys, or +.Ql M- +for alt (meta) keys. +The +.Fl r +flag indicates this key may repeat, see the +.Ic repeat-time +option. +.It Xo Ic break-pane +.Op Fl d +.Op Fl p Ar pane-index +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic breakp) +Break the current pane off from its containing window to make it the only pane +in a new window. +If +.Fl d +is given, the new window does not become the current window. +.It Xo Ic choose-session +.Op Fl t Ar target-window +.Xc +Put a window into session choice mode, where the session for the current +client may be selected interactively from a list. +This command works only from inside +.Nm . +.It Xo Ic choose-window +.Op Fl t Ar target-window +.Xc +Put a window into window choice mode, where the window for the session +attached to the current client may be selected interactively from a list. +This command works only from inside +.Nm . +.It Xo Ic clock-mode +.Op Fl t Ar target-window +.Xc +Display a large clock. +.It Xo Ic command-prompt +.Op Fl t Ar target-client +.Op Ar template +.Xc +Open the command prompt in a client. +This may be used from inside +.Nm +to execute commands interactively. +If +.Ar template +is specified, it is used as the command; any %% in the template will be +replaced by what is entered at the prompt. +.It Xo Ic confirm-before +.Op Fl t Ar target-client +.Ar command +.Xc +.D1 (alias: Ic confirm) +Ask for confirmation before executing +.Ar command . +This command works only from inside +.Nm . +.It Xo Ic copy-buffer +.Op Fl a Ar src-index +.Op Fl b Ar dst-index +.Op Fl s Ar src-session +.Op Fl t Ar dst-session +.Xc +.D1 (alias: Ic copyb) +Copy a session paste buffer to another session. +If no sessions are specified, the current one is used instead. +.It Xo Ic copy-mode +.Op Fl u +.Op Fl t Ar target-window +.Xc +Enter copy mode. +The +.Fl u +option scrolls one page up. +.It Xo Ic delete-buffer +.Op Fl b Ar buffer-index +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic deleteb ) +Delete the buffer at +.Ar buffer-index , +or the top buffer if not specified. +.It Xo Ic detach-client +.Op Fl t Ar target-client +.Xc +.D1 (alias: Ic detach ) +Detach the current client if bound to a key, or the specified client with +.Fl t . +.It Xo Ic down-pane +.Op Fl p Ar pane-index +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic downp ) +Move down a pane. +.It Xo Ic find-window +.Op Fl t Ar target-window +.Ar match-string +.Xc +.D1 (alias: Ic findw ) +Search for +.Ar match-string +in window names, titles, and visible content (but not history). +If only one window is matched, it'll be automatically selected, otherwise a +choice list is shown. +This command only works from inside +.Nm . +.It Xo Ic has-session +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic has ) +Report an error and exit with 1 if the specified session does not exist. +If it does exist, exit with 0. +.It Xo Ic kill-pane +.Op Fl p Ar pane-index +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic killp ) +Destroy the given pane. +.It Xo Ic kill-server +.Xc +Kill the +.Nm +server and clients and destroy all sessions. +.It Xo Ic kill-session +.Op Fl t Ar target-session +.Xc +Destroy the given session, closing any windows linked to it and no other +sessions, and detaching all clients attached to it. +.It Xo Ic kill-window +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic killw ) +Kill the current window or the window at +.Ar target-window , +removing it from any sessions to which it is linked. +.It Xo Ic last-window +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic last ) +Select the last (previously selected) window. +If no +.Ar target-session +is specified, select the last window of the current session. +.It Xo Ic link-window +.Op Fl dk +.Op Fl s Ar src-window +.Op Fl t Ar dst-window +.Xc +.D1 (alias: Ic linkw ) +Link the window at +.Ar src-window +to the specified +.Ar dst-window . +If +.Ar dst-window +is specified and no such window exists, the +.Ar src-window +is linked there. +If +.Fl k +is given and +.Ar dst-window +exists, it is killed, otherwise an error is generated. +If +.Fl d +is given, the newly linked window is not selected. +.It Xo Ic list-buffers +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic lsb ) +List the buffers in the given session. +.It Xo Ic list-clients +.Xc +.D1 (alias: Ic lsc ) +List all clients attached to the server. +.It Xo Ic list-commands +.Xc +.D1 (alias: Ic lscm ) +List the syntax of all commands supported by +.Nm . +.It Xo Ic list-keys +.Xc +.D1 (alias: Ic lsk ) +List all key bindings. +.It Xo Ic list-sessions +.Xc +.D1 (alias: Ic ls ) +List all sessions managed by the server. +.It Xo Ic list-windows +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic lsw ) +List windows in the current session or in +.Ar target-session . +.It Xo Ic load-buffer +.Op Fl b Ar buffer-index +.Op Fl t Ar target-session +.Ar path +.Xc +.D1 (alias: Ic loadb ) +Load the contents of the specified paste buffer from +.Ar path . +.It Xo Ic lock-server +.Xc +.D1 (alias: Ic lock ) +Lock the server until a password is entered. +.It Xo Ic move-window +.Op Fl d +.Op Fl s Ar src-window +.Op Fl t Ar dst-window +.Xc +.D1 (alias: Ic movew ) +This is similar to +.Ic link-window , +except the window at +.Ar src-window +is moved to +.Ar dst-window . +.It Xo Ic new-session +.Op Fl d +.Op Fl n Ar window-name +.Op Fl s Ar session-name +.Op Ar command +.Xc +.D1 (alias: Ic new ) +Create a new session with name +.Ar session-name . +The new session is attached to the current terminal unless +.Fl d +is given. +.Ar window-name +and +.Ar command +are the name of and command to execute in the initial window. +.It Xo Ic new-window +.Op Fl d +.Op Fl n Ar window-name +.Op Fl t Ar target-window +.Op Ar command +.Xc +.D1 (alias: Ic neww ) +Create a new window. +If +.Fl d +is given, the session does not make the new window the current window. +.Ar target-window +represents the window to be created. +.Ar command +is the command to execute. +If +.Ar command +is not specified, the default command is used. +.Pp +The +.Ev TERM +environment variable must be set to +.Dq screen +for all programs running +.Em inside +.Nm . +New windows will automatically have +.Dq TERM=screen +added to their environment, but care must be taken not to reset this in shell +start-up files. +.It Xo Ic next-layout +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic nextl ) +Move a window to the next layout and rearrange the panes to fit. +.It Xo Ic next-window +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic next ) +Move to the next window in the session. +.It Xo Ic paste-buffer +.Op Fl d +.Op Fl b Ar buffer-index +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic pasteb ) +Insert the contents of a paste buffer into the current window. +.It Xo Ic previous-window +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic prev ) +Move to the previous window in the session. +.It Xo Ic refresh-client +.Op Fl t Ar target-client +.Xc +.D1 (alias: Ic refresh ) +Refresh the current client if bound to a key, or a single client if one is given +with +.Fl t . +.It Xo Ic rename-session +.Op Fl t Ar target-session +.Ar new-name +.Xc +.D1 (alias: Ic rename ) +Rename the session to +.Ar new-name . +.It Xo Ic rename-window +.Op Fl t Ar target-window +.Ar new-name +.Xc +.D1 (alias: Ic renamew ) +Rename the current window, or the window at +.Ar target-window +if specified, to +.Ar new-name . +.It Xo Ic resize-pane +.Op Fl DU +.Op Fl p Ar pane-index +.Op Fl t Ar target-window +.Op Ar adjustment +.Xc +.D1 (alias: Ic resizep ) +Resize a pane, upward with +.Fl U +(the default) or downward with +.Fl D . +The +.Ar adjustment +is given in lines (the default is 1). +.It Xo Ic respawn-window +.Op Fl k +.Op Fl t Ar target-window +.Op Ar command +.Xc +.D1 (alias: Ic respawnw ) +Reactive a window in which the command has exited (see the +.Ic remain-on-exit +window option). +If +.Ar command +is not given, the command used when the window was created is executed. +The window must be already inactive, unless +.Fl k +is given, in which case any existing command is killed. +.It Xo Ic rotate-window +.Op Fl DU +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic rotatew ) +Rotate the positions of the panes within a window, either upward (numerically +lower) with +.Fl U +or downward (numerically higher). +.It Xo Ic save-buffer +.Op Fl a +.Op Fl b Ar buffer-index +.Op Fl t Ar target-session +.Ar path +.Xc +.D1 (alias: Ic saveb ) +Save the contents of the specified paste buffer to +.Ar path . +The +.Fl a +option appends to rather than overwriting the file. +.It Xo Ic scroll-mode +.Op Fl u +.Op Fl t Ar target-window +.Xc +Enter scroll mode. +The +.Fl u +has the same meaning as in the +.Ic copy-mode +command. +.It Xo Ic select-pane +.Op Fl p Ar pane-index +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic selectp ) +Make pane +.Ar pane-index +the active pane in window +.Ar target-window . +.It Xo Ic select-prompt +.Op Fl t Ar target-client +.Xc +Open a prompt inside +.Ar target-client +allowing a window index to be entered interactively. +.It Xo Ic select-window +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic selectw ) +Select the window at +.Ar target-window . +.It Xo Ic send-keys +.Op Fl t Ar target-window +.Ar key Ar ... +.Xc +.D1 (alias: Ic send ) +Send a key or keys to a window. +Each argument +.Ar key +is the name of the key (such as +.Ql C-a +or +.Ql npage +) to send; if the string is not recognised as a key, it is sent as a series of +characters. +All arguments are sent sequentially from first to last. +.It Xo Ic send-prefix +.Op Fl t Ar target-window +.Xc +Send the prefix key to a window as if it was pressed. +.It Xo Ic server-info +.Xc +.D1 (alias: Ic info ) +Show server information and terminal details. +.It Xo Ic set-buffer +.Op Fl b Ar buffer-index +.Op Fl t Ar target-session +.Ar data +.Xc +.D1 (alias: Ic setb ) +Set the contents of the specified buffer to +.Ar data . +.It Xo Ic set-option +.Op Fl gu +.Op Fl t Ar target-session +.Ar option Ar value +.Xc +.D1 (alias: Ic set ) +Set an option. +If +.Fl g +is specified, the option is set as a global option. +Global options apply to all sessions which don't have the option explicitly +set. +If +.Fl g +is not used, the option applies only to +.Ar target-session . +The +.Fl u +flag unsets an option, so a session inherits the option from the global +options - it is not possible to unset a global option. +.Pp +Possible options are: +.Bl -tag -width Ds +.It Xo Ic bell-action +.Op Ic any | Ic none | Ic current +.Xc +Set action on window bell. +.Ic any +means a bell in any window linked to a session causes a bell in the current +window of that session, +.Ic none +means all bells are ignored and +.Ic current +means only bell in windows other than the current window are ignored. +.It Ic buffer-limit Ar number +Set the number of buffers kept for each session; as new buffers are added to +the top of the stack, old ones are removed from the bottom if necessary to +maintain this maximum length. +.It Ic default-command Ar command +Set the command used for new windows (if not specified when the window is +created) to +.Ar command . +The default is +.Dq exec $SHELL . +.It Ic default-path Ar path +Set the default working directory for processes created from keys, or +interactively from the prompt. +The default is the current working directory when the server is started. +.It Ic history-limit Ar lines +Set the maximum number of lines held in window history. +This setting applies only to new windows - existing window histories are not +resized and retain the limit at the point they were created. +.It Ic lock-after-time Ar number +Lock the server after +.Ar number +seconds of inactivity. +The default is off (set to 0). +This has no effect as a session option; it must be set as a global option using +.Fl g . +.It Ic message-attr Ar attributes +Set status line message attributes, where +.Ar attributes +is either +.Ic default +or a comma-delimited list of one or more of: +.Ic bright +(or +.Ic bold ) , +.Ic dim , +.Ic underscore , +.Ic blink , +.Ic reverse , +.Ic hidden , +or +.Ic italics . +.It Ic message-bg Ar colour +Set status line message background colour, where +.Ar colour +is one of: +.Ic black , +.Ic red , +.Ic green , +.Ic yellow , +.Ic blue , +.Ic magenta , +.Ic cyan , +.Ic white +or +.Ic default . +.It Ic message-fg Ar colour +Set status line message foreground colour. +.It Ic prefix Ar key +Set the current prefix key. +.It Ic repeat-time Ar number +Allow multiple commands to be entered without pressing the prefix-key again +in the specified +.Ar number +milliseconds (the default is 500). +Whether a key repeats may be set when it is bound using the +.Fl r +flag to +.Ic bind-key . +Repeat is enabled for the default keys of the +.Ic up-pane , +.Ic down-pane , +.Ic resize-pane-up , +and +.Ic resize-pane-down +commands. +.It Xo Ic set-remain-on-exit +.Op Ic on | Ic off +.Xc +Set the +.Ic remain-on-exit +window option for any windows first created in this session. +.It Xo Ic set-titles +.Op Ic on | Ic off +.Xc +Attempt to set the window title using the \ee]2;...\e007 xterm code and +the terminal appears to be an xterm. +This option is enabled by default. +Note that +.Xr elinks 1 +will only attempt to set the window title if the STY environment +variable is set. +.It Xo Ic status +.Op Ic on | Ic off +.Xc +Show or hide the status line. +.It Ic status-attr Ar attributes +Set status line attributes. +.It Ic status-bg Ar colour +Set status line background colour. +.It Ic status-fg Ar colour +Set status line foreground colour. +.It Ic status-interval Ar interval +Update the status bar every +.Ar interval +seconds. +By default, updates will occur every 15 seconds. +A setting of zero disables redrawing at interval. +.It Xo Ic status-keys +.Op Ic vi | Ic emacs +.Xc +Use +.Xr vi 1 - +or +.Xr emacs 1 -style +key bindings in the status line, for example at the command prompt. +Defaults to emacs. +.It Ic status-left Ar string +Display +.Ar string +to the left of the status bar. +.Ar string +will be passed through +.Xr strftime 3 +before being used. +By default, the session name is shown. +.Ar string +may contain any of the following special character pairs: +.Bl -column "Character pair" "Replaced with" -offset indent +.It Sy "Character pair" Ta Sy "Replaced with" +.It Li "#(command)" Ta "First line of command's output" +.It Li "#H" Ta "Hostname of local host" +.It Li "#S" Ta "Session name" +.It Li "#T" Ta "Current window title" +.It Li "##" Ta "A literal" Ql # +.El +.Pp +Where appropriate, these may be prefixed with a number to specify the maximum +length, for example +.Ql #24T . +.It Ic status-left-length Ar length +Set the maximum +.Ar length +of the left component of the status bar. +The default is 10. +.It Ic status-right Ar string +Display +.Ar string +to the right of the status bar. +By default, the date and time will be shown. +As with +.Ic status-left , +.Ar string +will be passed to +.Xr strftime 3 +and character pairs are replaced. +.It Ic status-right-length Ar length +Set the maximum +.Ar length +of the right component of the status bar. +The default is 40. +.El +.It Xo Ic set-password +.Op Fl c +.Ar password +.Xc +.D1 (alias: Ic pass ) +Set the server password. +If the +.Fl c +option is given, a pre-encrypted password may be specified. +By default, the password is blank, thus any entered password will be accepted +when unlocking the server (see the +.Ic lock-server +command). +To prevent variable expansion when an encrypted password is read from a +configuration file, enclose it in single quotes ('). +.It Xo Ic set-window-option +.Op Fl gu +.Op Fl t Ar target-window +.Ar option Ar value +.Xc +.D1 (alias: Ic setw ) +Set a window-specific option. +The +.Fl g +and +.Fl u +flags work similarly to the +.Ic set-option +command. +.Pp +Supported options are: +.Bl -tag -width Ds +.It Xo Ic aggressive-resize +.Op Ic on | Ic off +.Xc +Aggressively resize the chosen window. +This means that +.Nm +will resize the window to the size of the smallest session for which it is the +current window, rather than the smallest session to which it is attached. +The window may resize when the current window is changed on another sessions; +this option is good for full-screen programs which support SIGWINCH and poor for +interactive programs such as shells. +.It Xo Ic automatic-rename +.Op Ic on | Ic off +.Xc +Control automatic window renaming. +When this setting is enabled, +.Nm +will attempt - on supported platforms - to rename the window to reflect the +command currently running in it. +This flag is automatically disabled for an individual window when a name +is specified at creation with +.Ic new-window or +.Ic new-session , +or later with +.Ic rename-window . +It may be switched off globally with: +.Bd -literal -offset indent +set-window-option -g automatic-rename off +.Ed +.It Ic clock-mode-colour Ar colour +Set clock colour. +.It Xo Ic clock-mode-style +.Op Ic 12 | Ic 24 +.Xc +Set clock hour format. +.It Ic force-height Ar height +.It Ic force-width Ar width +Prevent +.Nm +from resizing a window to greater than +.Ar width +or +.Ar height . +A value of zero restores the default unlimited setting. +.It Ic mode-attr Ar attributes +Set window modes attributes. +.It Ic mode-bg Ar colour +Set window modes background colour. +.It Ic mode-fg Ar colour +Set window modes foreground colour. +.It Xo Ic mode-keys +.Op Ic vi | Ic emacs +.Xc +Use +.Xr vi 1 - +or +.Xr emacs 1 -style +key bindings in scroll and copy modes. +Key bindings default to emacs. +.It Xo Ic monitor-activity +.Op Ic on | Ic off +.Xc +Monitor for activity in the window. +Windows with activity are highlighted in the status line. +.It Xo Ic monitor-content Ar match-string +.Xc +Monitor content in the window. When +.Ar match-string +appears in the window, it is highlighted in the status line. +.It Xo Ic remain-on-exit +.Op Ic on | Ic off +.Xc +A window with this flag set is not destroyed when the program running in it +exits. +The window may be reactivated with the +.Ic respawn-window +command. +.It Xo Ic utf8 +.Op Ic on | Ic off +.Xc +Instructs +.Nm +to expect UTF-8 sequences to appear in this window. +.It Ic window-status-attr Ar attributes +Set status line attributes for a single window. +.It Ic window-status-bg Ar colour +Set status line background colour for a single window. +.It Ic window-status-fg Ar colour +Set status line foreground colour for a single window. +.It Xo Ic xterm-keys +.Op Ic on | Ic off +.Xc +If this option is set, +.Nm +will generate +.Xr xterm 1 -style +function key sequences; these have a number included to indicate modifiers such +as shift, meta or ctrl. +.El +.It Xo Ic show-buffer +.Op Fl b Ar buffer-index +.Op Fl t Ar target-session +.Xc +.D1 (alias: Ic showb ) +Display the contents of the specified buffer. +.It Xo Ic show-options +.Op Fl t Ar target-session +.Ar option Ar value +.Xc +.D1 (alias: Ic show ) +Show the currently set options. +If a +.Ar target-session +is specified, the options for that session are shown; otherwise, the global +options are listed. +.It Xo Ic show-window-options +.Op Fl t Ar target-window +.Ar option Ar value +.Xc +.D1 (alias: Ic showw ) +List the current options for the given window. +.It Xo Ic source-file +.Ar path +.Xc +.D1 (alias: Ic source ) +Execute commands from +.Ar path . +.It Xo Ic split-window +.Op Fl d +.Oo Fl l +.Ar lines | +.Fl p Ar percentage Oc +.Op Fl t Ar target-window +.Op Ar command +.Xc +.D1 (alias: splitw ) +Creates a new window by splitting it vertically. +The +.Fl l +and +.Fl p +options specify the size of the new window in lines, or as a percentage, +respectively. +All other options have the same meaning as in the +.Ic new-window +command. +.Pp +A few notes with regard to panes: +.Bl -enum -compact +.It +If attempting to split a window with less than eight lines, an error will be +shown. +.It +If the window is resized, as many panes are shown as can fit without reducing +them below four lines. +.It +The minimum pane size is four lines (including the separator line). +.It +The panes are indexed from top (0) to bottom, with no numbers skipped. +.El +.It Xo Ic start-server +.Xc +.D1 (alias: Ic start ) +Start the +.Nm +server, if not already running, without creating any sessions. +.It Xo Ic suspend-client +.Op Fl c target-client +.Xc +.D1 (alias: Ic suspendc ) +Suspend a client by sending SIGTSTP (tty stop). +.It Xo Ic swap-pane +.Op Fl dDU +.Op Fl p Ar src-index +.Op Fl t Ar target-window +.Op Fl q Ar dst-index +.Xc +.D1 (alias: Ic swapp ) +Swap two panes within a window. +If +.Fl U +is used, the pane is swapped with the pane above (before it numerically); +.Fl D +swaps with the pane below (the next numerically); or +.Ar dst-index +may be give to swap with a specific pane. +.It Xo Ic swap-window +.Op Fl d +.Op Fl s Ar src-window +.Op Fl t Ar dst-window +.Xc +.D1 (alias: Ic swapw ) +This is similar to +.Ic link-window , +except the source and destination windows are swapped. +It is an error if no window exists at +.Ar src-window . +.It Xo Ic switch-client +.Op Fl c Ar target-client Fl t Ar target-session +.Xc +.D1 (alias: Ic switchc ) +Switch the current session for client +.Ar target-client +to +.Ar target-session . +.It Xo Ic unbind-key +.Ar key +.Xc +.D1 (alias: Ic unbind ) +Unbind the key bound to +.Ar key . +.It Xo Ic unlink-window +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic unlinkw ) +Unlink +.Ar target-window . +A window may be unlinked only if it is linked to multiple sessions - windows may +not be linked to no sessions. +.It Xo Ic up-pane +.Op Fl p Ar pane-index +.Op Fl t Ar target-window +.Xc +.D1 (alias: Ic upp ) +Move up a pane. +.El +.Sh FILES +.Bl -tag -width Ds -compact +.It Pa ~/.tmux.conf +default +.Nm +configuration file +.El +.Sh SEE ALSO +.Xr pty 4 +.Sh AUTHORS +.An Nicholas Marriott Aq nicm@users.sourceforge.net diff --git a/tmux.c b/tmux.c new file mode 100644 index 00000000..b9d502e3 --- /dev/null +++ b/tmux.c @@ -0,0 +1,487 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +#ifdef DEBUG +const char *malloc_options = "AFGJPX"; +#endif + +volatile sig_atomic_t sigwinch; +volatile sig_atomic_t sigterm; +volatile sig_atomic_t sigcont; +volatile sig_atomic_t sigchld; +volatile sig_atomic_t sigusr1; +volatile sig_atomic_t sigusr2; + +char *cfg_file; +struct options global_options; +struct options global_window_options; + +int server_locked; +char *server_password; +time_t server_activity; + +int debug_level; +int be_quiet; +time_t start_time; +char *socket_path; + +__dead void usage(void); +char *makesockpath(const char *); + +__dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-28dqUuVv] [-f file] " + "[-L socket-name] [-S socket-path] [command [flags]]\n", + __progname); + exit(1); +} + +void +logfile(const char *name) +{ + char *path; + + log_close(); + if (debug_level > 0) { + xasprintf( + &path, "%s-%s-%ld.log", __progname, name, (long) getpid()); + log_open_file(debug_level, path); + xfree(path); + } +} + +void +sighandler(int sig) +{ + int saved_errno; + + saved_errno = errno; + switch (sig) { + case SIGWINCH: + sigwinch = 1; + break; + case SIGTERM: + sigterm = 1; + break; + case SIGCHLD: + sigchld = 1; + break; + case SIGCONT: + sigcont = 1; + break; + case SIGUSR1: + sigusr1 = 1; + break; + case SIGUSR2: + sigusr2 = 1; + break; + } + errno = saved_errno; +} + +void +siginit(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof act); + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + + act.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGINT, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGTSTP, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGQUIT, &act, NULL) != 0) + fatal("sigaction failed"); + + act.sa_handler = sighandler; + if (sigaction(SIGWINCH, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGTERM, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGCHLD, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGUSR1, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGUSR2, &act, NULL) != 0) + fatal("sigaction failed"); +} + +void +sigreset(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof act); + sigemptyset(&act.sa_mask); + + act.sa_handler = SIG_DFL; + if (sigaction(SIGPIPE, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGUSR1, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGUSR2, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGINT, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGTSTP, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGQUIT, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGWINCH, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGTERM, &act, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGCHLD, &act, NULL) != 0) + fatal("sigaction failed"); +} + +char * +makesockpath(const char *label) +{ + char base[MAXPATHLEN], *path; + struct stat sb; + u_int uid; + + uid = getuid(); + xsnprintf(base, MAXPATHLEN, "%s/%s-%d", _PATH_TMP, __progname, uid); + + if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) + return (NULL); + + if (lstat(base, &sb) != 0) + return (NULL); + if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + return (NULL); + } + if (sb.st_uid != uid || (sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) { + errno = EACCES; + return (NULL); + } + + xasprintf(&path, "%s/%s", base, label); + return (path); +} + +int +main(int argc, char **argv) +{ + struct client_ctx cctx; + struct msg_command_data cmddata; + struct buffer *b; + struct cmd_list *cmdlist; + struct cmd *cmd; + struct pollfd pfd; + struct hdr hdr; + const char *shell; + struct passwd *pw; + char *s, *path, *label, *cause, *home, *pass = NULL; + char cwd[MAXPATHLEN]; + int retcode, opt, flags, unlock, start_server; + + unlock = flags = 0; + label = path = NULL; + while ((opt = getopt(argc, argv, "28df:L:qS:uUv")) != -1) { + switch (opt) { + case '2': + flags |= IDENTIFY_256COLOURS; + flags &= ~IDENTIFY_88COLOURS; + break; + case '8': + flags |= IDENTIFY_88COLOURS; + flags &= ~IDENTIFY_256COLOURS; + break; + case 'f': + cfg_file = xstrdup(optarg); + break; + case 'L': + if (path != NULL) { + log_warnx("-L and -S cannot be used together"); + exit(1); + } + if (label != NULL) + xfree(label); + label = xstrdup(optarg); + break; + case 'S': + if (label != NULL) { + log_warnx("-L and -S cannot be used together"); + exit(1); + } + if (path != NULL) + xfree(path); + path = xstrdup(optarg); + break; + case 'q': + be_quiet = 1; + break; + case 'u': + flags |= IDENTIFY_UTF8; + break; + case 'U': + unlock = 1; + break; + case 'd': + flags |= IDENTIFY_HASDEFAULTS; + break; + case 'v': + debug_level++; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + log_open_tty(debug_level); + siginit(); + + options_init(&global_options, NULL); + options_set_number(&global_options, "bell-action", BELL_ANY); + options_set_number(&global_options, "buffer-limit", 9); + options_set_number(&global_options, "display-time", 750); + options_set_number(&global_options, "history-limit", 2000); + options_set_number(&global_options, "lock-after-time", 0); + options_set_number(&global_options, "message-attr", GRID_ATTR_REVERSE); + options_set_number(&global_options, "message-bg", 3); + options_set_number(&global_options, "message-fg", 0); + options_set_number(&global_options, "prefix", '\002'); + options_set_number(&global_options, "repeat-time", 500); + options_set_number(&global_options, "set-remain-on-exit", 0); + options_set_number(&global_options, "set-titles", 1); + options_set_number(&global_options, "status", 1); + options_set_number(&global_options, "status-attr", GRID_ATTR_REVERSE); + options_set_number(&global_options, "status-bg", 2); + options_set_number(&global_options, "status-fg", 0); + options_set_number(&global_options, "status-interval", 15); + options_set_number(&global_options, "status-keys", MODEKEY_EMACS); + options_set_number(&global_options, "status-left-length", 10); + options_set_number(&global_options, "status-right-length", 40); + options_set_string(&global_options, "status-left", "[#S]"); + options_set_string( + &global_options, "status-right", "\"#24T\" %%H:%%M %%d-%%b-%%y"); + + options_init(&global_window_options, NULL); + options_set_number(&global_window_options, "aggressive-resize", 0); + options_set_number(&global_window_options, "automatic-rename", 1); + options_set_number(&global_window_options, "clock-mode-colour", 4); + options_set_number(&global_window_options, "clock-mode-style", 1); + options_set_number(&global_window_options, "force-height", 0); + options_set_number(&global_window_options, "force-width", 0); + options_set_number( + &global_window_options, "mode-attr", GRID_ATTR_REVERSE); + options_set_number(&global_window_options, "main-pane-width", 81); + options_set_number(&global_window_options, "main-pane-height", 24); + options_set_number(&global_window_options, "mode-bg", 3); + options_set_number(&global_window_options, "mode-fg", 0); + options_set_number(&global_window_options, "mode-keys", MODEKEY_EMACS); + options_set_number(&global_window_options, "monitor-activity", 0); + options_set_string(&global_window_options, "monitor-content", "%s", ""); + options_set_number(&global_window_options, "utf8", 0); + options_set_number(&global_window_options, "window-status-attr", 0); + options_set_number(&global_window_options, "window-status-bg", 8); + options_set_number(&global_window_options, "window-status-fg", 8); + options_set_number(&global_window_options, "xterm-keys", 0); + options_set_number(&global_window_options, "remain-on-exit", 0); + + if (!(flags & IDENTIFY_UTF8)) { + /* + * If the user has set LANG to contain UTF-8, it is a safe + * assumption that either they are using a UTF-8 terminal, or + * if not they know that output from UTF-8-capable programs may + * be wrong. + */ + if ((s = getenv("LANG")) != NULL && strstr(s, "UTF-8") != NULL) + flags |= IDENTIFY_UTF8; + } + + if (cfg_file == NULL) { + home = getenv("HOME"); + if (home == NULL || *home == '\0') { + pw = getpwuid(getuid()); + if (pw != NULL) + home = pw->pw_dir; + } + xasprintf(&cfg_file, "%s/%s", home, DEFAULT_CFG); + if (access(cfg_file, R_OK) != 0) { + xfree(cfg_file); + cfg_file = NULL; + } + } else { + if (access(cfg_file, R_OK) != 0) { + log_warn("%s", cfg_file); + exit(1); + } + } + + if (label == NULL) + label = xstrdup("default"); + if (path == NULL && (path = makesockpath(label)) == NULL) { + log_warn("can't create socket"); + exit(1); + } + xfree(label); + + shell = getenv("SHELL"); + if (shell == NULL || *shell == '\0') { + pw = getpwuid(getuid()); + if (pw != NULL) + shell = pw->pw_shell; + if (shell == NULL || *shell == '\0') + shell = _PATH_BSHELL; + } + options_set_string( + &global_options, "default-command", "exec %s", shell); + + if (getcwd(cwd, sizeof cwd) == NULL) { + log_warn("getcwd"); + exit(1); + } + options_set_string(&global_options, "default-path", "%s", cwd); + + if (unlock) { + if (argc != 0) { + log_warnx("can't specify a command when unlocking"); + exit(1); + } + cmdlist = NULL; + if ((pass = getpass("Password: ")) == NULL) + exit(1); + start_server = 0; + } else { + if (argc == 0) { + cmd = xmalloc(sizeof *cmd); + cmd->entry = &cmd_new_session_entry; + cmd->entry->init(cmd, 0); + + cmdlist = xmalloc(sizeof *cmdlist); + TAILQ_INIT(cmdlist); + TAILQ_INSERT_HEAD(cmdlist, cmd, qentry); + } else { + cmdlist = cmd_list_parse(argc, argv, &cause); + if (cmdlist == NULL) { + log_warnx("%s", cause); + exit(1); + } + } + start_server = 0; + TAILQ_FOREACH(cmd, cmdlist, qentry) { + if (cmd->entry->flags & CMD_STARTSERVER) { + start_server = 1; + break; + } + } + } + + memset(&cctx, 0, sizeof cctx); + if (client_init(path, &cctx, start_server, flags) != 0) + exit(1); + xfree(path); + + b = buffer_create(BUFSIZ); + if (unlock) { + cmd_send_string(b, pass); + client_write_server( + &cctx, MSG_UNLOCK, BUFFER_OUT(b), BUFFER_USED(b)); + } else { + cmd_list_send(cmdlist, b); + cmd_list_free(cmdlist); + client_fill_session(&cmddata); + client_write_server2(&cctx, MSG_COMMAND, + &cmddata, sizeof cmddata, BUFFER_OUT(b), BUFFER_USED(b)); + } + buffer_destroy(b); + + retcode = 0; + for (;;) { + pfd.fd = cctx.srv_fd; + pfd.events = POLLIN; + if (BUFFER_USED(cctx.srv_out) > 0) + pfd.events |= POLLOUT; + + if (poll(&pfd, 1, INFTIM) == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + fatal("poll failed"); + } + + if (buffer_poll(&pfd, cctx.srv_in, cctx.srv_out) != 0) + goto out; + + restart: + if (BUFFER_USED(cctx.srv_in) < sizeof hdr) + continue; + memcpy(&hdr, BUFFER_OUT(cctx.srv_in), sizeof hdr); + if (BUFFER_USED(cctx.srv_in) < (sizeof hdr) + hdr.size) + continue; + buffer_remove(cctx.srv_in, sizeof hdr); + + switch (hdr.type) { + case MSG_EXIT: + case MSG_SHUTDOWN: + goto out; + case MSG_ERROR: + retcode = 1; + /* FALLTHROUGH */ + case MSG_PRINT: + if (hdr.size > INT_MAX - 1) + fatalx("bad MSG_PRINT size"); + log_info("%.*s", + (int) hdr.size, BUFFER_OUT(cctx.srv_in)); + if (hdr.size != 0) + buffer_remove(cctx.srv_in, hdr.size); + goto restart; + case MSG_READY: + retcode = client_main(&cctx); + goto out; + default: + fatalx("unexpected command"); + } + } + +out: + options_free(&global_options); + options_free(&global_window_options); + + close(cctx.srv_fd); + buffer_destroy(cctx.srv_in); + buffer_destroy(cctx.srv_out); + + return (retcode); +} diff --git a/tmux.cat1 b/tmux.cat1 new file mode 100644 index 00000000..fe815603 --- /dev/null +++ b/tmux.cat1 @@ -0,0 +1,813 @@ +TMUX(1) OpenBSD Reference Manual TMUX(1) + +NNAAMMEE + ttmmuuxx - terminal multiplexer + +SSYYNNOOPPSSIISS + ttmmuuxx [--2288ddqqUUuuvv] [--ff _f_i_l_e] [--LL _s_o_c_k_e_t_-_n_a_m_e] [--SS _s_o_c_k_e_t_-_p_a_t_h] + [_c_o_m_m_a_n_d [_f_l_a_g_s]] + +DDEESSCCRRIIPPTTIIOONN + ttmmuuxx is a terminal multiplexer; it enables a number of terminals to be + accessed and controlled from a single terminal. + + ttmmuuxx runs as a server-client system. A server is created automatically + when necessary and holds a number of _s_e_s_s_i_o_n_s, each of which may have a + number of _w_i_n_d_o_w_s linked to it. A window may be split on screen into one + or more _p_a_n_e_s, each of which is a separate terminal. Any number of + _c_l_i_e_n_t_s may connect to a session, or the server may be controlled by is- + suing commands with ttmmuuxx. Communication takes place through a socket, by + default placed in _/_t_m_p. + + The options are as follows: + + --22 Force ttmmuuxx to assume the terminal supports 256 colours. + + --88 Like --22, indicates the terminal supports 88 colours. + + --dd Force ttmmuuxx to assume the terminal supports default colours. + + --ff _f_i_l_e Specify an alternative configuration file. By default, + ttmmuuxx will look for a config file at _~_/_._t_m_u_x_._c_o_n_f. The con- + figuration file is a set of ttmmuuxx commands which are execut- + ed in sequence when the server is first started. + + --qq Prevent the server sending various information messages, + for example when window flags are altered. + + --LL _s_o_c_k_e_t_-_n_a_m_e + ttmmuuxx stores the server socket in a directory under _/_t_m_p; + the default socket is named _d_e_f_a_u_l_t. This option allows a + different socket name to be specified, allowing several in- + dependent ttmmuuxx servers to be run. Unlike --SS a full path is + not necessary: the sockets are all created in the same di- + rectory. + + --SS _s_o_c_k_e_t_-_p_a_t_h + Specify a full alternative path to the server socket. If + --SS is specified, the default socket directory is not used + and any --LL flag is ignored. + + --UU Unlock the server. + + --uu Instruct ttmmuuxx that the terminal support UTF-8. + + --vv Request verbose logging. This option may be specified mul- + tiple times for increasing verbosity. Log messages will be + saved into _t_m_u_x_-_c_l_i_e_n_t_-_P_I_D_._l_o_g and _t_m_u_x_-_s_e_r_v_e_r_-_P_I_D_._l_o_g + files in the current directory, where _P_I_D is the pid of the + server or client process. + + _c_o_m_m_a_n_d [_f_l_a_g_s] + This specifies one of a set of commands used to control + ttmmuuxx, and described in the following sections. If no com- + mand and flags is specified, the nneeww--sseessssiioonn command is as- + sumed. + +QQUUIICCKK SSTTAARRTT + To create a new tmux session running vi(1): + + $ tmux new-session vi + + Most commands have a shorter form, known as an alias. For new-session, + this is nneeww: + + $ tmux new vi + + Alternatively, the shortest unambiguous form of a command is accepted. + If there are several options, they are listed: + + $ tmux n + ambiguous command: n, could be: new-session, new-window, next-window + $ + + Within an active session, a new window may be created by typing `C-b' + (ctrl-b, known as the prefix key) followed by the `c' key. + + Windows may be navigated with: `C-b 0' (to select window 0), `C-b 1' (to + select window 1), and so on; `C-b n' to select the next window; and `C-b + p' to select the previous window. + + A session may be detached using `C-b d' and reattached with: + + $ tmux attach-session + + Typing `C-b ?' lists the current key bindings in the current window; up + and down may be used to navigate the list or `Q' to exit from it. + +KKEEYY BBIINNDDIINNGGSS + ttmmuuxx may be controlled from an attached client by using a key combination + of a prefix key, `C-b' (ctrl-b) by default, followed by a command key. + + Some of the default key bindings include: + + `d' Detach current client. + `c' Create new window. + `n' Change to next window in the current session. + `p' Change to previous window in the current session. + `l' Move to last (previously selected) window in the current session. + `t' Display a large clock. + `?' List current key bindings. + + A complete list may be obtained with the lliisstt--kkeeyyss command (bound to `?' + by default). Key bindings may be changed with the bbiinndd--kkeeyy and uunnbbiinndd-- + kkeeyy commands. + +HHIISSTTOORRYY + ttmmuuxx maintains a configurable history buffer for each window. By de- + fault, up to 2000 lines are kept, this can be altered with the hhiissttoorryy-- + lliimmiitt option (see the sseett--ooppttiioonn command below). + +MMOODDEESS + A ttmmuuxx window may be in one of several modes. The default permits direct + access to the terminal attached to the window. The others are: + + _o_u_t_p_u_t _m_o_d_e + This is entered when a command which produces output, such as + lliisstt--kkeeyyss, is executed from a key binding. + + _s_c_r_o_l_l _m_o_d_e + This is entered with the ssccrroollll--mmooddee command (bound to `=' by de- + fault) and permits the window history buffer to be inspected. + + _c_o_p_y _m_o_d_e + This permits a section of a window or its history to be copied to + a _p_a_s_t_e _b_u_f_f_e_r for later insertion into another window. This + mode is entered with the ccooppyy--mmooddee command, bound to `[' by de- + fault. + + The keys available depend on whether emacs(1) or vi(1) mode is selected + (see the mmooddee--kkeeyyss option). The following keys are supported as appro- + priate for the mode: + + FFuunnccttiioonn vvii eemmaaccss + Start of line 0 or ^ C-a + Clear selection Escape C-g + Copy selection Enter M-w + Cursor down j Down + End of line $ C-e + Cursor left h Left + Next page C-f Page down + Next word w M-f + Previous page C-u Page up + Previous word b M-b + Quit mode q Escape + Cursor right l Right + Start selection Space C-Space + Cursor up k Up + +BBUUFFFFEERRSS + ttmmuuxx maintains a stack of _p_a_s_t_e _b_u_f_f_e_r_s for each session. Up to the val- + ue of the bbuuffffeerr--lliimmiitt option are kept; when a new buffer is added, the + buffer at the bottom of the stack is removed. Buffers may be added using + ccooppyy--mmooddee or the sseett--bbuuffffeerr command, and pasted into a window using the + ppaassttee--bbuuffffeerr command. + +PPAANNEESS AANNDD LLAAYYOOUUTTSS + Each window displayed by ttmmuuxx may be split into one or more _p_a_n_e_s; each + pane takes up a certain area of the display and is a separate terminal. + A window may be split into panes using the sspplliitt--wwiinnddooww command. + + Panes are numbered beginning from zero; in horizontal layouts zero is the + leftmost pane and in vertical the topmost. + + Panes may be arranged using several layouts. The layout may be cycled + with the nneexxtt--llaayyoouutt command (bound to `C-space' by default), the current + pane may be changed with the uupp--ppaannee and ddoowwnn--ppaannee commands and the + rroottaattee--wwiinnddooww and sswwaapp--ppaannee commands may be used to swap panes without + changing the window layout. + + The following layouts are supported: + + mmaannuuaall Manual layout splits windows vertically (running across); only + with this layout may panes be resized using the rreessiizzee--ppaannee com- + mand. + + aaccttiivvee--oonnllyy + Only the active pane is shown - all other panes are hidden. + + eevveenn--hhoorriizzoonnttaall + Panes are spread out evenly from left to right across the window. + + eevveenn--vveerrttiiccaall + Panes are spread evenly from top to bottom. + + mmaaiinn--vveerrttiiccaall + A large (81 column) pane is shown on the left of the window and + the remaining panes are spread from top to bottom in the leftover + space to the right. + +CCOOMMMMAANNDDSS + This section contains a list of the commands supported by ttmmuuxx. Most + commands accept the optional --tt argument with one of _t_a_r_g_e_t_-_c_l_i_e_n_t, + _t_a_r_g_e_t_-_s_e_s_s_i_o_n or _t_a_r_g_e_t_-_w_i_n_d_o_w. These specify the client, session or + window which a command should affect. _t_a_r_g_e_t_-_c_l_i_e_n_t is the name of the + pty(4) file to which the client is connected, for example _/_d_e_v_/_t_t_y_p_1. + Clients may be listed with the lliisstt--cclliieennttss command. + + _t_a_r_g_e_t_-_s_e_s_s_i_o_n is either the name of a session (as listed by the lliisstt-- + sseessssiioonnss command); or the name of a client as for _t_a_r_g_e_t_-_c_l_i_e_n_t, in this + case, the session attached to the client is used. An fnmatch(3) pattern + may be used to match the session name. If a session is omitted when re- + quired, ttmmuuxx attempts to use the current session; if no current session + is available, the most recently created is chosen. If no client is spec- + ified, the current client is chosen, if possible, or an error is report- + ed. + + _t_a_r_g_e_t_-_w_i_n_d_o_w specifies a window in the form _s_e_s_s_i_o_n:_i_n_d_e_x, for example + mysession:1. The session is in the same form as for _t_a_r_g_e_t_-_s_e_s_s_i_o_n. + _s_e_s_s_i_o_n, _i_n_d_e_x or both may be omitted. If _s_e_s_s_i_o_n is omitted, the same + rules as for _t_a_r_g_e_t_-_s_e_s_s_i_o_n are followed; if _i_n_d_e_x is not present, the + current window for the given session is used. When the argument does not + contain a colon (:), ttmmuuxx first attempts to parse it as window index; if + that fails, an attempt is made to match a session or client name. + + Multiple commands may be specified together as part of a _c_o_m_m_a_n_d + _s_e_q_u_e_n_c_e. Each command should be separated by spaces and a semicolon (` + ; '); commands are executed sequentially from left to right. A literal + semicolon may be included by escaping it with a backslash (for example, + when specifying a command sequence to bbiinndd--kkeeyy). + + Examples include: + + refresh-client -t/dev/ttyp2 + + rename-session -tfirst newname + + set-window-option -t:0 monitor-activity on + + new-window ; split-window -d + + bind-key D detach-client \; lock-server + + The following commands are available: + + aattttaacchh--sseessssiioonn [--dd] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: aattttaacchh) + Create a new client in the current terminal and attach it to a + session. If --dd is specified, any other clients attached to the + session are detached. + + If no server is started, aattttaacchh--sseessssiioonn will attempt to start it; + this will fail unless sessions are created in the configuration + file. + + bbiinndd--kkeeyy [--rr] _k_e_y _c_o_m_m_a_n_d [_a_r_g_u_m_e_n_t_s] + (alias: bbiinndd) + Bind key _k_e_y to _c_o_m_m_a_n_d. Keys may be specified prefixed with + `C-' or `^' for ctrl keys, or `M-' for alt (meta) keys. The --rr + flag indicates this key may repeat, see the rreeppeeaatt--ttiimmee option. + + bbrreeaakk--ppaannee [--dd] [--pp _p_a_n_e_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: bbrreeaakkpp)) + Break the current pane off from its containing window to make it + the only pane in a new window. If --dd is given, the new window + does not become the current window. + + cchhoooossee--sseessssiioonn [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + Put a window into session choice mode, where the session for the + current client may be selected interactively from a list. This + command works only from inside ttmmuuxx. + + cchhoooossee--wwiinnddooww [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + Put a window into window choice mode, where the window for the + session attached to the current client may be selected interac- + tively from a list. This command works only from inside ttmmuuxx. + + cclloocckk--mmooddee [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + Display a large clock. + + ccoommmmaanndd--pprroommpptt [--tt _t_a_r_g_e_t_-_c_l_i_e_n_t] [_t_e_m_p_l_a_t_e] + Open the command prompt in a client. This may be used from in- + side ttmmuuxx to execute commands interactively. If _t_e_m_p_l_a_t_e is + specified, it is used as the command; any %% in the template will + be replaced by what is entered at the prompt. + + ccoonnffiirrmm--bbeeffoorree [--tt _t_a_r_g_e_t_-_c_l_i_e_n_t] _c_o_m_m_a_n_d + (alias: ccoonnffiirrmm)) + Ask for confirmation before executing _c_o_m_m_a_n_d. This command + works only from inside ttmmuuxx. + + ccooppyy--bbuuffffeerr [--aa _s_r_c_-_i_n_d_e_x] [--bb _d_s_t_-_i_n_d_e_x] [--ss _s_r_c_-_s_e_s_s_i_o_n] [--tt + _d_s_t_-_s_e_s_s_i_o_n] + (alias: ccooppyybb)) + Copy a session paste buffer to another session. If no sessions + are specified, the current one is used instead. + + ccooppyy--mmooddee [--uu] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + Enter copy mode. The --uu option scrolls one page up. + + ddeelleettee--bbuuffffeerr [--bb _b_u_f_f_e_r_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: ddeelleetteebb) + Delete the buffer at _b_u_f_f_e_r_-_i_n_d_e_x, or the top buffer if not spec- + ified. + + ddeettaacchh--cclliieenntt [--tt _t_a_r_g_e_t_-_c_l_i_e_n_t] + (alias: ddeettaacchh) + Detach the current client if bound to a key, or the specified + client with --tt. + + ddoowwnn--ppaannee [--pp _p_a_n_e_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: ddoowwnnpp) + Move down a pane. + + ffiinndd--wwiinnddooww [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] _m_a_t_c_h_-_s_t_r_i_n_g + (alias: ffiinnddww) + Search for _m_a_t_c_h_-_s_t_r_i_n_g in window names, titles, and visible con- + tent (but not history). If only one window is matched, it'll be + automatically selected, otherwise a choice list is shown. This + command only works from inside ttmmuuxx. + + hhaass--sseessssiioonn [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: hhaass) + Report an error and exit with 1 if the specified session does not + exist. If it does exist, exit with 0. + + kkiillll--ppaannee [--pp _p_a_n_e_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: kkiillllpp) + Destroy the given pane. + + kkiillll--sseerrvveerr + Kill the ttmmuuxx server and clients and destroy all sessions. + + kkiillll--sseessssiioonn [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + Destroy the given session, closing any windows linked to it and + no other sessions, and detaching all clients attached to it. + + kkiillll--wwiinnddooww [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: kkiillllww) + Kill the current window or the window at _t_a_r_g_e_t_-_w_i_n_d_o_w, removing + it from any sessions to which it is linked. + + llaasstt--wwiinnddooww [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: llaasstt) + Select the last (previously selected) window. If no _t_a_r_g_e_t_- + _s_e_s_s_i_o_n is specified, select the last window of the current ses- + sion. + + lliinnkk--wwiinnddooww [--ddkk] [--ss _s_r_c_-_w_i_n_d_o_w] [--tt _d_s_t_-_w_i_n_d_o_w] + (alias: lliinnkkww) + Link the window at _s_r_c_-_w_i_n_d_o_w to the specified _d_s_t_-_w_i_n_d_o_w. If + _d_s_t_-_w_i_n_d_o_w is specified and no such window exists, the _s_r_c_-_w_i_n_d_o_w + is linked there. If --kk is given and _d_s_t_-_w_i_n_d_o_w exists, it is + killed, otherwise an error is generated. If --dd is given, the + newly linked window is not selected. + + lliisstt--bbuuffffeerrss [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: llssbb) + List the buffers in the given session. + + lliisstt--cclliieennttss + (alias: llsscc) + List all clients attached to the server. + + lliisstt--ccoommmmaannddss + (alias: llssccmm) + List the syntax of all commands supported by ttmmuuxx. + + lliisstt--kkeeyyss + (alias: llsskk) + List all key bindings. + + lliisstt--sseessssiioonnss + (alias: llss) + List all sessions managed by the server. + + lliisstt--wwiinnddoowwss [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: llssww) + List windows in the current session or in _t_a_r_g_e_t_-_s_e_s_s_i_o_n. + + llooaadd--bbuuffffeerr [--bb _b_u_f_f_e_r_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] _p_a_t_h + (alias: llooaaddbb) + Load the contents of the specified paste buffer from _p_a_t_h. + + lloocckk--sseerrvveerr + (alias: lloocckk) + Lock the server until a password is entered. + + mmoovvee--wwiinnddooww [--dd] [--ss _s_r_c_-_w_i_n_d_o_w] [--tt _d_s_t_-_w_i_n_d_o_w] + (alias: mmoovveeww) + This is similar to lliinnkk--wwiinnddooww, except the window at _s_r_c_-_w_i_n_d_o_w + is moved to _d_s_t_-_w_i_n_d_o_w. + + nneeww--sseessssiioonn [--dd] [--nn _w_i_n_d_o_w_-_n_a_m_e] [--ss _s_e_s_s_i_o_n_-_n_a_m_e] [_c_o_m_m_a_n_d] + (alias: nneeww) + Create a new session with name _s_e_s_s_i_o_n_-_n_a_m_e. The new session is + attached to the current terminal unless --dd is given. _w_i_n_d_o_w_-_n_a_m_e + and _c_o_m_m_a_n_d are the name of and command to execute in the initial + window. + + nneeww--wwiinnddooww [--dd] [--nn _w_i_n_d_o_w_-_n_a_m_e] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] [_c_o_m_m_a_n_d] + (alias: nneewwww) + Create a new window. If --dd is given, the session does not make + the new window the current window. _t_a_r_g_e_t_-_w_i_n_d_o_w represents the + window to be created. _c_o_m_m_a_n_d is the command to execute. If + _c_o_m_m_a_n_d is not specified, the default command is used. + + The TERM environment variable must be set to ``screen'' for all + programs running _i_n_s_i_d_e ttmmuuxx. New windows will automatically + have ``TERM=screen'' added to their environment, but care must be + taken not to reset this in shell start-up files. + + nneexxtt--llaayyoouutt [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: nneexxttll) + Move a window to the next layout and rearrange the panes to fit. + + nneexxtt--wwiinnddooww [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: nneexxtt) + Move to the next window in the session. + + ppaassttee--bbuuffffeerr [--dd] [--bb _b_u_f_f_e_r_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: ppaasstteebb) + Insert the contents of a paste buffer into the current window. + + pprreevviioouuss--wwiinnddooww [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: pprreevv) + Move to the previous window in the session. + + rreeffrreesshh--cclliieenntt [--tt _t_a_r_g_e_t_-_c_l_i_e_n_t] + (alias: rreeffrreesshh) + Refresh the current client if bound to a key, or a single client + if one is given with --tt. + + rreennaammee--sseessssiioonn [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] _n_e_w_-_n_a_m_e + (alias: rreennaammee) + Rename the session to _n_e_w_-_n_a_m_e. + + rreennaammee--wwiinnddooww [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] _n_e_w_-_n_a_m_e + (alias: rreennaammeeww) + Rename the current window, or the window at _t_a_r_g_e_t_-_w_i_n_d_o_w if + specified, to _n_e_w_-_n_a_m_e. + + rreessiizzee--ppaannee [--DDUU] [--pp _p_a_n_e_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] [_a_d_j_u_s_t_m_e_n_t] + (alias: rreessiizzeepp) + Resize a pane, upward with --UU (the default) or downward with --DD. + The _a_d_j_u_s_t_m_e_n_t is given in lines (the default is 1). + + rreessppaawwnn--wwiinnddooww [--kk] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] [_c_o_m_m_a_n_d] + (alias: rreessppaawwnnww) + Reactive a window in which the command has exited (see the + rreemmaaiinn--oonn--eexxiitt window option). If _c_o_m_m_a_n_d is not given, the com- + mand used when the window was created is executed. The window + must be already inactive, unless --kk is given, in which case any + existing command is killed. + + rroottaattee--wwiinnddooww [--DDUU] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: rroottaatteeww) + Rotate the positions of the panes within a window, either upward + (numerically lower) with --UU or downward (numerically higher). + + ssaavvee--bbuuffffeerr [--aa] [--bb _b_u_f_f_e_r_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] _p_a_t_h + (alias: ssaavveebb) + Save the contents of the specified paste buffer to _p_a_t_h. The --aa + option appends to rather than overwriting the file. + + ssccrroollll--mmooddee [--uu] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + Enter scroll mode. The --uu has the same meaning as in the ccooppyy-- + mmooddee command. + + sseelleecctt--ppaannee [--pp _p_a_n_e_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: sseelleeccttpp) + Make pane _p_a_n_e_-_i_n_d_e_x the active pane in window _t_a_r_g_e_t_-_w_i_n_d_o_w. + + sseelleecctt--pprroommpptt [--tt _t_a_r_g_e_t_-_c_l_i_e_n_t] + Open a prompt inside _t_a_r_g_e_t_-_c_l_i_e_n_t allowing a window index to be + entered interactively. + + sseelleecctt--wwiinnddooww [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: sseelleeccttww) + Select the window at _t_a_r_g_e_t_-_w_i_n_d_o_w. + + sseenndd--kkeeyyss [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] _k_e_y _._._. + (alias: sseenndd) + Send a key or keys to a window. Each argument _k_e_y is the name of + the key (such as `C-a' or `npage' ) to send; if the string is not + recognised as a key, it is sent as a series of characters. All + arguments are sent sequentially from first to last. + + sseenndd--pprreeffiixx [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + Send the prefix key to a window as if it was pressed. + + sseerrvveerr--iinnffoo + (alias: iinnffoo) + Show server information and terminal details. + + sseett--bbuuffffeerr [--bb _b_u_f_f_e_r_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] _d_a_t_a + (alias: sseettbb) + Set the contents of the specified buffer to _d_a_t_a. + + sseett--ooppttiioonn [--gguu] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] _o_p_t_i_o_n _v_a_l_u_e + (alias: sseett) + Set an option. If --gg is specified, the option is set as a global + option. Global options apply to all sessions which don't have + the option explicitly set. If --gg is not used, the option applies + only to _t_a_r_g_e_t_-_s_e_s_s_i_o_n. The --uu flag unsets an option, so a ses- + sion inherits the option from the global options - it is not pos- + sible to unset a global option. + + Possible options are: + + bbeellll--aaccttiioonn [aannyy | nnoonnee | ccuurrrreenntt] + Set action on window bell. aannyy means a bell in any win- + dow linked to a session causes a bell in the current win- + dow of that session, nnoonnee means all bells are ignored and + ccuurrrreenntt means only bell in windows other than the current + window are ignored. + + bbuuffffeerr--lliimmiitt _n_u_m_b_e_r + Set the number of buffers kept for each session; as new + buffers are added to the top of the stack, old ones are + removed from the bottom if necessary to maintain this + maximum length. + + ddeeffaauulltt--ccoommmmaanndd _c_o_m_m_a_n_d + Set the command used for new windows (if not specified + when the window is created) to _c_o_m_m_a_n_d. The default is + ``exec $SHELL''. + + ddeeffaauulltt--ppaatthh _p_a_t_h + Set the default working directory for processes created + from keys, or interactively from the prompt. The default + is the current working directory when the server is + started. + + hhiissttoorryy--lliimmiitt _l_i_n_e_s + Set the maximum number of lines held in window history. + This setting applies only to new windows - existing win- + dow histories are not resized and retain the limit at the + point they were created. + + lloocckk--aafftteerr--ttiimmee _n_u_m_b_e_r + Lock the server after _n_u_m_b_e_r seconds of inactivity. The + default is off (set to 0). This has no effect as a ses- + sion option; it must be set as a global option using --gg. + + mmeessssaaggee--aattttrr _a_t_t_r_i_b_u_t_e_s + Set status line message attributes, where _a_t_t_r_i_b_u_t_e_s is + either ddeeffaauulltt or a comma-delimited list of one or more + of: bbrriigghhtt (or bboolldd), ddiimm, uunnddeerrssccoorree, bblliinnkk, rreevveerrssee, + hhiiddddeenn, or iittaalliiccss. + + mmeessssaaggee--bbgg _c_o_l_o_u_r + Set status line message background colour, where _c_o_l_o_u_r + is one of: bbllaacckk, rreedd, ggrreeeenn, yyeellllooww, bblluuee, mmaaggeennttaa, + ccyyaann, wwhhiittee or ddeeffaauulltt. + + mmeessssaaggee--ffgg _c_o_l_o_u_r + Set status line message foreground colour. + + pprreeffiixx _k_e_y + Set the current prefix key. + + rreeppeeaatt--ttiimmee _n_u_m_b_e_r + Allow multiple commands to be entered without pressing + the prefix-key again in the specified _n_u_m_b_e_r milliseconds + (the default is 500). Whether a key repeats may be set + when it is bound using the --rr flag to bbiinndd--kkeeyy. Repeat + is enabled for the default keys of the uupp--ppaannee, ddoowwnn-- + ppaannee, rreessiizzee--ppaannee--uupp, and rreessiizzee--ppaannee--ddoowwnn commands. + + sseett--rreemmaaiinn--oonn--eexxiitt [oonn | ooffff] + Set the rreemmaaiinn--oonn--eexxiitt window option for any windows + first created in this session. + + sseett--ttiittlleess [oonn | ooffff] + Attempt to set the window title using the \e]2;...\007 + xterm code and the terminal appears to be an xterm. This + option is enabled by default. Note that elinks(1) will + only attempt to set the window title if the STY environ- + ment variable is set. + + ssttaattuuss [oonn | ooffff] + Show or hide the status line. + + ssttaattuuss--aattttrr _a_t_t_r_i_b_u_t_e_s + Set status line attributes. + + ssttaattuuss--bbgg _c_o_l_o_u_r + Set status line background colour. + + ssttaattuuss--ffgg _c_o_l_o_u_r + Set status line foreground colour. + + ssttaattuuss--iinntteerrvvaall _i_n_t_e_r_v_a_l + Update the status bar every _i_n_t_e_r_v_a_l seconds. By de- + fault, updates will occur every 15 seconds. A setting of + zero disables redrawing at interval. + + ssttaattuuss--kkeeyyss [vvii | eemmaaccss] + Use vi(1)- or emacs(1)-style key bindings in the status + line, for example at the command prompt. Defaults to + emacs. + + ssttaattuuss--lleefftt _s_t_r_i_n_g + Display _s_t_r_i_n_g to the left of the status bar. _s_t_r_i_n_g + will be passed through strftime(3) before being used. By + default, the session name is shown. _s_t_r_i_n_g may contain + any of the following special character pairs: + + CChhaarraacctteerr ppaaiirr RReeppllaacceedd wwiitthh + #(command) First line of command's output + #H Hostname of local host + #S Session name + #T Current window title + ## A literal `#' + + Where appropriate, these may be prefixed with a number to + specify the maximum length, for example `#24T'. + + ssttaattuuss--lleefftt--lleennggtthh _l_e_n_g_t_h + Set the maximum _l_e_n_g_t_h of the left component of the sta- + tus bar. The default is 10. + + ssttaattuuss--rriigghhtt _s_t_r_i_n_g + Display _s_t_r_i_n_g to the right of the status bar. By de- + fault, the date and time will be shown. As with ssttaattuuss-- + lleefftt, _s_t_r_i_n_g will be passed to strftime(3) and character + pairs are replaced. + + ssttaattuuss--rriigghhtt--lleennggtthh _l_e_n_g_t_h + Set the maximum _l_e_n_g_t_h of the right component of the sta- + tus bar. The default is 40. + + sseett--ppaasssswwoorrdd [--cc] _p_a_s_s_w_o_r_d + (alias: ppaassss) + Set the server password. If the --cc option is given, a pre-en- + crypted password may be specified. By default, the password is + blank, thus any entered password will be accepted when unlocking + the server (see the lloocckk--sseerrvveerr command). To prevent variable + expansion when an encrypted password is read from a configuration + file, enclose it in single quotes ('). + + sseett--wwiinnddooww--ooppttiioonn [--gguu] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] _o_p_t_i_o_n _v_a_l_u_e + (alias: sseettww) + Set a window-specific option. The --gg and --uu flags work similarly + to the sseett--ooppttiioonn command. + + Supported options are: + + aaggggrreessssiivvee--rreessiizzee [oonn | ooffff] + Aggressively resize the chosen window. This means that + ttmmuuxx will resize the window to the size of the smallest + session for which it is the current window, rather than + the smallest session to which it is attached. The window + may resize when the current window is changed on another + sessions; this option is good for full-screen programs + which support SIGWINCH and poor for interactive programs + such as shells. + + aauuttoommaattiicc--rreennaammee [oonn | ooffff] + Control automatic window renaming. When this setting is + enabled, ttmmuuxx will attempt - on supported platforms - to + rename the window to reflect the command currently run- + ning in it. This flag is automatically disabled for an + individual window when a name is specified at creation + with nneeww--wwiinnddooww oorr nneeww--sseessssiioonn, or later with rreennaammee-- + wwiinnddooww. It may be switched off globally with: + + set-window-option -g automatic-rename off + + cclloocckk--mmooddee--ccoolloouurr _c_o_l_o_u_r + Set clock colour. + + cclloocckk--mmooddee--ssttyyllee [1122 | 2244] + Set clock hour format. + + ffoorrccee--hheeiigghhtt _h_e_i_g_h_t + + ffoorrccee--wwiiddtthh _w_i_d_t_h + Prevent ttmmuuxx from resizing a window to greater than _w_i_d_t_h + or _h_e_i_g_h_t. A value of zero restores the default unlimit- + ed setting. + + mmooddee--aattttrr _a_t_t_r_i_b_u_t_e_s + Set window modes attributes. + + mmooddee--bbgg _c_o_l_o_u_r + Set window modes background colour. + + mmooddee--ffgg _c_o_l_o_u_r + Set window modes foreground colour. + + mmooddee--kkeeyyss [vvii | eemmaaccss] + Use vi(1)- or emacs(1)-style key bindings in scroll and + copy modes. Key bindings default to emacs. + + mmoonniittoorr--aaccttiivviittyy [oonn | ooffff] + Monitor for activity in the window. Windows with activi- + ty are highlighted in the status line. + + mmoonniittoorr--ccoonntteenntt _m_a_t_c_h_-_s_t_r_i_n_g + Monitor content in the window. When _m_a_t_c_h_-_s_t_r_i_n_g appears + in the window, it is highlighted in the status line. + + rreemmaaiinn--oonn--eexxiitt [oonn | ooffff] + A window with this flag set is not destroyed when the + program running in it exits. The window may be reacti- + vated with the rreessppaawwnn--wwiinnddooww command. + + uuttff88 [oonn | ooffff] + Instructs ttmmuuxx to expect UTF-8 sequences to appear in + this window. + + wwiinnddooww--ssttaattuuss--aattttrr _a_t_t_r_i_b_u_t_e_s + Set status line attributes for a single window. + + wwiinnddooww--ssttaattuuss--bbgg _c_o_l_o_u_r + Set status line background colour for a single window. + + wwiinnddooww--ssttaattuuss--ffgg _c_o_l_o_u_r + Set status line foreground colour for a single window. + + xxtteerrmm--kkeeyyss [oonn | ooffff] + If this option is set, ttmmuuxx will generate xterm(1)-style + function key sequences; these have a number included to + indicate modifiers such as shift, meta or ctrl. + + sshhooww--bbuuffffeerr [--bb _b_u_f_f_e_r_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: sshhoowwbb) + Display the contents of the specified buffer. + + sshhooww--ooppttiioonnss [--tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] _o_p_t_i_o_n _v_a_l_u_e + (alias: sshhooww) + Show the currently set options. If a _t_a_r_g_e_t_-_s_e_s_s_i_o_n is speci- + fied, the options for that session are shown; otherwise, the + global options are listed. + + sshhooww--wwiinnddooww--ooppttiioonnss [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] _o_p_t_i_o_n _v_a_l_u_e + (alias: sshhoowwww) + List the current options for the given window. + + ssoouurrccee--ffiillee _p_a_t_h + (alias: ssoouurrccee) + Execute commands from _p_a_t_h. + + sspplliitt--wwiinnddooww [--dd] [--ll _l_i_n_e_s | --pp _p_e_r_c_e_n_t_a_g_e] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] [_c_o_m_m_a_n_d] + (alias: splitw) + Creates a new window by splitting it vertically. The --ll and --pp + options specify the size of the new window in lines, or as a per- + centage, respectively. All other options have the same meaning + as in the nneeww--wwiinnddooww command. + + A few notes with regard to panes: + 1. If attempting to split a window with less than eight lines, + an error will be shown. + 2. If the window is resized, as many panes are shown as can fit + without reducing them below four lines. + 3. The minimum pane size is four lines (including the separator + line). + 4. The panes are indexed from top (0) to bottom, with no num- + bers skipped. + + ssttaarrtt--sseerrvveerr + (alias: ssttaarrtt) + Start the ttmmuuxx server, if not already running, without creating + any sessions. + + ssuussppeenndd--cclliieenntt [--cc --ttaarrggeett--cclliieenntt] + (alias: ssuussppeennddcc) + Suspend a client by sending SIGTSTP (tty stop). + + sswwaapp--ppaannee [--ddDDUU] [--pp _s_r_c_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] [--qq _d_s_t_-_i_n_d_e_x] + (alias: sswwaapppp) + Swap two panes within a window. If --UU is used, the pane is + swapped with the pane above (before it numerically); --DD swaps + with the pane below (the next numerically); or _d_s_t_-_i_n_d_e_x may be + give to swap with a specific pane. + + sswwaapp--wwiinnddooww [--dd] [--ss _s_r_c_-_w_i_n_d_o_w] [--tt _d_s_t_-_w_i_n_d_o_w] + (alias: sswwaappww) + This is similar to lliinnkk--wwiinnddooww, except the source and destination + windows are swapped. It is an error if no window exists at _s_r_c_- + _w_i_n_d_o_w. + + sswwiittcchh--cclliieenntt [--cc _t_a_r_g_e_t_-_c_l_i_e_n_t --tt _t_a_r_g_e_t_-_s_e_s_s_i_o_n] + (alias: sswwiittcchhcc) + Switch the current session for client _t_a_r_g_e_t_-_c_l_i_e_n_t to _t_a_r_g_e_t_- + _s_e_s_s_i_o_n. + + uunnbbiinndd--kkeeyy _k_e_y + (alias: uunnbbiinndd) + Unbind the key bound to _k_e_y. + + uunnlliinnkk--wwiinnddooww [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: uunnlliinnkkww) + Unlink _t_a_r_g_e_t_-_w_i_n_d_o_w. A window may be unlinked only if it is + linked to multiple sessions - windows may not be linked to no + sessions. + + uupp--ppaannee [--pp _p_a_n_e_-_i_n_d_e_x] [--tt _t_a_r_g_e_t_-_w_i_n_d_o_w] + (alias: uupppp) + Move up a pane. + +FFIILLEESS + ~/.tmux.conf + default ttmmuuxx configuration file + +SSEEEE AALLSSOO + pty(4) + +AAUUTTHHOORRSS + Nicholas Marriott + +OpenBSD 4.5 April 20, 2009 13 diff --git a/tmux.h b/tmux.h new file mode 100644 index 00000000..a2d5f0da --- /dev/null +++ b/tmux.h @@ -0,0 +1,1576 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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. + */ + +#ifndef TMUX_H +#define TMUX_H + +#define PROTOCOL_VERSION -13 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "array.h" + +extern const char *__progname; + +/* Default configuration file. */ +#define DEFAULT_CFG ".tmux.conf" + +/* Default prompt history length. */ +#define PROMPT_HISTORY 100 + +/* Minimum pane size. */ +#define PANE_MINIMUM 4 /* includes separator line */ + +/* Automatic name refresh interval, in milliseconds. */ +#define NAME_INTERVAL 500 + +/* Escape timer period, in milliseconds. */ +#define ESCAPE_PERIOD 250 + +/* Maximum poll timeout (when attached). */ +#define POLL_TIMEOUT 50 + +/* Fatal errors. */ +#define fatal(msg) log_fatal("%s: %s", __func__, msg); +#define fatalx(msg) log_fatalx("%s: %s", __func__, msg); + +/* Definition to shut gcc up about unused arguments. */ +#define unused __attribute__ ((unused)) + +/* Attribute to make gcc check printf-like arguments. */ +#define printflike1 __attribute__ ((format (printf, 1, 2))) +#define printflike2 __attribute__ ((format (printf, 2, 3))) +#define printflike3 __attribute__ ((format (printf, 3, 4))) +#define printflike4 __attribute__ ((format (printf, 4, 5))) + +/* Number of items in array. */ +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) + +/* Buffer macros. */ +#define BUFFER_USED(b) ((b)->size) +#define BUFFER_FREE(b) ((b)->space - (b)->off - (b)->size) +#define BUFFER_IN(b) ((b)->base + (b)->off + (b)->size) +#define BUFFER_OUT(b) ((b)->base + (b)->off) + +/* Buffer structure. */ +struct buffer { + u_char *base; /* buffer start */ + size_t space; /* total size of buffer */ + + size_t size; /* size of data in buffer */ + size_t off; /* offset of data in buffer */ +}; + +/* Bell option values. */ +#define BELL_NONE 0 +#define BELL_ANY 1 +#define BELL_CURRENT 2 + +/* Key codes. ncurses defines KEY_*. Grrr. */ +#define KEYC_NONE 0x00ffff +#define KEYC_OFFSET 0x010000 +#define KEYC_ESCAPE 0x020000 +#define KEYC_CONTROL 0x080000 +#define KEYC_SHIFT 0x100000 + +#define KEYC_ADDESC(k) ((k) | KEYC_ESCAPE) +#define KEYC_REMOVEESC(k) ((k) & ~KEYC_ESCAPE) +#define KEYC_ISESC(k) ((k) != KEYC_NONE && ((k) & KEYC_ESCAPE)) + +#define KEYC_ADDCTL(k) ((k) | KEYC_CONTROL) +#define KEYC_REMOVECTL(k) ((k) & ~KEYC_CONTROL) +#define KEYC_ISCTL(k) ((k) != KEYC_NONE && ((k) & KEYC_CONTROL)) + +#define KEYC_ADDSFT(k) ((k) | KEYC_SHIFT) +#define KEYC_REMOVESFT(k) ((k) & ~KEYC_SHIFT) +#define KEYC_ISSFT(k) ((k) != KEYC_NONE && ((k) & KEYC_SHIFT)) + +/* Mouse key. */ +#define KEYC_MOUSE (KEYC_OFFSET + 0x00) + +/* Function keys. */ +#define KEYC_F1 (KEYC_OFFSET + 0x01) +#define KEYC_F2 (KEYC_OFFSET + 0x02) +#define KEYC_F3 (KEYC_OFFSET + 0x03) +#define KEYC_F4 (KEYC_OFFSET + 0x04) +#define KEYC_F5 (KEYC_OFFSET + 0x05) +#define KEYC_F6 (KEYC_OFFSET + 0x06) +#define KEYC_F7 (KEYC_OFFSET + 0x07) +#define KEYC_F8 (KEYC_OFFSET + 0x08) +#define KEYC_F9 (KEYC_OFFSET + 0x09) +#define KEYC_F10 (KEYC_OFFSET + 0x10) +#define KEYC_F11 (KEYC_OFFSET + 0x11) +#define KEYC_F12 (KEYC_OFFSET + 0x12) +#define KEYC_F13 (KEYC_OFFSET + 0x13) +#define KEYC_F14 (KEYC_OFFSET + 0x14) +#define KEYC_F15 (KEYC_OFFSET + 0x15) +#define KEYC_F16 (KEYC_OFFSET + 0x16) +#define KEYC_F17 (KEYC_OFFSET + 0x17) +#define KEYC_F18 (KEYC_OFFSET + 0x18) +#define KEYC_F19 (KEYC_OFFSET + 0x19) +#define KEYC_F20 (KEYC_OFFSET + 0x1a) +#define KEYC_IC (KEYC_OFFSET + 0x1b) +#define KEYC_DC (KEYC_OFFSET + 0x1c) +#define KEYC_HOME (KEYC_OFFSET + 0x1d) +#define KEYC_END (KEYC_OFFSET + 0x1e) +#define KEYC_NPAGE (KEYC_OFFSET + 0x1f) +#define KEYC_PPAGE (KEYC_OFFSET + 0x20) +#define KEYC_BTAB (KEYC_OFFSET + 0x21) + +/* Arrow keys. */ +#define KEYC_UP (KEYC_OFFSET + 0x50) +#define KEYC_DOWN (KEYC_OFFSET + 0x51) +#define KEYC_LEFT (KEYC_OFFSET + 0x52) +#define KEYC_RIGHT (KEYC_OFFSET + 0x53) + +/* Numeric keypad. Numbered from top-left, KPY_X. */ +#define KEYC_KP0_1 (KEYC_OFFSET + 0x100) +#define KEYC_KP0_2 (KEYC_OFFSET + 0x101) +#define KEYC_KP0_3 (KEYC_OFFSET + 0x102) +#define KEYC_KP1_0 (KEYC_OFFSET + 0x103) +#define KEYC_KP1_1 (KEYC_OFFSET + 0x104) +#define KEYC_KP1_2 (KEYC_OFFSET + 0x105) +#define KEYC_KP1_3 (KEYC_OFFSET + 0x106) +#define KEYC_KP2_0 (KEYC_OFFSET + 0x107) +#define KEYC_KP2_1 (KEYC_OFFSET + 0x108) +#define KEYC_KP2_2 (KEYC_OFFSET + 0x109) +#define KEYC_KP3_0 (KEYC_OFFSET + 0x10a) +#define KEYC_KP3_1 (KEYC_OFFSET + 0x10b) +#define KEYC_KP3_2 (KEYC_OFFSET + 0x10c) +#define KEYC_KP3_3 (KEYC_OFFSET + 0x10d) +#define KEYC_KP4_0 (KEYC_OFFSET + 0x10e) +#define KEYC_KP4_2 (KEYC_OFFSET + 0x10f) + +/* Termcap codes. */ +enum tty_code_code { + TTYC_AX = 0, + TTYC_ACSC, /* acs_chars, ac */ + TTYC_BEL, /* bell, bl */ + TTYC_BLINK, /* enter_blink_mode, mb */ + TTYC_BOLD, /* enter_bold_mode, md */ + TTYC_CIVIS, /* cursor_invisible, vi */ + TTYC_CLEAR, /* clear_screen, cl */ + TTYC_CNORM, /* cursor_normal, ve */ + TTYC_COLORS, /* max_colors, Co */ + TTYC_CSR, /* change_scroll_region, cs */ + TTYC_CUD, /* parm_down_cursor, DO */ + TTYC_CUD1, /* cursor_down, do */ + TTYC_CUP, /* cursor_address, cm */ + TTYC_DCH, /* parm_dch, DC */ + TTYC_DCH1, /* delete_character, dc */ + TTYC_DIM, /* enter_dim_mode, mh */ + TTYC_DL, /* parm_delete_line, DL */ + TTYC_DL1, /* delete_line, dl */ + TTYC_EL, /* clr_eol, ce */ + TTYC_EL1, /* clr_bol, cb */ + TTYC_ENACS, /* ena_acs, eA */ + TTYC_ICH, /* parm_ich, IC */ + TTYC_ICH1, /* insert_character, ic */ + TTYC_IL, /* parm_insert_line, IL */ + TTYC_IL1, /* insert_line, il */ + TTYC_INVIS, /* enter_secure_mode, mk */ + TTYC_IS1, /* init_1string, i1 */ + TTYC_IS2, /* init_2string, i2 */ + TTYC_IS3, /* init_3string, i3 */ + TTYC_KCBT, /* key_btab, kB */ + TTYC_KCUB1, /* key_left, kl */ + TTYC_KCUD1, /* key_down, kd */ + TTYC_KCUF1, /* key_right, kr */ + TTYC_KCUU1, /* key_up, ku */ + TTYC_KDCH1, /* key_dc, kD */ + TTYC_KEND, /* key_end, ke */ + TTYC_KF1, /* key_f1, k1 */ + TTYC_KF10, /* key_f10, k; */ + TTYC_KF11, /* key_f11, F1 */ + TTYC_KF12, /* key_f12, F2 */ + TTYC_KF13, /* key_f13, F3 */ + TTYC_KF14, /* key_f14, F4 */ + TTYC_KF15, /* key_f15, F5 */ + TTYC_KF16, /* key_f16, F6 */ + TTYC_KF17, /* key_f17, F7 */ + TTYC_KF18, /* key_f18, F8 */ + TTYC_KF19, /* key_f19, F9 */ + TTYC_KF20, /* key_f20, F10 */ + TTYC_KF2, /* key_f2, k2 */ + TTYC_KF3, /* key_f3, k3 */ + TTYC_KF4, /* key_f4, k4 */ + TTYC_KF5, /* key_f5, k5 */ + TTYC_KF6, /* key_f6, k6 */ + TTYC_KF7, /* key_f7, k7 */ + TTYC_KF8, /* key_f8, k8 */ + TTYC_KF9, /* key_f9, k9 */ + TTYC_KHOME, /* key_home, kh */ + TTYC_KICH1, /* key_ic, kI */ + TTYC_KMOUS, /* key_mouse, Km */ + TTYC_KNP, /* key_npage, kN */ + TTYC_KPP, /* key_ppage, kP */ + TTYC_OP, /* orig_pair, op */ + TTYC_REV, /* enter_reverse_mode, mr */ + TTYC_RI, /* scroll_reverse, sr */ + TTYC_RMACS, /* exit_alt_charset_mode */ + TTYC_RMCUP, /* exit_ca_mode, te */ + TTYC_RMIR, /* exit_insert_mode, ei */ + TTYC_RMKX, /* keypad_local, ke */ + TTYC_SETAB, /* set_a_background, AB */ + TTYC_SETAF, /* set_a_foreground, AF */ + TTYC_SGR0, /* exit_attribute_mode, me */ + TTYC_SMACS, /* enter_alt_charset_mode, as */ + TTYC_SMCUP, /* enter_ca_mode, ti */ + TTYC_SMIR, /* enter_insert_mode, im */ + TTYC_SMKX, /* keypad_xmit, ks */ + TTYC_SMSO, /* enter_standout_mode, so */ + TTYC_SMUL, /* enter_underline_mode, us */ + TTYC_XENL, /* eat_newline_glitch, xn */ +}; +#define NTTYCODE (TTYC_XENL + 1) + +/* Termcap types. */ +enum tty_code_type { + TTYCODE_NONE = 0, + TTYCODE_STRING, + TTYCODE_NUMBER, + TTYCODE_FLAG, +}; + +/* Termcap code. */ +struct tty_code { + enum tty_code_type type; + union { + char *string; + int number; + int flag; + } value; +}; + +/* Entry in terminal code table. */ +struct tty_term_code_entry { + enum tty_code_code code; + enum tty_code_type type; + const char *name; +}; + +/* Output commands. */ +enum tty_cmd { + TTY_CELL, + TTY_CLEARENDOFLINE, + TTY_CLEARENDOFSCREEN, + TTY_CLEARLINE, + TTY_CLEARSCREEN, + TTY_CLEARSTARTOFLINE, + TTY_CLEARSTARTOFSCREEN, + TTY_DELETECHARACTER, + TTY_DELETELINE, + TTY_INSERTCHARACTER, + TTY_INSERTLINE, + TTY_LINEFEED, + TTY_RAW, + TTY_REVERSEINDEX, +}; + +/* Message codes. */ +enum hdrtype { + MSG_COMMAND, + MSG_DETACH, + MSG_ERROR, + MSG_EXIT, + MSG_EXITED, + MSG_EXITING, + MSG_IDENTIFY, + MSG_PRINT, + MSG_READY, + MSG_RESIZE, + MSG_SHUTDOWN, + MSG_SUSPEND, + MSG_UNLOCK, + MSG_WAKEUP, +}; + +/* Message header structure. */ +struct hdr { + enum hdrtype type; + size_t size; +}; + +struct msg_command_data { + pid_t pid; /* pid from $TMUX or -1 */ + u_int idx; /* index from $TMUX */ + + size_t namelen; +}; + +struct msg_identify_data { + char tty[TTY_NAME_MAX]; + int version; + + char cwd[MAXPATHLEN]; + +#define IDENTIFY_UTF8 0x1 +#define IDENTIFY_256COLOURS 0x2 +#define IDENTIFY_88COLOURS 0x4 +#define IDENTIFY_HASDEFAULTS 0x8 + int flags; + + u_int sx; + u_int sy; + + size_t termlen; +}; + +struct msg_resize_data { + u_int sx; + u_int sy; +}; + +/* Editing keys. */ +enum mode_key_cmd { + MODEKEYCMD_BACKSPACE = 0x1000, + MODEKEYCMD_CHOOSE, + MODEKEYCMD_CLEARSELECTION, + MODEKEYCMD_COMPLETE, + MODEKEYCMD_COPYSELECTION, + MODEKEYCMD_DELETE, + MODEKEYCMD_DOWN, + MODEKEYCMD_ENDOFLINE, + MODEKEYCMD_LEFT, + MODEKEYCMD_NEXTPAGE, + MODEKEYCMD_NEXTWORD, + MODEKEYCMD_NONE, + MODEKEYCMD_OTHERKEY, + MODEKEYCMD_PASTE, + MODEKEYCMD_PREVIOUSPAGE, + MODEKEYCMD_PREVIOUSWORD, + MODEKEYCMD_QUIT, + MODEKEYCMD_RIGHT, + MODEKEYCMD_STARTOFLINE, + MODEKEYCMD_STARTSELECTION, + MODEKEYCMD_UP, +}; + +struct mode_key_data { + int type; + + int flags; +#define MODEKEY_EDITMODE 0x1 +#define MODEKEY_CANEDIT 0x2 +#define MODEKEY_CHOOSEMODE 0x4 +}; + +#define MODEKEY_EMACS 0 +#define MODEKEY_VI 1 + +/* Modes. */ +#define MODE_CURSOR 0x1 +#define MODE_INSERT 0x2 +#define MODE_KCURSOR 0x4 +#define MODE_KKEYPAD 0x8 +#define MODE_MOUSE 0x10 + +/* Grid output. */ +#if defined(DEBUG) && \ + ((defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ + (defined(__GNUC__) && __GNUC__ >= 3)) +#define GRID_DEBUG(gd, fmt, ...) log_debug3("%s: (sx=%u, sy=%u, hsize=%u) " \ + fmt, __func__, (gd)->sx, (gd)->sy, (gd)->hsize, ## __VA_ARGS__) +#else +#define GRID_DEBUG(...) +#endif + +/* Grid attributes. */ +#define GRID_ATTR_BRIGHT 0x1 +#define GRID_ATTR_DIM 0x2 +#define GRID_ATTR_UNDERSCORE 0x4 +#define GRID_ATTR_BLINK 0x8 +#define GRID_ATTR_REVERSE 0x10 +#define GRID_ATTR_HIDDEN 0x20 +#define GRID_ATTR_ITALICS 0x40 +#define GRID_ATTR_CHARSET 0x80 /* alternative character set */ + +/* Grid flags. */ +#define GRID_FLAG_FG256 0x1 +#define GRID_FLAG_BG256 0x2 +#define GRID_FLAG_PADDING 0x4 +#define GRID_FLAG_UTF8 0x8 + +/* Grid cell data. */ +struct grid_cell { + u_char attr; + u_char flags; + u_char fg; + u_char bg; + u_char data; +} __packed; + +/* Grid cell UTF-8 data. Used instead of data in grid_cell for UTF-8 cells. */ +#define UTF8_SIZE 8 +struct grid_utf8 { + u_char width; + u_char data[UTF8_SIZE]; +} __packed; + +/* Entire grid of cells. */ +struct grid { + u_int sx; + u_int sy; + + u_int hsize; + u_int hlimit; + + u_int *size; + struct grid_cell **data; + + u_int *usize; + struct grid_utf8 **udata; +}; + +/* Option data structures. */ +struct options_entry { + char *name; + + enum { + OPTIONS_STRING, + OPTIONS_NUMBER, + OPTIONS_KEY, + } type; + union { + char *string; + long long number; + int key; + } value; + + SPLAY_ENTRY(options_entry) entry; +}; + +struct options { + SPLAY_HEAD(options_tree, options_entry) tree; + struct options *parent; +}; + +/* Screen selection. */ +struct screen_sel { + int flag; + + u_int sx; + u_int sy; + + u_int ex; + u_int ey; + + struct grid_cell cell; +}; + +/* Virtual screen. */ +struct screen { + char *title; + + struct grid *grid; /* grid data */ + + u_int cx; /* cursor x */ + u_int cy; /* cursor y */ + + u_int old_cx; + u_int old_cy; + + u_int rupper; /* scroll region top */ + u_int rlower; /* scroll region bottom */ + + u_int old_rupper; + u_int old_rlower; + + int mode; + + struct screen_sel sel; +}; + +/* Screen write context. */ +struct screen_write_ctx { + struct window_pane *wp; + struct screen *s; +}; + +/* Screen size. */ +#define screen_size_x(s) ((s)->grid->sx) +#define screen_size_y(s) ((s)->grid->sy) +#define screen_hsize(s) ((s)->grid->hsize) +#define screen_hlimit(s) ((s)->grid->hlimit) + +/* Input parser sequence argument. */ +struct input_arg { + u_char data[64]; + size_t used; +}; + +/* Input parser context. */ +struct input_ctx { + struct window_pane *wp; + struct screen_write_ctx ctx; + + u_char *buf; + size_t len; + size_t off; + + struct grid_cell cell; + + + struct grid_cell saved_cell; + u_int saved_cx; + u_int saved_cy; + +#define MAXSTRINGLEN 1024 + u_char *string_buf; + size_t string_len; + int string_type; +#define STRING_SYSTEM 0 +#define STRING_APPLICATION 1 +#define STRING_NAME 2 + + u_char utf8_buf[4]; + u_int utf8_len; + u_int utf8_off; + + u_char intermediate; + void *(*state)(u_char, struct input_ctx *); + + u_char private; + ARRAY_DECL(, struct input_arg) args; +}; + +/* + * Window mode. Windows can be in several modes and this is used to call the + * right function to handle input and output. + */ +struct client; +struct window; +struct window_mode { + struct screen *(*init)(struct window_pane *); + void (*free)(struct window_pane *); + void (*resize)(struct window_pane *, u_int, u_int); + void (*key)(struct window_pane *, struct client *, int); + void (*mouse)(struct window_pane *, + struct client *, u_char, u_char, u_char); + void (*timer)(struct window_pane *); +}; + +/* Child window structure. */ +struct window_pane { + struct window *window; + + u_int sx; + u_int sy; + + u_int xoff; + u_int yoff; + + int flags; +#define PANE_HIDDEN 0x1 +#define PANE_RESTART 0x2 +#define PANE_REDRAW 0x4 + + char *cmd; + char *cwd; + + pid_t pid; + int fd; + char tty[TTY_NAME_MAX]; + struct buffer *in; + struct buffer *out; + + struct input_ctx ictx; + + struct screen *screen; + struct screen base; + + const struct window_mode *mode; + void *modedata; + + TAILQ_ENTRY(window_pane) entry; +}; +TAILQ_HEAD(window_panes, window_pane); + +/* Window structure. */ +struct window { + char *name; + struct timeval name_timer; + + struct window_pane *active; + struct window_panes panes; + u_int layout; + + u_int sx; + u_int sy; + + int flags; +#define WINDOW_BELL 0x1 +#define WINDOW_HIDDEN 0x2 +#define WINDOW_ACTIVITY 0x4 +#define WINDOW_CONTENT 0x6 +#define WINDOW_REDRAW 0x8 + + struct options options; + + u_int references; +}; +ARRAY_DECL(windows, struct window *); + +/* Entry on local window list. */ +struct winlink { + int idx; + struct window *window; + + RB_ENTRY(winlink) entry; + SLIST_ENTRY(winlink) sentry; +}; +RB_HEAD(winlinks, winlink); +SLIST_HEAD(winlink_stack, winlink); + +/* Paste buffer. */ +struct paste_buffer { + char *data; + struct timeval tv; +}; +ARRAY_DECL(paste_stack, struct paste_buffer *); + +/* Client session. */ +struct session_alert { + struct winlink *wl; + int type; + + SLIST_ENTRY(session_alert) entry; +}; + +struct session { + char *name; + struct timeval tv; + + u_int sx; + u_int sy; + + struct winlink *curw; + struct winlink_stack lastw; + struct winlinks windows; + + struct options options; + + struct paste_stack buffers; + + SLIST_HEAD(, session_alert) alerts; + +#define SESSION_UNATTACHED 0x1 /* not attached to any clients */ + int flags; +}; +ARRAY_DECL(sessions, struct session *); + +/* TTY information. */ +struct tty_key { + int key; + char *string; + + int flags; +#define TTYKEY_CTRL 0x1 +#define TTYKEY_RAW 0x2 + + RB_ENTRY(tty_key) entry; +}; + +struct tty_term { + char *name; + u_int references; + + struct tty_code codes[NTTYCODE]; + +#define TERM_HASDEFAULTS 0x1 +#define TERM_256COLOURS 0x2 +#define TERM_88COLOURS 0x4 +#define TERM_EARLYWRAP 0x8 + int flags; + + SLIST_ENTRY(tty_term) entry; +}; +SLIST_HEAD(tty_terms, tty_term); + +struct tty { + char *path; + + u_int sx; + u_int sy; + + u_int cx; + u_int cy; + + int mode; + + u_int rlower; + u_int rupper; + + char *termname; + struct tty_term *term; + + int fd; + struct buffer *in; + struct buffer *out; + + int log_fd; + + struct termios tio; + + struct grid_cell cell; + + u_char acs[UCHAR_MAX + 1]; + +#define TTY_NOCURSOR 0x1 +#define TTY_FREEZE 0x2 +#define TTY_ESCAPE 0x4 +#define TTY_UTF8 0x8 + int flags; + + int term_flags; + + struct timeval key_timer; + + size_t ksize; /* maximum key size */ + RB_HEAD(tty_keys, tty_key) ktree; +}; + +/* Client connection. */ +struct client { + int fd; + struct buffer *in; + struct buffer *out; + + char *title; + char *cwd; + + struct tty tty; + struct timeval status_timer; + struct timeval repeat_timer; + + struct screen status; + +#define CLIENT_TERMINAL 0x1 +#define CLIENT_PREFIX 0x2 +#define CLIENT_MOUSE 0x4 +#define CLIENT_REDRAW 0x8 +#define CLIENT_STATUS 0x10 +#define CLIENT_REPEAT 0x20 /* allow command to repeat within repeat time */ +#define CLIENT_SUSPENDED 0x40 + int flags; + + char *message_string; + struct timeval message_timer; + + char *prompt_string; + char *prompt_buffer; + size_t prompt_index; + int (*prompt_callback)(void *, const char *); + void *prompt_data; + +#define PROMPT_HIDDEN 0x1 +#define PROMPT_SINGLE 0x2 + int prompt_flags; + + u_int prompt_hindex; + ARRAY_DECL(, char *) prompt_hdata; + + struct mode_key_data prompt_mdata; + + struct session *session; +}; +ARRAY_DECL(clients, struct client *); + +/* Client context. */ +struct client_ctx { + int srv_fd; + struct buffer *srv_in; + struct buffer *srv_out; + +#define CCTX_DETACH 0x1 +#define CCTX_EXIT 0x2 +#define CCTX_SHUTDOWN 0x4 + int flags; +}; + +/* Key/command line command. */ +struct cmd_ctx { + struct client *cmdclient; + + struct client *curclient; + struct session *cursession; + struct msg_command_data *msgdata; + + void (*print)(struct cmd_ctx *, const char *, ...); + void (*info)(struct cmd_ctx *, const char *, ...); + void (*error)(struct cmd_ctx *, const char *, ...); +}; + +struct cmd { + const struct cmd_entry *entry; + void *data; + + TAILQ_ENTRY(cmd) qentry; +}; +TAILQ_HEAD(cmd_list, cmd); + +struct cmd_entry { + const char *name; + const char *alias; + const char *usage; + +#define CMD_STARTSERVER 0x1 +#define CMD_CANTNEST 0x2 +#define CMD_ARG1 0x4 +#define CMD_ARG01 0x8 +#define CMD_AFLAG 0x10 +#define CMD_DFLAG 0x20 +#define CMD_GFLAG 0x40 +#define CMD_KFLAG 0x80 +#define CMD_UFLAG 0x100 +#define CMD_BIGDFLAG 0x200 +#define CMD_BIGUFLAG 0x400 + + int flags; + + void (*init)(struct cmd *, int); + int (*parse)(struct cmd *, int, char **, char **); + int (*exec)(struct cmd *, struct cmd_ctx *); + void (*send)(struct cmd *, struct buffer *); + void (*recv)(struct cmd *, struct buffer *); + void (*free)(struct cmd *); + size_t (*print)(struct cmd *, char *, size_t); +}; + +/* Generic command data. */ +struct cmd_target_data { + int flags; + char *target; + char *arg; +}; + +struct cmd_srcdst_data { + int flags; + char *src; + char *dst; + char *arg; +}; + +struct cmd_buffer_data { + int flags; + char *target; + int buffer; + char *arg; +}; + +struct cmd_option_data { + int flags; + char *target; + char *option; + char *value; +}; + +struct cmd_pane_data { + int flags; + char *target; + char *arg; + int pane; +}; + +/* Key binding. */ +struct key_binding { + int key; + struct cmd_list *cmdlist; + int can_repeat; + + SPLAY_ENTRY(key_binding) entry; +}; +SPLAY_HEAD(key_bindings, key_binding); + +/* Set/display option data. */ +struct set_option_entry { + const char *name; + enum { + SET_OPTION_STRING, + SET_OPTION_NUMBER, + SET_OPTION_KEY, + SET_OPTION_COLOUR, + SET_OPTION_ATTRIBUTES, + SET_OPTION_FLAG, + SET_OPTION_CHOICE + } type; + + u_int minimum; + u_int maximum; + + const char **choices; +}; +extern const struct set_option_entry set_option_table[]; +extern const struct set_option_entry set_window_option_table[]; +#define NSETOPTION 24 +#define NSETWINDOWOPTION 19 + +/* tmux.c */ +extern volatile sig_atomic_t sigwinch; +extern volatile sig_atomic_t sigterm; +extern volatile sig_atomic_t sigcont; +extern volatile sig_atomic_t sigchld; +extern volatile sig_atomic_t sigusr1; +extern volatile sig_atomic_t sigusr2; +extern struct options global_options; +extern struct options global_window_options; +extern char *cfg_file; +extern int server_locked; +extern char *server_password; +extern time_t server_activity; +extern int debug_level; +extern int be_quiet; +extern time_t start_time; +extern char *socket_path; +void logfile(const char *); +void siginit(void); +void sigreset(void); +void sighandler(int); + +/* cfg.c */ +int load_cfg(const char *, char **x); + +/* mode-key.c */ +void mode_key_init(struct mode_key_data *, int, int); +void mode_key_free(struct mode_key_data *); +enum mode_key_cmd mode_key_lookup(struct mode_key_data *, int); + +/* options.c */ +int options_cmp(struct options_entry *, struct options_entry *); +SPLAY_PROTOTYPE(options_tree, options_entry, entry, options_cmp); +void options_init(struct options *, struct options *); +void options_free(struct options *); +struct options_entry *options_find1(struct options *, const char *); +struct options_entry *options_find(struct options *, const char *); +int options_remove(struct options *, const char *); +void printflike3 options_set_string( + struct options *, const char *, const char *, ...); +char *options_get_string(struct options *, const char *); +void options_set_number(struct options *, const char *, long long); +long long options_get_number(struct options *, const char *); + +/* tty.c */ +u_char tty_get_acs(struct tty *, u_char); +void tty_emulate_repeat(struct tty *, + enum tty_code_code, enum tty_code_code, u_int); +void tty_reset(struct tty *); +void tty_region(struct tty *, u_int, u_int, u_int); +void tty_cursor(struct tty *, u_int, u_int, u_int, u_int); +void tty_cell(struct tty *, + const struct grid_cell *, const struct grid_utf8 *); +void tty_putcode(struct tty *, enum tty_code_code); +void tty_putcode1(struct tty *, enum tty_code_code, int); +void tty_putcode2(struct tty *, enum tty_code_code, int, int); +void tty_puts(struct tty *, const char *); +void tty_putc(struct tty *, u_char); +void tty_init(struct tty *, char *, char *); +void tty_start_tty(struct tty *); +void tty_stop_tty(struct tty *); +void tty_detect_utf8(struct tty *); +void tty_set_title(struct tty *, const char *); +void tty_update_mode(struct tty *, int); +void tty_draw_line( + struct tty *, struct screen *, u_int, u_int, u_int); +void tty_redraw_region(struct tty *, struct window_pane *); +int tty_open(struct tty *, char **); +void tty_close(struct tty *, int); +void tty_free(struct tty *, int); +void tty_write( + struct tty *, struct window_pane *, enum tty_cmd, ...); +void tty_vwrite( + struct tty *, struct window_pane *, enum tty_cmd, va_list); + +/* tty-term.c */ +extern struct tty_terms tty_terms; +extern struct tty_term_code_entry tty_term_codes[NTTYCODE]; +struct tty_term *tty_term_find(char *, int, char **); +void tty_term_free(struct tty_term *); +int tty_term_has(struct tty_term *, enum tty_code_code); +const char *tty_term_string(struct tty_term *, enum tty_code_code); +const char *tty_term_string1(struct tty_term *, enum tty_code_code, int); +const char *tty_term_string2( + struct tty_term *, enum tty_code_code, int, int); +int tty_term_number(struct tty_term *, enum tty_code_code); +int tty_term_flag(struct tty_term *, enum tty_code_code); + +/* tty-keys.c */ +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 *, u_char *); + +/* tty-write.c */ +void tty_write_cmd(struct window_pane *, enum tty_cmd, ...); +void tty_write_mode(struct window_pane *, int); + +/* options-cmd.c */ +void set_option_string(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); +void set_option_number(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); +void set_option_key(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); +void set_option_colour(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); +void set_option_attributes(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); +void set_option_flag(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); +void set_option_choice(struct cmd_ctx *, + struct options *, const struct set_option_entry *, char *); + +/* paste.c */ +void paste_init_stack(struct paste_stack *); +void paste_free_stack(struct paste_stack *); +struct paste_buffer *paste_walk_stack(struct paste_stack *, uint *); +struct paste_buffer *paste_get_top(struct paste_stack *); +struct paste_buffer *paste_get_index(struct paste_stack *, u_int); +int paste_free_top(struct paste_stack *); +int paste_free_index(struct paste_stack *, u_int); +void paste_add(struct paste_stack *, char *, u_int); +int paste_replace(struct paste_stack *, u_int, char *); + +/* clock.c */ +void clock_draw(struct screen_write_ctx *, u_int, int); + +/* arg.c */ +struct client *arg_parse_client(const char *); +struct session *arg_parse_session(const char *); +int arg_parse_window(const char *, struct session **, int *); + +/* cmd.c */ +struct cmd *cmd_parse(int, char **, char **); +int cmd_exec(struct cmd *, struct cmd_ctx *); +void cmd_send(struct cmd *, struct buffer *); +struct cmd *cmd_recv(struct buffer *); +void cmd_free(struct cmd *); +size_t cmd_print(struct cmd *, char *, size_t); +void cmd_send_string(struct buffer *, const char *); +char *cmd_recv_string(struct buffer *); +struct session *cmd_current_session(struct cmd_ctx *); +struct client *cmd_find_client(struct cmd_ctx *, const char *); +struct session *cmd_find_session(struct cmd_ctx *, const char *); +struct winlink *cmd_find_window( + struct cmd_ctx *, const char *, struct session **); +extern const struct cmd_entry *cmd_table[]; +extern const struct cmd_entry cmd_attach_session_entry; +extern const struct cmd_entry cmd_bind_key_entry; +extern const struct cmd_entry cmd_break_pane_entry; +extern const struct cmd_entry cmd_choose_session_entry; +extern const struct cmd_entry cmd_choose_window_entry; +extern const struct cmd_entry cmd_clear_history_entry; +extern const struct cmd_entry cmd_clock_mode_entry; +extern const struct cmd_entry cmd_command_prompt_entry; +extern const struct cmd_entry cmd_confirm_before_entry; +extern const struct cmd_entry cmd_copy_buffer_entry; +extern const struct cmd_entry cmd_copy_mode_entry; +extern const struct cmd_entry cmd_delete_buffer_entry; +extern const struct cmd_entry cmd_detach_client_entry; +extern const struct cmd_entry cmd_down_pane_entry; +extern const struct cmd_entry cmd_find_window_entry; +extern const struct cmd_entry cmd_has_session_entry; +extern const struct cmd_entry cmd_kill_pane_entry; +extern const struct cmd_entry cmd_kill_server_entry; +extern const struct cmd_entry cmd_kill_session_entry; +extern const struct cmd_entry cmd_kill_window_entry; +extern const struct cmd_entry cmd_last_window_entry; +extern const struct cmd_entry cmd_link_window_entry; +extern const struct cmd_entry cmd_list_buffers_entry; +extern const struct cmd_entry cmd_list_clients_entry; +extern const struct cmd_entry cmd_list_commands_entry; +extern const struct cmd_entry cmd_list_keys_entry; +extern const struct cmd_entry cmd_list_sessions_entry; +extern const struct cmd_entry cmd_list_windows_entry; +extern const struct cmd_entry cmd_load_buffer_entry; +extern const struct cmd_entry cmd_lock_server_entry; +extern const struct cmd_entry cmd_move_window_entry; +extern const struct cmd_entry cmd_new_session_entry; +extern const struct cmd_entry cmd_new_window_entry; +extern const struct cmd_entry cmd_next_layout_entry; +extern const struct cmd_entry cmd_next_window_entry; +extern const struct cmd_entry cmd_paste_buffer_entry; +extern const struct cmd_entry cmd_previous_layout_entry; +extern const struct cmd_entry cmd_previous_window_entry; +extern const struct cmd_entry cmd_refresh_client_entry; +extern const struct cmd_entry cmd_rename_session_entry; +extern const struct cmd_entry cmd_rename_window_entry; +extern const struct cmd_entry cmd_resize_pane_entry; +extern const struct cmd_entry cmd_respawn_window_entry; +extern const struct cmd_entry cmd_rotate_window_entry; +extern const struct cmd_entry cmd_save_buffer_entry; +extern const struct cmd_entry cmd_scroll_mode_entry; +extern const struct cmd_entry cmd_select_layout_entry; +extern const struct cmd_entry cmd_select_pane_entry; +extern const struct cmd_entry cmd_select_prompt_entry; +extern const struct cmd_entry cmd_select_window_entry; +extern const struct cmd_entry cmd_send_keys_entry; +extern const struct cmd_entry cmd_send_prefix_entry; +extern const struct cmd_entry cmd_server_info_entry; +extern const struct cmd_entry cmd_set_buffer_entry; +extern const struct cmd_entry cmd_set_option_entry; +extern const struct cmd_entry cmd_set_password_entry; +extern const struct cmd_entry cmd_set_window_option_entry; +extern const struct cmd_entry cmd_show_buffer_entry; +extern const struct cmd_entry cmd_show_options_entry; +extern const struct cmd_entry cmd_show_window_options_entry; +extern const struct cmd_entry cmd_source_file_entry; +extern const struct cmd_entry cmd_split_window_entry; +extern const struct cmd_entry cmd_start_server_entry; +extern const struct cmd_entry cmd_suspend_client_entry; +extern const struct cmd_entry cmd_swap_pane_entry; +extern const struct cmd_entry cmd_swap_window_entry; +extern const struct cmd_entry cmd_switch_client_entry; +extern const struct cmd_entry cmd_unbind_key_entry; +extern const struct cmd_entry cmd_unlink_window_entry; +extern const struct cmd_entry cmd_up_pane_entry; + +/* cmd-list.c */ +struct cmd_list *cmd_list_parse(int, char **, char **); +int cmd_list_exec(struct cmd_list *, struct cmd_ctx *); +void cmd_list_send(struct cmd_list *, struct buffer *); +struct cmd_list *cmd_list_recv(struct buffer *); +void cmd_list_free(struct cmd_list *); +size_t cmd_list_print(struct cmd_list *, char *, size_t); + +/* cmd-string.c */ +int cmd_string_parse(const char *, struct cmd_list **, char **); + +/* cmd-generic.c */ +size_t cmd_prarg(char *, size_t, const char *, char *); +#define CMD_TARGET_WINDOW_USAGE "[-t target-window]" +#define CMD_TARGET_SESSION_USAGE "[-t target-session]" +#define CMD_TARGET_CLIENT_USAGE "[-t target-client]" +void cmd_target_init(struct cmd *, int); +int cmd_target_parse(struct cmd *, int, char **, char **); +void cmd_target_send(struct cmd *, struct buffer *); +void cmd_target_recv(struct cmd *, struct buffer *); +void cmd_target_free(struct cmd *); +size_t cmd_target_print(struct cmd *, char *, size_t); +#define CMD_SRCDST_WINDOW_USAGE "[-s src-window] [-t dst-window]" +#define CMD_SRCDST_SESSION_USAGE "[-s src-session] [-t dst-session]" +#define CMD_SRCDST_CLIENT_USAGE "[-s src-client] [-t dst-client]" +void cmd_srcdst_init(struct cmd *, int); +int cmd_srcdst_parse(struct cmd *, int, char **, char **); +void cmd_srcdst_send(struct cmd *, struct buffer *); +void cmd_srcdst_recv(struct cmd *, struct buffer *); +void cmd_srcdst_free(struct cmd *); +size_t cmd_srcdst_print(struct cmd *, char *, size_t); +#define CMD_BUFFER_WINDOW_USAGE "[-b buffer-index] [-t target-window]" +#define CMD_BUFFER_SESSION_USAGE "[-b buffer-index] [-t target-session]" +#define CMD_BUFFER_CLIENT_USAGE "[-b buffer-index] [-t target-client]" +void cmd_buffer_init(struct cmd *, int); +int cmd_buffer_parse(struct cmd *, int, char **, char **); +void cmd_buffer_send(struct cmd *, struct buffer *); +void cmd_buffer_recv(struct cmd *, struct buffer *); +void cmd_buffer_free(struct cmd *); +size_t cmd_buffer_print(struct cmd *, char *, size_t); +#define CMD_OPTION_WINDOW_USAGE "[-gu] [-t target-window] option [value]" +#define CMD_OPTION_SESSION_USAGE "[-gu] [-t target-session] option [value]" +#define CMD_OPTION_CLIENT_USAGE "[-gu] [-t target-client] option [value]" +void cmd_option_init(struct cmd *, int); +int cmd_option_parse(struct cmd *, int, char **, char **); +void cmd_option_send(struct cmd *, struct buffer *); +void cmd_option_recv(struct cmd *, struct buffer *); +void cmd_option_free(struct cmd *); +size_t cmd_option_print(struct cmd *, char *, size_t); +#define CMD_PANE_WINDOW_USAGE "[-t target-window] [-p pane-index]" +#define CMD_PANE_SESSION_USAGE "[-t target-session] [-p pane-index]" +#define CMD_PANE_CLIENT_USAGE "[-t target-client] [-p pane-index]" +void cmd_pane_init(struct cmd *, int); +int cmd_pane_parse(struct cmd *, int, char **, char **); +void cmd_pane_send(struct cmd *, struct buffer *); +void cmd_pane_recv(struct cmd *, struct buffer *); +void cmd_pane_free(struct cmd *); +size_t cmd_pane_print(struct cmd *, char *, size_t); + +/* client.c */ +int client_init(char *, struct client_ctx *, int, int); +int client_flush(struct client_ctx *); +int client_main(struct client_ctx *); + +/* client-msg.c */ +int client_msg_dispatch(struct client_ctx *, char **); + +/* client-fn.c */ +void client_write_server(struct client_ctx *, enum hdrtype, void *, size_t); +void client_write_server2( + struct client_ctx *, enum hdrtype, void *, size_t, void *, size_t); +void client_fill_session(struct msg_command_data *); + +/* key-bindings.c */ +extern struct key_bindings key_bindings; +int key_bindings_cmp(struct key_binding *, struct key_binding *); +SPLAY_PROTOTYPE(key_bindings, key_binding, entry, key_bindings_cmp); +struct key_binding *key_bindings_lookup(int); +void key_bindings_add(int, int, struct cmd_list *); +void key_bindings_remove(int); +void key_bindings_init(void); +void key_bindings_free(void); +void key_bindings_dispatch(struct key_binding *, struct client *); +void printflike2 key_bindings_error(struct cmd_ctx *, const char *, ...); +void printflike2 key_bindings_print(struct cmd_ctx *, const char *, ...); +void printflike2 key_bindings_info(struct cmd_ctx *, const char *, ...); + +/* key-string.c */ +int key_string_lookup_string(const char *); +const char *key_string_lookup_key(int); + +/* server.c */ +extern struct clients clients; +struct client *server_create_client(int); +int server_client_index(struct client *); +int server_start(char *); + +/* server-msg.c */ +int server_msg_dispatch(struct client *); + +/* server-fn.c */ +const char **server_fill_environ(struct session *); +void server_write_client( + struct client *, enum hdrtype, const void *, size_t); +void server_write_session( + struct session *, enum hdrtype, const void *, size_t); +void server_write_window( + struct window *, enum hdrtype, const void *, size_t); +void server_redraw_client(struct client *); +void server_status_client(struct client *); +void server_redraw_session(struct session *); +void server_status_session(struct session *); +void server_redraw_window(struct window *); +void server_status_window(struct window *); +void server_lock(void); +int server_unlock(const char *); + +/* status.c */ +int status_redraw(struct client *); +void status_message_set(struct client *, const char *); +void status_message_clear(struct client *); +int status_message_redraw(struct client *); +void status_prompt_set(struct client *, + const char *, int (*)(void *, const char *), void *, int); +void status_prompt_clear(struct client *); +int status_prompt_redraw(struct client *); +void status_prompt_key(struct client *, int); + +/* resize.c */ +void recalculate_sizes(void); + +/* input.c */ +void input_init(struct window_pane *); +void input_free(struct window_pane *); +void input_parse(struct window_pane *); + +/* input-key.c */ +void input_key(struct window_pane *, int); +void input_mouse(struct window_pane *, u_char, u_char, u_char); + +/* colour.c */ +const char *colour_tostring(u_char); +int colour_fromstring(const char *); +u_char colour_256to16(u_char); +u_char colour_256to88(u_char); + +/* attributes.c */ +const char *attributes_tostring(u_char); +int attributes_fromstring(const char *); + +/* grid.c */ +extern const struct grid_cell grid_default_cell; +struct grid *grid_create(u_int, u_int, u_int); +void grid_destroy(struct grid *); +int grid_compare(struct grid *, struct grid *); +void grid_reduce_line(struct grid *, u_int, u_int); +void grid_expand_line(struct grid *, u_int, u_int); +void grid_expand_line_utf8(struct grid *, u_int, u_int); +void grid_scroll_line(struct grid *); +const struct grid_cell *grid_peek_cell(struct grid *, u_int, u_int); +struct grid_cell *grid_get_cell(struct grid *, u_int, u_int); +void grid_set_cell(struct grid *, u_int, u_int, const struct grid_cell *); +const struct grid_utf8 *grid_peek_utf8(struct grid *, u_int, u_int); +struct grid_utf8 *grid_get_utf8(struct grid *, u_int, u_int); +void grid_set_utf8(struct grid *, u_int, u_int, const struct grid_utf8 *); +void grid_clear(struct grid *, u_int, u_int, u_int, u_int); +void grid_clear_lines(struct grid *, u_int, u_int); +void grid_move_lines(struct grid *, u_int, u_int, u_int); +void grid_clear_cells(struct grid *, u_int, u_int, u_int); +void grid_move_cells(struct grid *, u_int, u_int, u_int, u_int); + +/* grid-view.c */ +const struct grid_cell *grid_view_peek_cell(struct grid *, u_int, u_int); +struct grid_cell *grid_view_get_cell(struct grid *, u_int, u_int); +void grid_view_set_cell( + struct grid *, u_int, u_int, const struct grid_cell *); +const struct grid_utf8 *grid_view_peek_utf8(struct grid *, u_int, u_int); +struct grid_utf8 *grid_view_get_utf8(struct grid *, u_int, u_int); +void grid_view_set_utf8( + struct grid *, u_int, u_int, const struct grid_utf8 *); +void grid_view_clear(struct grid *, u_int, u_int, u_int, u_int); +void grid_view_scroll_region_up(struct grid *, u_int, u_int); +void grid_view_scroll_region_down(struct grid *, u_int, u_int); +void grid_view_insert_lines(struct grid *, u_int, u_int); +void grid_view_insert_lines_region( + struct grid *, u_int, u_int, u_int, u_int); +void grid_view_delete_lines(struct grid *, u_int, u_int); +void grid_view_delete_lines_region( + struct grid *, u_int, u_int, u_int, u_int); +void grid_view_insert_cells(struct grid *, u_int, u_int, u_int); +void grid_view_delete_cells(struct grid *, u_int, u_int, u_int); + +/* screen-write.c */ +void screen_write_start( + struct screen_write_ctx *, struct window_pane *, struct screen *); +void screen_write_stop(struct screen_write_ctx *); +void printflike3 screen_write_puts( + struct screen_write_ctx *, struct grid_cell *, const char *, ...); +void screen_write_putc( + struct screen_write_ctx *, struct grid_cell *, u_char); +void screen_write_copy(struct screen_write_ctx *, + struct screen *, u_int, u_int, u_int, u_int); +void screen_write_cursorup(struct screen_write_ctx *, u_int); +void screen_write_cursordown(struct screen_write_ctx *, u_int); +void screen_write_cursorright(struct screen_write_ctx *, u_int); +void screen_write_cursorleft(struct screen_write_ctx *, u_int); +void screen_write_insertcharacter(struct screen_write_ctx *, u_int); +void screen_write_deletecharacter(struct screen_write_ctx *, u_int); +void screen_write_insertline(struct screen_write_ctx *, u_int); +void screen_write_deleteline(struct screen_write_ctx *, u_int); +void screen_write_clearline(struct screen_write_ctx *); +void screen_write_clearendofline(struct screen_write_ctx *); +void screen_write_clearstartofline(struct screen_write_ctx *); +void screen_write_cursormove(struct screen_write_ctx *, u_int, u_int); +void screen_write_cursormode(struct screen_write_ctx *, int); +void screen_write_reverseindex(struct screen_write_ctx *); +void screen_write_scrollregion(struct screen_write_ctx *, u_int, u_int); +void screen_write_insertmode(struct screen_write_ctx *, int); +void screen_write_mousemode(struct screen_write_ctx *, int); +void screen_write_linefeed(struct screen_write_ctx *); +void screen_write_carriagereturn(struct screen_write_ctx *); +void screen_write_kcursormode(struct screen_write_ctx *, int); +void screen_write_kkeypadmode(struct screen_write_ctx *, int); +void screen_write_clearendofscreen(struct screen_write_ctx *); +void screen_write_clearstartofscreen(struct screen_write_ctx *); +void screen_write_clearscreen(struct screen_write_ctx *); +void screen_write_cell( + struct screen_write_ctx *, const struct grid_cell *, u_char *); + +/* screen-redraw.c */ +void screen_redraw_screen(struct client *); +void screen_redraw_pane(struct client *, struct window_pane *); +void screen_redraw_status(struct client *); + +/* screen.c */ +void screen_init(struct screen *, u_int, u_int, u_int); +void screen_reinit(struct screen *); +void screen_free(struct screen *); +void screen_set_title(struct screen *, const char *); +void screen_resize(struct screen *, u_int, u_int); +void screen_set_selection( + struct screen *, u_int, u_int, u_int, u_int, struct grid_cell *); +void screen_clear_selection(struct screen *); +int screen_check_selection(struct screen *, u_int, u_int); +void screen_display_copy_area(struct screen *, struct screen *, + u_int, u_int, u_int, u_int, u_int, u_int); + +/* window.c */ +extern struct windows windows; +int window_cmp(struct window *, struct window *); +int winlink_cmp(struct winlink *, struct winlink *); +RB_PROTOTYPE(windows, window, entry, window_cmp); +RB_PROTOTYPE(winlinks, winlink, entry, winlink_cmp); +struct winlink *winlink_find_by_index(struct winlinks *, int); +struct winlink *winlink_find_by_window(struct winlinks *, struct window *); +int winlink_next_index(struct winlinks *); +u_int winlink_count(struct winlinks *); +struct winlink *winlink_add(struct winlinks *, struct window *, int); +void winlink_remove(struct winlinks *, struct winlink *); +struct winlink *winlink_next(struct winlinks *, struct winlink *); +struct winlink *winlink_previous(struct winlinks *, struct winlink *); +void winlink_stack_push(struct winlink_stack *, struct winlink *); +void winlink_stack_remove(struct winlink_stack *, struct winlink *); +int window_index(struct window *, u_int *); +struct window *window_create1(u_int, u_int); +struct window *window_create(const char *, const char *, + const char *, const char **, u_int, u_int, u_int, char **); +void window_destroy(struct window *); +int window_resize(struct window *, u_int, u_int); +void window_set_active_pane(struct window *, struct window_pane *); +struct window_pane *window_add_pane(struct window *, int, + const char *, const char *, const char **, u_int, char **); +void window_remove_pane(struct window *, struct window_pane *); +u_int window_index_of_pane(struct window *, struct window_pane *); +struct window_pane *window_pane_at_index(struct window *, u_int); +u_int window_count_panes(struct window *); +void window_destroy_panes(struct window *); +struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); +void window_pane_destroy(struct window_pane *); +int window_pane_spawn(struct window_pane *, + const char *, const char *, const char **, char **); +int window_pane_resize(struct window_pane *, u_int, u_int); +void window_calculate_sizes(struct window *); +int window_pane_set_mode( + struct window_pane *, const struct window_mode *); +void window_pane_reset_mode(struct window_pane *); +void window_pane_parse(struct window_pane *); +void window_pane_key(struct window_pane *, struct client *, int); +void window_pane_mouse(struct window_pane *, + struct client *, u_char, u_char, u_char); +char *window_pane_search(struct window_pane *, const char *); + +/* layout.c */ +const char * layout_name(struct window *); +int layout_lookup(const char *); +void layout_refresh(struct window *, int); +int layout_resize(struct window_pane *, int); +int layout_select(struct window *, u_int); +void layout_next(struct window *); +void layout_previous(struct window *); + +/* layout-manual.c */ +void layout_manual_v_refresh(struct window *, int); +void layout_manual_v_resize(struct window_pane *, int); + +/* window-clock.c */ +extern const struct window_mode window_clock_mode; + +/* window-copy.c */ +extern const struct window_mode window_copy_mode; +void window_copy_pageup(struct window_pane *); + +/* window-scroll.c */ +extern const struct window_mode window_scroll_mode; +void window_scroll_pageup(struct window_pane *); + +/* window-more.c */ +extern const struct window_mode window_more_mode; +void window_more_vadd(struct window_pane *, const char *, va_list); +void printflike2 window_more_add(struct window_pane *, const char *, ...); + +/* window-choose.c */ +extern const struct window_mode window_choose_mode; +void window_choose_vadd( + struct window_pane *, int, const char *, va_list); +void printflike3 window_choose_add( + struct window_pane *, int, const char *, ...); +void window_choose_ready(struct window_pane *, + u_int, void (*)(void *, int), void *); + +/* names.c */ +void set_window_names(void); +char *default_window_name(struct window *); + +/* session.c */ +extern struct sessions sessions; +void session_alert_add(struct session *, struct window *, int); +void session_alert_cancel(struct session *, struct winlink *); +int session_alert_has(struct session *, struct winlink *, int); +int session_alert_has_window(struct session *, struct window *, int); +struct session *session_find(const char *); +struct session *session_create(const char *, const char *, + const char *, u_int, u_int, char **); +void session_destroy(struct session *); +int session_index(struct session *, u_int *); +struct winlink *session_new(struct session *, + const char *, const char *, const char *, int, char **); +struct winlink *session_attach( + struct session *, struct window *, int, char **); +int session_detach(struct session *, struct winlink *); +int session_has(struct session *, struct window *); +int session_next(struct session *, int); +int session_previous(struct session *, int); +int session_select(struct session *, int); +int session_last(struct session *); + +/* utf8.c */ +void utf8_build(void); +int utf8_width(u_char *); + +/* util.c */ +char *section_string(char *, size_t, size_t, size_t); +void clean_string(const char *, char *, size_t); + +/* procname.c */ +char *get_proc_name(int, char *); + +/* buffer.c */ +struct buffer *buffer_create(size_t); +void buffer_destroy(struct buffer *); +void buffer_clear(struct buffer *); +void buffer_ensure(struct buffer *, size_t); +void buffer_add(struct buffer *, size_t); +void buffer_reverse_add(struct buffer *, size_t); +void buffer_remove(struct buffer *, size_t); +void buffer_reverse_remove(struct buffer *, size_t); +void buffer_insert_range(struct buffer *, size_t, size_t); +void buffer_delete_range(struct buffer *, size_t, size_t); +void buffer_write(struct buffer *, const void *, size_t); +void buffer_read(struct buffer *, void *, size_t); +void buffer_write8(struct buffer *, uint8_t); +void buffer_write16(struct buffer *, uint16_t); +uint8_t buffer_read8(struct buffer *); +uint16_t buffer_read16(struct buffer *); + +/* buffer-poll.c */ +void buffer_set( + struct pollfd *, int, struct buffer *, struct buffer *); +int buffer_poll(struct pollfd *, struct buffer *, struct buffer *); +void buffer_flush(int, struct buffer *n, struct buffer *); + +/* log.c */ +#define LOG_FACILITY LOG_DAEMON +void log_open_syslog(int); +void log_open_tty(int); +void log_open_file(int, const char *); +void log_close(void); +void log_vwrite(int, const char *, va_list); +void log_write(int, const char *, ...); +void printflike1 log_warn(const char *, ...); +void printflike1 log_warnx(const char *, ...); +void printflike1 log_info(const char *, ...); +void printflike1 log_debug(const char *, ...); +void printflike1 log_debug2(const char *, ...); +void printflike1 log_debug3(const char *, ...); +__dead void log_vfatal(const char *, va_list); +__dead void log_fatal(const char *, ...); +__dead void log_fatalx(const char *, ...); + +/* xmalloc.c */ +char *xstrdup(const char *); +void *xcalloc(size_t, size_t); +void *xmalloc(size_t); +void *xrealloc(void *, size_t, size_t); +void xfree(void *); +int printflike2 xasprintf(char **, const char *, ...); +int xvasprintf(char **, const char *, va_list); +int printflike3 xsnprintf(char *, size_t, const char *, ...); +int xvsnprintf(char *, size_t, const char *, va_list); +int printflike3 printpath(char *, size_t, const char *, ...); + +#endif /* TMUX_H */ diff --git a/tty-keys.c b/tty-keys.c new file mode 100644 index 00000000..3b8a3b45 --- /dev/null +++ b/tty-keys.c @@ -0,0 +1,412 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include + +#include "tmux.h" + +void tty_keys_add(struct tty *, const char *, int, int); +int tty_keys_parse_xterm(struct tty *, char *, size_t, size_t *); +int tty_keys_parse_mouse(struct tty *, char *, size_t, size_t *, u_char *); + +struct tty_key_ent { + enum tty_code_code code; + const char *string; + + int key; + int flags; +}; + +struct tty_key_ent tty_keys[] = { + /* Function keys. */ + { TTYC_KF1, NULL, KEYC_F1, TTYKEY_CTRL }, + { TTYC_KF2, NULL, KEYC_F2, TTYKEY_CTRL }, + { TTYC_KF3, NULL, KEYC_F3, TTYKEY_CTRL }, + { TTYC_KF4, NULL, KEYC_F4, TTYKEY_CTRL }, + { TTYC_KF5, NULL, KEYC_F5, TTYKEY_CTRL }, + { TTYC_KF6, NULL, KEYC_F6, TTYKEY_CTRL }, + { TTYC_KF7, NULL, KEYC_F7, TTYKEY_CTRL }, + { TTYC_KF8, NULL, KEYC_F8, TTYKEY_CTRL }, + { TTYC_KF9, NULL, KEYC_F9, TTYKEY_CTRL }, + { TTYC_KF10, NULL, KEYC_F10, TTYKEY_CTRL }, + { TTYC_KF11, NULL, KEYC_F11, TTYKEY_CTRL }, + { TTYC_KF12, NULL, KEYC_F12, TTYKEY_CTRL }, + { TTYC_KF13, NULL, KEYC_F13, TTYKEY_CTRL }, + { TTYC_KF14, NULL, KEYC_F14, TTYKEY_CTRL }, + { TTYC_KF15, NULL, KEYC_F15, TTYKEY_CTRL }, + { TTYC_KF16, NULL, KEYC_F16, TTYKEY_CTRL }, + { TTYC_KF17, NULL, KEYC_F17, TTYKEY_CTRL }, + { TTYC_KF18, NULL, KEYC_F18, TTYKEY_CTRL }, + { TTYC_KF19, NULL, KEYC_F19, TTYKEY_CTRL }, + { TTYC_KF20, NULL, KEYC_F20, TTYKEY_CTRL }, + { TTYC_KICH1, NULL, KEYC_IC, TTYKEY_CTRL }, + { TTYC_KDCH1, NULL, KEYC_DC, TTYKEY_CTRL }, + { TTYC_KHOME, NULL, KEYC_HOME, TTYKEY_CTRL }, + { TTYC_KEND, NULL, KEYC_END, TTYKEY_CTRL }, + { TTYC_KNP, NULL, KEYC_NPAGE, TTYKEY_CTRL }, + { TTYC_KPP, NULL, KEYC_PPAGE, TTYKEY_CTRL }, + { TTYC_KCBT, NULL, KEYC_BTAB, TTYKEY_CTRL }, + + /* Arrow keys. */ + { 0, "\033OA", KEYC_UP, TTYKEY_RAW }, + { 0, "\033OB", KEYC_DOWN, TTYKEY_RAW }, + { 0, "\033OC", KEYC_RIGHT, TTYKEY_RAW }, + { 0, "\033OD", KEYC_LEFT, TTYKEY_RAW }, + + { 0, "\033[A", KEYC_UP, TTYKEY_RAW }, + { 0, "\033[B", KEYC_DOWN, TTYKEY_RAW }, + { 0, "\033[C", KEYC_RIGHT, TTYKEY_RAW }, + { 0, "\033[D", KEYC_LEFT, TTYKEY_RAW }, + + { 0, "\033Oa", KEYC_ADDCTL(KEYC_UP), TTYKEY_RAW }, + { 0, "\033Ob", KEYC_ADDCTL(KEYC_DOWN), TTYKEY_RAW }, + { 0, "\033Oc", KEYC_ADDCTL(KEYC_RIGHT), TTYKEY_RAW }, + { 0, "\033Od", KEYC_ADDCTL(KEYC_LEFT), TTYKEY_RAW }, + { 0, "\033[a", KEYC_ADDSFT(KEYC_UP), TTYKEY_RAW }, + { 0, "\033[b", KEYC_ADDSFT(KEYC_DOWN), TTYKEY_RAW }, + { 0, "\033[c", KEYC_ADDSFT(KEYC_RIGHT), TTYKEY_RAW }, + { 0, "\033[d", KEYC_ADDSFT(KEYC_LEFT), TTYKEY_RAW }, + + { TTYC_KCUU1, NULL, KEYC_UP, TTYKEY_CTRL }, + { TTYC_KCUD1, NULL, KEYC_DOWN, TTYKEY_CTRL }, + { TTYC_KCUB1, NULL, KEYC_LEFT, TTYKEY_CTRL }, + { TTYC_KCUF1, NULL, KEYC_RIGHT, TTYKEY_CTRL }, + + /* + * Numeric keypad. termcap and terminfo are totally confusing for this. + * There are definitions for some keypad keys and for function keys, + * but these seem to now be used for the real function keys rather than + * for the keypad keys in application mode (which is different from + * what it says in the termcap file). So, we just hardcode the vt100 + * escape sequences here and always put the terminal into keypad_xmit + * mode. Translation of numbers mode/applications mode is done in + * input-keys.c. + */ + { 0, "\033Oo", KEYC_KP0_1, TTYKEY_RAW }, + { 0, "\033Oj", KEYC_KP0_2, TTYKEY_RAW }, + { 0, "\033Om", KEYC_KP0_3, TTYKEY_RAW }, + { 0, "\033Ow", KEYC_KP1_0, TTYKEY_RAW }, + { 0, "\033Ox", KEYC_KP1_1, TTYKEY_RAW }, + { 0, "\033Oy", KEYC_KP1_2, TTYKEY_RAW }, + { 0, "\033Ok", KEYC_KP1_3, TTYKEY_RAW }, + { 0, "\033Ot", KEYC_KP2_0, TTYKEY_RAW }, + { 0, "\033Ou", KEYC_KP2_1, TTYKEY_RAW }, + { 0, "\033Ov", KEYC_KP2_2, TTYKEY_RAW }, + { 0, "\033Oq", KEYC_KP3_0, TTYKEY_RAW }, + { 0, "\033Or", KEYC_KP3_1, TTYKEY_RAW }, + { 0, "\033Os", KEYC_KP3_2, TTYKEY_RAW }, + { 0, "\033OM", KEYC_KP3_3, TTYKEY_RAW }, + { 0, "\033Op", KEYC_KP4_0, TTYKEY_RAW }, + { 0, "\033On", KEYC_KP4_2, TTYKEY_RAW }, +}; + +RB_GENERATE(tty_keys, tty_key, entry, tty_keys_cmp); + +struct tty_key *tty_keys_find(struct tty *, char *, size_t, size_t *); + +int +tty_keys_cmp(struct tty_key *k1, struct tty_key *k2) +{ + return (strcmp(k1->string, k2->string)); +} + +void +tty_keys_add(struct tty *tty, const char *s, int key, int flags) +{ + struct tty_key *tk, *tl; + + tk = xmalloc(sizeof *tk); + tk->string = xstrdup(s); + tk->key = key; + tk->flags = flags; + + if ((tl = RB_INSERT(tty_keys, &tty->ktree, tk)) != NULL) { + xfree(tk->string); + xfree(tk); + log_debug("key exists: %s (old %x, new %x)", s, tl->key, key); + return; + } + + if (strlen(tk->string) > tty->ksize) + tty->ksize = strlen(tk->string); + log_debug("new key %x: size now %zu (%s)", key, tty->ksize, tk->string); +} + +void +tty_keys_init(struct tty *tty) +{ + struct tty_key_ent *tke; + u_int i; + const char *s; + char tmp[64]; + + RB_INIT(&tty->ktree); + + tty->ksize = 0; + for (i = 0; i < nitems(tty_keys); i++) { + tke = &tty_keys[i]; + + if (tke->flags & TTYKEY_RAW) + s = tke->string; + else { + if (!tty_term_has(tty->term, tke->code)) + continue; + s = tty_term_string(tty->term, tke->code); + if (s[0] != '\033' || s[1] == '\0') + continue; + } + + tty_keys_add(tty, s + 1, tke->key, tke->flags); + if (tke->flags & TTYKEY_CTRL) { + if (strlcpy(tmp, s, sizeof tmp) >= sizeof tmp) + continue; + tmp[strlen(tmp) - 1] ^= 0x20; + tty_keys_add(tty, tmp + 1, KEYC_ADDCTL(tke->key), 0); + } + } +} + +void +tty_keys_free(struct tty *tty) +{ + struct tty_key *tk; + + while (!RB_EMPTY(&tty->ktree)) { + tk = RB_ROOT(&tty->ktree); + RB_REMOVE(tty_keys, &tty->ktree, tk); + xfree(tk->string); + xfree(tk); + } +} + +struct tty_key * +tty_keys_find(struct tty *tty, char *buf, size_t len, size_t *size) +{ + struct tty_key *tk, tl; + char *s; + + if (len == 0) + return (NULL); + + s = xmalloc(tty->ksize + 1); + for (*size = tty->ksize; (*size) > 0; (*size)--) { + if ((*size) > len) + continue; + memcpy(s, buf, *size); + s[*size] = '\0'; + + log_debug2("looking for key: %s", s); + + tl.string = s; + tk = RB_FIND(tty_keys, &tty->ktree, &tl); + if (tk != NULL) { + log_debug2("got key: 0x%x", tk->key); + xfree(s); + return (tk); + } + } + xfree(s); + + return (NULL); +} + +int +tty_keys_next(struct tty *tty, int *key, u_char *mouse) +{ + struct tty_key *tk; + struct timeval tv; + char *buf; + size_t len, size; + + buf = BUFFER_OUT(tty->in); + len = BUFFER_USED(tty->in); + if (len == 0) + return (1); + log_debug("keys are %zu (%.*s)", len, (int) len, buf); + + /* If a normal key, return it. */ + if (*buf != '\033') { + *key = buffer_read8(tty->in); + goto found; + } + + /* Look for matching key string and return if found. */ + tk = tty_keys_find(tty, buf + 1, len - 1, &size); + if (tk != NULL) { + buffer_remove(tty->in, size + 1); + *key = tk->key; + goto found; + } + + /* Not found. Is this a mouse key press? */ + *key = tty_keys_parse_mouse(tty, buf, len, &size, mouse); + if (*key != KEYC_NONE) { + buffer_remove(tty->in, size); + goto found; + } + + /* Not found. Try to parse xterm-type arguments. */ + *key = tty_keys_parse_xterm(tty, buf, len, &size); + if (*key != KEYC_NONE) { + buffer_remove(tty->in, 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"); + timeradd(&tty->key_timer, &tv, &tty->key_timer); + + tty->flags |= TTY_ESCAPE; + return (1); + } + + /* Skip the escape. */ + buf++; + len--; + + /* Is there a normal key following? */ + if (len != 0 && *buf != '\033') { + buffer_remove(tty->in, 1); + *key = KEYC_ADDESC(buffer_read8(tty->in)); + goto found; + } + + /* Or a key string? */ + if (len > 1) { + tk = tty_keys_find(tty, buf + 1, len - 1, &size); + if (tk != NULL) { + buffer_remove(tty->in, size + 2); + *key = KEYC_ADDESC(tk->key); + goto found; + } + } + + /* If the timer hasn't expired, keep waiting. */ + if (gettimeofday(&tv, NULL) != 0) + fatal("gettimeofday"); + if (timercmp(&tty->key_timer, &tv, >)) + return (1); + + /* Give up and return the escape. */ + buffer_remove(tty->in, 1); + *key = '\033'; + +found: + tty->flags &= ~TTY_ESCAPE; + return (0); +} + +int +tty_keys_parse_mouse( + unused struct tty *tty, char *buf, size_t len, size_t *size, u_char *mouse) +{ + /* + * Mouse sequences are \033[M followed by three characters indicating + * buttons, X and Y, all based at 32 with 1,1 top-left. + */ + + log_debug("mouse input is: %.*s", (int) len, buf); + if (len != 6 || memcmp(buf, "\033[M", 3) != 0) + return (KEYC_NONE); + *size = 6; + + if (buf[3] < 32 || buf[4] < 33 || buf[5] < 33) + return (KEYC_NONE); + + mouse[0] = buf[3] - 32; + mouse[1] = buf[4] - 33; + mouse[2] = buf[5] - 33; + return (KEYC_MOUSE); +} + +int +tty_keys_parse_xterm(struct tty *tty, char *buf, size_t len, size_t *size) +{ + struct tty_key *tk; + char tmp[5]; + size_t tmplen; + int key; + + /* + * xterm sequences with modifier keys are of the form: + * + * ^[[1;xD becomes ^[[D + * ^[[5;x~ becomes ^[[5~ + * + * This function is a bit of a hack. Need to figure out what exact + * format and meaning xterm outputs and fix it. XXX + */ + + log_debug("xterm input is: %.*s", (int) len, buf); + if (len != 6 || memcmp(buf, "\033[1;", 4) != 0) + return (KEYC_NONE); + *size = 6; + + tmplen = 0; + tmp[tmplen++] = '['; + if (buf[5] == '~') { + tmp[tmplen++] = buf[2]; + tmp[tmplen++] = '~'; + } else + tmp[tmplen++] = buf[5]; + log_debug("xterm output is: %.*s", (int) tmplen, tmp); + + tk = tty_keys_find(tty, tmp, tmplen, size); + if (tk == NULL) + return (KEYC_NONE); + key = tk->key; + + switch (buf[4]) { + case '8': + key = KEYC_ADDSFT(key); + key = KEYC_ADDESC(key); + key = KEYC_ADDCTL(key); + break; + case '7': + key = KEYC_ADDESC(key); + key = KEYC_ADDCTL(key); + break; + case '6': + key = KEYC_ADDSFT(key); + key = KEYC_ADDCTL(key); + break; + case '5': + key = KEYC_ADDCTL(key); + break; + case '4': + key = KEYC_ADDSFT(key); + key = KEYC_ADDESC(key); + break; + case '3': + key = KEYC_ADDESC(key); + break; + case '2': + key = KEYC_ADDSFT(key); + break; + } + + *size = 6; + return (key); +} diff --git a/tty-term.c b/tty-term.c new file mode 100644 index 00000000..200cc020 --- /dev/null +++ b/tty-term.c @@ -0,0 +1,393 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include +#include +#include + +#include "tmux.h" + +void tty_term_quirks(struct tty_term *); +char *tty_term_strip(const char *); + +struct tty_terms tty_terms = SLIST_HEAD_INITIALIZER(tty_terms); + +struct tty_term_code_entry tty_term_codes[NTTYCODE] = { + { TTYC_AX, TTYCODE_FLAG, "AX" }, + { TTYC_ACSC, TTYCODE_STRING, "acsc" }, + { TTYC_BEL, TTYCODE_STRING, "bel" }, + { TTYC_BLINK, TTYCODE_STRING, "blink" }, + { TTYC_BOLD, TTYCODE_STRING, "bold" }, + { TTYC_CIVIS, TTYCODE_STRING, "civis" }, + { TTYC_CLEAR, TTYCODE_STRING, "clear" }, + { TTYC_CNORM, TTYCODE_STRING, "cnorm" }, + { TTYC_COLORS, TTYCODE_NUMBER, "colors" }, + { TTYC_CSR, TTYCODE_STRING, "csr" }, + { TTYC_CUD, TTYCODE_STRING, "cud" }, + { TTYC_CUD1, TTYCODE_STRING, "cud1" }, + { TTYC_CUP, TTYCODE_STRING, "cup" }, + { TTYC_DCH, TTYCODE_STRING, "dch" }, + { TTYC_DCH1, TTYCODE_STRING, "dch1" }, + { TTYC_DIM, TTYCODE_STRING, "dim" }, + { TTYC_DL, TTYCODE_STRING, "dl" }, + { TTYC_DL1, TTYCODE_STRING, "dl1" }, + { TTYC_EL, TTYCODE_STRING, "el" }, + { TTYC_EL1, TTYCODE_STRING, "el1" }, + { TTYC_ENACS, TTYCODE_STRING, "enacs" }, + { TTYC_ICH, TTYCODE_STRING, "ich" }, + { TTYC_ICH1, TTYCODE_STRING, "ich1" }, + { TTYC_IL, TTYCODE_STRING, "il" }, + { TTYC_IL1, TTYCODE_STRING, "il1" }, + { TTYC_INVIS, TTYCODE_STRING, "invis" }, + { TTYC_IS1, TTYCODE_STRING, "is1" }, + { TTYC_IS2, TTYCODE_STRING, "is2" }, + { TTYC_IS3, TTYCODE_STRING, "is3" }, + { TTYC_KCBT, TTYCODE_STRING, "kcbt" }, + { TTYC_KCUB1, TTYCODE_STRING, "kcub1" }, + { TTYC_KCUD1, TTYCODE_STRING, "kcud1" }, + { TTYC_KCUF1, TTYCODE_STRING, "kcuf1" }, + { TTYC_KCUU1, TTYCODE_STRING, "kcuu1" }, + { TTYC_KDCH1, TTYCODE_STRING, "kdch1" }, + { TTYC_KEND, TTYCODE_STRING, "kend" }, + { TTYC_KF1, TTYCODE_STRING, "kf1" }, + { TTYC_KF10, TTYCODE_STRING, "kf10" }, + { TTYC_KF11, TTYCODE_STRING, "kf11" }, + { TTYC_KF12, TTYCODE_STRING, "kf12" }, + { TTYC_KF13, TTYCODE_STRING, "kf13" }, + { TTYC_KF14, TTYCODE_STRING, "kf14" }, + { TTYC_KF15, TTYCODE_STRING, "kf15" }, + { TTYC_KF16, TTYCODE_STRING, "kf16" }, + { TTYC_KF17, TTYCODE_STRING, "kf17" }, + { TTYC_KF18, TTYCODE_STRING, "kf18" }, + { TTYC_KF19, TTYCODE_STRING, "kf19" }, + { TTYC_KF20, TTYCODE_STRING, "kf20" }, + { TTYC_KF2, TTYCODE_STRING, "kf2" }, + { TTYC_KF3, TTYCODE_STRING, "kf3" }, + { TTYC_KF4, TTYCODE_STRING, "kf4" }, + { TTYC_KF5, TTYCODE_STRING, "kf5" }, + { TTYC_KF6, TTYCODE_STRING, "kf6" }, + { TTYC_KF7, TTYCODE_STRING, "kf7" }, + { TTYC_KF8, TTYCODE_STRING, "kf8" }, + { TTYC_KF9, TTYCODE_STRING, "kf9" }, + { TTYC_KHOME, TTYCODE_STRING, "khome" }, + { TTYC_KICH1, TTYCODE_STRING, "kich1" }, + { TTYC_KMOUS, TTYCODE_STRING, "kmous" }, + { TTYC_KNP, TTYCODE_STRING, "knp" }, + { TTYC_KPP, TTYCODE_STRING, "kpp" }, + { TTYC_OP, TTYCODE_STRING, "op" }, + { TTYC_REV, TTYCODE_STRING, "rev" }, + { TTYC_RI, TTYCODE_STRING, "ri" }, + { TTYC_RMACS, TTYCODE_STRING, "rmacs" }, + { TTYC_RMCUP, TTYCODE_STRING, "rmcup" }, + { TTYC_RMIR, TTYCODE_STRING, "rmir" }, + { TTYC_RMKX, TTYCODE_STRING, "rmkx" }, + { TTYC_SETAB, TTYCODE_STRING, "setab" }, + { TTYC_SETAF, TTYCODE_STRING, "setaf" }, + { TTYC_SGR0, TTYCODE_STRING, "sgr0" }, + { TTYC_SMACS, TTYCODE_STRING, "smacs" }, + { TTYC_SMCUP, TTYCODE_STRING, "smcup" }, + { TTYC_SMIR, TTYCODE_STRING, "smir" }, + { TTYC_SMKX, TTYCODE_STRING, "smkx" }, + { TTYC_SMSO, TTYCODE_STRING, "smso" }, + { TTYC_SMUL, TTYCODE_STRING, "smul" }, + { TTYC_XENL, TTYCODE_FLAG, "xenl" }, +}; + +char * +tty_term_strip(const char *s) +{ + const char *ptr; + static char buf[BUFSIZ]; + size_t len; + + /* Ignore strings with no padding. */ + if (strchr(s, '$') == NULL) + return (xstrdup(s)); + + len = 0; + for (ptr = s; *ptr != '\0'; ptr++) { + if (*ptr == '$' && *(ptr + 1) == '<') { + while (*ptr != '\0' && *ptr != '>') + ptr++; + if (*ptr == '>') + ptr++; + } + + buf[len++] = *ptr; + if (len == (sizeof buf) - 1) + break; + } + buf[len] = '\0'; + + return (xstrdup(buf)); +} + +void +tty_term_quirks(struct tty_term *term) +{ + if (strncmp(term->name, "rxvt", 4) == 0) { + /* rxvt supports dch1 but some termcap files do not have it. */ + if (!tty_term_has(term, TTYC_DCH1)) { + term->codes[TTYC_DCH1].type = TTYCODE_STRING; + term->codes[TTYC_DCH1].value.string = xstrdup("\033[P"); + } + } + + if (strncmp(term->name, "xterm", 5) == 0) { + /* xterm supports ich1 but some termcaps omit it. */ + if (!tty_term_has(term, TTYC_ICH1)) { + term->codes[TTYC_ICH1].type = TTYCODE_STRING; + term->codes[TTYC_ICH1].value.string = xstrdup("\033[@"); + } + } +} + +struct tty_term * +tty_term_find(char *name, int fd, char **cause) +{ + struct tty_term *term; + struct tty_term_code_entry *ent; + struct tty_code *code; + u_int i; + int n, error; + char *s; + + SLIST_FOREACH(term, &tty_terms, entry) { + if (strcmp(term->name, name) == 0) { + term->references++; + return (term); + } + } + + log_debug("new term: %s", name); + term = xmalloc(sizeof *term); + term->name = xstrdup(name); + term->references = 1; + term->flags = 0; + SLIST_INSERT_HEAD(&tty_terms, term, entry); + + /* Set up ncurses terminal. */ + if (setupterm(name, fd, &error) != OK) { + switch (error) { + case 0: + xasprintf(cause, "can't use hardcopy terminal"); + break; + case 1: + xasprintf(cause, "missing or unsuitable terminal"); + break; + case 2: + xasprintf(cause, "can't find terminfo database"); + break; + default: + xasprintf(cause, "unknown error"); + break; + } + goto error; + } + + /* Fill in codes. */ + memset(&term->codes, 0, sizeof term->codes); + for (i = 0; i < NTTYCODE; i++) { + ent = &tty_term_codes[i]; + + code = &term->codes[ent->code]; + code->type = TTYCODE_NONE; + switch (ent->type) { + case TTYCODE_NONE: + break; + case TTYCODE_STRING: + s = tigetstr((char *) ent->name); + if (s == NULL || s == (char *) -1) + break; + code->type = TTYCODE_STRING; + code->value.string = tty_term_strip(s); + break; + case TTYCODE_NUMBER: + n = tigetnum((char *) ent->name); + if (n == -1 || n == -2) + break; + code->type = TTYCODE_NUMBER; + code->value.number = n; + break; + case TTYCODE_FLAG: + n = tigetflag((char *) ent->name); + if (n == -1) + break; + code->type = TTYCODE_FLAG; + code->value.number = n; + break; + } + } + tty_term_quirks(term); + + /* Delete ncurses data. */ + del_curterm(cur_term); + + /* These are always required. */ + if (!tty_term_has(term, TTYC_CLEAR)) { + xasprintf(cause, "terminal does not support clear"); + goto error; + } + if (!tty_term_has(term, TTYC_RI)) { + xasprintf(cause, "terminal does not support ri"); + goto error; + } + if (!tty_term_has(term, TTYC_CUP)) { + xasprintf(cause, "terminal does not support cup"); + goto error; + } + + /* These can be emulated so one of the two is required. */ + if (!tty_term_has(term, TTYC_CUD1) && !tty_term_has(term, TTYC_CUD)) { + xasprintf(cause, "terminal does not support cud1 or cud"); + goto error; + } + if (!tty_term_has(term, TTYC_IL1) && !tty_term_has(term, TTYC_IL)) { + xasprintf(cause, "terminal does not support il1 or il"); + goto error; + } + if (!tty_term_has(term, TTYC_DL1) && !tty_term_has(term, TTYC_DL)) { + xasprintf(cause, "terminal does not support dl1 or dl"); + goto error; + } + if (!tty_term_has(term, TTYC_ICH1) && + !tty_term_has(term, TTYC_ICH) && (!tty_term_has(term, TTYC_SMIR) || + !tty_term_has(term, TTYC_RMIR))) { + xasprintf(cause, + "terminal does not support ich1 or ich or smir and rmir"); + goto error; + } + if (!tty_term_has(term, TTYC_DCH1) && !tty_term_has(term, TTYC_DCH)) { + xasprintf(cause, "terminal does not support dch1 or dch"); + goto error; + } + + /* + * Figure out if terminal support default colours. AX is a screen + * extension which indicates this. Also check if op (orig_pair) uses + * the default colours - if it does, this is a good indication the + * terminal supports them. + */ + if (tty_term_flag(term, TTYC_AX)) + term->flags |= TERM_HASDEFAULTS; + if (strcmp(tty_term_string(term, TTYC_OP), "\033[39;49m") == 0) + term->flags |= TERM_HASDEFAULTS; + + /* + * Try to figure out if we have 256 or 88 colours. The standard xterm + * definitions are broken (well, or the way they are parsed is: in any + * case they end up returning 8). So also do a hack. + */ + if (tty_term_number(term, TTYC_COLORS) == 256) + term->flags |= TERM_256COLOURS; + if (strstr(name, "256col") != NULL) /* XXX HACK */ + term->flags |= TERM_256COLOURS; + if (tty_term_number(term, TTYC_COLORS) == 88) + term->flags |= TERM_88COLOURS; + if (strstr(name, "88col") != NULL) /* XXX HACK */ + term->flags |= TERM_88COLOURS; + + /* + * Terminals without xenl (eat newline glitch) wrap at at $COLUMNS - 1 + * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). + * + * This is irritating, most notably because it is impossible to write + * to the very bottom-right of the screen without scrolling. + * + * Flag the terminal here and apply some workarounds in other places to + * do the best possible. + */ + if (!tty_term_flag(term, TTYC_XENL)) + term->flags |= TERM_EARLYWRAP; + + return (term); + +error: + tty_term_free(term); + return (NULL); +} + +void +tty_term_free(struct tty_term *term) +{ + u_int i; + + if (--term->references != 0) + return; + + SLIST_REMOVE(&tty_terms, term, tty_term, entry); + + for (i = 0; i < NTTYCODE; i++) { + if (term->codes[i].type == TTYCODE_STRING) + xfree(term->codes[i].value.string); + } + xfree(term->name); + xfree(term); +} + +int +tty_term_has(struct tty_term *term, enum tty_code_code code) +{ + return (term->codes[code].type != TTYCODE_NONE); +} + +const char * +tty_term_string(struct tty_term *term, enum tty_code_code code) +{ + if (!tty_term_has(term, code)) + return (""); + if (term->codes[code].type != TTYCODE_STRING) + log_fatalx("not a string: %d", code); + return (term->codes[code].value.string); +} + +/* No vtparm. Fucking ncurses. */ +const char * +tty_term_string1(struct tty_term *term, enum tty_code_code code, int a) +{ + return (tparm((char *) tty_term_string(term, code), a)); +} + +const char * +tty_term_string2(struct tty_term *term, enum tty_code_code code, int a, int b) +{ + return (tparm((char *) tty_term_string(term, code), a, b)); +} + +int +tty_term_number(struct tty_term *term, enum tty_code_code code) +{ + if (!tty_term_has(term, code)) + return (0); + if (term->codes[code].type != TTYCODE_NUMBER) + log_fatalx("not a number: %d", code); + return (term->codes[code].value.number); +} + +int +tty_term_flag(struct tty_term *term, enum tty_code_code code) +{ + if (!tty_term_has(term, code)) + return (0); + if (term->codes[code].type != TTYCODE_FLAG) + log_fatalx("not a flag: %d", code); + return (term->codes[code].value.flag); +} + diff --git a/tty-write.c b/tty-write.c new file mode 100644 index 00000000..46cb8e8c --- /dev/null +++ b/tty-write.c @@ -0,0 +1,91 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +void tty_vwrite_cmd(struct window_pane *, enum tty_cmd, va_list); + +void +tty_write_cmd(struct window_pane *wp, enum tty_cmd cmd, ...) +{ + va_list ap; + + va_start(ap, cmd); + tty_vwrite_cmd(wp, cmd, ap); + va_end(ap); +} + +void +tty_vwrite_cmd(struct window_pane *wp, enum tty_cmd cmd, va_list ap) +{ + struct client *c; + va_list aq; + u_int i; + + if (wp == NULL) + return; + + if (wp->window->flags & WINDOW_REDRAW || wp->flags & PANE_REDRAW) + return; + if (wp->window->flags & WINDOW_HIDDEN || wp->flags & PANE_HIDDEN) + return; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->flags & CLIENT_SUSPENDED) + continue; + + if (c->session->curw->window == wp->window) { + tty_update_mode(&c->tty, c->tty.mode & ~MODE_CURSOR); + + va_copy(aq, ap); + tty_vwrite(&c->tty, wp, cmd, aq); + va_end(aq); + } + } +} + +void +tty_write_mode(struct window_pane *wp, int mode) +{ + struct client *c; + u_int i; + + if (wp == NULL) + return; + + if (wp->window->flags & WINDOW_REDRAW || wp->flags & PANE_REDRAW) + return; + if (wp->window->flags & WINDOW_HIDDEN || wp->flags & PANE_HIDDEN) + return; + + for (i = 0; i < ARRAY_LENGTH(&clients); i++) { + c = ARRAY_ITEM(&clients, i); + if (c == NULL || c->session == NULL) + continue; + if (c->flags & CLIENT_SUSPENDED) + continue; + + tty_update_mode(&c->tty, mode); + } +} + diff --git a/tty.c b/tty.c new file mode 100644 index 00000000..4d81910d --- /dev/null +++ b/tty.c @@ -0,0 +1,1076 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include +#include +#include + +#include "tmux.h" + +void tty_fill_acs(struct tty *); + +void tty_raw(struct tty *, const char *); + +int tty_try_256(struct tty *, u_char, const char *); +int tty_try_88(struct tty *, u_char, const char *); + +void tty_attributes(struct tty *, const struct grid_cell *); +void tty_attributes_fg(struct tty *, const struct grid_cell *); +void tty_attributes_bg(struct tty *, const struct grid_cell *); + +void tty_cmd_cell(struct tty *, struct window_pane *, va_list); +void tty_cmd_clearendofline(struct tty *, struct window_pane *, va_list); +void tty_cmd_clearendofscreen(struct tty *, struct window_pane *, va_list); +void tty_cmd_clearline(struct tty *, struct window_pane *, va_list); +void tty_cmd_clearscreen(struct tty *, struct window_pane *, va_list); +void tty_cmd_clearstartofline(struct tty *, struct window_pane *, va_list); +void tty_cmd_clearstartofscreen(struct tty *, struct window_pane *, va_list); +void tty_cmd_deletecharacter(struct tty *, struct window_pane *, va_list); +void tty_cmd_deleteline(struct tty *, struct window_pane *, va_list); +void tty_cmd_insertcharacter(struct tty *, struct window_pane *, va_list); +void tty_cmd_insertline(struct tty *, struct window_pane *, va_list); +void tty_cmd_linefeed(struct tty *, struct window_pane *, va_list); +void tty_cmd_raw(struct tty *, struct window_pane *, va_list); +void tty_cmd_reverseindex(struct tty *, struct window_pane *, va_list); + +void (*tty_cmds[])(struct tty *, struct window_pane *, va_list) = { + tty_cmd_cell, + tty_cmd_clearendofline, + tty_cmd_clearendofscreen, + tty_cmd_clearline, + tty_cmd_clearscreen, + tty_cmd_clearstartofline, + tty_cmd_clearstartofscreen, + tty_cmd_deletecharacter, + tty_cmd_deleteline, + tty_cmd_insertcharacter, + tty_cmd_insertline, + tty_cmd_linefeed, + tty_cmd_raw, + tty_cmd_reverseindex, +}; + +void +tty_init(struct tty *tty, char *path, char *term) +{ + tty->path = xstrdup(path); + if (term == NULL) + tty->termname = xstrdup("unknown"); + else + tty->termname = xstrdup(term); + tty->flags = 0; + tty->term_flags = 0; +} + +int +tty_open(struct tty *tty, char **cause) +{ + int mode; + + tty->fd = open(tty->path, O_RDWR|O_NONBLOCK); + if (tty->fd == -1) { + xasprintf(cause, "%s: %s", tty->path, strerror(errno)); + return (-1); + } + + if ((mode = fcntl(tty->fd, F_GETFL)) == -1) + fatal("fcntl failed"); + if (fcntl(tty->fd, F_SETFL, mode|O_NONBLOCK) == -1) + fatal("fcntl failedo"); + if (fcntl(tty->fd, F_SETFD, FD_CLOEXEC) == -1) + fatal("fcntl failed"); + + if (debug_level > 3) + tty->log_fd = open("tmux.out", O_WRONLY|O_CREAT|O_TRUNC, 0644); + else + tty->log_fd = -1; + + if ((tty->term = tty_term_find(tty->termname, tty->fd, cause)) == NULL) + goto error; + + tty->in = buffer_create(BUFSIZ); + tty->out = buffer_create(BUFSIZ); + + tty->flags &= TTY_UTF8; + + tty_start_tty(tty); + + tty_keys_init(tty); + + tty_fill_acs(tty); + + return (0); + +error: + close(tty->fd); + tty->fd = -1; + + return (-1); +} + +void +tty_start_tty(struct tty *tty) +{ + struct termios tio; + int what; + + tty_detect_utf8(tty); + + if (tcgetattr(tty->fd, &tty->tio) != 0) + fatal("tcgetattr failed"); + memcpy(&tio, &tty->tio, sizeof tio); + tio.c_iflag &= ~(IXON|IXOFF|ICRNL|INLCR|IGNCR|IMAXBEL|ISTRIP); + tio.c_iflag |= IGNBRK; + tio.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET); + tio.c_lflag &= ~(IEXTEN|ICANON|ECHO|ECHOE|ECHONL|ECHOCTL| + ECHOPRT|ECHOKE|ECHOCTL|ISIG); + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + if (tcsetattr(tty->fd, TCSANOW, &tio) != 0) + fatal("tcsetattr failed"); + + what = 0; + if (ioctl(tty->fd, TIOCFLUSH, &what) != 0) + fatal("ioctl(TIOCFLUSH)"); + + tty_putcode(tty, TTYC_IS1); + tty_putcode(tty, TTYC_IS2); + tty_putcode(tty, TTYC_IS3); + + tty_putcode(tty, TTYC_SMCUP); + tty_putcode(tty, TTYC_SMKX); + tty_putcode(tty, TTYC_ENACS); + tty_putcode(tty, TTYC_CLEAR); + + tty_putcode(tty, TTYC_CNORM); + if (tty_term_has(tty->term, TTYC_KMOUS)) + tty_puts(tty, "\033[?1000l"); + + memcpy(&tty->cell, &grid_default_cell, sizeof tty->cell); + + tty->cx = UINT_MAX; + tty->cy = UINT_MAX; + + tty->rlower = UINT_MAX; + tty->rupper = UINT_MAX; + + tty->mode = MODE_CURSOR; +} + +void +tty_stop_tty(struct tty *tty) +{ + struct winsize ws; + + /* + * Be flexible about error handling and try not kill the server just + * because the fd is invalid. Things like ssh -t can easily leave us + * with a dead tty. + */ + if (ioctl(tty->fd, TIOCGWINSZ, &ws) == -1) + return; + if (tcsetattr(tty->fd, TCSANOW, &tty->tio) == -1) + return; + + tty_raw(tty, tty_term_string2(tty->term, TTYC_CSR, 0, ws.ws_row - 1)); + tty_raw(tty, tty_term_string(tty->term, TTYC_RMACS)); + tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0)); + tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX)); + tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP)); + tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR)); + + tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM)); + if (tty_term_has(tty->term, TTYC_KMOUS)) + tty_raw(tty, "\033[?1000l"); +} + +void +tty_detect_utf8(struct tty *tty) +{ + struct pollfd pfd; + char buf[7]; + size_t len; + ssize_t n; + int nfds; + struct termios tio, old_tio; + int what; + + if (tty->flags & TTY_UTF8) + return; + + /* + * If the terminal looks reasonably likely to support this, try to + * write a three-byte UTF-8 wide character to the terminal, then read + * the cursor position. + * + * XXX This entire function is a hack. + */ + + /* Check if the terminal looks sort of vt100. */ + if (strstr(tty_term_string(tty->term, TTYC_CLEAR), "[2J") == NULL || + strstr(tty_term_string(tty->term, TTYC_CUP), "H") == NULL) + return; + + if (tcgetattr(tty->fd, &old_tio) != 0) + fatal("tcgetattr failed"); + cfmakeraw(&tio); + if (tcsetattr(tty->fd, TCSANOW, &tio) != 0) + fatal("tcsetattr failed"); + + what = 0; + if (ioctl(tty->fd, TIOCFLUSH, &what) != 0) + fatal("ioctl(TIOCFLUSH)"); + +#define UTF8_TEST_DATA "\033[H\357\277\246\033[6n" + if (write(tty->fd, UTF8_TEST_DATA, (sizeof UTF8_TEST_DATA) - 1) == -1) + fatal("write failed"); +#undef UTF8_TEST_DATA + + len = 0; + for (;;) { + pfd.fd = tty->fd; + pfd.events = POLLIN; + + nfds = poll(&pfd, 1, 500); + if (nfds == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + fatal("poll failed"); + } + if (nfds == 0) + break; + if (pfd.revents & (POLLERR|POLLNVAL|POLLHUP)) + break; + if (!(pfd.revents & POLLIN)) + continue; + + if ((n = read(tty->fd, buf + len, 1)) != 1) + break; + buf[++len] = '\0'; + + if (len == (sizeof buf) - 1) { + if (strcmp(buf, "\033[1;3R") == 0) + tty->flags |= TTY_UTF8; + break; + } + } + + if (tcsetattr(tty->fd, TCSANOW, &old_tio) != 0) + fatal("tcsetattr failed"); +} + +void +tty_fill_acs(struct tty *tty) +{ + const char *ptr; + + memset(tty->acs, 0, sizeof tty->acs); + if (!tty_term_has(tty->term, TTYC_ACSC)) + return; + + ptr = tty_term_string(tty->term, TTYC_ACSC); + if (strlen(ptr) % 2 != 0) + return; + for (; *ptr != '\0'; ptr += 2) + tty->acs[(u_char) ptr[0]] = ptr[1]; +} + +u_char +tty_get_acs(struct tty *tty, u_char ch) +{ + if (tty->acs[ch] != '\0') + return (tty->acs[ch]); + return (ch); +} + +void +tty_close(struct tty *tty, int no_stop) +{ + if (tty->fd == -1) + return; + + if (tty->log_fd != -1) { + close(tty->log_fd); + tty->log_fd = -1; + } + + if (!no_stop) + tty_stop_tty(tty); + + tty_term_free(tty->term); + tty_keys_free(tty); + + close(tty->fd); + tty->fd = -1; + + buffer_destroy(tty->in); + buffer_destroy(tty->out); +} + +void +tty_free(struct tty *tty, int no_stop) +{ + tty_close(tty, no_stop); + + if (tty->path != NULL) + xfree(tty->path); + if (tty->termname != NULL) + xfree(tty->termname); +} + +void +tty_raw(struct tty *tty, const char *s) +{ + write(tty->fd, s, strlen(s)); +} + +void +tty_putcode(struct tty *tty, enum tty_code_code code) +{ + tty_puts(tty, tty_term_string(tty->term, code)); +} + +void +tty_putcode1(struct tty *tty, enum tty_code_code code, int a) +{ + if (a < 0) + return; + tty_puts(tty, tty_term_string1(tty->term, code, a)); +} + +void +tty_putcode2(struct tty *tty, enum tty_code_code code, int a, int b) +{ + if (a < 0 || b < 0) + return; + tty_puts(tty, tty_term_string2(tty->term, code, a, b)); +} + +void +tty_puts(struct tty *tty, const char *s) +{ + if (*s == '\0') + return; + buffer_write(tty->out, s, strlen(s)); + + if (tty->log_fd != -1) + write(tty->log_fd, s, strlen(s)); +} + +void +tty_putc(struct tty *tty, u_char ch) +{ + u_int sx; + + if (tty->cell.attr & GRID_ATTR_CHARSET) + ch = tty_get_acs(tty, ch); + buffer_write8(tty->out, ch); + + if (ch >= 0x20 && ch != 0x7f) { + sx = tty->sx; + if (tty->term->flags & TERM_EARLYWRAP) + sx--; + + if (tty->cx == sx) { + tty->cx = 0; + tty->cy++; + } else + tty->cx++; + } + + if (tty->log_fd != -1) + write(tty->log_fd, &ch, 1); +} + +void +tty_set_title(struct tty *tty, const char *title) +{ + if (strstr(tty->termname, "xterm") == NULL && + strstr(tty->termname, "rxvt") == NULL && + strcmp(tty->termname, "screen") != 0) + return; + + tty_puts(tty, "\033]0;"); + tty_puts(tty, title); + tty_putc(tty, '\007'); +} + +void +tty_update_mode(struct tty *tty, int mode) +{ + int changed; + + if (tty->flags & TTY_NOCURSOR) + mode &= ~MODE_CURSOR; + + changed = mode ^ tty->mode; + if (changed & MODE_CURSOR) { + if (mode & MODE_CURSOR) + tty_putcode(tty, TTYC_CNORM); + else + tty_putcode(tty, TTYC_CIVIS); + } + if (changed & MODE_MOUSE) { + if (mode & MODE_MOUSE) + tty_puts(tty, "\033[?1000h"); + else + tty_puts(tty, "\033[?1000l"); + } + tty->mode = mode; +} + +void +tty_emulate_repeat( + struct tty *tty, enum tty_code_code code, enum tty_code_code code1, u_int n) +{ + if (tty_term_has(tty->term, code)) + tty_putcode1(tty, code, n); + else { + while (n-- > 0) + tty_putcode(tty, code1); + } +} + +/* + * Redraw scroll region using data from screen (already updated). Used when + * CSR not supported, or window is a pane that doesn't take up the full + * width of the terminal. + */ +void +tty_redraw_region(struct tty *tty, struct window_pane *wp) +{ + struct screen *s = wp->screen; + u_int i; + + /* + * If region is >= 50% of the screen, just schedule a window redraw. In + * most cases, this is likely to be followed by some more scrolling - + * without this, the entire pane ends up being redrawn many times which + * can be much more data. + */ + if (s->old_rupper - s->old_rlower >= screen_size_y(s) / 2) { + wp->flags |= PANE_REDRAW; + return; + } + + if (s->old_cy < s->old_rupper || s->old_cy > s->old_rlower) { + for (i = s->old_cy; i < screen_size_y(s); i++) + tty_draw_line(tty, s, i, wp->xoff, wp->yoff); + } else { + for (i = s->old_rupper; i <= s->old_rlower; i++) + tty_draw_line(tty, s, i, wp->xoff, wp->yoff); + } +} + +void +tty_draw_line(struct tty *tty, struct screen *s, u_int py, u_int ox, u_int oy) +{ + const struct grid_cell *gc; + const struct grid_utf8 *gu; + u_int i, sx; + + sx = screen_size_x(s); + if (sx > s->grid->size[s->grid->hsize + py]) + sx = s->grid->size[s->grid->hsize + py]; + if (sx > tty->sx) + sx = tty->sx; + + tty_cursor(tty, 0, py, ox, oy); + for (i = 0; i < sx; i++) { + gc = grid_view_peek_cell(s->grid, i, py); + + gu = NULL; + if (gc->flags & GRID_FLAG_UTF8) + gu = grid_view_peek_utf8(s->grid, i, py); + + if (screen_check_selection(s, i, py)) { + s->sel.cell.data = gc->data; + tty_cell(tty, &s->sel.cell, gu); + } else + tty_cell(tty, gc, gu); + } + + if (sx >= tty->sx) + return; + tty_reset(tty); + + tty_cursor(tty, sx, py, ox, oy); + if (screen_size_x(s) >= tty->sx && tty_term_has(tty->term, TTYC_EL)) + tty_putcode(tty, TTYC_EL); + else { + for (i = sx; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + } +} + +void +tty_write(struct tty *tty, struct window_pane *wp, enum tty_cmd cmd, ...) +{ + va_list ap; + + va_start(ap, cmd); + tty_vwrite(tty, wp, cmd, ap); + va_end(ap); +} + +void +tty_vwrite( + struct tty *tty, struct window_pane *wp, enum tty_cmd cmd, va_list ap) +{ + if (tty->flags & TTY_FREEZE || tty->term == NULL) + return; + if (tty_cmds[cmd] != NULL) + tty_cmds[cmd](tty, wp, ap); +} + +void +tty_cmd_insertcharacter(struct tty *tty, struct window_pane *wp, va_list ap) +{ + struct screen *s = wp->screen; + u_int ua; + + if (wp->xoff != 0 || screen_size_x(s) < tty->sx) { + tty_draw_line(tty, wp->screen, s->old_cy, wp->xoff, wp->yoff); + return; + } + + ua = va_arg(ap, u_int); + + tty_reset(tty); + + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + if (tty_term_has(tty->term, TTYC_ICH) || + tty_term_has(tty->term, TTYC_ICH1)) + tty_emulate_repeat(tty, TTYC_ICH, TTYC_ICH1, ua); + else { + tty_putcode(tty, TTYC_SMIR); + while (ua-- > 0) + tty_putc(tty, ' '); + tty_putcode(tty, TTYC_RMIR); + } +} + +void +tty_cmd_deletecharacter(struct tty *tty, struct window_pane *wp, va_list ap) +{ + struct screen *s = wp->screen; + u_int ua; + + if (wp->xoff != 0 || screen_size_x(s) < tty->sx) { + tty_draw_line(tty, wp->screen, s->old_cy, wp->xoff, wp->yoff); + return; + } + + ua = va_arg(ap, u_int); + + tty_reset(tty); + + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + tty_emulate_repeat(tty, TTYC_DCH, TTYC_DCH1, ua); +} + +void +tty_cmd_insertline(struct tty *tty, struct window_pane *wp, va_list ap) +{ + struct screen *s = wp->screen; + u_int ua; + + if (wp->xoff != 0 || screen_size_x(s) < tty->sx || + !tty_term_has(tty->term, TTYC_CSR)) { + tty_redraw_region(tty, wp); + return; + } + + ua = va_arg(ap, u_int); + + tty_reset(tty); + + tty_region(tty, s->old_rupper, s->old_rlower, wp->yoff); + + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + tty_emulate_repeat(tty, TTYC_IL, TTYC_IL1, ua); +} + +void +tty_cmd_deleteline(struct tty *tty, struct window_pane *wp, va_list ap) +{ + struct screen *s = wp->screen; + u_int ua; + + if (wp->xoff != 0 || screen_size_x(s) < tty->sx || + !tty_term_has(tty->term, TTYC_CSR)) { + tty_redraw_region(tty, wp); + return; + } + + ua = va_arg(ap, u_int); + + tty_reset(tty); + + tty_region(tty, s->old_rupper, s->old_rlower, wp->yoff); + + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + tty_emulate_repeat(tty, TTYC_DL, TTYC_DL1, ua); +} + +void +tty_cmd_clearline(struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + u_int i; + + tty_reset(tty); + + tty_cursor(tty, 0, s->old_cy, wp->xoff, wp->yoff); + if (wp->xoff == 0 && screen_size_x(s) >= tty->sx && + tty_term_has(tty->term, TTYC_EL)) { + tty_putcode(tty, TTYC_EL); + } else { + for (i = 0; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + } +} + +void +tty_cmd_clearendofline( + struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + u_int i; + + tty_reset(tty); + + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + if (wp->xoff == 0 && screen_size_x(s) >= tty->sx && + tty_term_has(tty->term, TTYC_EL)) + tty_putcode(tty, TTYC_EL); + else { + for (i = s->old_cx; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + } +} + +void +tty_cmd_clearstartofline( + struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + u_int i; + + tty_reset(tty); + + if (wp->xoff == 0 && tty_term_has(tty->term, TTYC_EL1)) { + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + tty_putcode(tty, TTYC_EL1); + } else { + tty_cursor(tty, 0, s->old_cy, wp->xoff, wp->yoff); + for (i = 0; i < s->old_cx + 1; i++) + tty_putc(tty, ' '); + } +} + +void +tty_cmd_reverseindex(struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + + if (wp->xoff != 0 || screen_size_x(s) < tty->sx || + !tty_term_has(tty->term, TTYC_CSR)) { + tty_redraw_region(tty, wp); + return; + } + + tty_reset(tty); + + tty_region(tty, s->old_rupper, s->old_rlower, wp->yoff); + + if (s->old_cy == s->old_rupper) { + tty_cursor(tty, s->old_cx, s->old_rupper, wp->xoff, wp->yoff); + tty_putcode(tty, TTYC_RI); + } +} + +void +tty_cmd_linefeed(struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + + if (wp->xoff != 0 || screen_size_x(s) < tty->sx || + !tty_term_has(tty->term, TTYC_CSR)) { + tty_redraw_region(tty, wp); + return; + } + + tty_reset(tty); + + tty_region(tty, s->old_rupper, s->old_rlower, wp->yoff); + + if (s->old_cy == s->old_rlower) { + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + tty_putc(tty, '\n'); + } +} + +void +tty_cmd_clearendofscreen( + struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + u_int i, j, oy; + + oy = wp->yoff; + + tty_reset(tty); + + tty_region(tty, 0, screen_size_y(s) - 1, wp->yoff); + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + if (wp->xoff == 0 && screen_size_x(s) >= tty->sx && + tty_term_has(tty->term, TTYC_EL)) { + tty_putcode(tty, TTYC_EL); + if (s->old_cy != screen_size_y(s) - 1) { + tty_cursor(tty, 0, s->old_cy + 1, wp->xoff, wp->yoff); + for (i = s->old_cy + 1; i < screen_size_y(s); i++) { + tty_putcode(tty, TTYC_EL); + if (i == screen_size_y(s) - 1) + continue; + tty_emulate_repeat(tty, TTYC_CUD, TTYC_CUD1, 1); + tty->cy++; + } + } + } else { + for (i = s->old_cx; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + for (j = s->old_cy; j < screen_size_y(s); j++) { + tty_cursor(tty, 0, j, wp->xoff, wp->yoff); + for (i = 0; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + } + } +} + +void +tty_cmd_clearstartofscreen( + struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + u_int i, j; + + tty_reset(tty); + + tty_region(tty, 0, screen_size_y(s) - 1, wp->yoff); + tty_cursor(tty, 0, 0, wp->xoff, wp->yoff); + if (wp->xoff == 0 && screen_size_x(s) >= tty->sx && + tty_term_has(tty->term, TTYC_EL)) { + for (i = 0; i < s->old_cy; i++) { + tty_putcode(tty, TTYC_EL); + tty_emulate_repeat(tty, TTYC_CUD, TTYC_CUD1, 1); + tty->cy++; + } + } else { + for (j = 0; j < s->old_cy; j++) { + tty_cursor(tty, 0, j, wp->xoff, wp->yoff); + for (i = 0; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + } + } + for (i = 0; i < s->old_cx; i++) + tty_putc(tty, ' '); +} + +void +tty_cmd_clearscreen( + struct tty *tty, struct window_pane *wp, unused va_list ap) +{ + struct screen *s = wp->screen; + u_int i, j; + + tty_reset(tty); + + tty_region(tty, 0, screen_size_y(s) - 1, wp->yoff); + tty_cursor(tty, 0, 0, wp->xoff, wp->yoff); + if (wp->xoff == 0 && screen_size_x(s) >= tty->sx && + tty_term_has(tty->term, TTYC_EL)) { + for (i = 0; i < screen_size_y(s); i++) { + tty_putcode(tty, TTYC_EL); + if (i != screen_size_y(s) - 1) { + tty_emulate_repeat(tty, TTYC_CUD, TTYC_CUD1, 1); + tty->cy++; + } + } + } else { + for (j = 0; j < screen_size_y(s); j++) { + tty_cursor(tty, 0, j, wp->xoff, wp->yoff); + for (i = 0; i < screen_size_x(s); i++) + tty_putc(tty, ' '); + } + } +} + +void +tty_cmd_cell(struct tty *tty, struct window_pane *wp, va_list ap) +{ + struct screen *s = wp->screen; + struct grid_cell *gc; + struct grid_utf8 *gu; + + gc = va_arg(ap, struct grid_cell *); + gu = va_arg(ap, struct grid_utf8 *); + + tty_cursor(tty, s->old_cx, s->old_cy, wp->xoff, wp->yoff); + + tty_cell(tty, gc, gu); +} + +void +tty_cmd_raw(struct tty *tty, unused struct window_pane *wp, va_list ap) +{ + u_char *buf; + size_t i, len; + + buf = va_arg(ap, u_char *); + len = va_arg(ap, size_t); + + for (i = 0; i < len; i++) + tty_putc(tty, buf[i]); +} + +void +tty_cell( + struct tty *tty, const struct grid_cell *gc, const struct grid_utf8 *gu) +{ + u_int i; + + /* Skip last character if terminal is stupid. */ + if (tty->term->flags & TERM_EARLYWRAP && + tty->cy == tty->sy - 1 && tty->cx == tty->sx - 1) + return; + + /* If this is a padding character, do nothing. */ + if (gc->flags & GRID_FLAG_PADDING) + return; + + /* Set the attributes. */ + tty_attributes(tty, gc); + + /* If not UTF-8, write directly. */ + if (!(gc->flags & GRID_FLAG_UTF8)) { + if (gc->data < 0x20 || gc->data == 0x7f) + return; + tty_putc(tty, gc->data); + return; + } + + /* If the terminal doesn't support UTF-8, write underscores. */ + if (!(tty->flags & TTY_UTF8)) { + for (i = 0; i < gu->width; i++) + tty_putc(tty, '_'); + return; + } + + /* Otherwise, write UTF-8. */ + for (i = 0; i < UTF8_SIZE; i++) { + if (gu->data[i] == 0xff) + break; + tty_putc(tty, gu->data[i]); + } +} + +void +tty_reset(struct tty *tty) +{ + struct grid_cell *gc = &tty->cell; + + if (memcmp(gc, &grid_default_cell, sizeof *gc) == 0) + return; + + if (tty_term_has(tty->term, TTYC_RMACS) && gc->attr & GRID_ATTR_CHARSET) + tty_putcode(tty, TTYC_RMACS); + tty_putcode(tty, TTYC_SGR0); + memcpy(gc, &grid_default_cell, sizeof *gc); +} + +void +tty_region(struct tty *tty, u_int rupper, u_int rlower, u_int oy) +{ + if (!tty_term_has(tty->term, TTYC_CSR)) + return; + if (tty->rlower != oy + rlower || tty->rupper != oy + rupper) { + tty->rlower = oy + rlower; + tty->rupper = oy + rupper; + tty->cx = 0; + tty->cy = 0; + tty_putcode2(tty, TTYC_CSR, tty->rupper, tty->rlower); + } +} + +void +tty_cursor(struct tty *tty, u_int cx, u_int cy, u_int ox, u_int oy) +{ + if (ox + cx == 0 && tty->cx != 0 && tty->cy == oy + cy) { + tty->cx = 0; + tty_putc(tty, '\r'); + } else if (tty->cx != ox + cx || tty->cy != oy + cy) { + tty->cx = ox + cx; + tty->cy = oy + cy; + tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx); + } +} + +void +tty_attributes(struct tty *tty, const struct grid_cell *gc) +{ + struct grid_cell *tc = &tty->cell; + u_char changed; + u_int fg, bg; + + /* If any bits are being cleared, reset everything. */ + if (tc->attr & ~gc->attr) + tty_reset(tty); + + /* Filter out attribute bits already set. */ + changed = gc->attr & ~tc->attr; + tc->attr = gc->attr; + + /* Set the attributes. */ + fg = gc->fg; + bg = gc->bg; + if (changed & GRID_ATTR_BRIGHT) + tty_putcode(tty, TTYC_BOLD); + if (changed & GRID_ATTR_DIM) + tty_putcode(tty, TTYC_DIM); + if (changed & GRID_ATTR_ITALICS) + tty_putcode(tty, TTYC_SMSO); + if (changed & GRID_ATTR_UNDERSCORE) + tty_putcode(tty, TTYC_SMUL); + if (changed & GRID_ATTR_BLINK) + tty_putcode(tty, TTYC_BLINK); + if (changed & GRID_ATTR_REVERSE) { + if (tty_term_has(tty->term, TTYC_REV)) + tty_putcode(tty, TTYC_REV); + else if (tty_term_has(tty->term, TTYC_SMSO)) + tty_putcode(tty, TTYC_SMSO); + } + if (changed & GRID_ATTR_HIDDEN) + tty_putcode(tty, TTYC_INVIS); + if (changed & GRID_ATTR_CHARSET) + tty_putcode(tty, TTYC_SMACS); + + /* Set foreground colour. */ + if (fg != tc->fg || + (gc->flags & GRID_FLAG_FG256) != (tc->flags & GRID_FLAG_FG256)) { + tty_attributes_fg(tty, gc); + tc->fg = fg; + } + + /* Set background colour. */ + if (bg != tc->bg || + (gc->flags & GRID_FLAG_BG256) != (tc->flags & GRID_FLAG_BG256)) { + tty_attributes_bg(tty, gc); + tc->bg = bg; + } +} + +int +tty_try_256(struct tty *tty, u_char colour, const char *type) +{ + char s[32]; + + if (!(tty->term->flags & TERM_256COLOURS) && + !(tty->term_flags & TERM_256COLOURS)) + return (-1); + + xsnprintf(s, sizeof s, "\033[%s;5;%hhum", type, colour); + tty_puts(tty, s); + return (0); +} + +int +tty_try_88(struct tty *tty, u_char colour, const char *type) +{ + char s[32]; + + if (!(tty->term->flags & TERM_88COLOURS) && + !(tty->term_flags & TERM_88COLOURS)) + return (-1); + colour = colour_256to88(colour); + + xsnprintf(s, sizeof s, "\033[%s;5;%hhum", type, colour); + tty_puts(tty, s); + return (0); +} + +void +tty_attributes_fg(struct tty *tty, const struct grid_cell *gc) +{ + u_char fg; + + fg = gc->fg; + if (gc->flags & GRID_FLAG_FG256) { + if (tty_try_256(tty, fg, "38") == 0) + return; + if (tty_try_88(tty, fg, "38") == 0) + return; + fg = colour_256to16(fg); + if (fg & 8) { + fg &= 7; + tty_putcode(tty, TTYC_BOLD); + tty->cell.attr |= GRID_ATTR_BRIGHT; + } else if (tty->cell.attr & GRID_ATTR_BRIGHT) + tty_reset(tty); + } + + if (fg == 8 && + !(tty->term->flags & TERM_HASDEFAULTS) && + !(tty->term_flags & TERM_HASDEFAULTS)) + fg = 7; + if (fg == 8) + tty_puts(tty, "\033[39m"); + else + tty_putcode1(tty, TTYC_SETAF, fg); +} + +void +tty_attributes_bg(struct tty *tty, const struct grid_cell *gc) +{ + u_char bg; + + bg = gc->bg; + if (gc->flags & GRID_FLAG_BG256) { + if (tty_try_256(tty, bg, "48") == 0) + return; + if (tty_try_88(tty, bg, "48") == 0) + return; + bg = colour_256to16(bg); + if (bg & 8) + bg &= 7; + } + + if (bg == 8 && + !(tty->term->flags & TERM_HASDEFAULTS) && + !(tty->term_flags & TERM_HASDEFAULTS)) + bg = 0; + if (bg == 8) + tty_puts(tty, "\033[49m"); + else + tty_putcode1(tty, TTYC_SETAB, bg); +} diff --git a/utf8.c b/utf8.c new file mode 100644 index 00000000..b0e3c3cb --- /dev/null +++ b/utf8.c @@ -0,0 +1,317 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2008 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +struct utf8_width_entry { + u_int first; + u_int last; + + int width; + + struct utf8_width_entry *left; + struct utf8_width_entry *right; +}; + +/* Random order. Not optimal but it'll do for now... */ +struct utf8_width_entry utf8_width_table[] = { + { 0x00951, 0x00954, 0, NULL, NULL }, + { 0x00ccc, 0x00ccd, 0, NULL, NULL }, + { 0x0fff9, 0x0fffb, 0, NULL, NULL }, + { 0x20000, 0x2fffd, 2, NULL, NULL }, + { 0x00ebb, 0x00ebc, 0, NULL, NULL }, + { 0x01932, 0x01932, 0, NULL, NULL }, + { 0x0070f, 0x0070f, 0, NULL, NULL }, + { 0x00a70, 0x00a71, 0, NULL, NULL }, + { 0x02329, 0x02329, 2, NULL, NULL }, + { 0x00acd, 0x00acd, 0, NULL, NULL }, + { 0x00ac7, 0x00ac8, 0, NULL, NULL }, + { 0x00a3c, 0x00a3c, 0, NULL, NULL }, + { 0x009cd, 0x009cd, 0, NULL, NULL }, + { 0x00591, 0x005bd, 0, NULL, NULL }, + { 0x01058, 0x01059, 0, NULL, NULL }, + { 0x0ffe0, 0x0ffe6, 2, NULL, NULL }, + { 0x01100, 0x0115f, 2, NULL, NULL }, + { 0x0fe20, 0x0fe23, 0, NULL, NULL }, + { 0x0302a, 0x0302f, 0, NULL, NULL }, + { 0x01772, 0x01773, 0, NULL, NULL }, + { 0x005bf, 0x005bf, 0, NULL, NULL }, + { 0x006ea, 0x006ed, 0, NULL, NULL }, + { 0x00bc0, 0x00bc0, 0, NULL, NULL }, + { 0x00962, 0x00963, 0, NULL, NULL }, + { 0x01732, 0x01734, 0, NULL, NULL }, + { 0x00d41, 0x00d43, 0, NULL, NULL }, + { 0x01b42, 0x01b42, 0, NULL, NULL }, + { 0x00a41, 0x00a42, 0, NULL, NULL }, + { 0x00eb4, 0x00eb9, 0, NULL, NULL }, + { 0x00b01, 0x00b01, 0, NULL, NULL }, + { 0x00e34, 0x00e3a, 0, NULL, NULL }, + { 0x03040, 0x03098, 2, NULL, NULL }, + { 0x0093c, 0x0093c, 0, NULL, NULL }, + { 0x00c4a, 0x00c4d, 0, NULL, NULL }, + { 0x01032, 0x01032, 0, NULL, NULL }, + { 0x00f37, 0x00f37, 0, NULL, NULL }, + { 0x00901, 0x00902, 0, NULL, NULL }, + { 0x00cbf, 0x00cbf, 0, NULL, NULL }, + { 0x0a806, 0x0a806, 0, NULL, NULL }, + { 0x00dd2, 0x00dd4, 0, NULL, NULL }, + { 0x00f71, 0x00f7e, 0, NULL, NULL }, + { 0x01752, 0x01753, 0, NULL, NULL }, + { 0x1d242, 0x1d244, 0, NULL, NULL }, + { 0x005c1, 0x005c2, 0, NULL, NULL }, + { 0x0309b, 0x0a4cf, 2, NULL, NULL }, + { 0xe0100, 0xe01ef, 0, NULL, NULL }, + { 0x017dd, 0x017dd, 0, NULL, NULL }, + { 0x00600, 0x00603, 0, NULL, NULL }, + { 0x009e2, 0x009e3, 0, NULL, NULL }, + { 0x00cc6, 0x00cc6, 0, NULL, NULL }, + { 0x0a80b, 0x0a80b, 0, NULL, NULL }, + { 0x01712, 0x01714, 0, NULL, NULL }, + { 0x00b3c, 0x00b3c, 0, NULL, NULL }, + { 0x01b00, 0x01b03, 0, NULL, NULL }, + { 0x007eb, 0x007f3, 0, NULL, NULL }, + { 0xe0001, 0xe0001, 0, NULL, NULL }, + { 0x1d185, 0x1d18b, 0, NULL, NULL }, + { 0x0feff, 0x0feff, 0, NULL, NULL }, + { 0x01b36, 0x01b3a, 0, NULL, NULL }, + { 0x01920, 0x01922, 0, NULL, NULL }, + { 0x00670, 0x00670, 0, NULL, NULL }, + { 0x00f90, 0x00f97, 0, NULL, NULL }, + { 0x01927, 0x01928, 0, NULL, NULL }, + { 0x0200b, 0x0200f, 0, NULL, NULL }, + { 0x0ff00, 0x0ff60, 2, NULL, NULL }, + { 0x0f900, 0x0faff, 2, NULL, NULL }, + { 0x0fb1e, 0x0fb1e, 0, NULL, NULL }, + { 0x00cbc, 0x00cbc, 0, NULL, NULL }, + { 0x00eb1, 0x00eb1, 0, NULL, NULL }, + { 0x10a38, 0x10a3a, 0, NULL, NULL }, + { 0x007a6, 0x007b0, 0, NULL, NULL }, + { 0x00f80, 0x00f84, 0, NULL, NULL }, + { 0x005c4, 0x005c5, 0, NULL, NULL }, + { 0x0ac00, 0x0d7a3, 2, NULL, NULL }, + { 0x017c9, 0x017d3, 0, NULL, NULL }, + { 0x00d4d, 0x00d4d, 0, NULL, NULL }, + { 0x1d167, 0x1d169, 0, NULL, NULL }, + { 0x01036, 0x01037, 0, NULL, NULL }, + { 0xe0020, 0xe007f, 0, NULL, NULL }, + { 0x00f35, 0x00f35, 0, NULL, NULL }, + { 0x017b4, 0x017b5, 0, NULL, NULL }, + { 0x0206a, 0x0206f, 0, NULL, NULL }, + { 0x00c46, 0x00c48, 0, NULL, NULL }, + { 0x01939, 0x0193b, 0, NULL, NULL }, + { 0x01dc0, 0x01dca, 0, NULL, NULL }, + { 0x10a0c, 0x10a0f, 0, NULL, NULL }, + { 0x0102d, 0x01030, 0, NULL, NULL }, + { 0x017c6, 0x017c6, 0, NULL, NULL }, + { 0x00ec8, 0x00ecd, 0, NULL, NULL }, + { 0x00b41, 0x00b43, 0, NULL, NULL }, + { 0x017b7, 0x017bd, 0, NULL, NULL }, + { 0x1d173, 0x1d182, 0, NULL, NULL }, + { 0x00a47, 0x00a48, 0, NULL, NULL }, + { 0x0232a, 0x0232a, 2, NULL, NULL }, + { 0x01b3c, 0x01b3c, 0, NULL, NULL }, + { 0x10a01, 0x10a03, 0, NULL, NULL }, + { 0x00ae2, 0x00ae3, 0, NULL, NULL }, + { 0x00483, 0x00486, 0, NULL, NULL }, + { 0x0135f, 0x0135f, 0, NULL, NULL }, + { 0x01a17, 0x01a18, 0, NULL, NULL }, + { 0x006e7, 0x006e8, 0, NULL, NULL }, + { 0x03099, 0x0309a, 0, NULL, NULL }, + { 0x00b4d, 0x00b4d, 0, NULL, NULL }, + { 0x00ce2, 0x00ce3, 0, NULL, NULL }, + { 0x00bcd, 0x00bcd, 0, NULL, NULL }, + { 0x00610, 0x00615, 0, NULL, NULL }, + { 0x00f99, 0x00fbc, 0, NULL, NULL }, + { 0x009c1, 0x009c4, 0, NULL, NULL }, + { 0x00730, 0x0074a, 0, NULL, NULL }, + { 0x00300, 0x0036f, 0, NULL, NULL }, + { 0x03030, 0x0303e, 2, NULL, NULL }, + { 0x01b34, 0x01b34, 0, NULL, NULL }, + { 0x1d1aa, 0x1d1ad, 0, NULL, NULL }, + { 0x00dca, 0x00dca, 0, NULL, NULL }, + { 0x006d6, 0x006e4, 0, NULL, NULL }, + { 0x00f86, 0x00f87, 0, NULL, NULL }, + { 0x00b3f, 0x00b3f, 0, NULL, NULL }, + { 0x0fe30, 0x0fe6f, 2, NULL, NULL }, + { 0x01039, 0x01039, 0, NULL, NULL }, + { 0x0094d, 0x0094d, 0, NULL, NULL }, + { 0x00c55, 0x00c56, 0, NULL, NULL }, + { 0x00488, 0x00489, 0, NULL, NULL }, + { 0x00e47, 0x00e4e, 0, NULL, NULL }, + { 0x00a81, 0x00a82, 0, NULL, NULL }, + { 0x00ac1, 0x00ac5, 0, NULL, NULL }, + { 0x0202a, 0x0202e, 0, NULL, NULL }, + { 0x00dd6, 0x00dd6, 0, NULL, NULL }, + { 0x018a9, 0x018a9, 0, NULL, NULL }, + { 0x0064b, 0x0065e, 0, NULL, NULL }, + { 0x00abc, 0x00abc, 0, NULL, NULL }, + { 0x00b82, 0x00b82, 0, NULL, NULL }, + { 0x00f39, 0x00f39, 0, NULL, NULL }, + { 0x020d0, 0x020ef, 0, NULL, NULL }, + { 0x01dfe, 0x01dff, 0, NULL, NULL }, + { 0x30000, 0x3fffd, 2, NULL, NULL }, + { 0x00711, 0x00711, 0, NULL, NULL }, + { 0x0fe00, 0x0fe0f, 0, NULL, NULL }, + { 0x01160, 0x011ff, 0, NULL, NULL }, + { 0x0180b, 0x0180d, 0, NULL, NULL }, + { 0x10a3f, 0x10a3f, 0, NULL, NULL }, + { 0x00981, 0x00981, 0, NULL, NULL }, + { 0x0a825, 0x0a826, 0, NULL, NULL }, + { 0x00941, 0x00948, 0, NULL, NULL }, + { 0x01b6b, 0x01b73, 0, NULL, NULL }, + { 0x00e31, 0x00e31, 0, NULL, NULL }, + { 0x0fe10, 0x0fe19, 2, NULL, NULL }, + { 0x00a01, 0x00a02, 0, NULL, NULL }, + { 0x00a4b, 0x00a4d, 0, NULL, NULL }, + { 0x00f18, 0x00f19, 0, NULL, NULL }, + { 0x00fc6, 0x00fc6, 0, NULL, NULL }, + { 0x02e80, 0x03029, 2, NULL, NULL }, + { 0x00b56, 0x00b56, 0, NULL, NULL }, + { 0x009bc, 0x009bc, 0, NULL, NULL }, + { 0x005c7, 0x005c7, 0, NULL, NULL }, + { 0x02060, 0x02063, 0, NULL, NULL }, + { 0x00c3e, 0x00c40, 0, NULL, NULL }, + { 0x10a05, 0x10a06, 0, NULL, NULL }, +}; + +struct utf8_width_entry *utf8_width_root = NULL; + +int utf8_overlap(struct utf8_width_entry *, struct utf8_width_entry *); +void utf8_print(struct utf8_width_entry *, int); +u_int utf8_combine(const u_char *); +void utf8_split(u_int, u_char *); + +int +utf8_overlap( + struct utf8_width_entry *item1, struct utf8_width_entry *item2) +{ + if (item1->first >= item2->first && item1->first <= item2->last) + return (1); + if (item1->last >= item2->first && item1->last <= item2->last) + return (1); + if (item2->first >= item1->first && item2->first <= item1->last) + return (1); + if (item2->last >= item1->first && item2->last <= item1->last) + return (1); + return (0); +} + +void +utf8_build(void) +{ + struct utf8_width_entry **ptr, *item, *node; + u_int i, j; + + for (i = 0; i < nitems(utf8_width_table); i++) { + item = &utf8_width_table[i]; + + for (j = 0; j < nitems(utf8_width_table); j++) { + if (i != j && utf8_overlap(item, &utf8_width_table[j])) + log_fatalx("utf8 overlap: %u %u", i, j); + } + + ptr = &utf8_width_root; + while (*ptr != NULL) { + node = *ptr; + if (item->last < node->first) + ptr = &(node->left); + else if (item->first > node->last) + ptr = &(node->right); + } + *ptr = item; + } +} + +void +utf8_print(struct utf8_width_entry *node, int n) +{ + log_debug("%*s%04x -> %04x", n, " ", node->first, node->last); + if (node->left != NULL) + utf8_print(node->left, n + 1); + if (node->right != NULL) + utf8_print(node->right, n + 1); +} + +u_int +utf8_combine(const u_char *data) +{ + u_int uvalue; + + if (data[1] == 0xff) + uvalue = data[0]; + else if (data[2] == 0xff) { + uvalue = data[1] & 0x3f; + uvalue |= (data[0] & 0x1f) << 6; + } else if (data[3] == 0xff) { + uvalue = data[2] & 0x3f; + uvalue |= (data[1] & 0x3f) << 6; + uvalue |= (data[0] & 0x0f) << 12; + } else { + uvalue = data[3] & 0x3f; + uvalue |= (data[2] & 0x3f) << 6; + uvalue |= (data[1] & 0x3f) << 12; + uvalue |= (data[0] & 0x3f) << 18; + } + return (uvalue); +} + +void +utf8_split(u_int uvalue, u_char *data) +{ + memset(data, 0xff, 4); + + if (uvalue <= 0x7f) + data[0] = uvalue; + else if (uvalue > 0x7f && uvalue <= 0x7ff) { + data[0] = (uvalue >> 6) | 0xc0; + data[1] = (uvalue & 0x3f) | 0x80; + } else if (uvalue > 0x7ff && uvalue <= 0xffff) { + data[0] = (uvalue >> 12) | 0xe0; + data[1] = ((uvalue >> 6) & 0x3f) | 0x80; + data[2] = (uvalue & 0x3f) | 0x80; + } else if (uvalue > 0xffff && uvalue <= 0x10ffff) { + data[0] = (uvalue >> 18) | 0xf0; + data[1] = ((uvalue >> 12) & 0x3f) | 0x80; + data[2] = ((uvalue >> 6) & 0x3f) | 0x80; + data[3] = (uvalue & 0x3f) | 0x80; + } +} + +int +utf8_width(u_char *udata) +{ + struct utf8_width_entry *item; + u_int uvalue; + + uvalue = utf8_combine(udata); + + item = utf8_width_root; + while (item != NULL) { + if (uvalue < item->first) + item = item->left; + else if (uvalue > item->last) + item = item->right; + else + return (item->width); + } + return (1); +} diff --git a/util.c b/util.c new file mode 100644 index 00000000..1455214a --- /dev/null +++ b/util.c @@ -0,0 +1,72 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include "tmux.h" + +/* Return a section of a string around a point. */ +char * +section_string(char *buf, size_t len, size_t sectoff, size_t sectlen) +{ + char *s; + size_t first, last; + + if (len <= sectlen) { + first = 0; + last = len; + } else if (sectoff < sectlen / 2) { + first = 0; + last = sectlen; + } else if (sectoff + sectlen / 2 > len) { + last = len; + first = last - sectlen; + } else { + first = sectoff - sectlen / 2; + last = first + sectlen; + } + + if (last - first > 3 && first != 0) + first += 3; + if (last - first > 3 && last != len) + last -= 3; + + xasprintf(&s, "%s%.*s%s", first == 0 ? "" : "...", + (int) (last - first), buf + first, last == len ? "" : "..."); + return (s); +} + +/* Clean string of invisible characters. */ +void +clean_string(const char *in, char *buf, size_t len) +{ + const u_char *cp; + size_t off; + + off = 0; + for (cp = in; *cp != '\0'; cp++) { + if (off >= len) + break; + if (*cp >= 0x20 && *cp <= 0x7f) + buf[off++] = *cp; + else + off += xsnprintf(buf + off, len - off, "\\%03hho", *cp); + } + if (off < len) + buf[off] = '\0'; +} diff --git a/window-choose.c b/window-choose.c new file mode 100644 index 00000000..5470a7e8 --- /dev/null +++ b/window-choose.c @@ -0,0 +1,361 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +struct screen *window_choose_init(struct window_pane *); +void window_choose_free(struct window_pane *); +void window_choose_resize(struct window_pane *, u_int, u_int); +void window_choose_key(struct window_pane *, struct client *, int); +void window_choose_mouse( + struct window_pane *, struct client *, u_char, u_char, u_char); + +void window_choose_redraw_screen(struct window_pane *); +void window_choose_write_line( + struct window_pane *, struct screen_write_ctx *, u_int); + +void window_choose_scroll_up(struct window_pane *); +void window_choose_scroll_down(struct window_pane *); + +const struct window_mode window_choose_mode = { + window_choose_init, + window_choose_free, + window_choose_resize, + window_choose_key, + window_choose_mouse, + NULL, +}; + +struct window_choose_mode_item { + char *name; + int idx; +}; + +struct window_choose_mode_data { + struct screen screen; + + struct mode_key_data mdata; + + ARRAY_DECL(, struct window_choose_mode_item) list; + u_int top; + u_int selected; + + void (*callback)(void *, int); + void *data; +}; + +void +window_choose_vadd(struct window_pane *wp, int idx, const char *fmt, va_list ap) +{ + struct window_choose_mode_data *data = wp->modedata; + struct window_choose_mode_item *item; + + ARRAY_EXPAND(&data->list, 1); + item = &ARRAY_LAST(&data->list); + xvasprintf(&item->name, fmt, ap); + item->idx = idx; +} + +void printflike3 +window_choose_add(struct window_pane *wp, int idx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + window_choose_vadd(wp, idx, fmt, ap); + va_end(ap); +} + +void +window_choose_ready(struct window_pane *wp, + u_int cur, void (*callback)(void *, int), void *cdata) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + data->selected = cur; + if (data->selected > screen_size_y(s) - 1) + data->top = ARRAY_LENGTH(&data->list) - screen_size_y(s); + + data->callback = callback; + data->data = cdata; + + window_choose_redraw_screen(wp); +} + +struct screen * +window_choose_init(struct window_pane *wp) +{ + struct window_choose_mode_data *data; + struct screen *s; + + wp->modedata = data = xmalloc(sizeof *data); + data->callback = NULL; + ARRAY_INIT(&data->list); + data->top = 0; + + s = &data->screen; + screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); + s->mode &= ~MODE_CURSOR; + s->mode |= MODE_MOUSE; + + mode_key_init(&data->mdata, + options_get_number(&wp->window->options, "mode-keys"), + MODEKEY_CHOOSEMODE); + + return (s); +} + +void +window_choose_free(struct window_pane *wp) +{ + struct window_choose_mode_data *data = wp->modedata; + u_int i; + + mode_key_free(&data->mdata); + + for (i = 0; i < ARRAY_LENGTH(&data->list); i++) + xfree(ARRAY_ITEM(&data->list, i).name); + ARRAY_FREE(&data->list); + + screen_free(&data->screen); + xfree(data); +} + +void +window_choose_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + data->top = 0; + if (data->selected > sy - 1) + data->top = data->selected - (sy - 1); + + screen_resize(s, sx, sy); + window_choose_redraw_screen(wp); +} + +void +window_choose_key(struct window_pane *wp, unused struct client *c, int key) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + struct window_choose_mode_item *item; + u_int items; + + items = ARRAY_LENGTH(&data->list); + + switch (mode_key_lookup(&data->mdata, key)) { + case MODEKEYCMD_QUIT: + data->callback(data->data, -1); + window_pane_reset_mode(wp); + break; + case MODEKEYCMD_CHOOSE: + item = &ARRAY_ITEM(&data->list, data->selected); + data->callback(data->data, item->idx); + window_pane_reset_mode(wp); + break; + case MODEKEYCMD_UP: + if (items == 0) + break; + if (data->selected == 0) { + data->selected = items - 1; + if (data->selected > screen_size_y(s) - 1) + data->top = items - screen_size_y(s); + window_choose_redraw_screen(wp); + break; + } + data->selected--; + if (data->selected < data->top) + window_choose_scroll_up(wp); + else { + screen_write_start(&ctx, wp, NULL); + window_choose_write_line( + wp, &ctx, data->selected - data->top); + window_choose_write_line( + wp, &ctx, data->selected + 1 - data->top); + screen_write_stop(&ctx); + } + break; + case MODEKEYCMD_DOWN: + if (items == 0) + break; + if (data->selected == items - 1) { + data->selected = 0; + data->top = 0; + window_choose_redraw_screen(wp); + break; + } + data->selected++; + if (data->selected >= data->top + screen_size_y(&data->screen)) + window_choose_scroll_down(wp); + else { + screen_write_start(&ctx, wp, NULL); + window_choose_write_line( + wp, &ctx, data->selected - data->top); + window_choose_write_line( + wp, &ctx, data->selected - 1 - data->top); + screen_write_stop(&ctx); + } + break; + case MODEKEYCMD_PREVIOUSPAGE: + if (data->selected < screen_size_y(s)) { + data->selected = 0; + data->top = 0; + } else { + data->selected -= screen_size_y(s); + if (data->top < screen_size_y(s)) + data->top = 0; + else + data->top -= screen_size_y(s); + } + window_choose_redraw_screen(wp); + break; + case MODEKEYCMD_NEXTPAGE: + data->selected += screen_size_y(s); + if (data->selected > items - 1) + data->selected = items - 1; + data->top += screen_size_y(s); + if (screen_size_y(s) < items) { + if (data->top + screen_size_y(s) > items) + data->top = items - screen_size_y(s); + } else + data->top = 0; + if (data->selected < data->top) + data->top = data->selected; + window_choose_redraw_screen(wp); + break; + default: + break; + } +} + +void +window_choose_mouse(struct window_pane *wp, + unused struct client *c, u_char b, u_char x, u_char y) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct window_choose_mode_item *item; + u_int idx; + + if ((b & 3) == 3) + return; + if (x >= screen_size_x(s)) + return; + if (y >= screen_size_y(s)) + return; + + idx = data->top + y; + if (idx >= ARRAY_LENGTH(&data->list)) + return; + data->selected = idx; + + item = &ARRAY_ITEM(&data->list, data->selected); + data->callback(data->data, item->idx); + window_pane_reset_mode(wp); +} + +void +window_choose_write_line( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int py) +{ + struct window_choose_mode_data *data = wp->modedata; + struct window_choose_mode_item *item; + struct screen *s = &data->screen; + struct grid_cell gc; + + if (data->callback == NULL) + fatalx("called before callback assigned"); + + memcpy(&gc, &grid_default_cell, sizeof gc); + if (data->selected == data->top + py) { + gc.fg = options_get_number(&wp->window->options, "mode-bg"); + gc.bg = options_get_number(&wp->window->options, "mode-fg"); + gc.attr |= options_get_number(&wp->window->options, "mode-attr"); + } + + screen_write_cursormove(ctx, 0, py); + if (data->top + py < ARRAY_LENGTH(&data->list)) { + item = &ARRAY_ITEM(&data->list, data->top + py); + screen_write_puts( + ctx, &gc, "%.*s", (int) screen_size_x(s), item->name); + } + while (s->cx < screen_size_x(s)) + screen_write_putc(ctx, &gc, ' '); + +} + +void +window_choose_redraw_screen(struct window_pane *wp) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + screen_write_start(&ctx, wp, NULL); + for (i = 0; i < screen_size_y(s); i++) + window_choose_write_line(wp, &ctx, i); + screen_write_stop(&ctx); +} + +void +window_choose_scroll_up(struct window_pane *wp) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen_write_ctx ctx; + + if (data->top == 0) + return; + data->top--; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_insertline(&ctx, 1); + window_choose_write_line(wp, &ctx, 0); + if (screen_size_y(&data->screen) > 1) + window_choose_write_line(wp, &ctx, 1); + screen_write_stop(&ctx); +} + +void +window_choose_scroll_down(struct window_pane *wp) +{ + struct window_choose_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + + if (data->top >= ARRAY_LENGTH(&data->list)) + return; + data->top++; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_deleteline(&ctx, 1); + window_choose_write_line(wp, &ctx, screen_size_y(s) - 1); + if (screen_size_y(&data->screen) > 1) + window_choose_write_line(wp, &ctx, screen_size_y(s) - 2); + screen_write_stop(&ctx); +} diff --git a/window-clock.c b/window-clock.c new file mode 100644 index 00000000..e84635f6 --- /dev/null +++ b/window-clock.c @@ -0,0 +1,124 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +struct screen *window_clock_init(struct window_pane *); +void window_clock_free(struct window_pane *); +void window_clock_resize(struct window_pane *, u_int, u_int); +void window_clock_key(struct window_pane *, struct client *, int); +void window_clock_timer(struct window_pane *); + +void window_clock_draw_screen(struct window_pane *); + +const struct window_mode window_clock_mode = { + window_clock_init, + window_clock_free, + window_clock_resize, + window_clock_key, + NULL, + window_clock_timer, +}; + +struct window_clock_mode_data { + struct screen screen; + time_t tim; +}; + +struct screen * +window_clock_init(struct window_pane *wp) +{ + struct window_clock_mode_data *data; + struct screen *s; + + wp->modedata = data = xmalloc(sizeof *data); + data->tim = time(NULL); + + s = &data->screen; + screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); + s->mode &= ~MODE_CURSOR; + + window_clock_draw_screen(wp); + + return (s); +} + +void +window_clock_free(struct window_pane *wp) +{ + struct window_clock_mode_data *data = wp->modedata; + + screen_free(&data->screen); + xfree(data); +} + +void +window_clock_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_clock_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + screen_resize(s, sx, sy); + window_clock_draw_screen(wp); +} + +void +window_clock_key( + struct window_pane *wp, unused struct client *c, unused int key) +{ + window_pane_reset_mode(wp); +} + +void +window_clock_timer(struct window_pane *wp) +{ + struct window_clock_mode_data *data = wp->modedata; + struct tm *now, *then; + time_t t; + + t = time(NULL); + now = gmtime(&t); + then = gmtime(&data->tim); + if (now->tm_min == then->tm_min) + return; + data->tim = t; + + window_clock_draw_screen(wp); + server_redraw_window(wp->window); +} + +void +window_clock_draw_screen(struct window_pane *wp) +{ + struct window_clock_mode_data *data = wp->modedata; + struct screen_write_ctx ctx; + u_int colour; + int style; + + colour = options_get_number(&wp->window->options, "clock-mode-colour"); + style = options_get_number(&wp->window->options, "clock-mode-style"); + + screen_write_start(&ctx, NULL, &data->screen); + clock_draw(&ctx, colour, style); + screen_write_stop(&ctx); +} diff --git a/window-copy.c b/window-copy.c new file mode 100644 index 00000000..dc2a4d45 --- /dev/null +++ b/window-copy.c @@ -0,0 +1,968 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +struct screen *window_copy_init(struct window_pane *); +void window_copy_free(struct window_pane *); +void window_copy_resize(struct window_pane *, u_int, u_int); +void window_copy_key(struct window_pane *, struct client *, int); +void window_copy_mouse( + struct window_pane *, struct client *, u_char, u_char, u_char); + +void window_copy_redraw_lines(struct window_pane *, u_int, u_int); +void window_copy_redraw_screen(struct window_pane *); +void window_copy_write_line( + struct window_pane *, struct screen_write_ctx *, u_int); +void window_copy_write_lines( + struct window_pane *, struct screen_write_ctx *, u_int, u_int); +void window_copy_write_column( + struct window_pane *, struct screen_write_ctx *, u_int); +void window_copy_write_columns( + struct window_pane *, struct screen_write_ctx *, u_int, u_int); + +void window_copy_update_cursor(struct window_pane *); +void window_copy_start_selection(struct window_pane *); +int window_copy_update_selection(struct window_pane *); +void window_copy_copy_selection(struct window_pane *, struct client *); +void window_copy_copy_line( + struct window_pane *, char **, size_t *, u_int, u_int, u_int); +int window_copy_is_space(struct window_pane *, u_int, u_int); +u_int window_copy_find_length(struct window_pane *, u_int); +void window_copy_cursor_start_of_line(struct window_pane *); +void window_copy_cursor_end_of_line(struct window_pane *); +void window_copy_cursor_left(struct window_pane *); +void window_copy_cursor_right(struct window_pane *); +void window_copy_cursor_up(struct window_pane *); +void window_copy_cursor_down(struct window_pane *); +void window_copy_cursor_next_word(struct window_pane *); +void window_copy_cursor_previous_word(struct window_pane *); +void window_copy_scroll_left(struct window_pane *, u_int); +void window_copy_scroll_right(struct window_pane *, u_int); +void window_copy_scroll_up(struct window_pane *, u_int); +void window_copy_scroll_down(struct window_pane *, u_int); + +const struct window_mode window_copy_mode = { + window_copy_init, + window_copy_free, + window_copy_resize, + window_copy_key, + window_copy_mouse, + NULL, +}; + +struct window_copy_mode_data { + struct screen screen; + + struct mode_key_data mdata; + + u_int ox; + u_int oy; + + u_int selx; + u_int sely; + + u_int cx; + u_int cy; +}; + +struct screen * +window_copy_init(struct window_pane *wp) +{ + struct window_copy_mode_data *data; + struct screen *s; + struct screen_write_ctx ctx; + u_int i; + + wp->modedata = data = xmalloc(sizeof *data); + data->ox = 0; + data->oy = 0; + data->cx = wp->base.cx; + data->cy = wp->base.cy; + + s = &data->screen; + screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); + s->mode |= MODE_MOUSE; + + mode_key_init(&data->mdata, + options_get_number(&wp->window->options, "mode-keys"), 0); + + s->cx = data->cx; + s->cy = data->cy; + + screen_write_start(&ctx, NULL, s); + for (i = 0; i < screen_size_y(s); i++) + window_copy_write_line(wp, &ctx, i); + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); + + return (s); +} + +void +window_copy_free(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + + mode_key_free(&data->mdata); + + screen_free(&data->screen); + xfree(data); +} + +void +window_copy_pageup(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + if (data->oy + screen_size_y(s) > screen_hsize(&wp->base)) + data->oy = screen_hsize(&wp->base); + else + data->oy += screen_size_y(s); + window_copy_update_selection(wp); + window_copy_redraw_screen(wp); +} + +void +window_copy_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + + screen_resize(s, sx, sy); + screen_write_start(&ctx, NULL, s); + window_copy_write_lines(wp, &ctx, 0, screen_size_y(s) - 1); + screen_write_stop(&ctx); + window_copy_update_selection(wp); +} + +void +window_copy_key(struct window_pane *wp, struct client *c, int key) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + switch (mode_key_lookup(&data->mdata, key)) { + case MODEKEYCMD_QUIT: + window_pane_reset_mode(wp); + break; + case MODEKEYCMD_LEFT: + window_copy_cursor_left(wp); + return; + case MODEKEYCMD_RIGHT: + window_copy_cursor_right(wp); + return; + case MODEKEYCMD_UP: + window_copy_cursor_up(wp); + return; + case MODEKEYCMD_DOWN: + window_copy_cursor_down(wp); + return; + case MODEKEYCMD_PREVIOUSPAGE: + window_copy_pageup(wp); + break; + case MODEKEYCMD_NEXTPAGE: + if (data->oy < screen_size_y(s)) + data->oy = 0; + else + data->oy -= screen_size_y(s); + window_copy_update_selection(wp); + window_copy_redraw_screen(wp); + break; + case MODEKEYCMD_STARTSELECTION: + window_copy_start_selection(wp); + break; + case MODEKEYCMD_CLEARSELECTION: + screen_clear_selection(&data->screen); + window_copy_redraw_screen(wp); + break; + case MODEKEYCMD_COPYSELECTION: + if (c != NULL && c->session != NULL) { + window_copy_copy_selection(wp, c); + window_pane_reset_mode(wp); + } + break; + case MODEKEYCMD_STARTOFLINE: + window_copy_cursor_start_of_line(wp); + break; + case MODEKEYCMD_ENDOFLINE: + window_copy_cursor_end_of_line(wp); + break; + case MODEKEYCMD_NEXTWORD: + window_copy_cursor_next_word(wp); + break; + case MODEKEYCMD_PREVIOUSWORD: + window_copy_cursor_previous_word(wp); + break; + default: + break; + } +} + +void +window_copy_mouse(struct window_pane *wp, + unused struct client *c, u_char b, u_char x, u_char y) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + if ((b & 3) == 3) + return; + if (x >= screen_size_x(s)) + return; + if (y >= screen_size_y(s)) + return; + + data->cx = x; + data->cy = y; + + if (window_copy_update_selection(wp)) + window_copy_redraw_screen(wp); + window_copy_update_cursor(wp); +} + +void +window_copy_write_line(struct window_pane *wp, struct screen_write_ctx *ctx, u_int py) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct grid_cell gc; + char hdr[32]; + size_t size; + + if (py == 0) { + memcpy(&gc, &grid_default_cell, sizeof gc); + size = xsnprintf(hdr, sizeof hdr, + "[%u,%u/%u]", data->ox, data->oy, screen_hsize(&wp->base)); + gc.bg = options_get_number(&wp->window->options, "mode-fg"); + gc.fg = options_get_number(&wp->window->options, "mode-bg"); + gc.attr |= options_get_number(&wp->window->options, "mode-attr"); + screen_write_cursormove(ctx, screen_size_x(s) - size, 0); + screen_write_puts(ctx, &gc, "%s", hdr); + } else + size = 0; + + screen_write_cursormove(ctx, 0, py); + screen_write_copy(ctx, &wp->base, data->ox, (screen_hsize(&wp->base) - + data->oy) + py, screen_size_x(s) - size, 1); +} + +void +window_copy_write_lines( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int py, u_int ny) +{ + u_int yy; + + for (yy = py; yy < py + ny; yy++) + window_copy_write_line(wp, ctx, py); +} + +void +window_copy_write_column( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int px) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + screen_write_cursormove(ctx, px, 0); + screen_write_copy(ctx, &wp->base, + data->ox + px, screen_hsize(&wp->base) - data->oy, 1, screen_size_y(s)); +} + +void +window_copy_write_columns( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int px, u_int nx) +{ + u_int xx; + + for (xx = px; xx < px + nx; xx++) + window_copy_write_column(wp, ctx, xx); +} + +void +window_copy_redraw_lines(struct window_pane *wp, u_int py, u_int ny) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen_write_ctx ctx; + u_int i; + + screen_write_start(&ctx, wp, NULL); + for (i = py; i < py + ny; i++) + window_copy_write_line(wp, &ctx, i); + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); +} + +void +window_copy_redraw_screen(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + + window_copy_redraw_lines(wp, 0, screen_size_y(&data->screen)); +} + +void +window_copy_update_cursor(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen_write_ctx ctx; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); +} + +void +window_copy_start_selection(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + data->selx = data->cx + data->ox; + data->sely = screen_hsize(&wp->base) + data->cy - data->oy; + + s->sel.flag = 1; + window_copy_update_selection(wp); +} + +int +window_copy_update_selection(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct grid_cell gc; + u_int sx, sy, tx, ty; + + if (!s->sel.flag) + return (0); + + /* Set colours. */ + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.bg = options_get_number(&wp->window->options, "mode-fg"); + gc.fg = options_get_number(&wp->window->options, "mode-bg"); + gc.attr |= options_get_number(&wp->window->options, "mode-attr"); + + /* Find top-left of screen. */ + tx = data->ox; + ty = screen_hsize(&wp->base) - data->oy; + + /* Adjust the selection. */ + sx = data->selx; + sy = data->sely; + if (sy < ty) { + /* Above it. */ + sx = 0; + sy = 0; + } else if (sy > ty + screen_size_y(s) - 1) { + /* Below it. */ + sx = screen_size_x(s) - 1; + sy = screen_size_y(s) - 1; + } else if (sx < tx) { + /* To the left. */ + sx = 0; + } else if (sx > tx + screen_size_x(s) - 1) { + /* To the right. */ + sx = 0; + sy++; + if (sy > screen_size_y(s) - 1) + sy = screen_size_y(s) - 1; + } else { + sx -= tx; + sy -= ty; + } + sy = screen_hsize(s) + sy; + + screen_set_selection( + s, sx, sy, data->cx, screen_hsize(s) + data->cy, &gc); + return (1); +} + +void +window_copy_copy_selection(struct window_pane *wp, struct client *c) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + char *buf; + size_t off; + u_int i, xx, yy, sx, sy, ex, ey, limit; + + if (!s->sel.flag) + return; + + buf = xmalloc(1); + off = 0; + + *buf = '\0'; + + /* + * The selection extends from selx,sely to (adjusted) cx,cy on + * the base screen. + */ + + /* Find start and end. */ + xx = data->cx + data->ox; + yy = screen_hsize(&wp->base) + data->cy - data->oy; + if (xx < data->selx || (yy == data->sely && xx < data->selx)) { + sx = xx; sy = yy; + ex = data->selx; ey = data->sely; + } else { + sx = data->selx; sy = data->sely; + ex = xx; ey = yy; + } + + /* Trim ex to end of line. */ + xx = window_copy_find_length(wp, ey); + if (ex > xx) + ex = xx; + + /* Copy the lines. */ + if (sy == ey) + window_copy_copy_line(wp, &buf, &off, sy, sx, ex); + else { + xx = window_copy_find_length(wp, sy); + window_copy_copy_line(wp, &buf, &off, sy, sx, xx); + if (ey - sy > 1) { + for (i = sy + 1; i < ey - 1; i++) { + xx = window_copy_find_length(wp, i); + window_copy_copy_line(wp, &buf, &off, i, 0, xx); + } + } + window_copy_copy_line(wp, &buf, &off, ey, 0, ex); + } + + /* Terminate buffer, overwriting final \n. */ + if (off != 0) + buf[off - 1] = '\0'; + + /* Add the buffer to the stack. */ + limit = options_get_number(&c->session->options, "buffer-limit"); + paste_add(&c->session->buffers, buf, limit); +} + +void +window_copy_copy_line(struct window_pane *wp, + char **buf, size_t *off, u_int sy, u_int sx, u_int ex) +{ + const struct grid_cell *gc; + const struct grid_utf8 *gu; + u_int i, j, xx; + + if (sx > ex) + return; + + xx = window_copy_find_length(wp, sy); + if (ex > xx) + ex = xx; + if (sx > xx) + sx = xx; + + if (sx < ex) { + for (i = sx; i < ex; i++) { + gc = grid_peek_cell(wp->base.grid, i, sy); + if (gc->flags & GRID_FLAG_PADDING) + continue; + if (!(gc->flags & GRID_FLAG_UTF8)) { + *buf = xrealloc(*buf, 1, (*off) + 1); + (*buf)[(*off)++] = gc->data; + } else { + gu = grid_peek_utf8(wp->base.grid, i, sy); + *buf = xrealloc(*buf, 1, (*off) + UTF8_SIZE); + for (j = 0; j < UTF8_SIZE; j++) { + if (gu->data[j] == 0xff) + break; + (*buf)[(*off)++] = gu->data[j]; + } + } + } + } + + *buf = xrealloc(*buf, 1, (*off) + 1); + (*buf)[*off] = '\n'; + (*off)++; +} + +int +window_copy_is_space(struct window_pane *wp, u_int px, u_int py) +{ + const struct grid_cell *gc; + const char *spaces = " -_@"; + + gc = grid_peek_cell(wp->base.grid, px, py); + if (gc->flags & (GRID_FLAG_PADDING|GRID_FLAG_UTF8)) + return (0); + if (gc->data == 0x00 || gc->data == 0x7f) + return (0); + return (strchr(spaces, gc->data) != NULL); +} + +u_int +window_copy_find_length(struct window_pane *wp, u_int py) +{ + const struct grid_cell *gc; + u_int px; + + px = wp->base.grid->size[py]; + while (px > 0) { + gc = grid_peek_cell(wp->base.grid, px - 1, py); + if (gc->flags & GRID_FLAG_UTF8) + break; + if (gc->data != ' ') + break; + px--; + } + return (px); +} + +void +window_copy_cursor_start_of_line(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + + if (data->ox != 0) + window_copy_scroll_right(wp, data->ox); + data->cx = 0; + + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 1); + else + window_copy_update_cursor(wp); +} + +void +window_copy_cursor_end_of_line(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + u_int px, py; + + py = screen_hsize(&wp->base) + data->cy - data->oy; + px = window_copy_find_length(wp, py); + + /* On screen. */ + if (px > data->ox && px <= data->ox + screen_size_x(s) - 1) + data->cx = px - data->ox; + + /* Off right of screen. */ + if (px > data->ox + screen_size_x(s) - 1) { + /* Move cursor to last and scroll screen. */ + window_copy_scroll_left( + wp, px - data->ox - (screen_size_x(s) - 1)); + data->cx = screen_size_x(s) - 1; + } + + /* Off left of screen. */ + if (px <= data->ox) { + if (px < screen_size_x(s) - 1) { + /* Short enough to fit on screen. */ + window_copy_scroll_right(wp, data->ox); + data->cx = px; + } else { + /* Too long to fit on screen. */ + window_copy_scroll_right( + wp, data->ox - (px - (screen_size_x(s) - 1))); + data->cx = screen_size_x(s) - 1; + } + } + + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 1); + else + window_copy_update_cursor(wp); +} + +void +window_copy_cursor_left(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + + if (data->cx == 0) { + if (data->ox > 0) + window_copy_scroll_right(wp, 1); + else { + window_copy_cursor_up(wp); + window_copy_cursor_end_of_line(wp); + } + } else { + data->cx--; + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 1); + else + window_copy_update_cursor(wp); + } +} + +void +window_copy_cursor_right(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + u_int px, py; + + py = screen_hsize(&wp->base) + data->cy - data->oy; + px = window_copy_find_length(wp, py); + + if (data->cx >= px) { + window_copy_cursor_start_of_line(wp); + window_copy_cursor_down(wp); + } else { + data->cx++; + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 1); + else + window_copy_update_cursor(wp); + } +} + +void +window_copy_cursor_up(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + u_int ox, oy, px, py; + + oy = screen_hsize(&wp->base) + data->cy - data->oy; + ox = window_copy_find_length(wp, oy); + + if (data->cy == 0) + window_copy_scroll_down(wp, 1); + else { + data->cy--; + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 2); + else + window_copy_update_cursor(wp); + } + + py = screen_hsize(&wp->base) + data->cy - data->oy; + px = window_copy_find_length(wp, py); + + if (data->cx + data->ox >= px || data->cx + data->ox >= ox) + window_copy_cursor_end_of_line(wp); +} + +void +window_copy_cursor_down(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + u_int ox, oy, px, py; + + oy = screen_hsize(&wp->base) + data->cy - data->oy; + ox = window_copy_find_length(wp, oy); + + if (data->cy == screen_size_y(s) - 1) + window_copy_scroll_up(wp, 1); + else { + data->cy++; + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy - 1, 2); + else + window_copy_update_cursor(wp); + } + + py = screen_hsize(&wp->base) + data->cy - data->oy; + px = window_copy_find_length(wp, py); + + if (data->cx + data->ox >= px || data->cx + data->ox >= ox) + window_copy_cursor_end_of_line(wp); +} + +void +window_copy_cursor_next_word(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + u_int px, py, xx, skip; + + px = data->ox + data->cx; + py = screen_hsize(&wp->base) + data->cy - data->oy; + xx = window_copy_find_length(wp, py); + + skip = 1; + if (px < xx) { + /* If currently on a space, skip space. */ + if (window_copy_is_space(wp, px, py)) + skip = 0; + } else + skip = 0; + for (;;) { + if (px >= xx) { + if (skip) { + px = xx; + break; + } + + while (px >= xx) { + if (data->cy == screen_size_y(s) - 1) { + if (data->oy == 0) + goto out; + } + + px = 0; + window_copy_cursor_down(wp); + + py =screen_hsize( + &wp->base) + data->cy - data->oy; + xx = window_copy_find_length(wp, py); + } + } + + if (skip) { + /* Currently skipping non-space (until space). */ + if (window_copy_is_space(wp, px, py)) + break; + } else { + /* Currently skipping space (until non-space). */ + if (!window_copy_is_space(wp, px, py)) + skip = 1; + } + + px++; + } +out: + + /* On screen. */ + if (px > data->ox && px <= data->ox + screen_size_x(s) - 1) + data->cx = px - data->ox; + + /* Off right of screen. */ + if (px > data->ox + screen_size_x(s) - 1) { + /* Move cursor to last and scroll screen. */ + window_copy_scroll_left( + wp, px - data->ox - (screen_size_x(s) - 1)); + data->cx = screen_size_x(s) - 1; + } + + /* Off left of screen. */ + if (px <= data->ox) { + if (px < screen_size_x(s) - 1) { + /* Short enough to fit on screen. */ + window_copy_scroll_right(wp, data->ox); + data->cx = px; + } else { + /* Too long to fit on screen. */ + window_copy_scroll_right( + wp, data->ox - (px - (screen_size_x(s) - 1))); + data->cx = screen_size_x(s) - 1; + } + } + + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 1); + else + window_copy_update_cursor(wp); +} + +void +window_copy_cursor_previous_word(struct window_pane *wp) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + u_int ox, px, py, skip; + + ox = px = data->ox + data->cx; + py = screen_hsize(&wp->base) + data->cy - data->oy; + + skip = 1; + if (px != 0) { + /* If currently on a space, skip space. */ + if (window_copy_is_space(wp, px - 1, py)) + skip = 0; + } + for (;;) { + if (px == 0) { + if (ox != 0) + break; + + while (px == 0) { + if (data->cy == 0 && + (screen_hsize(&wp->base) == 0 || + data->oy >= screen_hsize(&wp->base) - 1)) + goto out; + + window_copy_cursor_up(wp); + + py = screen_hsize( + &wp->base) + data->cy - data->oy; + px = window_copy_find_length(wp, py); + } + goto out; + } + + if (skip) { + /* Currently skipping non-space (until space). */ + if (window_copy_is_space(wp, px - 1, py)) + skip = 0; + } else { + /* Currently skipping space (until non-space). */ + if (!window_copy_is_space(wp, px - 1, py)) + break; + } + + px--; + } +out: + + /* On screen. */ + if (px > data->ox && px <= data->ox + screen_size_x(s) - 1) + data->cx = px - data->ox; + + /* Off right of screen. */ + if (px > data->ox + screen_size_x(s) - 1) { + /* Move cursor to last and scroll screen. */ + window_copy_scroll_left( + wp, px - data->ox - (screen_size_x(s) - 1)); + data->cx = screen_size_x(s) - 1; + } + + /* Off left of screen. */ + if (px <= data->ox) { + if (px < screen_size_x(s) - 1) { + /* Short enough to fit on screen. */ + window_copy_scroll_right(wp, data->ox); + data->cx = px; + } else { + /* Too long to fit on screen. */ + window_copy_scroll_right( + wp, data->ox - (px - (screen_size_x(s) - 1))); + data->cx = screen_size_x(s) - 1; + } + } + + if (window_copy_update_selection(wp)) + window_copy_redraw_lines(wp, data->cy, 1); + else + window_copy_update_cursor(wp); +} + +void +window_copy_scroll_left(struct window_pane *wp, u_int nx) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + if (data->ox > SHRT_MAX - nx) + nx = SHRT_MAX - data->ox; + if (nx == 0) + return; + data->ox += nx; + window_copy_update_selection(wp); + + screen_write_start(&ctx, wp, NULL); + for (i = 1; i < screen_size_y(s); i++) { + screen_write_cursormove(&ctx, 0, i); + screen_write_deletecharacter(&ctx, nx); + } + window_copy_write_columns(wp, &ctx, screen_size_x(s) - nx, nx); + window_copy_write_line(wp, &ctx, 0); + if (s->sel.flag) { + window_copy_update_selection(wp); + window_copy_write_lines(wp, &ctx, data->cy, 1); + } + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); +} + +void +window_copy_scroll_right(struct window_pane *wp, u_int nx) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + if (data->ox < nx) + nx = data->ox; + if (nx == 0) + return; + data->ox -= nx; + window_copy_update_selection(wp); + + screen_write_start(&ctx, wp, NULL); + for (i = 1; i < screen_size_y(s); i++) { + screen_write_cursormove(&ctx, 0, i); + screen_write_insertcharacter(&ctx, nx); + } + window_copy_write_columns(wp, &ctx, 0, nx); + window_copy_write_line(wp, &ctx, 0); + if (s->sel.flag) + window_copy_write_line(wp, &ctx, data->cy); + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); +} + +void +window_copy_scroll_up(struct window_pane *wp, u_int ny) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + + if (data->oy < ny) + ny = data->oy; + if (ny == 0) + return; + data->oy -= ny; + window_copy_update_selection(wp); + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_deleteline(&ctx, ny); + window_copy_write_lines(wp, &ctx, screen_size_y(s) - ny, ny); + window_copy_write_line(wp, &ctx, 0); + window_copy_write_line(wp, &ctx, 1); + if (s->sel.flag && screen_size_y(s) > ny) + window_copy_write_line(wp, &ctx, screen_size_y(s) - ny - 1); + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); +} + +void +window_copy_scroll_down(struct window_pane *wp, u_int ny) +{ + struct window_copy_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + + if (ny > screen_hsize(&wp->base)) + return; + + if (data->oy > screen_hsize(&wp->base) - ny) + ny = screen_hsize(&wp->base) - data->oy; + if (ny == 0) + return; + data->oy += ny; + window_copy_update_selection(wp); + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_insertline(&ctx, ny); + window_copy_write_lines(wp, &ctx, 0, ny); + if (s->sel.flag && screen_size_y(s) > ny) + window_copy_write_line(wp, &ctx, ny); + else if (ny == 1) /* nuke position */ + window_copy_write_line(wp, &ctx, 1); + screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_stop(&ctx); +} diff --git a/window-more.c b/window-more.c new file mode 100644 index 00000000..b09cc23c --- /dev/null +++ b/window-more.c @@ -0,0 +1,252 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +struct screen *window_more_init(struct window_pane *); +void window_more_free(struct window_pane *); +void window_more_resize(struct window_pane *, u_int, u_int); +void window_more_key(struct window_pane *, struct client *, int); + +void window_more_redraw_screen(struct window_pane *); +void window_more_write_line( + struct window_pane *, struct screen_write_ctx *, u_int); + +void window_more_scroll_up(struct window_pane *); +void window_more_scroll_down(struct window_pane *); + +const struct window_mode window_more_mode = { + window_more_init, + window_more_free, + window_more_resize, + window_more_key, + NULL, + NULL, +}; + +struct window_more_mode_data { + struct screen screen; + + struct mode_key_data mdata; + + ARRAY_DECL(, char *) list; + u_int top; +}; + +void +window_more_vadd(struct window_pane *wp, const char *fmt, va_list ap) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + char *msg; + u_int size; + + xvasprintf(&msg, fmt, ap); + ARRAY_ADD(&data->list, msg); + + screen_write_start(&ctx, wp, NULL); + size = ARRAY_LENGTH(&data->list) - 1; + if (size >= data->top && size <= data->top + screen_size_y(s) - 1) { + window_more_write_line(wp, &ctx, size - data->top); + if (size != data->top) + window_more_write_line(wp, &ctx, 0); + } else + window_more_write_line(wp, &ctx, 0); + screen_write_stop(&ctx); +} + +void printflike2 +window_more_add(struct window_pane *wp, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + window_more_vadd(wp, fmt, ap); + va_end(ap); +} + +struct screen * +window_more_init(struct window_pane *wp) +{ + struct window_more_mode_data *data; + struct screen *s; + + wp->modedata = data = xmalloc(sizeof *data); + ARRAY_INIT(&data->list); + data->top = 0; + + s = &data->screen; + screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); + s->mode &= ~MODE_CURSOR; + + mode_key_init(&data->mdata, + options_get_number(&wp->window->options, "mode-keys"), 0); + + return (s); +} + +void +window_more_free(struct window_pane *wp) +{ + struct window_more_mode_data *data = wp->modedata; + u_int i; + + mode_key_free(&data->mdata); + + for (i = 0; i < ARRAY_LENGTH(&data->list); i++) + xfree(ARRAY_ITEM(&data->list, i)); + ARRAY_FREE(&data->list); + + screen_free(&data->screen); + xfree(data); +} + +void +window_more_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + screen_resize(s, sx, sy); + window_more_redraw_screen(wp); +} + +void +window_more_key(struct window_pane *wp, unused struct client *c, int key) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + switch (mode_key_lookup(&data->mdata, key)) { + case MODEKEYCMD_QUIT: + window_pane_reset_mode(wp); + break; + case MODEKEYCMD_UP: + window_more_scroll_up(wp); + break; + case MODEKEYCMD_DOWN: + window_more_scroll_down(wp); + break; + case MODEKEYCMD_PREVIOUSPAGE: + if (data->top < screen_size_y(s)) + data->top = 0; + else + data->top -= screen_size_y(s); + window_more_redraw_screen(wp); + break; + case MODEKEYCMD_NEXTPAGE: + if (data->top + screen_size_y(s) > ARRAY_LENGTH(&data->list)) + data->top = ARRAY_LENGTH(&data->list); + else + data->top += screen_size_y(s); + window_more_redraw_screen(wp); + break; + default: + break; + } +} + +void +window_more_write_line( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int py) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct grid_cell gc; + char *msg, hdr[32]; + size_t size; + + memcpy(&gc, &grid_default_cell, sizeof gc); + + if (py == 0) { + size = xsnprintf(hdr, sizeof hdr, + "[%u/%u]", data->top, ARRAY_LENGTH(&data->list)); + screen_write_cursormove(ctx, screen_size_x(s) - size, 0); + gc.bg = options_get_number(&wp->window->options, "mode-fg"); + gc.fg = options_get_number(&wp->window->options, "mode-bg"); + gc.attr |= options_get_number(&wp->window->options, "mode-attr"); + screen_write_puts(ctx, &gc, "%s", hdr); + memcpy(&gc, &grid_default_cell, sizeof gc); + } else + size = 0; + + screen_write_cursormove(ctx, 0, py); + if (data->top + py < ARRAY_LENGTH(&data->list)) { + msg = ARRAY_ITEM(&data->list, data->top + py); + screen_write_puts( + ctx, &gc, "%.*s", (int) (screen_size_x(s) - size), msg); + } + while (s->cx < screen_size_x(s) - size) + screen_write_putc(ctx, &gc, ' '); +} + +void +window_more_redraw_screen(struct window_pane *wp) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + screen_write_start(&ctx, wp, NULL); + for (i = 0; i < screen_size_y(s); i++) + window_more_write_line(wp, &ctx, i); + screen_write_stop(&ctx); +} + +void +window_more_scroll_up(struct window_pane *wp) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen_write_ctx ctx; + + if (data->top == 0) + return; + data->top--; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_insertline(&ctx, 1); + window_more_write_line(wp, &ctx, 0); + window_more_write_line(wp, &ctx, 1); + screen_write_stop(&ctx); +} + +void +window_more_scroll_down(struct window_pane *wp) +{ + struct window_more_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + + if (data->top >= ARRAY_LENGTH(&data->list)) + return; + data->top++; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_deleteline(&ctx, 1); + window_more_write_line(wp, &ctx, screen_size_y(s) - 1); + window_more_write_line(wp, &ctx, 0); + screen_write_stop(&ctx); +} diff --git a/window-scroll.c b/window-scroll.c new file mode 100644 index 00000000..9db91ebd --- /dev/null +++ b/window-scroll.c @@ -0,0 +1,296 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 + +#include + +#include "tmux.h" + +struct screen *window_scroll_init(struct window_pane *); +void window_scroll_free(struct window_pane *); +void window_scroll_resize(struct window_pane *, u_int, u_int); +void window_scroll_key(struct window_pane *, struct client *, int); + +void window_scroll_redraw_screen(struct window_pane *); +void window_scroll_write_line( + struct window_pane *, struct screen_write_ctx *, u_int); +void window_scroll_write_column( + struct window_pane *, struct screen_write_ctx *, u_int); + +void window_scroll_scroll_up(struct window_pane *); +void window_scroll_scroll_down(struct window_pane *); +void window_scroll_scroll_left(struct window_pane *); +void window_scroll_scroll_right(struct window_pane *); + +const struct window_mode window_scroll_mode = { + window_scroll_init, + window_scroll_free, + window_scroll_resize, + window_scroll_key, + NULL, + NULL, +}; + +struct window_scroll_mode_data { + struct screen screen; + + struct mode_key_data mdata; + + u_int ox; + u_int oy; +}; + +struct screen * +window_scroll_init(struct window_pane *wp) +{ + struct window_scroll_mode_data *data; + struct screen *s; + struct screen_write_ctx ctx; + u_int i; + + wp->modedata = data = xmalloc(sizeof *data); + data->ox = 0; + data->oy = 0; + + s = &data->screen; + screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); + s->mode &= ~MODE_CURSOR; + + mode_key_init(&data->mdata, + options_get_number(&wp->window->options, "mode-keys"), 0); + + screen_write_start(&ctx, NULL, s); + for (i = 0; i < screen_size_y(s); i++) + window_scroll_write_line(wp, &ctx, i); + screen_write_stop(&ctx); + + return (s); +} + +void +window_scroll_free(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + + mode_key_free(&data->mdata); + + screen_free(&data->screen); + xfree(data); +} + +void +window_scroll_pageup(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + if (data->oy + screen_size_y(s) > screen_hsize(&wp->base)) + data->oy = screen_hsize(&wp->base); + else + data->oy += screen_size_y(s); + + window_scroll_redraw_screen(wp); +} + +void +window_scroll_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + screen_resize(s, sx, sy); + screen_write_start(&ctx, NULL, s); + for (i = 0; i < screen_size_y(s); i++) + window_scroll_write_line(wp, &ctx, i); + screen_write_stop(&ctx); +} + +void +window_scroll_key(struct window_pane *wp, unused struct client *c, int key) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + switch (mode_key_lookup(&data->mdata, key)) { + case MODEKEYCMD_QUIT: + window_pane_reset_mode(wp); + break; + case MODEKEYCMD_LEFT: + window_scroll_scroll_left(wp); + break; + case MODEKEYCMD_RIGHT: + window_scroll_scroll_right(wp); + break; + case MODEKEYCMD_UP: + window_scroll_scroll_up(wp); + break; + case MODEKEYCMD_DOWN: + window_scroll_scroll_down(wp); + break; + case MODEKEYCMD_PREVIOUSPAGE: + window_scroll_pageup(wp); + break; + case MODEKEYCMD_NEXTPAGE: + if (data->oy < screen_size_y(s)) + data->oy = 0; + else + data->oy -= screen_size_y(s); + window_scroll_redraw_screen(wp); + break; + default: + break; + } +} + +void +window_scroll_write_line( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int py) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct grid_cell gc; + char hdr[32]; + size_t size; + + if (py == 0) { + memcpy(&gc, &grid_default_cell, sizeof gc); + size = xsnprintf(hdr, sizeof hdr, + "[%u,%u/%u]", data->ox, data->oy, screen_hsize(&wp->base)); + gc.bg = options_get_number(&wp->window->options, "mode-fg"); + gc.fg = options_get_number(&wp->window->options, "mode-bg"); + gc.attr |= options_get_number(&wp->window->options, "mode-attr"); + screen_write_cursormove(ctx, screen_size_x(s) - size, 0); + screen_write_puts(ctx, &gc, "%s", hdr); + memcpy(&gc, &grid_default_cell, sizeof gc); + } else + size = 0; + + screen_write_cursormove(ctx, 0, py); + screen_write_copy(ctx, &wp->base, data->ox, (screen_hsize(&wp->base) - + data->oy) + py, screen_size_x(s) - size, 1); +} + +void +window_scroll_write_column( + struct window_pane *wp, struct screen_write_ctx *ctx, u_int px) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + + screen_write_cursormove(ctx, px, 0); + screen_write_copy(ctx, &wp->base, data->ox + px, + screen_hsize(&wp->base) - data->oy, 1, screen_size_y(s)); +} + +void +window_scroll_redraw_screen(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + screen_write_start(&ctx, wp, NULL); + for (i = 0; i < screen_size_y(s); i++) + window_scroll_write_line(wp, &ctx, i); + screen_write_stop(&ctx); +} + +void +window_scroll_scroll_up(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen_write_ctx ctx; + + if (data->oy >= screen_hsize(&wp->base)) + return; + data->oy++; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_insertline(&ctx, 1); + window_scroll_write_line(wp, &ctx, 0); + window_scroll_write_line(wp, &ctx, 1); + screen_write_stop(&ctx); +} + +void +window_scroll_scroll_down(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + + if (data->oy == 0) + return; + data->oy--; + + screen_write_start(&ctx, wp, NULL); + screen_write_cursormove(&ctx, 0, 0); + screen_write_deleteline(&ctx, 1); + window_scroll_write_line(wp, &ctx, screen_size_y(s) - 1); + window_scroll_write_line(wp, &ctx, 0); + screen_write_stop(&ctx); +} + +void +window_scroll_scroll_right(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + if (data->ox >= SHRT_MAX) + return; + data->ox++; + + screen_write_start(&ctx, wp, NULL); + for (i = 1; i < screen_size_y(s); i++) { + screen_write_cursormove(&ctx, 0, i); + screen_write_deletecharacter(&ctx, 1); + } + window_scroll_write_column(wp, &ctx, screen_size_x(s) - 1); + window_scroll_write_line(wp, &ctx, 0); + screen_write_stop(&ctx); +} + +void +window_scroll_scroll_left(struct window_pane *wp) +{ + struct window_scroll_mode_data *data = wp->modedata; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + u_int i; + + if (data->ox == 0) + return; + data->ox--; + + screen_write_start(&ctx, wp, NULL); + for (i = 1; i < screen_size_y(s); i++) { + screen_write_cursormove(&ctx, 0, i); + screen_write_insertcharacter(&ctx, 1); + } + window_scroll_write_column(wp, &ctx, 0); + window_scroll_write_line(wp, &ctx, 0); + screen_write_stop(&ctx); +} diff --git a/window.c b/window.c new file mode 100644 index 00000000..06733201 --- /dev/null +++ b/window.c @@ -0,0 +1,625 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +/* + * Each window is attached to one or two panes, each of which is a pty. This + * file contains code to handle them. + * + * A pane has two buffers attached, these are filled and emptied by the main + * server poll loop. Output data is received from pty's in screen format, + * translated and returned as a series of escape sequences and strings via + * input_parse (in input.c). Input data is received as key codes and written + * directly via input_key. + * + * Each pane also has a "virtual" screen (screen.c) which contains the current + * state and is redisplayed when the window is reattached to a client. + * + * Windows are stored directly on a global array and wrapped in any number of + * winlink structs to be linked onto local session RB trees. A reference count + * is maintained and a window removed from the global list and destroyed when + * it reaches zero. + */ + +/* Global window list. */ +struct windows windows; + +RB_GENERATE(winlinks, winlink, entry, winlink_cmp); + +int +winlink_cmp(struct winlink *wl1, struct winlink *wl2) +{ + return (wl1->idx - wl2->idx); +} + +struct winlink * +winlink_find_by_index(struct winlinks *wwl, int idx) +{ + struct winlink wl; + + if (idx < 0) + fatalx("bad index"); + + wl.idx = idx; + return (RB_FIND(winlinks, wwl, &wl)); +} + +int +winlink_next_index(struct winlinks *wwl) +{ + u_int i; + + for (i = 0; i < INT_MAX; i++) { + if (winlink_find_by_index(wwl, i) == NULL) + return (i); + } + + fatalx("no free indexes"); +} + +u_int +winlink_count(struct winlinks *wwl) +{ + struct winlink *wl; + u_int n; + + n = 0; + RB_FOREACH(wl, winlinks, wwl) + n++; + + return (n); +} + +struct winlink * +winlink_add(struct winlinks *wwl, struct window *w, int idx) +{ + struct winlink *wl; + + if (idx == -1) + idx = winlink_next_index(wwl); + else if (winlink_find_by_index(wwl, idx) != NULL) + return (NULL); + + if (idx < 0) + fatalx("bad index"); + + wl = xcalloc(1, sizeof *wl); + wl->idx = idx; + wl->window = w; + RB_INSERT(winlinks, wwl, wl); + + w->references++; + + return (wl); +} + +void +winlink_remove(struct winlinks *wwl, struct winlink *wl) +{ + struct window *w = wl->window; + + RB_REMOVE(winlinks, wwl, wl); + xfree(wl); + + if (w->references == 0) + fatal("bad reference count"); + w->references--; + if (w->references == 0) + window_destroy(w); +} + +struct winlink * +winlink_next(unused struct winlinks *wwl, struct winlink *wl) +{ + return (RB_NEXT(winlinks, wwl, wl)); +} + +struct winlink * +winlink_previous(unused struct winlinks *wwl, struct winlink *wl) +{ + return (RB_PREV(winlinks, wwl, wl)); +} + +void +winlink_stack_push(struct winlink_stack *stack, struct winlink *wl) +{ + if (wl == NULL) + return; + + winlink_stack_remove(stack, wl); + SLIST_INSERT_HEAD(stack, wl, sentry); +} + +void +winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl) +{ + struct winlink *wl2; + + if (wl == NULL) + return; + + SLIST_FOREACH(wl2, stack, sentry) { + if (wl2 == wl) { + SLIST_REMOVE(stack, wl, winlink, sentry); + return; + } + } +} + +int +window_index(struct window *s, u_int *i) +{ + for (*i = 0; *i < ARRAY_LENGTH(&windows); (*i)++) { + if (s == ARRAY_ITEM(&windows, *i)) + return (0); + } + return (-1); +} + +struct window * +window_create1(u_int sx, u_int sy) +{ + struct window *w; + u_int i; + + w = xmalloc(sizeof *w); + w->name = NULL; + w->flags = 0; + + TAILQ_INIT(&w->panes); + w->active = NULL; + w->layout = 0; + + w->sx = sx; + w->sy = sy; + + options_init(&w->options, &global_window_options); + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + if (ARRAY_ITEM(&windows, i) == NULL) { + ARRAY_SET(&windows, i, w); + break; + } + } + if (i == ARRAY_LENGTH(&windows)) + ARRAY_ADD(&windows, w); + w->references = 0; + + return (w); +} + +struct window * +window_create(const char *name, const char *cmd, const char *cwd, + const char **envp, u_int sx, u_int sy, u_int hlimit, char **cause) +{ + struct window *w; + + w = window_create1(sx, sy); + if (window_add_pane(w, -1, cmd, cwd, envp, hlimit, cause) == NULL) { + window_destroy(w); + return (NULL); + } + w->active = TAILQ_FIRST(&w->panes); + + if (name != NULL) { + w->name = xstrdup(name); + options_set_number(&w->options, "automatic-rename", 0); + } else + w->name = default_window_name(w); + return (w); +} + +void +window_destroy(struct window *w) +{ + u_int i; + + if (window_index(w, &i) != 0) + fatalx("index not found"); + ARRAY_SET(&windows, i, NULL); + while (!ARRAY_EMPTY(&windows) && ARRAY_LAST(&windows) == NULL) + ARRAY_TRUNC(&windows, 1); + + options_free(&w->options); + + window_destroy_panes(w); + + if (w->name != NULL) + xfree(w->name); + xfree(w); +} + +int +window_resize(struct window *w, u_int sx, u_int sy) +{ + w->sx = sx; + w->sy = sy; + + return (0); +} + +void +window_set_active_pane(struct window *w, struct window_pane *wp) +{ + w->active = wp; + while (w->active->flags & PANE_HIDDEN) + w->active = TAILQ_PREV(w->active, window_panes, entry); +} + +struct window_pane * +window_add_pane(struct window *w, int wanty, const char *cmd, + const char *cwd, const char **envp, u_int hlimit, char **cause) +{ + struct window_pane *wp; + u_int sizey; + + if (TAILQ_EMPTY(&w->panes)) + wanty = w->sy; + else { + sizey = w->active->sy - 1; /* for separator */ + if (sizey < PANE_MINIMUM * 2) { + *cause = xstrdup("pane too small"); + return (NULL); + } + + if (wanty == -1) + wanty = sizey / 2; + + if (wanty < PANE_MINIMUM) + wanty = PANE_MINIMUM; + if ((u_int) wanty > sizey - PANE_MINIMUM) + wanty = sizey - PANE_MINIMUM; + + window_pane_resize(w->active, w->sx, sizey - wanty); + } + + wp = window_pane_create(w, w->sx, wanty, hlimit); + if (TAILQ_EMPTY(&w->panes)) + TAILQ_INSERT_HEAD(&w->panes, wp, entry); + else + TAILQ_INSERT_AFTER(&w->panes, w->active, wp, entry); + if (window_pane_spawn(wp, cmd, cwd, envp, cause) != 0) { + window_remove_pane(w, wp); + return (NULL); + } + return (wp); +} + +void +window_remove_pane(struct window *w, struct window_pane *wp) +{ + w->active = TAILQ_PREV(wp, window_panes, entry); + if (w->active == NULL) + w->active = TAILQ_NEXT(wp, entry); + + TAILQ_REMOVE(&w->panes, wp, entry); + window_pane_destroy(wp); +} + +u_int +window_index_of_pane(struct window *w, struct window_pane *find) +{ + struct window_pane *wp; + u_int n; + + n = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == find) + return (n); + n++; + } + fatalx("unknown pane"); +} + +struct window_pane * +window_pane_at_index(struct window *w, u_int idx) +{ + struct window_pane *wp; + u_int n; + + n = 0; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (n == idx) + return (wp); + n++; + } + return (NULL); +} + +u_int +window_count_panes(struct window *w) +{ + struct window_pane *wp; + u_int n; + + n = 0; + TAILQ_FOREACH(wp, &w->panes, entry) + n++; + return (n); +} + +void +window_destroy_panes(struct window *w) +{ + struct window_pane *wp; + + while (!TAILQ_EMPTY(&w->panes)) { + wp = TAILQ_FIRST(&w->panes); + TAILQ_REMOVE(&w->panes, wp, entry); + window_pane_destroy(wp); + } +} + +struct window_pane * +window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) +{ + struct window_pane *wp; + + wp = xcalloc(1, sizeof *wp); + wp->window = w; + + wp->cmd = NULL; + wp->cwd = NULL; + + wp->fd = -1; + wp->in = buffer_create(BUFSIZ); + wp->out = buffer_create(BUFSIZ); + + wp->mode = NULL; + + wp->xoff = 0; + wp->yoff = 0; + + wp->sx = sx; + wp->sy = sy; + + screen_init(&wp->base, sx, sy, hlimit); + wp->screen = &wp->base; + + input_init(wp); + + return (wp); +} + +void +window_pane_destroy(struct window_pane *wp) +{ + if (wp->fd != -1) + close(wp->fd); + + input_free(wp); + + window_pane_reset_mode(wp); + screen_free(&wp->base); + + buffer_destroy(wp->in); + buffer_destroy(wp->out); + + if (wp->cwd != NULL) + xfree(wp->cwd); + if (wp->cmd != NULL) + xfree(wp->cmd); + xfree(wp); +} + +int +window_pane_spawn(struct window_pane *wp, + const char *cmd, const char *cwd, const char **envp, char **cause) +{ + struct winsize ws; + int mode; + const char **envq; + struct timeval tv; + + if (wp->fd != -1) + close(wp->fd); + if (cmd != NULL) { + if (wp->cmd != NULL) + xfree(wp->cmd); + wp->cmd = xstrdup(cmd); + } + if (cwd != NULL) { + if (wp->cwd != NULL) + xfree(wp->cwd); + wp->cwd = xstrdup(cwd); + } + + memset(&ws, 0, sizeof ws); + ws.ws_col = screen_size_x(&wp->base); + ws.ws_row = screen_size_y(&wp->base); + + if (gettimeofday(&wp->window->name_timer, NULL) != 0) + fatal("gettimeofday"); + tv.tv_sec = 0; + tv.tv_usec = NAME_INTERVAL * 1000L; + timeradd(&wp->window->name_timer, &tv, &wp->window->name_timer); + + switch (wp->pid = forkpty(&wp->fd, wp->tty, NULL, &ws)) { + case -1: + wp->fd = -1; + xasprintf(cause, "%s: %s", cmd, strerror(errno)); + return (-1); + case 0: + if (chdir(wp->cwd) != 0) + chdir("/"); + for (envq = envp; *envq != NULL; envq++) { + if (putenv((char *) *envq) != 0) + fatal("putenv failed"); + } + sigreset(); + log_close(); + + execl(_PATH_BSHELL, "sh", "-c", wp->cmd, (char *) NULL); + fatal("execl failed"); + } + + if ((mode = fcntl(wp->fd, F_GETFL)) == -1) + fatal("fcntl failed"); + if (fcntl(wp->fd, F_SETFL, mode|O_NONBLOCK) == -1) + fatal("fcntl failed"); + if (fcntl(wp->fd, F_SETFD, FD_CLOEXEC) == -1) + fatal("fcntl failed"); + + return (0); +} + +int +window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) +{ + struct winsize ws; + + if (sx == wp->sx && sy == wp->sy) + return (-1); + wp->sx = sx; + wp->sy = sy; + + memset(&ws, 0, sizeof ws); + ws.ws_col = sx; + ws.ws_row = sy; + + screen_resize(&wp->base, sx, sy); + if (wp->mode != NULL) + wp->mode->resize(wp, sx, sy); + + if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) + fatal("ioctl failed"); + return (0); +} + +int +window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode) +{ + struct screen *s; + + if (wp->mode != NULL || wp->mode == mode) + return (1); + + wp->mode = mode; + + if ((s = wp->mode->init(wp)) != NULL) + wp->screen = s; + server_redraw_window(wp->window); + return (0); +} + +void +window_pane_reset_mode(struct window_pane *wp) +{ + if (wp->mode == NULL) + return; + + wp->mode->free(wp); + wp->mode = NULL; + + wp->screen = &wp->base; + server_redraw_window(wp->window); +} + +void +window_pane_parse(struct window_pane *wp) +{ + input_parse(wp); +} + +void +window_pane_key(struct window_pane *wp, struct client *c, int key) +{ + if (wp->mode != NULL) { + if (wp->mode->key != NULL) + wp->mode->key(wp, c, key); + } else + input_key(wp, key); +} + +void +window_pane_mouse( + struct window_pane *wp, struct client *c, u_char b, u_char x, u_char y) +{ + /* XXX convert from 1-based? */ + + if (x < wp->xoff || x >= wp->xoff + wp->sx) + return; + if (y < wp->yoff || y >= wp->yoff + wp->sy) + return; + x -= wp->xoff; + y -= wp->yoff; + + if (wp->mode != NULL) { + if (wp->mode->mouse != NULL) + wp->mode->mouse(wp, c, b, x, y); + } else + input_mouse(wp, b, x, y); +} + +char * +window_pane_search(struct window_pane *wp, const char *searchstr) +{ + const struct grid_cell *gc; + const struct grid_utf8 *gu; + char *buf, *s; + size_t off; + u_int i, j, k; + + buf = xmalloc(1); + + for (j = 0; j < screen_size_y(&wp->base); j++) { + off = 0; + for (i = 0; i < screen_size_x(&wp->base); i++) { + gc = grid_view_peek_cell(wp->base.grid, i, j); + if (gc->flags & GRID_FLAG_UTF8) { + gu = grid_view_peek_utf8(wp->base.grid, i, j); + buf = xrealloc(buf, 1, off + 8); + for (k = 0; k < UTF8_SIZE; k++) { + if (gu->data[k] == 0xff) + break; + buf[off++] = gu->data[k]; + } + } else { + buf = xrealloc(buf, 1, off + 1); + buf[off++] = gc->data; + } + } + while (off > 0 && buf[off - 1] == ' ') + off--; + buf[off] = '\0'; + + if ((s = strstr(buf, searchstr)) != NULL) { + s = section_string(buf, off, s - buf, 40); + xfree(buf); + return (s); + } + } + + xfree(buf); + return (NULL); +} diff --git a/xmalloc.c b/xmalloc.c new file mode 100644 index 00000000..6bf63bef --- /dev/null +++ b/xmalloc.c @@ -0,0 +1,144 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2004 Nicholas Marriott + * + * 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 + +#include +#include +#include +#include +#include + +#include "tmux.h" + +char * +xstrdup(const char *s) +{ + void *ptr; + size_t len; + + len = strlen(s) + 1; + ptr = xmalloc(len); + + return (strncpy(ptr, s, len)); +} + +void * +xcalloc(size_t nmemb, size_t size) +{ + void *ptr; + + if (size == 0 || nmemb == 0) + fatalx("zero size"); + if (SIZE_MAX / nmemb < size) + fatalx("nmemb * size > SIZE_MAX"); + if ((ptr = calloc(nmemb, size)) == NULL) + fatal("xcalloc failed"); + + return (ptr); +} + +void * +xmalloc(size_t size) +{ + void *ptr; + + if (size == 0) + fatalx("zero size"); + if ((ptr = malloc(size)) == NULL) + fatal("xmalloc failed"); + + return (ptr); +} + +void * +xrealloc(void *oldptr, size_t nmemb, size_t size) +{ + size_t newsize = nmemb * size; + void *newptr; + + if (newsize == 0) + fatalx("zero size"); + if (SIZE_MAX / nmemb < size) + fatalx("nmemb * size > SIZE_MAX"); + if ((newptr = realloc(oldptr, newsize)) == NULL) + fatal("xrealloc failed"); + + return (newptr); +} + +void +xfree(void *ptr) +{ + if (ptr == NULL) + fatalx("null pointer"); + free(ptr); +} + +int printflike2 +xasprintf(char **ret, const char *fmt, ...) +{ + va_list ap; + int i; + + va_start(ap, fmt); + i = xvasprintf(ret, fmt, ap); + va_end(ap); + + return (i); +} + +int +xvasprintf(char **ret, const char *fmt, va_list ap) +{ + int i; + + i = vasprintf(ret, fmt, ap); + if (i < 0 || *ret == NULL) + fatal("xvasprintf failed"); + + return (i); +} + +int printflike3 +xsnprintf(char *buf, size_t len, const char *fmt, ...) +{ + va_list ap; + int i; + + va_start(ap, fmt); + i = xvsnprintf(buf, len, fmt, ap); + va_end(ap); + + return (i); +} + +int +xvsnprintf(char *buf, size_t len, const char *fmt, va_list ap) +{ + int i; + + if (len > INT_MAX) + fatalx("len > INT_MAX"); + + i = vsnprintf(buf, len, fmt, ap); + if (i < 0) + fatal("vsnprintf failed"); + + return (i); +}