From 8d4e4aeba4c297cc93b0500c3a84a25d2bb85ddd Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 28 May 2019 21:36:42 -0700 Subject: [PATCH] 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. --- Makefile | 2 +- bb.c | 253 ++++++++++++++++-------------- bterm.h | 431 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 108 ++++++++----- keys.h | 244 ----------------------------- 5 files changed, 634 insertions(+), 404 deletions(-) create mode 100644 bterm.h delete mode 100644 keys.h diff --git a/Makefile b/Makefile index 5dcf77a..47d1795 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/bb.c b/bb.c index a5b0ac4..bd61491 100644 --- a/bb.c +++ b/bb.c @@ -22,9 +22,9 @@ #include #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); diff --git a/bterm.h b/bterm.h new file mode 100644 index 0000000..faf6efa --- /dev/null +++ b/bterm.h @@ -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 +#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) + +/* 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 diff --git a/config.def.h b/config.def.h index 6258605..c9e7647 100644 --- a/config.def.h +++ b/config.def.h @@ -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 ""` ////////////////////////////////////////////////////////////////////////// - {{'?'}, "bb -b | less -r", "Show the help menu", NORMAL_TERM}, + {{'?'}, "bb -b | $PAGER -r", "Show the help menu", NORMAL_TERM}, {{KEY_CTRL_H}, "", "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 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 diff --git a/keys.h b/keys.h deleted file mode 100644 index a7cb0ce..0000000 --- a/keys.h +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Definitions of some key character constants - */ - -#ifndef FILE__KEYS_H -#define FILE__KEYS_H - -#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) - -/* 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