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.
This commit is contained in:
Bruce Hill 2019-10-12 16:04:19 -07:00
parent e9c75a7837
commit 9e4e0ee314
5 changed files with 271 additions and 259 deletions

2
API.md
View File

@ -47,6 +47,7 @@ is one of the following commands (or a unique prefix of one):
- `dotfiles:[01]` Whether dotfiles are visible
- `goto:<filename>` Move the cursor to <filename> (changing directory if needed)
- `interleave:[01]` Whether or not directories should be interleaved with files in the display
- `kill` Exit with failure
- `move:<num*>` 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:<filename>` Select <filename>
- `sort:([+-]method)+` Set sorting method (+: normal, -: reverse), additional methods act as tiebreaker
- `spread:<num*>` Spread the selection state at the cursor
- `suspend` Suspend `bb` (SIGSTP signal)
- `toggle:<filename>` Toggle the selection status of <filename>
For any of these commands (e.g. `+select`), an empty parameter followed by

152
bb.c
View File

@ -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 (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;
}
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;
}
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, "<column label>");
} 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;

2
bb.h
View File

@ -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

View File

@ -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" = "<column label>" ]; 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

360
bterm.h
View File

@ -11,97 +11,70 @@
#define FILE__BTERM_H
#include <stdio.h>
#include <time.h>
#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