From 9e4e0ee314e6d29538eddf3da9b7ee9704287f3f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 12 Oct 2019 16:04:19 -0700 Subject: verhaul of terminal keypress handling and labeling to support more functionality for mouse clicking/dragging/left clicking/middle clicking/double clicking and to have more standardized behavior for key names and better handling of key modifiers (Ctrl, Alt, Meta, Shift). Also moved Ctrl-c and Ctrl-z to custom key bindings with (bb +kill and bb +suspend). Mouse clicking is now also handled in key bindings. --- API.md | 2 + bb.c | 152 ++++++++++--------------- bb.h | 2 +- bindings.bb | 14 ++- bterm.h | 360 +++++++++++++++++++++++++++++++++--------------------------- 5 files changed, 271 insertions(+), 259 deletions(-) diff --git a/API.md b/API.md index 6b70093..61974a8 100644 --- a/API.md +++ b/API.md @@ -47,6 +47,7 @@ is one of the following commands (or a unique prefix of one): - `dotfiles:[01]` Whether dotfiles are visible - `goto:` Move the cursor to (changing directory if needed) - `interleave:[01]` Whether or not directories should be interleaved with files in the display +- `kill` Exit with failure - `move:` Move the cursor a numeric amount - `quit` Quit bb - `refresh` Refresh the file listing @@ -54,6 +55,7 @@ is one of the following commands (or a unique prefix of one): - `select:` Select - `sort:([+-]method)+` Set sorting method (+: normal, -: reverse), additional methods act as tiebreaker - `spread:` Spread the selection state at the cursor +- `suspend` Suspend `bb` (SIGSTP signal) - `toggle:` Toggle the selection status of For any of these commands (e.g. `+select`), an empty parameter followed by diff --git a/bb.c b/bb.c index 9c21fce..2272b0c 100644 --- a/bb.c +++ b/bb.c @@ -80,96 +80,58 @@ void bb_browse(bb_t *bb, const char *path) } int key, mouse_x, mouse_y; - static struct timespec lastclick = {0, 0}; get_keyboard_input: key = bgetkey(tty_in, &mouse_x, &mouse_y); - switch (key) { - case KEY_MOUSE_LEFT: { - struct timespec clicktime; - clock_gettime(CLOCK_MONOTONIC, &clicktime); - double dt_ms = 1e3*(double)(clicktime.tv_sec - lastclick.tv_sec); - dt_ms += 1e-6*(double)(clicktime.tv_nsec - lastclick.tv_nsec); - lastclick = clicktime; - // Get column: - char column[3] = "+?"; - for (int col = 0, x = 0; bb->columns[col]; col++) { - if (col > 0) x += 1; - x += columns[(int)bb->columns[col]].width; - if (x >= mouse_x) { - column[1] = bb->columns[col]; - break; - } - } - if (mouse_y == 1) { - char *pos; - if ((pos = strstr(bb->sort, column)) && pos == bb->sort) - column[0] = '-'; - set_sort(bb, column); - sort_files(bb); - goto redraw; - } else if (2 <= mouse_y && mouse_y <= termheight - 2) { - int clicked = bb->scroll + (mouse_y - 2); - if (clicked > bb->nfiles - 1) goto next_input; - if (column[1] == COL_SELECTED) { - set_selected(bb, bb->files[clicked], !IS_SELECTED(bb->files[clicked])); - bb->dirty = 1; - goto redraw; - } - set_cursor(bb, clicked); - if (dt_ms <= 200) { - key = KEY_MOUSE_DOUBLE_LEFT; - goto user_bindings; - } - goto redraw; + if (key == -1) goto next_input; + static char bbmousecol[2] = {0, 0}; + static char bbclicked[PATH_MAX]; + if (mouse_x != -1 && mouse_y != -1) { + bbmousecol[0] = '\0'; + // Get bb column: + for (int col = 0, x = 0; bb->columns[col]; col++, x++) { + x += columns[(int)bb->columns[col]].width; + if (x >= mouse_x) { + bbmousecol[0] = bb->columns[col]; + break; } - break; } - - case KEY_CTRL_C: - cleanup_and_exit(SIGINT); - - case KEY_CTRL_Z: - fputs(T_LEAVE_BBMODE, tty_out); - restore_term(&orig_termios); - raise(SIGTSTP); - init_term(); - bb->dirty = 1; - goto redraw; - - case -1: - goto next_input; - - default: { - // Search user-defined key bindings - binding_t *binding; - user_bindings: - for (int i = 0; bindings[i].key != 0 && i < sizeof(bindings)/sizeof(bindings[0]); i++) { - if (key == bindings[i].key) { - binding = &bindings[i]; - goto run_binding; - } - } - // Nothing matched - goto next_input; - - run_binding: - if (cmdpos != 0) - err("Command file still open"); - if (is_simple_bbcmd(binding->script)) { - process_cmd(bb, binding->script); - } else { - move_cursor(tty_out, 0, termheight-1); - restore_term(&default_termios); - run_script(bb, binding->script); - init_term(); - check_cmds = 1; - } - if (bb->should_quit) goto quit; - goto redraw; + if (mouse_y == 1) { + strcpy(bbclicked, ""); + } else if (2 <= mouse_y && mouse_y <= termheight - 2 + && bb->scroll + (mouse_y - 2) <= bb->nfiles - 1) { + strcpy(bbclicked, bb->files[bb->scroll + (mouse_y - 2)]->fullname); + } else { + bbclicked[0] = '\0'; } + setenv("BBMOUSECOL", bbmousecol, 1); + setenv("BBCLICKED", bbclicked, 1); } + // Search user-defined key bindings + binding_t *binding; + for (int i = 0; bindings[i].script && i < sizeof(bindings)/sizeof(bindings[0]); i++) { + if (key == bindings[i].key) { + binding = &bindings[i]; + goto run_binding; + } + } + // Nothing matched goto next_input; + run_binding: + if (cmdpos != 0) + err("Command file still open"); + if (is_simple_bbcmd(binding->script)) { + process_cmd(bb, binding->script); + } else { + move_cursor(tty_out, 0, termheight-1); + restore_term(&default_termios); + run_script(bb, binding->script); + init_term(); + check_cmds = 1; + } + if (!bb->should_quit) + goto redraw; + quit: if (tty_out) { fputs(T_LEAVE_BBMODE, tty_out); @@ -572,7 +534,7 @@ void populate_files(bb_t *bb, int samedir) void print_bindings(int fd) { char buf[1000], buf2[1024]; - for (int i = 0; bindings[i].key != 0 && i < sizeof(bindings)/sizeof(bindings[0]); i++) { + for (int i = 0; bindings[i].script && i < sizeof(bindings)/sizeof(bindings[0]); i++) { if (bindings[i].key == -1) { const char *label = bindings[i].description; sprintf(buf, "\n\033[33;1;4m\033[%dG%s\033[0m\n", (termwidth-(int)strlen(label))/2, label); @@ -581,17 +543,11 @@ void print_bindings(int fd) } int to_skip = -1; char *p = buf; - for (int j = i; bindings[j].key && strcmp(bindings[j].description, bindings[i].description) == 0; j++) { + for (int j = i; bindings[j].script && strcmp(bindings[j].description, bindings[i].description) == 0; j++) { if (j > i) p = stpcpy(p, ", "); ++to_skip; int key = bindings[j].key; - const char *name = bkeyname(key); - if (name) - p = stpcpy(p, name); - else if (' ' <= key && key <= '~') - p += sprintf(p, "%c", (char)key); - else - p += sprintf(p, "\033[31m\\x%02X", key); + p = bkeyname(key, p); } *p = '\0'; sprintf(buf2, "\033[1m\033[%dG%s\033[0m", termwidth/2 - 1 - (int)strlen(buf), buf); @@ -643,10 +599,10 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd) } else description = script; for (char *key; (key = strsep(&keys, ",")); ) { int is_section = strcmp(key, "Section") == 0; - int keyval = strlen(key) == 1 ? key[0] : bkeywithname(key); + int keyval = bkeywithname(key); if (keyval == -1 && !is_section) continue; for (int i = 0; i < sizeof(bindings)/sizeof(bindings[0]); i++) { - if (bindings[i].key && (bindings[i].key != keyval || is_section)) + if (bindings[i].script && (bindings[i].key != keyval || is_section)) continue; binding_t binding = {keyval, memcheck(strdup(script)), memcheck(strdup(description))}; @@ -766,6 +722,8 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd) sort_files(bb); return BB_OK; } + case 'k': // +kill + cleanup_and_exit(SIGINT); case 'm': { // +move: int oldcur, isdelta, n; move: @@ -822,6 +780,14 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd) case 'p': // +spread: goto move; + + case 'u': // +suspend + fputs(T_LEAVE_BBMODE, tty_out); + restore_term(&orig_termios); + raise(SIGTSTP); + init_term(); + bb->dirty = 1; + break; } case 't': { // +toggle: if (!value && !bb->nfiles) return BB_INVALID; diff --git a/bb.h b/bb.h index 79b7d27..358016c 100644 --- a/bb.h +++ b/bb.h @@ -26,7 +26,7 @@ #include "bterm.h" // Macros: -#define BB_VERSION "0.16.1" +#define BB_VERSION "0.17.0" #ifndef PATH_MAX #define PATH_MAX 4096 diff --git a/bindings.bb b/bindings.bb index 83b6de3..312ff8f 100644 --- a/bindings.bb +++ b/bindings.bb @@ -5,6 +5,10 @@ Section: BB Commands bb +help q,Q: # Quit bb +quit +Ctrl-c: # Exit with failure + bb +kill +Ctrl-z: # Suspend + bb +suspend Section: File Navigation j,Down: # Next file @@ -79,6 +83,14 @@ Ctrl-a: # Select all files here else find -mindepth 1 -maxdepth 1 ! -path '*/.*' -print0; fi | bb +sel: Section: File Actions +Left click: # Move cursor to file + if [ "$BBCLICKED" = "" ]; then + bb +sort:"~$BBMOUSECOL" + elif [ "$BBCLICKED" -a "$BBMOUSECOL" = "*" ]; then + bb +toggle:"$BBCLICKED" + elif [ "$BBCLICKED" ]; then + bb +goto:"$BBCLICKED" + fi Enter,Double left click: # Open file/directory if [ "$(uname)" = "Darwin" ]; then if [ -d "$BBCURSOR" ]; then bb +cd:"$BBCURSOR"; @@ -91,7 +103,7 @@ Enter,Double left click: # Open file/directory fi e: # Edit file in $EDITOR $EDITOR "$BBCURSOR" || pause -d: # Delete a file +d,Delete: # Delete a file printf "\033[1mDeleting \033[33m$BBCURSOR\033[0;1m...\033[0m " && confirm && rm -rf "$BBCURSOR" && bb +deselect:"$BBCURSOR" +refresh D: # Delete all selected files diff --git a/bterm.h b/bterm.h index deab9da..6092638 100644 --- a/bterm.h +++ b/bterm.h @@ -11,97 +11,70 @@ #define FILE__BTERM_H #include +#include -#define KEY_F1 (0xFFFF-0) -#define KEY_F2 (0xFFFF-1) -#define KEY_F3 (0xFFFF-2) -#define KEY_F4 (0xFFFF-3) -#define KEY_F5 (0xFFFF-4) -#define KEY_F6 (0xFFFF-5) -#define KEY_F7 (0xFFFF-6) -#define KEY_F8 (0xFFFF-7) -#define KEY_F9 (0xFFFF-8) -#define KEY_F10 (0xFFFF-9) -#define KEY_F11 (0xFFFF-10) -#define KEY_F12 (0xFFFF-11) -#define KEY_INSERT (0xFFFF-12) -#define KEY_DELETE (0xFFFF-13) -#define KEY_HOME (0xFFFF-14) -#define KEY_END (0xFFFF-15) -#define KEY_PGUP (0xFFFF-16) -#define KEY_PGDN (0xFFFF-17) -#define KEY_ARROW_UP (0xFFFF-18) -#define KEY_ARROW_DOWN (0xFFFF-19) -#define KEY_ARROW_LEFT (0xFFFF-20) -#define KEY_ARROW_RIGHT (0xFFFF-21) -#define KEY_MOUSE_LEFT (0xFFFF-22) -#define KEY_MOUSE_RIGHT (0xFFFF-23) -#define KEY_MOUSE_MIDDLE (0xFFFF-24) -#define KEY_MOUSE_RELEASE (0xFFFF-25) -#define KEY_MOUSE_WHEEL_UP (0xFFFF-26) -#define KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) -#define KEY_MOUSE_DOUBLE_LEFT (0xFFFF-28) +// Maximum time in milliseconds between double clicks +#define DOUBLECLICK_THRESHOLD 200 -/* These are all ASCII code points below SPACE character and a BACKSPACE key. */ -#define KEY_CTRL_TILDE 0x00 -#define KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ -#define KEY_CTRL_A 0x01 -#define KEY_CTRL_B 0x02 -#define KEY_CTRL_C 0x03 -#define KEY_CTRL_D 0x04 -#define KEY_CTRL_E 0x05 -#define KEY_CTRL_F 0x06 -#define KEY_CTRL_G 0x07 -#define KEY_BACKSPACE 0x08 -#define KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ -#define KEY_TAB 0x09 -#define KEY_CTRL_I 0x09 /* clash with 'TAB' */ -#define KEY_CTRL_J 0x0A -#define KEY_CTRL_K 0x0B -#define KEY_CTRL_L 0x0C -#define KEY_ENTER 0x0D -#define KEY_CTRL_M 0x0D /* clash with 'ENTER' */ -#define KEY_CTRL_N 0x0E -#define KEY_CTRL_O 0x0F -#define KEY_CTRL_P 0x10 -#define KEY_CTRL_Q 0x11 -#define KEY_CTRL_R 0x12 -#define KEY_CTRL_S 0x13 -#define KEY_CTRL_T 0x14 -#define KEY_CTRL_U 0x15 -#define KEY_CTRL_V 0x16 -#define KEY_CTRL_W 0x17 -#define KEY_CTRL_X 0x18 -#define KEY_CTRL_Y 0x19 -#define KEY_CTRL_Z 0x1A -#define KEY_ESC 0x1B -#define KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */ -#define KEY_CTRL_3 0x1B /* clash with 'ESC' */ -#define KEY_CTRL_4 0x1C -#define KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */ -#define KEY_CTRL_5 0x1D -#define KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */ -#define KEY_CTRL_6 0x1E -#define KEY_CTRL_7 0x1F -#define KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */ -#define KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */ + +typedef enum { + // ASCII chars: + KEY_CTRL_AT = 0x00, KEY_CTRL_A, KEY_CTRL_B, KEY_CTRL_C, KEY_CTRL_D, + KEY_CTRL_E, KEY_CTRL_F, KEY_CTRL_G, KEY_CTRL_H, KEY_CTRL_I, KEY_CTRL_J, + KEY_CTRL_K, KEY_CTRL_L, KEY_CTRL_M, KEY_CTRL_N, KEY_CTRL_O, KEY_CTRL_P, + KEY_CTRL_Q, KEY_CTRL_R, KEY_CTRL_S, KEY_CTRL_T, KEY_CTRL_U, KEY_CTRL_V, + KEY_CTRL_W, KEY_CTRL_X, KEY_CTRL_Y, KEY_CTRL_Z, + KEY_CTRL_LSQ_BRACKET, KEY_CTRL_BACKSLASH, KEY_CTRL_RSQ_BRACKET, + KEY_CTRL_CARET, KEY_CTRL_UNDERSCORE, KEY_SPACE, + // Printable chars would be here + KEY_BACKSPACE2 = 0x7F, + + // Non-ascii multi-byte keys: + KEY_F0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, + KEY_F9, KEY_F10, KEY_F11, KEY_F12, + KEY_INSERT, KEY_DELETE, KEY_HOME, KEY_END, KEY_PGUP, KEY_PGDN, + KEY_ARROW_UP, KEY_ARROW_DOWN, KEY_ARROW_LEFT, KEY_ARROW_RIGHT, + MOUSE_LEFT_PRESS, MOUSE_RIGHT_PRESS, MOUSE_MIDDLE_PRESS, + MOUSE_LEFT_DRAG, MOUSE_RIGHT_DRAG, MOUSE_MIDDLE_DRAG, + MOUSE_LEFT_RELEASE, MOUSE_RIGHT_RELEASE, MOUSE_MIDDLE_RELEASE, + MOUSE_LEFT_DOUBLE, MOUSE_RIGHT_DOUBLE, MOUSE_MIDDLE_DOUBLE, + MOUSE_WHEEL_RELEASE, MOUSE_WHEEL_PRESS, +} bkey_t; + +#define MOD_SUPER (1 << 9) +#define MOD_CTRL (1 << 10) +#define MOD_ALT (1 << 11) +#define MOD_SHIFT (1 << 12) + +// Overlapping key codes: +#define KEY_CTRL_BACKTICK 0x00 /* clash with ^@ */ +#define KEY_CTRL_2 0x00 /* clash with ^@ */ +#define KEY_BACKSPACE 0x08 /* clash with ^H */ +#define KEY_TAB 0x09 /* clash with ^I */ +#define KEY_ENTER 0x0D /* clash with ^M */ +#define KEY_ESC 0x1B /* clash with ^[ */ +#define KEY_CTRL_3 0x1B /* clash with ^[ */ +#define KEY_CTRL_4 0x1C /* clash with ^\ */ +#define KEY_CTRL_5 0x1D /* clash with ^] */ +#define KEY_CTRL_TILDE 0x1E /* clash with ^^ */ +#define KEY_CTRL_6 0x1E /* clash with ^^ */ +#define KEY_CTRL_7 0x1F /* clash with ^_ */ +#define KEY_CTRL_SLASH 0x1F /* clash with ^_ */ #define KEY_SPACE 0x20 #define KEY_BACKSPACE2 0x7F #define KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */ - // Terminal escape sequences: -#define CSI "\033[" #define T_WRAP "7" #define T_SHOW_CURSOR "25" #define T_MOUSE_XY "1000" #define T_MOUSE_CELL "1002" #define T_MOUSE_SGR "1006" #define T_ALT_SCREEN "1049" -#define T_ON(opt) CSI "?" opt "h" -#define T_OFF(opt) CSI "?" opt "l" +#define T_ON(opt) "\033[?" opt "h" +#define T_OFF(opt) "\033[?" opt "l" -#define move_cursor(f, x, y) fprintf((f), CSI "%d;%dH", (int)(y)+1, (int)(x)+1) +#define move_cursor(f, x, y) fprintf((f), "\033[%d;%dH", (int)(y)+1, (int)(x)+1) typedef struct { int key; @@ -109,49 +82,45 @@ typedef struct { } keyname_t; static keyname_t key_names[] = { + {KEY_SPACE, "Space"}, {KEY_BACKSPACE2, "Backspace"}, {KEY_F1, "F1"}, {KEY_F2, "F2"}, {KEY_F3, "F3"}, {KEY_F4, "F4"}, {KEY_F5, "F5"}, {KEY_F6, "F6"}, {KEY_F7, "F7"}, {KEY_F8, "F8"}, {KEY_F9, "F9"}, {KEY_F10, "F10"}, {KEY_F11, "F11"}, {KEY_F12, "F12"}, - {KEY_INSERT, "Insert"}, - {KEY_DELETE, "Delete"}, - {KEY_HOME, "Home"}, - {KEY_END, "End"}, + {KEY_INSERT, "Insert"}, {KEY_DELETE, "Delete"}, + {KEY_HOME, "Home"}, {KEY_END, "End"}, {KEY_PGUP, "PgUp"}, {KEY_PGUP, "Page Up"}, {KEY_PGDN, "PgDn"}, {KEY_PGDN, "Page Down"}, - {KEY_ARROW_UP, "Up"}, - {KEY_ARROW_DOWN, "Down"}, - {KEY_ARROW_LEFT, "Left"}, - {KEY_ARROW_RIGHT, "Right"}, - {KEY_MOUSE_LEFT, "Left click"}, - {KEY_MOUSE_RIGHT, "Right click"}, - {KEY_MOUSE_MIDDLE, "Middle click"}, - {KEY_MOUSE_RELEASE, "Mouse release"}, - {KEY_MOUSE_WHEEL_UP, "Mouse wheel up"}, - {KEY_MOUSE_WHEEL_DOWN, "Mouse wheel down"}, - {KEY_MOUSE_DOUBLE_LEFT, "Double left click"}, + {KEY_ARROW_UP, "Up"}, {KEY_ARROW_DOWN, "Down"}, {KEY_ARROW_LEFT, "Left"}, {KEY_ARROW_RIGHT, "Right"}, + {MOUSE_LEFT_PRESS, "Left press"}, {MOUSE_RIGHT_PRESS, "Right press"}, {MOUSE_MIDDLE_PRESS, "Middle press"}, + {MOUSE_LEFT_DRAG, "Left drag"}, {MOUSE_RIGHT_DRAG, "Right drag"}, {MOUSE_MIDDLE_DRAG, "Middle drag"}, + {MOUSE_LEFT_RELEASE, "Left click"}, {MOUSE_RIGHT_RELEASE, "Right click"}, {MOUSE_MIDDLE_RELEASE, "Middle click"}, + {MOUSE_LEFT_RELEASE, "Left up"}, {MOUSE_RIGHT_RELEASE, "Right up"}, {MOUSE_MIDDLE_RELEASE, "Middle up"}, + {MOUSE_LEFT_RELEASE, "Left release"}, {MOUSE_RIGHT_RELEASE, "Right release"}, {MOUSE_MIDDLE_RELEASE, "Middle release"}, + {MOUSE_LEFT_DOUBLE, "Double left click"}, {MOUSE_RIGHT_DOUBLE, "Double right click"}, {MOUSE_MIDDLE_DOUBLE, "Double middle click"}, + {MOUSE_WHEEL_RELEASE, "Mouse wheel up"}, {MOUSE_WHEEL_PRESS, "Mouse wheel down"}, + {KEY_TAB, "Tab"}, {KEY_ENTER, "Enter"}, {KEY_ENTER, "Return"}, {KEY_CTRL_A, "Ctrl-a"}, {KEY_CTRL_B, "Ctrl-b"}, {KEY_CTRL_C, "Ctrl-c"}, {KEY_CTRL_D, "Ctrl-d"}, {KEY_CTRL_E, "Ctrl-e"}, {KEY_CTRL_F, "Ctrl-f"}, - {KEY_CTRL_G, "Ctrl-g"}, {KEY_CTRL_H, "Ctrl-h"}, - {KEY_TAB, "Tab"}, + {KEY_CTRL_G, "Ctrl-g"}, {KEY_CTRL_H, "Ctrl-h"}, {KEY_CTRL_I, "Ctrl-i"}, {KEY_CTRL_J, "Ctrl-j"}, {KEY_CTRL_K, "Ctrl-k"}, {KEY_CTRL_L, "Ctrl-l"}, - {KEY_ENTER, "Enter"}, - {KEY_CTRL_N, "Ctrl-n"}, {KEY_CTRL_O, "Ctrl-o"}, {KEY_CTRL_P, "Ctrl-p"}, - {KEY_CTRL_Q, "Ctrl-q"}, {KEY_CTRL_R, "Ctrl-r"}, {KEY_CTRL_S, "Ctrl-s"}, - {KEY_CTRL_T, "Ctrl-t"}, {KEY_CTRL_U, "Ctrl-u"}, {KEY_CTRL_V, "Ctrl-v"}, - {KEY_CTRL_W, "Ctrl-w"}, {KEY_CTRL_X, "Ctrl-x"}, {KEY_CTRL_Y, "Ctrl-y"}, - {KEY_CTRL_Z, "Ctrl-z"}, + {KEY_CTRL_M, "Ctrl-m"}, {KEY_CTRL_N, "Ctrl-n"}, {KEY_CTRL_O, "Ctrl-o"}, + {KEY_CTRL_P, "Ctrl-p"}, {KEY_CTRL_Q, "Ctrl-q"}, {KEY_CTRL_R, "Ctrl-r"}, + {KEY_CTRL_S, "Ctrl-s"}, {KEY_CTRL_T, "Ctrl-t"}, {KEY_CTRL_U, "Ctrl-u"}, + {KEY_CTRL_V, "Ctrl-v"}, {KEY_CTRL_W, "Ctrl-w"}, {KEY_CTRL_X, "Ctrl-x"}, + {KEY_CTRL_Y, "Ctrl-y"}, {KEY_CTRL_Z, "Ctrl-z"}, {KEY_ESC, "Esc"}, {KEY_ESC, "Escape"}, - {KEY_CTRL_TILDE, "Ctrl-~"}, {KEY_CTRL_TILDE, "Ctrl-2"}, - {KEY_CTRL_BACKSLASH, "Ctrl-\\"}, {KEY_CTRL_BACKSLASH, "Ctrl-4"}, - {KEY_CTRL_RSQ_BRACKET, "Ctrl-]"}, {KEY_CTRL_RSQ_BRACKET, "Ctrl-5"}, - {KEY_CTRL_6, "Ctrl-6"}, - {KEY_CTRL_SLASH, "Ctrl-_"}, {KEY_CTRL_SLASH, "Ctrl-/"}, {KEY_CTRL_SLASH, "Ctrl-7"}, - {KEY_SPACE, "Space"}, - {KEY_BACKSPACE2, "Backspace"}, + {KEY_CTRL_TILDE, "Ctrl-~"}, {KEY_CTRL_BACKSLASH, "Ctrl-\\"}, + {KEY_CTRL_LSQ_BRACKET, "Ctrl-]"}, {KEY_CTRL_RSQ_BRACKET, "Ctrl-]"}, + {KEY_CTRL_UNDERSCORE, "Ctrl-_"}, {KEY_CTRL_SLASH, "Ctrl-/"}, + {KEY_CTRL_AT, "Ctrl-@"}, {KEY_CTRL_CARET, "Ctrl-^"}, + {KEY_CTRL_BACKTICK, "Ctrl-`"}, + {KEY_CTRL_2, "Ctrl-2"}, {KEY_CTRL_3, "Ctrl-3"}, {KEY_CTRL_4, "Ctrl-4"}, + {KEY_CTRL_5, "Ctrl-5"}, {KEY_CTRL_6, "Ctrl-6"}, {KEY_CTRL_7, "Ctrl-7"}, + {KEY_CTRL_5, "Ctrl-8"}, {KEY_CTRL_6, "Ctrl-9"}, }; int bgetkey(FILE *in, int *mouse_x, int *mouse_y); -const char *bkeyname(int key); +char *bkeyname(int key, char *buf); int bkeywithname(const char *name); static inline int nextchar(int fd) @@ -160,6 +129,13 @@ static inline int nextchar(int fd) return read(fd, &c, 1) == 1 ? c : -1; } +static inline char nextnum(int fd, char c, int *n) +{ + for (*n = 0; '0' <= c && c <= '9'; c = nextchar(fd)) + *n = 10*(*n) + (c - '0'); + return c; +} + /* * Get one key of input from the given file. Returns -1 on failure. * If mouse_x or mouse_y are non-null and a mouse event occurs, they will be @@ -167,8 +143,10 @@ static inline int nextchar(int fd) */ int bgetkey(FILE *in, int *mouse_x, int *mouse_y) { + if (mouse_x) *mouse_x = -1; + if (mouse_y) *mouse_y = -1; int fd = fileno(in); - int numcode = 0, super = 0; + int numcode = 0, modifiers = 0; int c = nextchar(fd); if (c == '\x1b') goto escape; @@ -182,76 +160,110 @@ int bgetkey(FILE *in, int *mouse_x, int *mouse_y) return KEY_ESC; switch (c) { - case '\x1b': ++super; goto escape; - case '[': goto CSI_start; + case '\x1b': return KEY_ESC; + case '[': c = nextchar(fd); goto CSI_start; case 'P': goto DCS; case 'O': goto SS3; - default: return -1; + default: return MOD_ALT | c; } CSI_start: - c = nextchar(fd); if (c == -1) - return -1; + return MOD_ALT | '['; switch (c) { - case 'A': return KEY_ARROW_UP; - case 'B': return KEY_ARROW_DOWN; - case 'C': return KEY_ARROW_RIGHT; - case 'D': return KEY_ARROW_LEFT; - case 'F': return KEY_END; - case 'H': return KEY_HOME; - case 'P': return KEY_DELETE; + case 'A': return modifiers | KEY_ARROW_UP; + case 'B': return modifiers | KEY_ARROW_DOWN; + case 'C': return modifiers | KEY_ARROW_RIGHT; + case 'D': return modifiers | KEY_ARROW_LEFT; + case 'F': return modifiers | KEY_END; + case 'H': return modifiers | KEY_HOME; + case 'M': return MOD_CTRL | KEY_DELETE; + case 'P': return modifiers | (numcode == 1 ? KEY_F1 : KEY_DELETE); + case 'Q': return numcode == 1 ? (modifiers | KEY_F2) : -1; + case 'R': return numcode == 1 ? (modifiers | KEY_F3) : -1; + case 'S': return numcode == 1 ? (modifiers | KEY_F4) : -1; case '~': switch (numcode) { - case 1: return KEY_HOME; - case 2: return KEY_INSERT; - case 3: return KEY_DELETE; - case 4: return KEY_END; - case 5: return KEY_PGUP; - case 6: return KEY_PGDN; - case 15: return KEY_F5; - case 17: return KEY_F6; - case 18: return KEY_F7; - case 19: return KEY_F8; - case 20: return KEY_F9; - case 21: return KEY_F10; - case 23: return KEY_F11; - case 24: return KEY_F12; + case 1: return modifiers | KEY_HOME; + case 2: return modifiers | KEY_INSERT; + case 3: return modifiers | KEY_DELETE; + case 4: return modifiers | KEY_END; + case 5: return modifiers | KEY_PGUP; + case 6: return modifiers | KEY_PGDN; + case 7: return modifiers | KEY_HOME; + case 8: return modifiers | KEY_END; + case 10: return modifiers | KEY_F0; + case 11: return modifiers | KEY_F1; + case 12: return modifiers | KEY_F2; + case 13: return modifiers | KEY_F3; + case 14: return modifiers | KEY_F4; + case 15: return modifiers | KEY_F5; + case 17: return modifiers | KEY_F6; + case 18: return modifiers | KEY_F7; + case 19: return modifiers | KEY_F8; + case 20: return modifiers | KEY_F9; + case 21: return modifiers | KEY_F10; + case 23: return modifiers | KEY_F11; + case 24: return modifiers | KEY_F12; } return -1; case '<': { // Mouse clicks int buttons = 0, x = 0, y = 0; - char buf; - while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') - buttons = buttons * 10 + (buf - '0'); - if (buf != ';') return -1; - while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') - x = x * 10 + (buf - '0'); - if (buf != ';') return -1; - while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9') - y = y * 10 + (buf - '0'); - if (buf != 'm' && buf != 'M') return -1; + c = nextnum(fd, nextchar(fd), &buttons); + if (c != ';') return -1; + c = nextnum(fd, nextchar(fd), &x); + if (c != ';') return -1; + c = nextnum(fd, nextchar(fd), &y); + if (c != 'm' && c != 'M') return -1; if (mouse_x) *mouse_x = x - 1; if (mouse_y) *mouse_y = y - 1; - if (buf == 'm') - return KEY_MOUSE_RELEASE; - switch (buttons) { - case 64: return KEY_MOUSE_WHEEL_UP; - case 65: return KEY_MOUSE_WHEEL_DOWN; - case 0: return KEY_MOUSE_LEFT; - case 1: return KEY_MOUSE_RIGHT; - case 2: return KEY_MOUSE_MIDDLE; + if (buttons & 4) modifiers |= MOD_SHIFT; + if (buttons & 8) modifiers |= MOD_SUPER; + if (buttons & 16) modifiers |= MOD_CTRL; + int key = -1; + switch (buttons & ~(4|8|16)) { + case 0: key = c == 'm' ? MOUSE_LEFT_RELEASE : MOUSE_LEFT_PRESS; break; + case 1: key = c == 'm' ? MOUSE_MIDDLE_RELEASE : MOUSE_MIDDLE_PRESS; break; + case 2: key = c == 'm' ? MOUSE_RIGHT_RELEASE : MOUSE_RIGHT_PRESS; break; + case 32: key = MOUSE_LEFT_DRAG; break; + case 33: key = MOUSE_MIDDLE_DRAG; break; + case 34: key = MOUSE_RIGHT_DRAG; break; + case 64: key = MOUSE_WHEEL_RELEASE; break; + case 65: key = MOUSE_WHEEL_PRESS; break; default: return -1; } - break; + if (key == MOUSE_LEFT_RELEASE || key == MOUSE_RIGHT_RELEASE || key == MOUSE_MIDDLE_RELEASE) { + static int lastclick = -1; + static struct timespec lastclicktime = {0, 0}; + struct timespec clicktime; + clock_gettime(CLOCK_MONOTONIC, &clicktime); + if (key == lastclick) { + double dt_ms = 1e3*(double)(clicktime.tv_sec - lastclicktime.tv_sec) + + 1e-6*(double)(clicktime.tv_nsec - lastclicktime.tv_nsec); + if (dt_ms < DOUBLECLICK_THRESHOLD) { + switch (key) { + case MOUSE_LEFT_RELEASE: key = MOUSE_LEFT_DOUBLE; break; + case MOUSE_RIGHT_RELEASE: key = MOUSE_RIGHT_DOUBLE; break; + case MOUSE_MIDDLE_RELEASE: key = MOUSE_MIDDLE_DOUBLE; break; + } + } + } + lastclicktime = clicktime; + lastclick = key; + } + return modifiers | key; } default: if ('0' <= c && c <= '9') { // Ps prefix - numcode = 10*numcode + (c - '0'); + c = nextnum(fd, c, &numcode); + if (c == ';') { + c = nextnum(fd, nextchar(fd), &modifiers); + modifiers = (modifiers >> 1) << 9; + } goto CSI_start; } } @@ -272,29 +284,49 @@ int bgetkey(FILE *in, int *mouse_x, int *mouse_y) } /* - * Return the name of a key, if one exists and is different from the key itself - * (i.e. bkeyname('c') == NULL, bkeyname(' ') == "Space") + * Populate `buf` with the name of a key. */ -const char *bkeyname(int key) +char *bkeyname(int key, char *buf) { + if (key & MOD_SUPER) buf = stpcpy(buf, "Super-"); + if (key & MOD_CTRL) buf = stpcpy(buf, "Ctrl-"); + if (key & MOD_ALT) buf = stpcpy(buf, "Alt-"); + if (key & MOD_SHIFT) buf = stpcpy(buf, "Shift-"); + key &= ~(MOD_SUPER | MOD_CTRL | MOD_ALT | MOD_SHIFT); for (int i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) { - if (key_names[i].key == key) - return key_names[i].name; + if (key_names[i].key == key) { + return stpcpy(buf, key_names[i].name); + } } - return NULL; + if (' ' < key && key <= '~') + return buf + sprintf(buf, "%c", key); + else + return buf + sprintf(buf, "\\x%02X", key); } /* * Return the key with the given name, if one exists, otherwise -1. - * (i.e. bkeyname("Space") == ' ', bkeyname("x") == -1, bkeyname("???") == -1) + * (i.e. bkeyname("Space") == ' ', bkeyname("x") == 'x', bkeywithname("F1") == KEY_F1, bkeyname("???") == -1) */ int bkeywithname(const char *name) { + int modifiers = 0; + static const struct { const char *prefix; int modifier; } modnames[] = { + {"Super-", MOD_SUPER}, {"Ctrl-", MOD_CTRL}, {"Alt-", MOD_ALT}, {"Shift-", MOD_SHIFT} + }; + check_names: for (int i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) { if (strcmp(key_names[i].name, name) == 0) - return key_names[i].key; + return modifiers | key_names[i].key; } - return -1; + for (int i = 0; i < sizeof(modnames)/sizeof(modnames[0]); i++) { + if (strncmp(name, modnames[i].prefix, strlen(modnames[i].prefix)) == 0) { + modifiers |= modnames[i].modifier; + name += strlen(modnames[i].prefix); + goto check_names; + } + } + return strlen(name) == 1 ? name[0] : -1; } #endif -- cgit v1.2.3