summaryrefslogtreecommitdiffstats
path: root/input-keys.c
diff options
context:
space:
mode:
Diffstat (limited to 'input-keys.c')
-rw-r--r--input-keys.c664
1 files changed, 664 insertions, 0 deletions
diff --git a/input-keys.c b/input-keys.c
new file mode 100644
index 0000000..ebf6133
--- /dev/null
+++ b/input-keys.c
@@ -0,0 +1,664 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * This file is rather misleadingly named, it contains the code which takes a
+ * key code and translates it into something suitable to be sent to the
+ * application running in a pane (similar to input.c does in the other
+ * direction with output).
+ */
+
+static void input_key_mouse(struct window_pane *, struct mouse_event *);
+
+/* Entry in the key tree. */
+struct input_key_entry {
+ key_code key;
+ const char *data;
+
+ RB_ENTRY(input_key_entry) entry;
+};
+RB_HEAD(input_key_tree, input_key_entry);
+
+/* Tree of input keys. */
+static int input_key_cmp(struct input_key_entry *,
+ struct input_key_entry *);
+RB_GENERATE_STATIC(input_key_tree, input_key_entry, entry, input_key_cmp);
+struct input_key_tree input_key_tree = RB_INITIALIZER(&input_key_tree);
+
+/* List of default keys, the tree is built from this. */
+static struct input_key_entry input_key_defaults[] = {
+ /* Paste keys. */
+ { .key = KEYC_PASTE_START,
+ .data = "\033[200~"
+ },
+ { .key = KEYC_PASTE_END,
+ .data = "\033[201~"
+ },
+
+ /* Function keys. */
+ { .key = KEYC_F1,
+ .data = "\033OP"
+ },
+ { .key = KEYC_F2,
+ .data = "\033OQ"
+ },
+ { .key = KEYC_F3,
+ .data = "\033OR"
+ },
+ { .key = KEYC_F4,
+ .data = "\033OS"
+ },
+ { .key = KEYC_F5,
+ .data = "\033[15~"
+ },
+ { .key = KEYC_F6,
+ .data = "\033[17~"
+ },
+ { .key = KEYC_F7,
+ .data = "\033[18~"
+ },
+ { .key = KEYC_F8,
+ .data = "\033[19~"
+ },
+ { .key = KEYC_F9,
+ .data = "\033[20~"
+ },
+ { .key = KEYC_F10,
+ .data = "\033[21~"
+ },
+ { .key = KEYC_F11,
+ .data = "\033[23~"
+ },
+ { .key = KEYC_F12,
+ .data = "\033[24~"
+ },
+ { .key = KEYC_IC,
+ .data = "\033[2~"
+ },
+ { .key = KEYC_DC,
+ .data = "\033[3~"
+ },
+ { .key = KEYC_HOME,
+ .data = "\033[1~"
+ },
+ { .key = KEYC_END,
+ .data = "\033[4~"
+ },
+ { .key = KEYC_NPAGE,
+ .data = "\033[6~"
+ },
+ { .key = KEYC_PPAGE,
+ .data = "\033[5~"
+ },
+ { .key = KEYC_BTAB,
+ .data = "\033[Z"
+ },
+
+ /* Arrow keys. */
+ { .key = KEYC_UP|KEYC_CURSOR,
+ .data = "\033OA"
+ },
+ { .key = KEYC_DOWN|KEYC_CURSOR,
+ .data = "\033OB"
+ },
+ { .key = KEYC_RIGHT|KEYC_CURSOR,
+ .data = "\033OC"
+ },
+ { .key = KEYC_LEFT|KEYC_CURSOR,
+ .data = "\033OD"
+ },
+ { .key = KEYC_UP,
+ .data = "\033[A"
+ },
+ { .key = KEYC_DOWN,
+ .data = "\033[B"
+ },
+ { .key = KEYC_RIGHT,
+ .data = "\033[C"
+ },
+ { .key = KEYC_LEFT,
+ .data = "\033[D"
+ },
+
+ /* Keypad keys. */
+ { .key = KEYC_KP_SLASH|KEYC_KEYPAD,
+ .data = "\033Oo"
+ },
+ { .key = KEYC_KP_STAR|KEYC_KEYPAD,
+ .data = "\033Oj"
+ },
+ { .key = KEYC_KP_MINUS|KEYC_KEYPAD,
+ .data = "\033Om"
+ },
+ { .key = KEYC_KP_SEVEN|KEYC_KEYPAD,
+ .data = "\033Ow"
+ },
+ { .key = KEYC_KP_EIGHT|KEYC_KEYPAD,
+ .data = "\033Ox"
+ },
+ { .key = KEYC_KP_NINE|KEYC_KEYPAD,
+ .data = "\033Oy"
+ },
+ { .key = KEYC_KP_PLUS|KEYC_KEYPAD,
+ .data = "\033Ok"
+ },
+ { .key = KEYC_KP_FOUR|KEYC_KEYPAD,
+ .data = "\033Ot"
+ },
+ { .key = KEYC_KP_FIVE|KEYC_KEYPAD,
+ .data = "\033Ou"
+ },
+ { .key = KEYC_KP_SIX|KEYC_KEYPAD,
+ .data = "\033Ov"
+ },
+ { .key = KEYC_KP_ONE|KEYC_KEYPAD,
+ .data = "\033Oq"
+ },
+ { .key = KEYC_KP_TWO|KEYC_KEYPAD,
+ .data = "\033Or"
+ },
+ { .key = KEYC_KP_THREE|KEYC_KEYPAD,
+ .data = "\033Os"
+ },
+ { .key = KEYC_KP_ENTER|KEYC_KEYPAD,
+ .data = "\033OM"
+ },
+ { .key = KEYC_KP_ZERO|KEYC_KEYPAD,
+ .data = "\033Op"
+ },
+ { .key = KEYC_KP_PERIOD|KEYC_KEYPAD,
+ .data = "\033On"
+ },
+ { .key = KEYC_KP_SLASH,
+ .data = "/"
+ },
+ { .key = KEYC_KP_STAR,
+ .data = "*"
+ },
+ { .key = KEYC_KP_MINUS,
+ .data = "-"
+ },
+ { .key = KEYC_KP_SEVEN,
+ .data = "7"
+ },
+ { .key = KEYC_KP_EIGHT,
+ .data = "8"
+ },
+ { .key = KEYC_KP_NINE,
+ .data = "9"
+ },
+ { .key = KEYC_KP_PLUS,
+ .data = "+"
+ },
+ { .key = KEYC_KP_FOUR,
+ .data = "4"
+ },
+ { .key = KEYC_KP_FIVE,
+ .data = "5"
+ },
+ { .key = KEYC_KP_SIX,
+ .data = "6"
+ },
+ { .key = KEYC_KP_ONE,
+ .data = "1"
+ },
+ { .key = KEYC_KP_TWO,
+ .data = "2"
+ },
+ { .key = KEYC_KP_THREE,
+ .data = "3"
+ },
+ { .key = KEYC_KP_ENTER,
+ .data = "\n"
+ },
+ { .key = KEYC_KP_ZERO,
+ .data = "0"
+ },
+ { .key = KEYC_KP_PERIOD,
+ .data = "."
+ },
+
+ /* Keys with an embedded modifier. */
+ { .key = KEYC_F1|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_P"
+ },
+ { .key = KEYC_F2|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_Q"
+ },
+ { .key = KEYC_F3|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_R"
+ },
+ { .key = KEYC_F4|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_S"
+ },
+ { .key = KEYC_F5|KEYC_BUILD_MODIFIERS,
+ .data = "\033[15;_~"
+ },
+ { .key = KEYC_F6|KEYC_BUILD_MODIFIERS,
+ .data = "\033[17;_~"
+ },
+ { .key = KEYC_F7|KEYC_BUILD_MODIFIERS,
+ .data = "\033[18;_~"
+ },
+ { .key = KEYC_F8|KEYC_BUILD_MODIFIERS,
+ .data = "\033[19;_~"
+ },
+ { .key = KEYC_F9|KEYC_BUILD_MODIFIERS,
+ .data = "\033[20;_~"
+ },
+ { .key = KEYC_F10|KEYC_BUILD_MODIFIERS,
+ .data = "\033[21;_~"
+ },
+ { .key = KEYC_F11|KEYC_BUILD_MODIFIERS,
+ .data = "\033[23;_~"
+ },
+ { .key = KEYC_F12|KEYC_BUILD_MODIFIERS,
+ .data = "\033[24;_~"
+ },
+ { .key = KEYC_UP|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_A"
+ },
+ { .key = KEYC_DOWN|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_B"
+ },
+ { .key = KEYC_RIGHT|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_C"
+ },
+ { .key = KEYC_LEFT|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_D"
+ },
+ { .key = KEYC_HOME|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_H"
+ },
+ { .key = KEYC_END|KEYC_BUILD_MODIFIERS,
+ .data = "\033[1;_F"
+ },
+ { .key = KEYC_PPAGE|KEYC_BUILD_MODIFIERS,
+ .data = "\033[5;_~"
+ },
+ { .key = KEYC_NPAGE|KEYC_BUILD_MODIFIERS,
+ .data = "\033[6;_~"
+ },
+ { .key = KEYC_IC|KEYC_BUILD_MODIFIERS,
+ .data = "\033[2;_~"
+ },
+ { .key = KEYC_DC|KEYC_BUILD_MODIFIERS,
+ .data = "\033[3;_~"
+ }
+};
+static const key_code input_key_modifiers[] = {
+ 0,
+ 0,
+ KEYC_SHIFT,
+ KEYC_META|KEYC_IMPLIED_META,
+ KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META,
+ KEYC_CTRL,
+ KEYC_SHIFT|KEYC_CTRL,
+ KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL,
+ KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL
+};
+
+/* Input key comparison function. */
+static int
+input_key_cmp(struct input_key_entry *ike1, struct input_key_entry *ike2)
+{
+ if (ike1->key < ike2->key)
+ return (-1);
+ if (ike1->key > ike2->key)
+ return (1);
+ return (0);
+}
+
+/* Look for key in tree. */
+static struct input_key_entry *
+input_key_get(key_code key)
+{
+ struct input_key_entry entry = { .key = key };
+
+ return (RB_FIND(input_key_tree, &input_key_tree, &entry));
+}
+
+/* Split a character into two UTF-8 bytes. */
+static size_t
+input_key_split2(u_int c, u_char *dst)
+{
+ if (c > 0x7f) {
+ dst[0] = (c >> 6) | 0xc0;
+ dst[1] = (c & 0x3f) | 0x80;
+ return (2);
+ }
+ dst[0] = c;
+ return (1);
+}
+
+/* Build input key tree. */
+void
+input_key_build(void)
+{
+ struct input_key_entry *ike, *new;
+ u_int i, j;
+ char *data;
+ key_code key;
+
+ for (i = 0; i < nitems(input_key_defaults); i++) {
+ ike = &input_key_defaults[i];
+ if (~ike->key & KEYC_BUILD_MODIFIERS) {
+ RB_INSERT(input_key_tree, &input_key_tree, ike);
+ continue;
+ }
+
+ for (j = 2; j < nitems(input_key_modifiers); j++) {
+ key = (ike->key & ~KEYC_BUILD_MODIFIERS);
+ data = xstrdup(ike->data);
+ data[strcspn(data, "_")] = '0' + j;
+
+ new = xcalloc(1, sizeof *new);
+ new->key = key|input_key_modifiers[j];
+ new->data = data;
+ RB_INSERT(input_key_tree, &input_key_tree, new);
+ }
+ }
+
+ RB_FOREACH(ike, input_key_tree, &input_key_tree) {
+ log_debug("%s: 0x%llx (%s) is %s", __func__, ike->key,
+ key_string_lookup_key(ike->key, 1), ike->data);
+ }
+}
+
+/* Translate a key code into an output key sequence for a pane. */
+int
+input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m)
+{
+ if (log_get_level() != 0) {
+ log_debug("writing key 0x%llx (%s) to %%%u", key,
+ key_string_lookup_key(key, 1), wp->id);
+ }
+
+ if (KEYC_IS_MOUSE(key)) {
+ if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id)
+ input_key_mouse(wp, m);
+ return (0);
+ }
+ return (input_key(wp->screen, wp->event, key));
+}
+
+static void
+input_key_write(const char *from, struct bufferevent *bev, const char *data,
+ size_t size)
+{
+ log_debug("%s: %.*s", from, (int)size, data);
+ bufferevent_write(bev, data, size);
+}
+
+/* Translate a key code into an output key sequence. */
+int
+input_key(struct screen *s, struct bufferevent *bev, key_code key)
+{
+ struct input_key_entry *ike;
+ key_code justkey, newkey, outkey, modifiers;
+ struct utf8_data ud;
+ char tmp[64], modifier;
+
+ /* Mouse keys need a pane. */
+ if (KEYC_IS_MOUSE(key))
+ return (0);
+
+ /* Literal keys go as themselves (can't be more than eight bits). */
+ if (key & KEYC_LITERAL) {
+ ud.data[0] = (u_char)key;
+ input_key_write(__func__, bev, &ud.data[0], 1);
+ return (0);
+ }
+
+ /* Is this backspace? */
+ if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) {
+ newkey = options_get_number(global_options, "backspace");
+ if (newkey >= 0x7f)
+ newkey = '\177';
+ key = newkey|(key & (KEYC_MASK_MODIFIERS|KEYC_MASK_FLAGS));
+ }
+
+ /*
+ * If this is a normal 7-bit key, just send it, with a leading escape
+ * if necessary. If it is a UTF-8 key, split it and send it.
+ */
+ justkey = (key & ~(KEYC_META|KEYC_IMPLIED_META));
+ if (justkey <= 0x7f) {
+ if (key & KEYC_META)
+ input_key_write(__func__, bev, "\033", 1);
+ ud.data[0] = justkey;
+ input_key_write(__func__, bev, &ud.data[0], 1);
+ return (0);
+ }
+ if (KEYC_IS_UNICODE(justkey)) {
+ if (key & KEYC_META)
+ input_key_write(__func__, bev, "\033", 1);
+ utf8_to_data(justkey, &ud);
+ input_key_write(__func__, bev, ud.data, ud.size);
+ return (0);
+ }
+
+ /*
+ * Look up in the tree. If not in application keypad or cursor mode,
+ * remove the flags from the key.
+ */
+ if (~s->mode & MODE_KKEYPAD)
+ key &= ~KEYC_KEYPAD;
+ if (~s->mode & MODE_KCURSOR)
+ key &= ~KEYC_CURSOR;
+ ike = input_key_get(key);
+ if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META))
+ ike = input_key_get(key & ~KEYC_META);
+ if (ike == NULL && (key & KEYC_CURSOR))
+ ike = input_key_get(key & ~KEYC_CURSOR);
+ if (ike == NULL && (key & KEYC_KEYPAD))
+ ike = input_key_get(key & ~KEYC_KEYPAD);
+ if (ike != NULL) {
+ log_debug("found key 0x%llx: \"%s\"", key, ike->data);
+ if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META))
+ input_key_write(__func__, bev, "\033", 1);
+ input_key_write(__func__, bev, ike->data, strlen(ike->data));
+ return (0);
+ }
+
+ /* No builtin key sequence; construct an extended key sequence. */
+ if (~s->mode & MODE_KEXTENDED) {
+ if ((key & KEYC_MASK_MODIFIERS) != KEYC_CTRL)
+ goto missing;
+ justkey = (key & KEYC_MASK_KEY);
+ switch (justkey) {
+ case ' ':
+ case '2':
+ key = 0|(key & ~KEYC_MASK_KEY);
+ break;
+ case '|':
+ key = 28|(key & ~KEYC_MASK_KEY);
+ break;
+ case '6':
+ key = 30|(key & ~KEYC_MASK_KEY);
+ break;
+ case '-':
+ case '/':
+ key = 31|(key & ~KEYC_MASK_KEY);
+ break;
+ case '?':
+ key = 127|(key & ~KEYC_MASK_KEY);
+ break;
+ default:
+ if (justkey >= 'A' && justkey <= '_')
+ key = (justkey - 'A')|(key & ~KEYC_MASK_KEY);
+ else if (justkey >= 'a' && justkey <= '~')
+ key = (justkey - 96)|(key & ~KEYC_MASK_KEY);
+ else
+ return (0);
+ break;
+ }
+ return (input_key(s, bev, key & ~KEYC_CTRL));
+ }
+ outkey = (key & KEYC_MASK_KEY);
+ modifiers = (key & KEYC_MASK_MODIFIERS);
+ if (outkey < 32 && outkey != 9 && outkey != 13 && outkey != 27) {
+ outkey = 64 + outkey;
+ modifiers |= KEYC_CTRL;
+ }
+ switch (modifiers) {
+ case KEYC_SHIFT:
+ modifier = '2';
+ break;
+ case KEYC_META:
+ modifier = '3';
+ break;
+ case KEYC_SHIFT|KEYC_META:
+ modifier = '4';
+ break;
+ case KEYC_CTRL:
+ modifier = '5';
+ break;
+ case KEYC_SHIFT|KEYC_CTRL:
+ modifier = '6';
+ break;
+ case KEYC_META|KEYC_CTRL:
+ modifier = '7';
+ break;
+ case KEYC_SHIFT|KEYC_META|KEYC_CTRL:
+ modifier = '8';
+ break;
+ default:
+ goto missing;
+ }
+ xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier);
+ input_key_write(__func__, bev, tmp, strlen(tmp));
+ return (0);
+
+missing:
+ log_debug("key 0x%llx missing", key);
+ return (-1);
+}
+
+/* Get mouse event string. */
+int
+input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y,
+ const char **rbuf, size_t *rlen)
+{
+ static char buf[40];
+ size_t len;
+
+ *rbuf = NULL;
+ *rlen = 0;
+
+ /* If this pane is not in button or all mode, discard motion events. */
+ if (MOUSE_DRAG(m->b) && (s->mode & MOTION_MOUSE_MODES) == 0)
+ return (0);
+ if ((s->mode & ALL_MOUSE_MODES) == 0)
+ return (0);
+
+ /*
+ * If this event is a release event and not in all mode, discard it.
+ * In SGR mode we can tell absolutely because a release is normally
+ * shown by the last character. Without SGR, we check if the last
+ * buttons was also a release.
+ */
+ if (m->sgr_type != ' ') {
+ if (MOUSE_DRAG(m->sgr_b) &&
+ MOUSE_RELEASE(m->sgr_b) &&
+ (~s->mode & MODE_MOUSE_ALL))
+ return (0);
+ } else {
+ if (MOUSE_DRAG(m->b) &&
+ MOUSE_RELEASE(m->b) &&
+ MOUSE_RELEASE(m->lb) &&
+ (~s->mode & MODE_MOUSE_ALL))
+ return (0);
+ }
+
+ /*
+ * Use the SGR (1006) extension only if the application requested it
+ * and the underlying terminal also sent the event in this format (this
+ * is because an old style mouse release event cannot be converted into
+ * the new SGR format, since the released button is unknown). Otherwise
+ * pretend that tmux doesn't speak this extension, and fall back to the
+ * UTF-8 (1005) extension if the application requested, or to the
+ * legacy format.
+ */
+ if (m->sgr_type != ' ' && (s->mode & MODE_MOUSE_SGR)) {
+ len = xsnprintf(buf, sizeof buf, "\033[<%u;%u;%u%c",
+ m->sgr_b, x + 1, y + 1, m->sgr_type);
+ } else if (s->mode & MODE_MOUSE_UTF8) {
+ if (m->b > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_BTN_OFF ||
+ x > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF ||
+ y > MOUSE_PARAM_UTF8_MAX - MOUSE_PARAM_POS_OFF)
+ return (0);
+ len = xsnprintf(buf, sizeof buf, "\033[M");
+ len += input_key_split2(m->b + MOUSE_PARAM_BTN_OFF, &buf[len]);
+ len += input_key_split2(x + MOUSE_PARAM_POS_OFF, &buf[len]);
+ len += input_key_split2(y + MOUSE_PARAM_POS_OFF, &buf[len]);
+ } else {
+ if (m->b + MOUSE_PARAM_BTN_OFF > MOUSE_PARAM_MAX)
+ return (0);
+
+ len = xsnprintf(buf, sizeof buf, "\033[M");
+ buf[len++] = m->b + MOUSE_PARAM_BTN_OFF;
+
+ /*
+ * The incoming x and y may be out of the range which can be
+ * supported by the "normal" mouse protocol. Clamp the
+ * coordinates to the supported range.
+ */
+ if (x + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX)
+ buf[len++] = MOUSE_PARAM_MAX;
+ else
+ buf[len++] = x + MOUSE_PARAM_POS_OFF;
+ if (y + MOUSE_PARAM_POS_OFF > MOUSE_PARAM_MAX)
+ buf[len++] = MOUSE_PARAM_MAX;
+ else
+ buf[len++] = y + MOUSE_PARAM_POS_OFF;
+ }
+
+ *rbuf = buf;
+ *rlen = len;
+ return (1);
+}
+
+/* Translate mouse and output. */
+static void
+input_key_mouse(struct window_pane *wp, struct mouse_event *m)
+{
+ struct screen *s = wp->screen;
+ u_int x, y;
+ const char *buf;
+ size_t len;
+
+ /* Ignore events if no mouse mode or the pane is not visible. */
+ if (m->ignore || (s->mode & ALL_MOUSE_MODES) == 0)
+ return;
+ if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
+ return;
+ if (!window_pane_visible(wp))
+ return;
+ if (!input_key_get_mouse(s, m, x, y, &buf, &len))
+ return;
+ log_debug("writing mouse %.*s to %%%u", (int)len, buf, wp->id);
+ input_key_write(__func__, wp->event, buf, len);
+}