Some major refactoring including moving more stuff into bterm.h (renamed

from keys.h), a custom readline (`read` was buggy), better
customization, and improved renaming bindings.
This commit is contained in:
Bruce Hill 2019-05-28 21:36:42 -07:00
parent 92a198d6b6
commit 8d4e4aeba4
5 changed files with 634 additions and 404 deletions

View File

@ -13,7 +13,7 @@ clean:
config.h:
cp config.def.h config.h
$(NAME): $(NAME).c keys.h config.h
$(NAME): $(NAME).c bterm.h config.h
$(CC) $(NAME).c $(LIBS) $(CFLAGS) $(G) -o $(NAME)
test: $(NAME)

253
bb.c
View File

@ -22,9 +22,9 @@
#include <unistd.h>
#include "config.h"
#include "keys.h"
#include "bterm.h"
#define BB_VERSION "0.9.1"
#define BB_VERSION "0.10.0"
#ifndef PATH_MAX
#define PATH_MAX 4096
@ -34,23 +34,10 @@
#define MIN(a,b) ((a) > (b) ? (b) : (a))
#define IS_SELECTED(p) (((p)->atme) != NULL)
// 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"
static const char *T_ENTER_BBMODE = T_OFF(T_SHOW_CURSOR) T_ON(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR);
static const char *T_LEAVE_BBMODE = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR ";" T_ALT_SCREEN);
static const char *T_LEAVE_BBMODE_PARTIAL = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR);
#define move_cursor(f, x, y) fprintf((f), CSI "%d;%dH", (int)(y)+1, (int)(x)+1)
#define err(...) do { \
close_term(); \
fputs(T_OFF(T_ALT_SCREEN), stdout); \
@ -92,11 +79,10 @@ typedef struct entry_s {
char *name, *linkname;
// TODO: inline only the relevant fields:
struct stat info;
mode_t linkedmode;
int refcount : 2; // Should only be between 0-2
int isdir : 1;
int islink : 1;
int needs_esc : 1;
int link_needs_esc : 1;
int no_esc : 1;
int link_no_esc : 1;
char fullname[1]; // Must be last
} entry_t;
@ -111,6 +97,8 @@ typedef struct bb_s {
int colwidths[10];
} bb_t;
typedef enum { BB_NOP = 0, BB_INVALID, BB_REFRESH, BB_QUIT } bb_result_t;
// Functions
static void update_term_size(int sig);
static void init_term(void);
@ -118,6 +106,8 @@ static void cleanup_and_exit(int sig);
static void close_term(void);
static void* memcheck(void *p);
static int run_cmd_on_selection(bb_t *bb, const char *cmd);
static int fputs_escaped(FILE *f, const char *str, const char *color);
static const char *color_of(mode_t mode);
static void render(bb_t *bb, int lazy);
static int compare_files(void *r, const void *v1, const void *v2);
static int find_file(bb_t *bb, const char *name);
@ -126,8 +116,10 @@ static int select_file(bb_t *bb, entry_t *e);
static int deselect_file(bb_t *bb, entry_t *e);
static void set_cursor(bb_t *bb, int i);
static void set_scroll(bb_t *bb, int i);
static entry_t *load_entry(const char *path);
static void populate_files(bb_t *bb, const char *path);
static void sort_files(bb_t *bb);
static bb_result_t execute_cmd(bb_t *bb, const char *cmd);
static void explore(bb_t *bb, const char *path);
static void print_bindings(int verbose);
@ -169,7 +161,8 @@ void init_term(void)
bb_termios.c_cflag |= (unsigned long)CS8;
bb_termios.c_cc[VMIN] = 0;
bb_termios.c_cc[VTIME] = 0;
tcsetattr(fileno(tty_out), TCSAFLUSH, &bb_termios);
if (tcsetattr(fileno(tty_out), TCSAFLUSH, &bb_termios) == -1)
err("Couldn't tcsetattr");
update_term_size(0);
signal(SIGWINCH, update_term_size);
// Initiate mouse tracking and disable text wrapping:
@ -181,9 +174,6 @@ void cleanup_and_exit(int sig)
{
(void)sig;
close_term();
fputs(T_OFF(T_ALT_SCREEN), stdout);
fputs(T_ON(T_WRAP), stdout);
fflush(stdout);
unlink(cmdfilename);
exit(EXIT_FAILURE);
}
@ -191,17 +181,20 @@ void cleanup_and_exit(int sig)
void close_term(void)
{
if (tty_out) {
fflush(tty_out);
tcsetattr(fileno(tty_out), TCSAFLUSH, &orig_termios);
fputs(T_LEAVE_BBMODE_PARTIAL, tty_out);
fputs(T_ON(T_WRAP), tty_out);
fflush(tty_out);
fclose(tty_out);
tty_out = NULL;
fclose(tty_in);
tty_in = NULL;
} else {
fputs(T_LEAVE_BBMODE_PARTIAL, stdout);
fputs(T_ON(T_WRAP), stdout);
fflush(stdout);
}
fputs(T_LEAVE_BBMODE_PARTIAL, stdout);
fputs(T_ON(T_WRAP), stdout);
fflush(stdout);
signal(SIGWINCH, SIG_IGN);
signal(SIGWINCH, SIG_DFL);
}
void* memcheck(void *p)
@ -237,8 +230,8 @@ int run_cmd_on_selection(bb_t *bb, const char *cmd)
}
args[i] = NULL;
setenv("BBCURSOR", bb->files[bb->cursor]->name, 1);
setenv("BBFULLCURSOR", bb->files[bb->cursor]->fullname, 1);
setenv("BBSELECTED", bb->firstselected ? "1" : "", 1);
setenv("BBCURSOR", bb->files[bb->cursor]->fullname, 1);
execvp("sh", args);
err("Failed to execute command: '%s'", cmd);
@ -255,7 +248,7 @@ int run_cmd_on_selection(bb_t *bb, const char *cmd)
return status;
}
static int fputs_escaped(FILE *f, const char *str, const char *color)
int fputs_escaped(FILE *f, const char *str, const char *color)
{
static const char *escapes = " abtnvfr e";
int escaped = 0;
@ -273,6 +266,18 @@ static int fputs_escaped(FILE *f, const char *str, const char *color)
return escaped;
}
const char *color_of(mode_t mode)
{
if (S_ISDIR(mode))
return DIR_COLOR;
else if (S_ISLNK(mode))
return LINK_COLOR;
else if (mode & (S_IXUSR | S_IXGRP | S_IXOTH))
return EXECUTABLE_COLOR;
else
return NORMAL_COLOR;
}
void render(bb_t *bb, int lazy)
{
static int lastcursor = -1, lastscroll = -1;
@ -330,13 +335,14 @@ void render(bb_t *bb, int lazy)
if (!lazy) {
// Path
move_cursor(tty_out, 0, 0);
const char *color = "\033[0;1;37m";
const char *color = TITLE_COLOR;
fputs(color, tty_out);
fputs_escaped(tty_out, bb->path, color);
fputs(" \033[K\033[0m", tty_out);
// Columns
move_cursor(tty_out, 0, 1);
fputs("\033[41m \033[0;44;30m\033[K", tty_out);
fputs(" \033[0;44;30m\033[K", tty_out);
int x = 1;
for (int col = 0; col < cols; col++) {
move_cursor(tty_out, x, 1);
@ -391,20 +397,9 @@ void render(bb_t *bb, int lazy)
entry_t *entry = files[i];
if (IS_SELECTED(entry))
fputs("\033[41m \033[0m", tty_out);
else
fputs(" ", tty_out);
fputs(IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR, tty_out);
const char *color;
if (i == bb->cursor)
color = CURSOR_COLOR;
else if (entry->islink)
color = entry->isdir ? LINKDIR_COLOR : LINK_COLOR;
else if (entry->isdir)
color = DIR_COLOR;
else
color = NORMAL_COLOR;
const char *color = (i == bb->cursor) ? CURSOR_COLOR : color_of(entry->info.st_mode);
fputs(color, tty_out);
int x = 1;
@ -459,20 +454,22 @@ void render(bb_t *bb, int lazy)
break;
case 'n': {
if (entry->needs_esc)
entry->needs_esc &= fputs_escaped(tty_out, entry->name, color) > 0;
else fputs(entry->name, tty_out);
if (entry->no_esc) fputs(entry->name, tty_out);
else entry->no_esc |= !fputs_escaped(tty_out, entry->name, color);
if (entry->isdir)
if (S_ISDIR(S_ISLNK(entry->info.st_mode) ? entry->linkedmode : entry->info.st_mode))
fputs("/", tty_out);
if (entry->linkname) {
fputs("\033[2m -> ", tty_out);
if (entry->link_needs_esc)
entry->link_needs_esc &= fputs_escaped(tty_out, entry->linkname, color) > 0;
else fputs(entry->linkname, tty_out);
if (i != bb->cursor)
fputs("\033[37m", tty_out);
fputs("\033[2m -> \033[22;3m", tty_out);
color = i == bb->cursor ? CURSOR_COLOR : color_of(entry->linkedmode);
fputs(color, tty_out);
if (entry->link_no_esc) fputs(entry->linkname, tty_out);
else entry->link_no_esc |= !fputs_escaped(tty_out, entry->linkname, color);
if (entry->isdir)
if (S_ISDIR(entry->linkedmode))
fputs("/", tty_out);
fputs("\033[22m", tty_out);
@ -527,8 +524,10 @@ int compare_files(void *v, const void *v1, const void *v2)
const entry_t *f1 = *((const entry_t**)v1), *f2 = *((const entry_t**)v2);
int diff = 0;
if (!bb->options['i']) {
diff = -(f1->isdir - f2->isdir);
if (diff) return -diff;
// Unless interleave mode is on, sort dirs before files
int d1 = S_ISDIR(f1->info.st_mode) || (S_ISLNK(f1->info.st_mode) && S_ISDIR(f1->linkedmode));
int d2 = S_ISDIR(f2->info.st_mode) || (S_ISLNK(f2->info.st_mode) && S_ISDIR(f2->linkedmode));
if (d1 != d2) return d2 - d1;
}
if (sort == SORT_NAME) {
const char *p1 = f1->name, *p2 = f2->name;
@ -591,9 +590,10 @@ void clear_selection(bb_t *bb)
{
for (entry_t *next, *e = bb->firstselected; e; e = next) {
next = e->next;
*(e->atme) = NULL;
e->atme = NULL;
if (--e->refcount <= 0) free(e);
}
bb->firstselected = NULL;
}
int select_file(bb_t *bb, entry_t *e)
@ -663,6 +663,32 @@ void set_scroll(bb_t *bb, int newscroll)
if (bb->cursor < 0) bb->cursor = 0;
}
entry_t *load_entry(const char *path)
{
ssize_t linkpathlen = -1;
char linkbuf[PATH_MAX];
struct stat linkedstat, filestat;
if (lstat(path, &filestat) == -1) return NULL;
if (S_ISLNK(filestat.st_mode)) {
linkpathlen = readlink(path, linkbuf, sizeof(linkbuf));
if (linkpathlen < 0) err("Couldn't read link: '%s'", path);
linkbuf[linkpathlen] = 0;
if (stat(path, &linkedstat) == -1) memset(&linkedstat, 0, sizeof(linkedstat));
}
size_t entry_size = sizeof(entry_t) + (strlen(path) + 1) + (size_t)(linkpathlen + 1);
entry_t *entry = memcheck(calloc(entry_size, 1));
char *end = stpcpy(entry->fullname, path);
if (linkpathlen >= 0)
entry->linkname = strcpy(end + 1, linkbuf);
entry->name = strrchr(entry->fullname, '/');
if (!entry->name) err("No slash found in '%s'", entry->fullname);
++entry->name;
if (S_ISLNK(filestat.st_mode))
entry->linkedmode = linkedstat.st_mode;
entry->info = filestat;
return entry;
}
void populate_files(bb_t *bb, const char *path)
{
ino_t old_inode = 0;
@ -707,7 +733,9 @@ void populate_files(bb_t *bb, const char *path)
err("Couldn't open dir: %s", bb->path);
size_t pathlen = strlen(bb->path);
size_t filecap = 0;
char linkbuf[PATH_MAX+1];
char pathbuf[PATH_MAX];
strcpy(pathbuf, path);
pathbuf[pathlen] = '/';
for (struct dirent *dp; (dp = readdir(dir)) != NULL; ) {
if (dp->d_name[0] == '.' && dp->d_name[1] == '\0')
continue;
@ -729,35 +757,10 @@ void populate_files(bb_t *bb, const char *path)
}
}
ssize_t linkpathlen = -1;
linkbuf[0] = 0;
if (dp->d_type == DT_LNK) {
linkpathlen = readlink(dp->d_name, linkbuf, sizeof(linkbuf));
linkbuf[linkpathlen] = 0;
}
entry_t *entry = memcheck(calloc(sizeof(entry_t) + pathlen + strlen(dp->d_name) + 2 + (size_t)(linkpathlen + 1), 1));
if (pathlen > PATH_MAX) err("Path is too big");
strncpy(entry->fullname, bb->path, pathlen);
entry->fullname[pathlen] = '/';
entry->name = &entry->fullname[pathlen + 1];
strcpy(entry->name, dp->d_name);
if (linkpathlen >= 0) {
entry->linkname = entry->name + strlen(dp->d_name) + 2;
strncpy(entry->linkname, linkbuf, linkpathlen+1);
}
entry->needs_esc = 1; // populate on-the-fly
entry->link_needs_esc = 1; // populate on-the-fly
entry->isdir = dp->d_type == DT_DIR;
entry->islink = dp->d_type == DT_LNK;
strcpy(&pathbuf[pathlen+1], dp->d_name);
entry_t *entry = load_entry(pathbuf);
if (!entry) err("Failed to load entry: '%s'", pathbuf);
++entry->refcount;
if (!entry->isdir && entry->islink) {
struct stat statbuf;
if (stat(entry->fullname, &statbuf) == 0)
entry->isdir = S_ISDIR(statbuf.st_mode);
}
entry->next = NULL; entry->atme = NULL;
lstat(entry->fullname, &entry->info);
bb->files[bb->nfiles++] = entry;
next_file:
continue;
@ -791,9 +794,12 @@ void sort_files(bb_t *bb)
if (bb->options['s'] == SORT_RANDOM) {
entry_t **files = &bb->files[1];
int ndirs = 0, nents = bb->nfiles - 1;
for (int i = 0; i < nents; i++) {
if (bb->files[i]->isdir) ++ndirs;
else break;
if (!bb->options['i']) {
for (int i = 0; i < nents; i++) {
if (S_ISDIR(bb->files[i]->info.st_mode)
|| (S_ISLNK(bb->files[i]->info.st_mode) && S_ISDIR(bb->files[i]->linkedmode)))
++ndirs;
}
}
for (int i = 0; i < ndirs - 1; i++) {
int j = i + rand() / (RAND_MAX / (ndirs - 1 - i));
@ -816,8 +822,7 @@ void sort_files(bb_t *bb)
}
}
static enum { BB_NOP = 0, BB_INVALID, BB_REFRESH, BB_DIRTY, BB_QUIT }
execute_cmd(bb_t *bb, const char *cmd)
bb_result_t execute_cmd(bb_t *bb, const char *cmd)
{
char *value = strchr(cmd, ':');
if (value) ++value;
@ -857,7 +862,7 @@ execute_cmd(bb_t *bb, const char *cmd)
if (f >= 0) select_file(bb, bb->files[f]);
// TODO: support selecting files in other directories
}
return BB_DIRTY;
return BB_REFRESH;
}
case 'c': { // cd:
char pbuf[PATH_MAX];
@ -898,7 +903,7 @@ execute_cmd(bb_t *bb, const char *cmd)
entry_t *e = bb->files[f];
if (IS_SELECTED(e)) deselect_file(bb, e);
else select_file(bb, e);
return f == bb->cursor ? BB_NOP : BB_DIRTY;
return f == bb->cursor ? BB_NOP : BB_REFRESH;
}
case 'o': { // options:
if (!value) return BB_INVALID;
@ -923,12 +928,12 @@ execute_cmd(bb_t *bb, const char *cmd)
if (!value) value = bb->files[bb->cursor]->name;
if (strcmp(value, "*") == 0) {
clear_selection(bb);
return BB_DIRTY;
return BB_REFRESH;
} else {
int f = find_file(bb, value);
if (f >= 0)
select_file(bb, bb->files[f]);
return f == bb->cursor ? BB_NOP : BB_DIRTY;
return f == bb->cursor ? BB_NOP : BB_REFRESH;
}
case 'g': { // goto:
@ -987,7 +992,7 @@ execute_cmd(bb_t *bb, const char *cmd)
deselect_file(bb, bb->files[i]);
}
if (abs(oldcur - bb->cursor) > 1)
return BB_DIRTY;
return BB_REFRESH;
}
return BB_NOP;
}
@ -1002,7 +1007,7 @@ execute_cmd(bb_t *bb, const char *cmd)
}
return BB_INVALID;
}
default: break;
default: err("UNKNOWN COMMAND: '%s'", cmd); break;
}
return BB_INVALID;
}
@ -1066,9 +1071,9 @@ void explore(bb_t *bb, const char *path)
switch (execute_cmd(bb, cmd)) {
case BB_INVALID:
break;
case BB_DIRTY:
lazy = 0;
case BB_NOP:
free(cmd);
fclose(cmdfile);
goto redraw;
case BB_REFRESH:
free(cmd);
@ -1090,7 +1095,7 @@ void explore(bb_t *bb, const char *path)
int key;
get_keyboard_input:
key = term_getkey(fileno(tty_in), &mouse_x, &mouse_y, KEY_DELAY);
key = bgetkey(tty_in, &mouse_x, &mouse_y, KEY_DELAY);
switch (key) {
case KEY_MOUSE_LEFT: {
struct timespec clicktime;
@ -1137,10 +1142,8 @@ void explore(bb_t *bb, const char *path)
goto quit; // Unreachable
case KEY_CTRL_Z:
fputs(T_OFF(T_ALT_SCREEN), tty_out);
close_term();
fputs(T_OFF(T_ALT_SCREEN), stdout);
fputs(T_ON(T_WRAP), stdout);
fflush(stdout);
raise(SIGTSTP);
init_term();
fputs(T_ON(T_ALT_SCREEN), tty_out);
@ -1150,11 +1153,11 @@ void explore(bb_t *bb, const char *path)
case KEY_CTRL_H: {
move_cursor(tty_out, 0,termheight-1);
fputs("\033[K\033[33;1mPress any key...\033[0m", tty_out);
while ((key = term_getkey(fileno(tty_in), &mouse_x, &mouse_y, 1000)) == -1)
while ((key = bgetkey(tty_in, &mouse_x, &mouse_y, 1000)) == -1)
;
move_cursor(tty_out, 0,termheight-1);
fputs("\033[K\033[1m<\033[33m", tty_out);
const char *name = keyname(key);
const char *name = bkeyname(key);
if (name) fputs(name, tty_out);
else if (' ' <= key && key <= '~')
fputc((char)key, tty_out);
@ -1207,7 +1210,6 @@ void explore(bb_t *bb, const char *path)
if (binding->command[0] == '+') {
switch (execute_cmd(bb, binding->command + 1)) {
case BB_INVALID: break;
case BB_DIRTY: lazy = 0;
case BB_NOP: goto redraw;
case BB_REFRESH: goto refresh;
case BB_QUIT: goto quit;
@ -1215,14 +1217,15 @@ void explore(bb_t *bb, const char *path)
goto get_keyboard_input;
}
move_cursor(tty_out, 0, termheight-1);
close_term();
if (binding->flags & NORMAL_TERM) {
fputs(T_OFF(T_ALT_SCREEN), stdout);
fputs(T_ON(T_WRAP), stdout);
fflush(stdout);
fputs(T_OFF(T_ALT_SCREEN), tty_out);
fputs(T_ON(T_WRAP), tty_out);
}
if (binding->flags & SHOW_CURSOR)
fputs(T_ON(T_SHOW_CURSOR), stdout);
if (binding->flags & AT_CURSOR && !bb->firstselected) {
move_cursor(tty_out, 0, 3 + bb->cursor - bb->scroll);
fputs("\033[K", tty_out);
}
close_term();
run_cmd_on_selection(bb, binding->command);
init_term();
if (binding->flags & NORMAL_TERM)
@ -1239,9 +1242,6 @@ void explore(bb_t *bb, const char *path)
populate_files(bb, NULL);
fputs(T_LEAVE_BBMODE, tty_out);
close_term();
fputs(T_OFF(T_ALT_SCREEN), stdout);
fputs(T_ON(T_WRAP), stdout);
fflush(stdout);
}
void print_bindings(int verbose)
@ -1259,7 +1259,7 @@ void print_bindings(int verbose)
for (int j = 0; bindings[i].keys[j]; j++) {
if (j > 0) *(p++) = ',';
int key = bindings[i].keys[j];
const char *name = keyname(key);
const char *name = bkeyname(key);
if (name)
p = stpcpy(p, name);
else if (' ' <= key && key <= '~')
@ -1327,6 +1327,17 @@ int main(int argc, char *argv[])
int i;
for (i = 1; i < argc; i++) {
if (argv[i][0] == '?') {
fclose(cmdfile);
init_term();
char *line = breadline(tty_in, tty_out, argv[i] + 1, argv[i+1]);
close_term();
if (!line) return 1;
fputs(line, stdout);
free(line);
fflush(stdout);
return 0;
}
if (argv[i][0] == '+') {
fwrite(argv[i]+1, sizeof(char), strlen(argv[i]+1)+1, cmdfile);
continue;
@ -1371,9 +1382,13 @@ int main(int argc, char *argv[])
if (cmdfile)
fclose(cmdfile);
if (!initial_path) {
if (!initial_path)
return 0;
}
// Default values
setenv("SHELL", "bash", 0);
setenv("EDITOR", "nano", 0);
setenv("PAGER", "less", 0);
signal(SIGTERM, cleanup_and_exit);
signal(SIGINT, cleanup_and_exit);

431
bterm.h Normal file
View File

@ -0,0 +1,431 @@
/*
* bterm.h
* Copyright 2019 Bruce Hill
* Released under the MIT License
*
* Definitions of some basic terminal stuff, like reading keys
* and some terminal escape sequences.
*/
#ifndef FILE__BTERM_H
#define FILE__BTERM_H
#include <poll.h>
#include <stdio.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)
/* 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' */
#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 move_cursor(f, x, y) fprintf((f), CSI "%d;%dH", (int)(y)+1, (int)(x)+1)
#define ESC_DELAY 10
int bgetkey(FILE *in, int *mouse_x, int *mouse_y, int timeout);
const char *bkeyname(int key);
char *breadline(FILE *in, FILE *out, const char *prompt, const char *initial);
static inline int nextchar(int fd, int timeout)
{
char c;
struct pollfd pfd = {fd, POLLIN, 0};
if (poll(&pfd, 1, timeout) > 0)
return read(fd, &c, 1) == 1 ? c : -1;
return -1;
}
/**
* 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
* set to the position of the mouse (0-indexed).
*/
int bgetkey(FILE *in, int *mouse_x, int *mouse_y, int timeout)
{
int fd = fileno(in);
int numcode = 0, super = 0;
int c = nextchar(fd, timeout);
if (c == '\x1b')
goto escape;
return c;
escape:
c = nextchar(fd, ESC_DELAY);
// Actual escape key:
if (c < 0)
return KEY_ESC;
switch (c) {
case '\x1b': ++super; goto escape;
case '[': goto CSI_start;
case 'P': goto DCS;
case 'O': goto SS3;
default: return -1;
}
CSI_start:
c = nextchar(fd, ESC_DELAY);
if (c == -1)
return -1;
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 '~':
switch (numcode) {
case 3: return KEY_DELETE;
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;
}
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;
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;
default: return -1;
}
break;
}
default:
if ('0' <= c && c <= '9') {
// Ps prefix
numcode = 10*numcode + (c - '0');
goto CSI_start;
}
}
return -1;
DCS:
return -1;
SS3:
switch (nextchar(fd, ESC_DELAY)) {
case 'P': return KEY_F1;
case 'Q': return KEY_F2;
case 'R': return KEY_F3;
case 'S': return KEY_F4;
default: break;
}
return -1;
}
/**
* Return the name of a key, if one exists and is different from the key itself
* (i.e. bkeyname('c') == NULL, bkeyname(' ') == "Space")
*/
const char *bkeyname(int key)
{
// TODO: currently only the keys I'm using are named
switch (key) {
case KEY_F1: return "F1";
case KEY_F2: return "F2";
case KEY_F3: return "F3";
case KEY_F4: return "F4";
case KEY_F5: return "F5";
case KEY_F6: return "F6";
case KEY_F7: return "F7";
case KEY_F8: return "F8";
case KEY_F9: return "F9";
case KEY_F10: return "F10";
case KEY_F11: return "F11";
case KEY_F12: return "F12";
case KEY_INSERT: return "Insert";
case KEY_DELETE: return "Delete";
case KEY_HOME: return "Home";
case KEY_END: return "End";
case KEY_PGUP: return "PgUp";
case KEY_PGDN: return "PgDn";
case KEY_ARROW_UP: return "Up";
case KEY_ARROW_DOWN: return "Down";
case KEY_ARROW_LEFT: return "Left";
case KEY_ARROW_RIGHT: return "Right";
case KEY_MOUSE_LEFT: return "Left click";
case KEY_MOUSE_RIGHT: return "Right click";
case KEY_MOUSE_MIDDLE: return "Middle click";
case KEY_MOUSE_RELEASE: return "Mouse release";
case KEY_MOUSE_WHEEL_UP: return "Mouse wheel up";
case KEY_MOUSE_WHEEL_DOWN: return "Mouse wheel down";
case KEY_MOUSE_DOUBLE_LEFT: return "Mouse double left";
case KEY_CTRL_TILDE: return "Ctrl-[2~]";
case KEY_CTRL_A: return "Ctrl-a";
case KEY_CTRL_B: return "Ctrl-b";
case KEY_CTRL_C: return "Ctrl-c";
case KEY_CTRL_D: return "Ctrl-d";
case KEY_CTRL_E: return "Ctrl-e";
case KEY_CTRL_F: return "Ctrl-f";
case KEY_CTRL_G: return "Ctrl-g";
case KEY_BACKSPACE: return "Backspace";
case KEY_TAB: return "Tab";
case KEY_CTRL_J: return "Ctrl-j";
case KEY_CTRL_K: return "Ctrl-k";
case KEY_CTRL_L: return "Ctrl-l";
case KEY_ENTER: return "Enter";
case KEY_CTRL_N: return "Ctrl-n";
case KEY_CTRL_O: return "Ctrl-o";
case KEY_CTRL_P: return "Ctrl-p";
case KEY_CTRL_Q: return "Ctrl-q";
case KEY_CTRL_R: return "Ctrl-r";
case KEY_CTRL_S: return "Ctrl-s";
case KEY_CTRL_T: return "Ctrl-t";
case KEY_CTRL_U: return "Ctrl-u";
case KEY_CTRL_V: return "Ctrl-v";
case KEY_CTRL_W: return "Ctrl-w";
case KEY_CTRL_X: return "Ctrl-x";
case KEY_CTRL_Y: return "Ctrl-y";
case KEY_CTRL_Z: return "Ctrl-z";
case KEY_ESC: return "Esc";
case KEY_CTRL_BACKSLASH: return "Ctrl-[4\\]";
case KEY_CTRL_RSQ_BRACKET: return "Ctrl-[5]]";
case KEY_CTRL_6: return "Ctrl-6";
case KEY_CTRL_SLASH: return "Ctrl-[7/_]";
case KEY_SPACE: return "Space";
case KEY_BACKSPACE2: return "Backspace";
}
return NULL;
}
/**
* A basic readline implementation. No history, but all the normal editing
* key bindings like Ctrl-U to clear to the beginning of the line, backspace,
* arrow keys, etc.
*
* Takes an input file and output file, and an optional prompt and returns
* a malloc'd string containing the user's input or NULL if an error occurred
* or if the user hit Escape or Ctrl-c.
*/
char *breadline(FILE *in, FILE *out, const char *prompt, const char *initial)
{
size_t cap = initial ? strlen(initial) + 100 : 100;
char *buf = calloc(cap, 1);
if (!buf) return NULL;
int i = 0, len = 0;
if (prompt) {
fputs("\033[1G\033[K\033[37;1m", out);
fputs(prompt, out);
fputs("\033[0m", out);
}
if (initial) {
strcpy(buf, initial);
len = (int)strlen(initial);
i = len;
fputs(initial, out);
}
// Show cursor and set to blinking line:
fputs("\033[?25h\033[5 q", out);
while (1) {
fflush(out);
int key = bgetkey(in, NULL, NULL, -1);
switch (key) {
case -1: case -2: case -3: continue;
case '\r':
// TODO: support backslash-enter
goto finished;
case KEY_CTRL_C: case KEY_ESC:
free(buf);
buf = NULL;
goto finished;
case KEY_CTRL_A:
if (i > 0) {
fprintf(out, "\033[%dD", i);
i = 0;
}
break;
case KEY_CTRL_E:
if (i < len) {
fprintf(out, "\033[%dC", len-i);
i = len;
}
break;
case KEY_CTRL_U: {
int to_clear = i;
if (to_clear) {
memmove(buf, buf+i, len-i);
buf[len -= i] = 0;
i = 0;
fprintf(out, "\033[%dD\033[K", to_clear);
if (len > 0)
fprintf(out, "%s\033[%dD", buf, len);
}
break;
}
case KEY_CTRL_K:
if (i < len) {
buf[len = i] = 0;
fputs("\033[K", out);
}
break;
case KEY_BACKSPACE: case KEY_BACKSPACE2:
if (i > 0) {
memmove(buf+i, buf+i+1, len-i);
--i;
buf[--len] = 0;
if (i == len) fputs("\033[D \033[D", out);
else fprintf(out, "\033[D%s\033[K\033[%dD", buf+i, len-i);
}
break;
case KEY_DELETE: case KEY_CTRL_D:
if (i < len) {
memmove(buf+i, buf+i+1, len-i);
buf[--len] = 0;
if (i == len) fputs(" \033[D", out);
else fprintf(out, "%s\033[K\033[%dD", buf+i, len-i);
}
break;
case KEY_ARROW_LEFT: case KEY_CTRL_B:
if (i > 0) {
--i;
fputs("\033[D", out);
}
break;
case KEY_ARROW_RIGHT: case KEY_CTRL_F:
if (i < len) {
++i;
fputs("\033[C", out);
}
break;
default:
if (' ' <= key && key <= '~') {
if (len + 1 >= (int)cap) {
cap += 100;
buf = realloc(buf, cap);
if (!buf) goto finished;
}
if (i < len)
memmove(buf+i+1, buf+i, len-i+1);
buf[i++] = (char)key;
buf[++len] = 0;
if (i == len)
fputc(key, out);
else
fprintf(out, "%c%s\033[%dD", (char)key, buf+i, len-i);
}
break;
}
}
finished:
// Reset cursor to block
fputs("\033[1G\033[1 q\033[2K", out);
return buf;
}
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1

View File

@ -13,8 +13,7 @@
When the scripts are run, the following values are provided as environment variables:
$@ (the list of arguments): the full paths of the selected files
$BBCURSOR: the (short) name of the file under the cursor
$BBFULLCURSOR: the full path name of the file under the cursor
$BBCURSOR: the full name of the file under the cursor
$BB_DEPTH: the number of `bb` instances deep (in case you want to run a
shell and have that shell print something special in the prompt)
$BBCMD: a file to which `bb` commands can be written (used internally)
@ -62,28 +61,33 @@
half a screen height down, and 100%n means the last file)
*/
#include "keys.h"
#include "bterm.h"
// Configurable options:
#define KEY_DELAY 50
#define SCROLLOFF MIN(5, (termheight-4)/2)
#define QUOTE(s) #s
#define CMDFILE_FORMAT "/tmp/bb.XXXXXX"
#define SORT_INDICATOR "↓"
#define RSORT_INDICATOR "↑"
#define SELECTED_INDICATOR "\033[33;1m★\033[0m"
#define NOT_SELECTED_INDICATOR "\033[1m \033[0m"
#define NORMAL_COLOR "\033[0m"
#define CURSOR_COLOR "\033[0;30;43m"
#define LINKDIR_COLOR "\033[0;36m"
#define DIR_COLOR "\033[0;34m"
#define LINK_COLOR "\033[0;33m"
#define TITLE_COLOR "\033[32;1m"
#define NORMAL_COLOR "\033[37m"
#define CURSOR_COLOR "\033[1;30;43m"
#define LINK_COLOR "\033[35m"
#define DIR_COLOR "\033[34m"
#define EXECUTABLE_COLOR "\033[31m"
#define PIPE_SELECTION_TO " printf '%s\\n' \"$@\" | "
#define PAUSE " read -n1 -p '\033[2m...press any key to continue...\033[0m\033[?25l'"
#define PAUSE " read -n1 -p '\033[2mPress any key to continue...\033[0m\033[?25l'"
#define NORMAL_TERM (1<<0)
#define SHOW_CURSOR (1<<1)
#define AT_CURSOR (1<<1)
#define MAX_REBINDINGS 8
@ -108,6 +112,8 @@ const char *startupcmds[] = {
NULL, // NULL-terminated array
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
extern binding_t bindings[];
binding_t bindings[] = {
//////////////////////////////////////////////////////////////////////////
@ -115,7 +121,7 @@ binding_t bindings[] = {
// Please note that these are sh scripts, not bash scripts, so bash-isms
// won't work unless you make your script use `bash -c "<your script>"`
//////////////////////////////////////////////////////////////////////////
{{'?'}, "bb -b | less -r", "Show the help menu", NORMAL_TERM},
{{'?'}, "bb -b | $PAGER -r", "Show the help menu", NORMAL_TERM},
{{KEY_CTRL_H}, "<placeholder>", "Figure out what key does"},
{{'q', 'Q'}, "+quit", "Quit"},
{{'k', KEY_ARROW_UP}, "+move:-1", "Move up"},
@ -126,42 +132,60 @@ binding_t bindings[] = {
{{'e'}, "$EDITOR \"$@\"", "Edit file in $EDITOR", NORMAL_TERM},
{{'\r', KEY_MOUSE_DOUBLE_LEFT},
#ifdef __APPLE__
"if test -d \"$BBCURSOR\"; then bb \"+cd:$BBCURSOR\";\n\
elif test -x \"$BBCURSOR\"; then \"$BBCURSOR\";\n\
read -n1 -p '\n\033[2m...press any key to continue...\033[0m';\n\
elif file -bI \"$BBCURSOR\" | grep '^\\(text/\\|inode/empty\\)' >/dev/null; then $EDITOR \"$BBCURSOR\";\n\
else open \"$BBCURSOR\"; fi",
QUOTE(
if test -d "$BBCURSOR"; then bb "+cd:$BBCURSOR";
elif test -x "$BBCURSOR"; then "$BBCURSOR"; read -p 'Press any key to continue...' -n1;
elif file -bI "$BBCURSOR" | grep '^\(text/\|inode/empty\)' >/dev/null; then $EDITOR "$BBCURSOR";
else open "$BBCURSOR"; fi
)/*ENDQUOTE*/,
#else
"if test -d \"$BBCURSOR\"; then bb \"+cd:$BBCURSOR\";\n\
elif file -bi \"$BBCURSOR\" | grep '^\\(text/\\|inode/empty\\)' >/dev/null; then $EDITOR \"$BBCURSOR\";\n\
else xdg-open \"$BBCURSOR\"; fi",
QUOTE(
if test -d "$BBCURSOR"; then bb "+cd:$BBCURSOR";
elif file -bi "$BBCURSOR" | grep '^\(text/\|inode/empty\)' >/dev/null; then $EDITOR "$BBCURSOR";
else xdg-open "$BBCURSOR"; fi
)/*ENDQUOTE*/,
#endif
"Open file", NORMAL_TERM},
{{'f'}, "bb \"+g:`fzf`\"", "Fuzzy search for file", NORMAL_TERM},
{{'/'}, "bb \"+g:`ls -a|fzf`\"", "Fuzzy select file", NORMAL_TERM},
{{'L'}, PIPE_SELECTION_TO "less", "List all selected files", NORMAL_TERM},
{{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+d:*' +r", "Delete files", SHOW_CURSOR},
{{'L'}, PIPE_SELECTION_TO "$PAGER", "List all selected files", NORMAL_TERM},
{{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+d:*' +r", "Delete files", AT_CURSOR},
{{'D'}, "rm -rf \"$@\"; bb '+d:*' +r", "Delete files without confirmation"},
{{'M'}, "mv -i \"$@\" .; bb '+d:*' +r", "Move files to current folder", SHOW_CURSOR},
{{'c'}, "cp -i \"$@\" .; bb +r", "Copy files to current folder", SHOW_CURSOR},
{{'M'}, "mv -i \"$@\" .; bb '+d:*' +r", "Move files to current folder"},
{{'c'}, "cp -i \"$@\" .; bb +r", "Copy files to current folder"},
{{'C'}, "for f; do cp \"$f\" \"$f.copy\"; done; bb +r", "Clone files"},
{{'n'}, "read -p 'New file: ' name && touch \"$name\"; bb +r \"+goto:$name\"", "New file", SHOW_CURSOR},
{{'N'}, "read -p 'New dir: ' name && mkdir \"$name\"; bb +r \"+goto:$name\"", "New folder", SHOW_CURSOR},
{{'|'}, "read -p '|' cmd && " PIPE_SELECTION_TO "sh -c \"$cmd\" && " PAUSE "; bb +r",
"Pipe selected files to a command", SHOW_CURSOR},
{{':'}, "read -p ':' cmd && sh -c \"$cmd\" -- \"$@\"; " PAUSE "; bb +r",
"Run a command", SHOW_CURSOR},
{{'>'}, "sh", "Open a shell", NORMAL_TERM},
{{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set mark", SHOW_CURSOR},
{{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", "Jump to mark", SHOW_CURSOR},
{{'r'}, "for f; do read -p \"Rename $f: \" renamed && mv \"$f\" \"$renamed\"; done; "
"bb '+d:*' +r",
"Rename files", SHOW_CURSOR},
{{'R'}, "read -p 'Rename pattern: ' patt && "
"for f; do mv -i \"$f\" \"`echo \"$f\" | sed \"$patt\"`\"; done &&" PAUSE,
"Regex rename files", SHOW_CURSOR},
{{'n'}, "name=`bb '?New file: '` && touch \"$name\"; bb +r \"+goto:$name\"", "New file"},
{{'N'}, "name=`bb '?New dir: '` && mkdir \"$name\"; bb +r \"+goto:$name\"", "New folder"},
{{'|'}, "cmd=`bb '?|'` && " PIPE_SELECTION_TO "sh -c \"$cmd\" && " PAUSE "; bb +r",
"Pipe selected files to a command"},
{{':'}, "$SHELL -c \"`bb '?:'`\" -- \"$@\"; " PAUSE "; bb +refresh",
"Run a command"},
{{'>'}, "$SHELL", "Open a shell", NORMAL_TERM},
{{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set mark"},
{{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", "Jump to mark"},
{{'r'}, QUOTE(
bb '+deselect:*' +refresh;
for f; do
if renamed="$(dirname "$f")/$(bb '?Rename: ' "$(basename "$f")")" &&
test "$f" != "$renamed" && mv -i "$f" "$renamed"; then
test $BBSELECTED && bb "+select:$renamed";
elif test $BBSELECTED; then bb "+select:$f"; fi
done)/*ENDQUOTE*/, "Rename files", AT_CURSOR},
{{'R'}, QUOTE(
if patt="`bb '?Rename pattern: ' 's/'`"; then true; else bb +r; exit; fi;
if sed -E "$patt" </dev/null; then true; else read -p 'Press any key to continue...' -n1; bb +r; exit; fi;
bb '+deselect:*' +refresh;
for f; do
renamed="`dirname "$f"`/`basename "$f" | sed -E "$patt"`";
if test "$f" != "$renamed" && mv -i "$f" "$renamed"; then
test $BBSELECTED && bb "+select:$renamed";
elif test $BBSELECTED; then bb "+select:$f"; fi
done)/*ENDQUOTE*/, "Regex rename files", AT_CURSOR},
// TODO debug:
{{'P'}, "read -p 'Select pattern: ' patt && "
{{'P'}, "patt=`bb '?Select pattern: '` && "
"for f; do echo \"$f\" | grep \"$patt\" >/dev/null 2>/dev/null && bb \"+sel:$f\"; done",
"Regex select files"},
{{'J'}, "+spread:+1", "Spread selection down"},
@ -170,7 +194,8 @@ else xdg-open \"$BBCURSOR\"; fi",
"\033[1m(s)\033[22mize \033[1m(m)\033[22modification \033[1m(c)\033[22mcreation "
"\033[1m(a)\033[22maccess \033[1m(r)\033[22mandom \033[1m(p)\033[22mermissions:\033[0m ' sort "
"&& bb \"+opt:s=$sort\"", "Sort by..."},
{{'#'}, "read -p 'Set columns: ' cols && bb \"+opt:`echo $cols 0 | fold -w1 | sed 10q | nl -v0 -s= -w1 | paste -sd '\\0' -`\"",
{{'#'}, "cols=`bb '?Set columns: '` && "
"bb \"+opt:`echo $cols 0 | fold -w1 | sed 10q | nl -v0 -s= -w1 | paste -sd '\\0' -`\"",
"Set columns"},
{{'.'}, "+opt:.~", "Toggle dotfiles"},
{{'g', KEY_HOME}, "+move:0", "Go to first file"},
@ -186,3 +211,6 @@ else xdg-open \"$BBCURSOR\"; fi",
{{KEY_MOUSE_WHEEL_UP}, "+scroll:-3", "Scroll up"},
{0}, // Array must be 0-terminated
};
#pragma clang diagnostic pop
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1

244
keys.h
View File

@ -1,244 +0,0 @@
/*
* Definitions of some key character constants
*/
#ifndef FILE__KEYS_H
#define FILE__KEYS_H
#include <poll.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)
/* 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' */
#define KEY_SPACE 0x20
#define KEY_BACKSPACE2 0x7F
#define KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */
#define ESC_DELAY 10
int term_getkey(int fd, int *mouse_x, int *mouse_y, int timeout);
const char *keyname(int key);
static inline int nextchar(int fd, int timeout)
{
char c;
struct pollfd pfd = {fd, POLLIN, 0};
if (poll(&pfd, 1, timeout) > 0)
return read(fd, &c, 1) == 1 ? c : -1;
return -1;
}
int term_getkey(int fd, int *mouse_x, int *mouse_y, int timeout)
{
int c = nextchar(fd, timeout);
if (c == '\x1b')
goto escape;
return c;
escape:
c = nextchar(fd, ESC_DELAY);
// Actual escape key:
if (c == -1)
return KEY_ESC;
switch (c) {
case '[': goto CSI;
case 'P': goto DCS;
case 'O': goto SS3;
default: return -1;
}
CSI:
c = nextchar(fd, ESC_DELAY);
if (c == -1)
return -1;
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 '1':
switch (nextchar(fd, ESC_DELAY)) {
case '5':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F5 : -1;
case '7':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F6 : -1;
case '8':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F7 : -1;
case '9':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F8 : -1;
default: return -1;
}
break;
case '2':
switch (nextchar(fd, ESC_DELAY)) {
case '0':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F9 : -1;
case '1':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F10 : -1;
case '3':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F11 : -1;
case '4':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_F12 : -1;
default: return -1;
}
break;
case '3':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_DELETE : -1;
case '5':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_PGUP : -1;
case '6':
return nextchar(fd, ESC_DELAY) == '~' ? KEY_PGDN : -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;
*mouse_x = x - 1;
*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;
default: return -1;
}
break;
}
}
return -1;
DCS:
return -1;
SS3:
switch (nextchar(fd, ESC_DELAY)) {
case 'P': return KEY_F1;
case 'Q': return KEY_F2;
case 'R': return KEY_F3;
case 'S': return KEY_F4;
default: break;
}
return -1;
}
const char *keyname(int key)
{
// TODO: currently only the keys I'm using are named
switch (key) {
case ' ': return "Space";
case '\r': return "Enter";
case KEY_MOUSE_DOUBLE_LEFT: return "Doubleclick";
case KEY_ARROW_UP: return "Up";
case KEY_ARROW_DOWN: return "Down";
case KEY_ARROW_LEFT: return "Left";
case KEY_ARROW_RIGHT: return "Right";
case KEY_HOME: return "Home";
case KEY_END: return "End";
case KEY_DELETE: return "Delete";
case KEY_ESC: return "Escape";
case KEY_F5: return "F5";
case KEY_CTRL_A: return "Ctrl-a";
case KEY_CTRL_D: return "Ctrl-d";
case KEY_CTRL_H: return "Ctrl-h";
case KEY_CTRL_R: return "Ctrl-r";
case KEY_CTRL_U: return "Ctrl-u";
case KEY_CTRL_T: return "Ctrl-t";
case KEY_CTRL_W: return "Ctrl-w";
case KEY_PGDN: return "PgDn";
case KEY_PGUP: return "PgUp";
case KEY_MOUSE_WHEEL_DOWN: return "Scroll down";
case KEY_MOUSE_WHEEL_UP: return "Scroll up";
}
return NULL;
}
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1