aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2020-12-30 22:47:49 -0800
committerBruce Hill <bruce@bruce-hill.com>2020-12-30 22:47:49 -0800
commit5371a49ce0eda4054cc4dcb73abea351482711c2 (patch)
tree7e0142fdd1ce742f49c1dcda51157941834faf7f
parent18681fa449dc10e408128dbe50c482bfff0ead05 (diff)
Refactoring into multiple files better
-rw-r--r--Makefile16
-rw-r--r--bb.c158
-rw-r--r--bb.h130
-rw-r--r--bterm.c268
-rw-r--r--bterm.h257
-rw-r--r--columns.c161
-rw-r--r--columns.h191
-rw-r--r--entry.h38
8 files changed, 646 insertions, 573 deletions
diff --git a/Makefile b/Makefile
index 37ee948..376cac6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,26 @@
NAME=bb
PREFIX=
CC ?= gcc
+G ?=
O ?= -O2
-CFLAGS=-std=c99 -D_XOPEN_SOURCE=500 -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L
+CFLAGS=-std=c99 -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L
CWARN=-Wall -Wpedantic -Wextra -Wno-unknown-pragmas -Wno-missing-field-initializers\
-Wno-padded -Wsign-conversion -Wno-missing-noreturn -Wno-cast-qual -Wtype-limits
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
-G=
+
+CFILES=columns.c bterm.c
+OBJFILES=$(CFILES:.c=.o)
all: $(NAME)
clean:
- rm -f $(NAME)
+ rm -f $(NAME) $(OBJFILES)
+
+.c.o:
+ $(CC) -c $(CFLAGS) $(CWARN) $(G) $(O) -o $@ $<
-$(NAME): $(NAME).c bterm.h bb.h columns.h
- $(CC) $(NAME).c $(CFLAGS) $(CWARN) $(G) $(O) -o $@
+$(NAME): $(OBJFILES) $(NAME).c
+ $(CC) $(CFLAGS) $(CWARN) $(G) $(O) -o $@ $(OBJFILES) $(NAME).c
install: $(NAME)
@prefix="$(PREFIX)"; \
diff --git a/bb.c b/bb.c
index 78c864e..33e5a04 100644
--- a/bb.c
+++ b/bb.c
@@ -6,9 +6,107 @@
* This file contains the main source code of `bb`.
*/
+#include <fcntl.h>
+#include <glob.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
#include "bb.h"
+#include "columns.h"
+
+// Functions
+void bb_browse(bb_t *bb, const char *initial_path);
+static void check_cmdfile(bb_t *bb);
+static void cleanup(void);
+static void cleanup_and_raise(int sig);
+static const char* color_of(mode_t mode);
+#ifdef __APPLE__
+static int compare_files(void *v, const void *v1, const void *v2);
+#else
+static int compare_files(const void *v1, const void *v2, void *v);
+#endif
+static int fputs_escaped(FILE *f, const char *str, const char *color);
+static void handle_next_key_binding(bb_t *bb);
+static void init_term(void);
+static int is_simple_bbcmd(const char *s);
+static entry_t* load_entry(bb_t *bb, const char *path);
+static inline int matches_cmd(const char *str, const char *cmd);
+static void* memcheck(void *p);
+static char* normalize_path(const char *root, const char *path, char *pbuf);
+static int populate_files(bb_t *bb, const char *path);
+static void print_bindings(int fd);
+static void run_bbcmd(bb_t *bb, const char *cmd);
+static void render(bb_t *bb);
+static void restore_term(const struct termios *term);
+static int run_script(bb_t *bb, const char *cmd);
+static void set_columns(bb_t *bb, const char *cols);
+static void set_cursor(bb_t *bb, int i);
+static void set_globs(bb_t *bb, const char *globs);
+static void set_interleave(bb_t *bb, int interleave);
+static void set_selected(bb_t *bb, entry_t *e, int selected);
+static void set_scroll(bb_t *bb, int i);
+static void set_sort(bb_t *bb, const char *sort);
+static void set_title(bb_t *bb);
+static void sort_files(bb_t *bb);
+static char *trim(char *s);
+static int try_free_entry(entry_t *e);
+static void update_term_size(int sig);
+static int wait_for_process(proc_t **proc);
+
+// Constants
+static const char *T_ENTER_BBMODE = T_OFF(T_SHOW_CURSOR ";" T_WRAP) T_ON(T_ALT_SCREEN ";" 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) T_ON(T_SHOW_CURSOR ";" T_WRAP);
+static const char *T_LEAVE_BBMODE_PARTIAL = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR) T_ON(T_WRAP);
+static const struct termios default_termios = {
+ .c_iflag = ICRNL,
+ .c_oflag = OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0,
+ .c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE,
+ .c_cflag = CS8 | CREAD,
+ .c_cc[VINTR] = '',
+ .c_cc[VQUIT] = '',
+ .c_cc[VERASE] = 127,
+ .c_cc[VKILL] = '',
+ .c_cc[VEOF] = '',
+ .c_cc[VSTART] = '',
+ .c_cc[VSTOP] = '',
+ .c_cc[VSUSP] = '',
+ .c_cc[VREPRINT] = '',
+ .c_cc[VWERASE] = '',
+ .c_cc[VLNEXT] = '',
+ .c_cc[VDISCARD] = '',
+ .c_cc[VMIN] = 1,
+ .c_cc[VTIME] = 0,
+};
+
+static const char *description_str = "bb - an itty bitty console TUI file browser\n";
+static const char *usage_str = "Usage: bb (-h/--help | -v/--version | -s | -d | -0 | +command)* [[--] directory]\n";
+
+column_t columns[255] = {
+ ['*'] = {.name = "*", .render = col_selected},
+ ['n'] = {.name = "Name", .render = col_name, .stretchy = 1},
+ ['s'] = {.name = " Size", .render = col_size},
+ ['p'] = {.name = "Perm", .render = col_perm},
+ ['m'] = {.name = " Modified", .render = col_mreltime},
+ ['M'] = {.name = " Modified ", .render = col_mtime},
+ ['a'] = {.name = " Accessed", .render = col_areltime},
+ ['A'] = {.name = " Accessed ", .render = col_atime},
+ ['c'] = {.name = " Created", .render = col_creltime},
+ ['C'] = {.name = " Created ", .render = col_ctime},
+ ['r'] = {.name = "Random", .render = col_random},
+};
+
// Variables used within this file to track global state
+static binding_t bindings[MAX_BINDINGS];
static struct termios orig_termios, bb_termios;
static FILE *tty_out = NULL, *tty_in = NULL;
static struct winsize winsize = {0};
@@ -36,7 +134,7 @@ void bb_browse(bb_t *bb, const char *initial_path)
* Check the bb command file and run any and all commands that have been
* written to it.
*/
-void check_cmdfile(bb_t *bb)
+static void check_cmdfile(bb_t *bb)
{
FILE *cmdfile = fopen(cmdfilename, "r");
if (!cmdfile) return;
@@ -55,7 +153,7 @@ void check_cmdfile(bb_t *bb)
/*
* Clean up the terminal before going to the default signal handling behavior.
*/
-void cleanup_and_raise(int sig)
+static void cleanup_and_raise(int sig)
{
cleanup();
int childsig = (sig == SIGTSTP || sig == SIGSTOP) ? sig : SIGHUP;
@@ -71,7 +169,7 @@ void cleanup_and_raise(int sig)
/*
* Reset the screen and delete the cmdfile
*/
-void cleanup(void)
+static void cleanup(void)
{
if (cmdfilename[0]) {
unlink(cmdfilename);
@@ -87,7 +185,7 @@ void cleanup(void)
/*
* Returns the color of a file listing, given its mode.
*/
-const char* color_of(mode_t mode)
+static const char* color_of(mode_t mode)
{
if (S_ISDIR(mode)) return DIR_COLOR;
else if (S_ISLNK(mode)) return LINK_COLOR;
@@ -101,9 +199,9 @@ const char* color_of(mode_t mode)
* like bb->sort
*/
#ifdef __APPLE__
-int compare_files(void *v, const void *v1, const void *v2)
+static int compare_files(void *v, const void *v1, const void *v2)
#else
-int compare_files(const void *v1, const void *v2, void *v)
+static int compare_files(const void *v1, const void *v2, void *v)
#endif
{
#define COMPARE(a, b) if ((a) != (b)) { return sign*((a) < (b) ? 1 : -1); }
@@ -168,7 +266,7 @@ int compare_files(const void *v1, const void *v2, void *v)
* The color argument is what color to put back after the red.
* Returns the number of bytes that were escaped.
*/
-int fputs_escaped(FILE *f, const char *str, const char *color)
+static int fputs_escaped(FILE *f, const char *str, const char *color)
{
static const char *escapes = " abtnvfr e";
int escaped = 0;
@@ -190,7 +288,7 @@ int fputs_escaped(FILE *f, const char *str, const char *color)
* Wait until the user has pressed a key with an associated key binding and run
* that binding.
*/
-void handle_next_key_binding(bb_t *bb)
+static void handle_next_key_binding(bb_t *bb)
{
int key, mouse_x, mouse_y;
binding_t *binding;
@@ -253,7 +351,7 @@ void handle_next_key_binding(bb_t *bb)
* Initialize the terminal files for /dev/tty and set up some desired
* attributes like passing Ctrl-c as a key instead of interrupting
*/
-void init_term(void)
+static void init_term(void)
{
if (tcsetattr(fileno(tty_out), TCSANOW, &bb_termios) == -1)
err("Couldn't tcsetattr");
@@ -287,7 +385,7 @@ static int is_simple_bbcmd(const char *s)
* Warning: this does not deduplicate entries, and it's best if there aren't
* duplicate entries hanging around.
*/
-entry_t* load_entry(bb_t *bb, const char *path)
+static entry_t* load_entry(bb_t *bb, const char *path)
{
struct stat linkedstat, filestat;
if (!path || !path[0]) return NULL;
@@ -353,7 +451,7 @@ static inline int matches_cmd(const char *str, const char *cmd)
* Memory allocation failures are unrecoverable in bb, so this wrapper just
* prints an error message and exits if that happens.
*/
-void* memcheck(void *p)
+static void* memcheck(void *p)
{
if (!p) err("Allocation failure");
return p;
@@ -363,7 +461,7 @@ void* memcheck(void *p)
* Prepend `root` to relative paths, replace "~" with $HOME.
* The normalized path is stored in `normalized`.
*/
-char *normalize_path(const char *root, const char *path, char *normalized)
+static char *normalize_path(const char *root, const char *path, char *normalized)
{
char pbuf[PATH_MAX] = {0};
if (path[0] == '~' && (path[1] == '\0' || path[1] == '/')) {
@@ -387,7 +485,7 @@ char *normalize_path(const char *root, const char *path, char *normalized)
* Remove all the files currently stored in bb->files and if `bb->path` is
* non-NULL, update `bb` with a listing of the files in `path`
*/
-int populate_files(bb_t *bb, const char *path)
+static int populate_files(bb_t *bb, const char *path)
{
int samedir = path && strcmp(bb->path, path) == 0;
int old_scroll = bb->scroll;
@@ -501,7 +599,7 @@ int populate_files(bb_t *bb, const char *path)
/*
* Print the current key bindings
*/
-void print_bindings(int fd)
+static void print_bindings(int fd)
{
char buf[1000], buf2[1024];
for (size_t i = 0; bindings[i].script && i < sizeof(bindings)/sizeof(bindings[0]); i++) {
@@ -535,7 +633,7 @@ void print_bindings(int fd)
* Run a bb internal command (e.g. "+refresh") and return an indicator of what
* needs to happen next.
*/
-void run_bbcmd(bb_t *bb, const char *cmd)
+static void run_bbcmd(bb_t *bb, const char *cmd)
{
while (*cmd == ' ' || *cmd == '\n') ++cmd;
if (strncmp(cmd, "bbcmd ", strlen("bbcmd ")) == 0) cmd = &cmd[strlen("bbcmd ")];
@@ -733,7 +831,7 @@ void run_bbcmd(bb_t *bb, const char *cmd)
* If `dirty` is false, then use terminal scrolling to move the file listing
* around and only update the files that have changed.
*/
-void render(bb_t *bb)
+static void render(bb_t *bb)
{
static int lastcursor = -1, lastscroll = -1;
@@ -888,7 +986,7 @@ void render(bb_t *bb)
/*
* Close the /dev/tty terminals and restore some of the attributes.
*/
-void restore_term(const struct termios *term)
+static void restore_term(const struct termios *term)
{
tcsetattr(fileno(tty_out), TCSANOW, term);
fputs(T_LEAVE_BBMODE_PARTIAL, tty_out);
@@ -900,7 +998,7 @@ void restore_term(const struct termios *term)
* the script (or pass the cursor file if none are selected).
* Return the exit status of the script.
*/
-int run_script(bb_t *bb, const char *cmd)
+static int run_script(bb_t *bb, const char *cmd)
{
proc_t *proc = memcheck(calloc(1, sizeof(proc_t)));
signal(SIGTTOU, SIG_IGN);
@@ -943,7 +1041,7 @@ int run_script(bb_t *bb, const char *cmd)
/*
* Set the columns displayed by bb.
*/
-void set_columns(bb_t *bb, const char *cols)
+static void set_columns(bb_t *bb, const char *cols)
{
strncpy(bb->columns, cols, MAX_COLS);
setenv("BBCOLUMNS", bb->columns, 1);
@@ -952,7 +1050,7 @@ void set_columns(bb_t *bb, const char *cols)
/*
* Set bb's file cursor to the given index (and adjust the scroll as necessary)
*/
-void set_cursor(bb_t *bb, int newcur)
+static void set_cursor(bb_t *bb, int newcur)
{
int oldcur = bb->cursor;
if (newcur > bb->nfiles - 1) newcur = bb->nfiles - 1;
@@ -981,7 +1079,7 @@ void set_cursor(bb_t *bb, int newcur)
/*
* Set the glob pattern(s) used by bb. Patterns are ' ' delimited
*/
-void set_globs(bb_t *bb, const char *globs)
+static void set_globs(bb_t *bb, const char *globs)
{
free(bb->globpats);
bb->globpats = memcheck(strdup(globs));
@@ -992,7 +1090,7 @@ void set_globs(bb_t *bb, const char *globs)
/*
* Set whether or not bb should interleave directories and files.
*/
-void set_interleave(bb_t *bb, int interleave)
+static void set_interleave(bb_t *bb, int interleave)
{
bb->interleave_dirs = interleave;
if (interleave) setenv("BBINTERLEAVE", "interleave", 1);
@@ -1003,7 +1101,7 @@ void set_interleave(bb_t *bb, int interleave)
/*
* Set bb's scroll to the given index (and adjust the cursor as necessary)
*/
-void set_scroll(bb_t *bb, int newscroll)
+static void set_scroll(bb_t *bb, int newscroll)
{
int delta = newscroll - bb->scroll;
if (bb->nfiles <= ONSCREEN) {
@@ -1023,7 +1121,7 @@ void set_scroll(bb_t *bb, int newscroll)
/*
* Select or deselect a file.
*/
-void set_selected(bb_t *bb, entry_t *e, int selected)
+static void set_selected(bb_t *bb, entry_t *e, int selected)
{
if (IS_SELECTED(e) == selected) return;
@@ -1043,7 +1141,7 @@ void set_selected(bb_t *bb, entry_t *e, int selected)
/*
* Set the sorting method used by bb to display files.
*/
-void set_sort(bb_t *bb, const char *sort)
+static void set_sort(bb_t *bb, const char *sort)
{
char sortbuf[strlen(sort)+1];
strcpy(sortbuf, sort);
@@ -1065,7 +1163,7 @@ void set_sort(bb_t *bb, const char *sort)
/*
* Set the xwindow title property to: bb - current path
*/
-void set_title(bb_t *bb)
+static void set_title(bb_t *bb)
{
char *home = getenv("HOME");
if (home && strncmp(bb->path, home, strlen(home)) == 0)
@@ -1078,7 +1176,7 @@ void set_title(bb_t *bb)
* If the given entry is not viewed or selected, remove it from the
* hash, free it, and return 1.
*/
-int try_free_entry(entry_t *e)
+static int try_free_entry(entry_t *e)
{
if (IS_SELECTED(e) || IS_VIEWED(e) || !IS_LOADED(e)) return 0;
LL_REMOVE(e, hash);
@@ -1089,7 +1187,7 @@ int try_free_entry(entry_t *e)
/*
* Sort the files in bb according to bb's settings.
*/
-void sort_files(bb_t *bb)
+static void sort_files(bb_t *bb)
{
#ifdef __APPLE__
qsort_r(bb->files, (size_t)bb->nfiles, sizeof(entry_t*), bb, compare_files);
@@ -1118,7 +1216,7 @@ static char *trim(char *s)
/*
* Hanlder for SIGWINCH events
*/
-void update_term_size(int sig)
+static void update_term_size(int sig)
{
(void)sig;
struct winsize oldsize = winsize;
@@ -1129,7 +1227,7 @@ void update_term_size(int sig)
/*
* Wait for a process to either suspend or exit and return the status.
*/
-int wait_for_process(proc_t **proc)
+static int wait_for_process(proc_t **proc)
{
tcsetpgrp(fileno(tty_out), (*proc)->pid);
int status;
diff --git a/bb.h b/bb.h
index 946f323..e69d45e 100644
--- a/bb.h
+++ b/bb.h
@@ -5,21 +5,12 @@
*
* This file contains definitions and customization for `bb`.
*/
-#include <fcntl.h>
-#include <glob.h>
-#include <limits.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/errno.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <termios.h>
-#include <unistd.h>
+#ifndef FILE_BB__H
+#define FILE_BB__H
#include "bterm.h"
+#include "entry.h"
+#include "columns.h"
// Macros:
#define BB_VERSION "0.27.0"
@@ -35,12 +26,9 @@
#define MAX_BINDINGS 1024
// Configurable options:
-#define BB_TIME_FMT " %T %D "
#define SCROLLOFF MIN(5, (winsize.ws_row-4)/2)
#define SORT_INDICATOR "↓"
#define RSORT_INDICATOR "↑"
-#define SELECTED_INDICATOR " \033[31;7m \033[0m"
-#define NOT_SELECTED_INDICATOR " "
// Colors (using ANSI escape sequences):
#define TITLE_COLOR "\033[37;1m"
#define NORMAL_COLOR "\033[37m"
@@ -53,9 +41,6 @@
#define MAX(a,b) ((a) < (b) ? (b) : (a))
#define MIN(a,b) ((a) > (b) ? (b) : (a))
-#define IS_SELECTED(p) (((p)->selected.atme) != NULL)
-#define IS_VIEWED(p) ((p)->index >= 0)
-#define IS_LOADED(p) ((p)->hash.atme != NULL)
#define LOWERCASE(c) ('A' <= (c) && (c) <= 'Z' ? ((c) + 'a' - 'A') : (c))
#define E_ISDIR(e) (S_ISDIR(S_ISLNK((e)->info.st_mode) ? (e)->linkedmode : (e)->info.st_mode))
#define ONSCREEN (winsize.ws_row - 3)
@@ -111,41 +96,6 @@ typedef struct {
char *description;
} binding_t;
-typedef enum {
- COL_NONE = 0,
- COL_NAME = 'n',
- COL_SIZE = 's',
- COL_PERM = 'p',
- COL_MTIME = 'm',
- COL_CTIME = 'c',
- COL_ATIME = 'a',
- COL_RANDOM = 'r',
- COL_SELECTED = '*',
-} column_e;
-
-/* entry_t uses intrusive linked lists. This means entries can only belong to
- * one list at a time, in this case the list of selected entries. 'atme' is an
- * indirect pointer to either the 'next' field of the previous list member, or
- * the variable that points to the first list member. In other words,
- * item->next->atme == &item->next and firstitem->atme == &firstitem.
- */
-typedef struct entry_s {
- struct {
- struct entry_s *next, **atme;
- } selected, hash;
- char *name, *linkname;
- struct stat info;
- mode_t linkedmode;
- int no_esc : 1;
- int link_no_esc : 1;
- int shufflepos;
- int index;
- char fullname[1];
- // ------- fullname must be last! --------------
- // When entries are allocated, extra space on the end is reserved to fill
- // in fullname.
-} entry_t;
-
typedef struct bb_s {
entry_t *hash[HASH_SIZE];
entry_t **files;
@@ -170,81 +120,11 @@ typedef struct proc_s {
} running;
} proc_t;
-static binding_t bindings[MAX_BINDINGS];
-
-#include "columns.h"
-
-// Functions
-void bb_browse(bb_t *bb, const char *initial_path);
-static void check_cmdfile(bb_t *bb);
-static void cleanup(void);
-static void cleanup_and_raise(int sig);
-static const char* color_of(mode_t mode);
-#ifdef __APPLE__
-static int compare_files(void *v, const void *v1, const void *v2);
-#else
-static int compare_files(const void *v1, const void *v2, void *v);
-#endif
-static int fputs_escaped(FILE *f, const char *str, const char *color);
-static void handle_next_key_binding(bb_t *bb);
-static void init_term(void);
-static int is_simple_bbcmd(const char *s);
-static entry_t* load_entry(bb_t *bb, const char *path);
-static inline int matches_cmd(const char *str, const char *cmd);
-static void* memcheck(void *p);
-static char* normalize_path(const char *root, const char *path, char *pbuf);
-static int populate_files(bb_t *bb, const char *path);
-static void print_bindings(int fd);
-static void run_bbcmd(bb_t *bb, const char *cmd);
-static void render(bb_t *bb);
-static void restore_term(const struct termios *term);
-static int run_script(bb_t *bb, const char *cmd);
-static void set_columns(bb_t *bb, const char *cols);
-static void set_cursor(bb_t *bb, int i);
-static void set_globs(bb_t *bb, const char *globs);
-static void set_interleave(bb_t *bb, int interleave);
-static void set_selected(bb_t *bb, entry_t *e, int selected);
-static void set_scroll(bb_t *bb, int i);
-static void set_sort(bb_t *bb, const char *sort);
-static void set_title(bb_t *bb);
-static void sort_files(bb_t *bb);
-static char *trim(char *s);
-static int try_free_entry(entry_t *e);
-static void update_term_size(int sig);
-static int wait_for_process(proc_t **proc);
-
-// Constants
-static const char *T_ENTER_BBMODE = T_OFF(T_SHOW_CURSOR ";" T_WRAP) T_ON(T_ALT_SCREEN ";" 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) T_ON(T_SHOW_CURSOR ";" T_WRAP);
-static const char *T_LEAVE_BBMODE_PARTIAL = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR) T_ON(T_WRAP);
-static const struct termios default_termios = {
- .c_iflag = ICRNL,
- .c_oflag = OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0,
- .c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE,
- .c_cflag = CS8 | CREAD,
- .c_cc[VINTR] = '',
- .c_cc[VQUIT] = '',
- .c_cc[VERASE] = 127,
- .c_cc[VKILL] = '',
- .c_cc[VEOF] = '',
- .c_cc[VSTART] = '',
- .c_cc[VSTOP] = '',
- .c_cc[VSUSP] = '',
- .c_cc[VREPRINT] = '',
- .c_cc[VWERASE] = '',
- .c_cc[VLNEXT] = '',
- .c_cc[VDISCARD] = '',
- .c_cc[VMIN] = 1,
- .c_cc[VTIME] = 0,
-};
-
-static const char *description_str = "bb - an itty bitty console TUI file browser\n";
-static const char *usage_str = "Usage: bb (-h/--help | -v/--version | -s | -d | -0 | +command)* [[--] directory]\n";
-
// Hack to get TinyCC (TCC) compilation to work:
// https://lists.nongnu.org/archive/html/tinycc-devel/2018-07/msg00000.html
#ifdef __TINYC__
void * __dso_handle __attribute((visibility("hidden"))) = &__dso_handle;
#endif
+#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
diff --git a/bterm.c b/bterm.c
new file mode 100644
index 0000000..0a5d68f
--- /dev/null
+++ b/bterm.c
@@ -0,0 +1,268 @@
+/*
+ * bterm.h
+ * Copyright 2019 Bruce Hill
+ * Released under the MIT License
+ *
+ * Implementations of some basic terminal stuff, like reading keys and some
+ * terminal escape sequences.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "bterm.h"
+
+typedef struct {
+ int key;
+ const char *name;
+} 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_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"},
+ {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_CTRL_I, "Ctrl-i"},
+ {KEY_CTRL_J, "Ctrl-j"}, {KEY_CTRL_K, "Ctrl-k"}, {KEY_CTRL_L, "Ctrl-l"},
+ {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_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"},
+ {':', "Colon"}, {',', "Comma"},
+};
+
+static inline int nextchar(int fd)
+{
+ char c;
+ return read(fd, &c, 1) == 1 ? c : -1;
+}
+
+static inline int nextnum(int fd, int 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
+ * set to the position of the mouse (0-indexed).
+ */
+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, modifiers = 0;
+ int c = nextchar(fd);
+ if (c == '\x1b')
+ goto escape;
+
+ return c;
+
+ escape:
+ c = nextchar(fd);
+ // Actual escape key:
+ if (c < 0)
+ return KEY_ESC;
+
+ switch (c) {
+ case '\x1b': return KEY_ESC;
+ case '[': c = nextchar(fd); goto CSI_start;
+ case 'P': goto DCS;
+ case 'O': goto SS3;
+ default: return MOD_ALT | c;
+ }
+
+ CSI_start:
+ if (c == -1)
+ return MOD_ALT | '[';
+
+ switch (c) {
+ 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 'J': return numcode == 2 ? (MOD_SHIFT | KEY_HOME) : -1;
+ case 'K': return MOD_SHIFT | KEY_END;
+ 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 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;
+ 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 (buttons & 4) modifiers |= MOD_SHIFT;
+ if (buttons & 8) modifiers |= MOD_META;
+ 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;
+ }
+ 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
+ c = nextnum(fd, c, &numcode);
+ if (c == ';') {
+ c = nextnum(fd, nextchar(fd), &modifiers);
+ modifiers = (modifiers >> 1) << MOD_BITSHIFT;
+ }
+ goto CSI_start;
+ }
+ }
+ return -1;
+
+ DCS:
+ return -1;
+
+ SS3:
+ switch (nextchar(fd)) {
+ case 'P': return KEY_F1;
+ case 'Q': return KEY_F2;
+ case 'R': return KEY_F3;
+ case 'S': return KEY_F4;
+ default: break;
+ }
+ return -1;
+}
+
+/*
+ * Populate `buf` with the name of a key.
+ */
+char *bkeyname(int key, char *buf)
+{
+ if (key & MOD_META) 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_META | MOD_CTRL | MOD_ALT | MOD_SHIFT);
+ for (size_t i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) {
+ if (key_names[i].key == key) {
+ return stpcpy(buf, key_names[i].name);
+ }
+ }
+ 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. bkeywithname("Space") == ' ', bkeywithname("x") == 'x', bkeywithname("F1") == KEY_F1, bkeywithname("???") == -1)
+ */
+int bkeywithname(const char *name)
+{
+ int modifiers = 0;
+ static const struct { const char *prefix; int modifier; } modnames[] = {
+ {"Super-", MOD_META}, {"Ctrl-", MOD_CTRL}, {"Alt-", MOD_ALT}, {"Shift-", MOD_SHIFT}
+ };
+ check_names:
+ for (size_t i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) {
+ if (strcmp(key_names[i].name, name) == 0)
+ return modifiers | key_names[i].key;
+ }
+ for (size_t 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;
+}
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
diff --git a/bterm.h b/bterm.h
index 3e42b27..d2fd963 100644
--- a/bterm.h
+++ b/bterm.h
@@ -3,8 +3,8 @@
* Copyright 2019 Bruce Hill
* Released under the MIT License
*
- * Definitions of some basic terminal stuff, like reading keys
- * and some terminal escape sequences.
+ * Definitions of some basic terminal stuff, like reading keys and some
+ * terminal escape sequences.
*/
#ifndef FILE__BTERM_H
@@ -12,6 +12,7 @@
#include <stdio.h>
#include <time.h>
+#include <unistd.h>
// Maximum time in milliseconds between double clicks
#define DOUBLECLICK_THRESHOLD 200
@@ -78,261 +79,9 @@ typedef enum {
#define move_cursor(f, x, y) fprintf((f), "\033[%d;%dH", (int)(y)+1, (int)(x)+1)
#define move_cursor_col(f, x) fprintf((f), "\033[%d`", (int)(x)+1)
-typedef struct {
- int key;
- const char *name;
-} 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_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"},
- {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_CTRL_I, "Ctrl-i"},
- {KEY_CTRL_J, "Ctrl-j"}, {KEY_CTRL_K, "Ctrl-k"}, {KEY_CTRL_L, "Ctrl-l"},
- {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_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"},
- {':', "Colon"}, {',', "Comma"},
-};
-
int bgetkey(FILE *in, int *mouse_x, int *mouse_y);
char *bkeyname(int key, char *buf);
int bkeywithname(const char *name);
-static inline int nextchar(int fd)
-{
- char c;
- return read(fd, &c, 1) == 1 ? c : -1;
-}
-
-static inline int nextnum(int fd, int 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
- * set to the position of the mouse (0-indexed).
- */
-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, modifiers = 0;
- int c = nextchar(fd);
- if (c == '\x1b')
- goto escape;
-
- return c;
-
- escape:
- c = nextchar(fd);
- // Actual escape key:
- if (c < 0)
- return KEY_ESC;
-
- switch (c) {
- case '\x1b': return KEY_ESC;
- case '[': c = nextchar(fd); goto CSI_start;
- case 'P': goto DCS;
- case 'O': goto SS3;
- default: return MOD_ALT | c;
- }
-
- CSI_start:
- if (c == -1)
- return MOD_ALT | '[';
-
- switch (c) {
- 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 'J': return numcode == 2 ? (MOD_SHIFT | KEY_HOME) : -1;
- case 'K': return MOD_SHIFT | KEY_END;
- 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 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;
- 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 (buttons & 4) modifiers |= MOD_SHIFT;
- if (buttons & 8) modifiers |= MOD_META;
- 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;
- }
- 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
- c = nextnum(fd, c, &numcode);
- if (c == ';') {
- c = nextnum(fd, nextchar(fd), &modifiers);
- modifiers = (modifiers >> 1) << MOD_BITSHIFT;
- }
- goto CSI_start;
- }
- }
- return -1;
-
- DCS:
- return -1;
-
- SS3:
- switch (nextchar(fd)) {
- case 'P': return KEY_F1;
- case 'Q': return KEY_F2;
- case 'R': return KEY_F3;
- case 'S': return KEY_F4;
- default: break;
- }
- return -1;
-}
-
-/*
- * Populate `buf` with the name of a key.
- */
-char *bkeyname(int key, char *buf)
-{
- if (key & MOD_META) 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_META | MOD_CTRL | MOD_ALT | MOD_SHIFT);
- for (size_t i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) {
- if (key_names[i].key == key) {
- return stpcpy(buf, key_names[i].name);
- }
- }
- 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. bkeywithname("Space") == ' ', bkeywithname("x") == 'x', bkeywithname("F1") == KEY_F1, bkeywithname("???") == -1)
- */
-int bkeywithname(const char *name)
-{
- int modifiers = 0;
- static const struct { const char *prefix; int modifier; } modnames[] = {
- {"Super-", MOD_META}, {"Ctrl-", MOD_CTRL}, {"Alt-", MOD_ALT}, {"Shift-", MOD_SHIFT}
- };
- check_names:
- for (size_t i = 0; i < sizeof(key_names)/sizeof(key_names[0]); i++) {
- if (strcmp(key_names[i].name, name) == 0)
- return modifiers | key_names[i].key;
- }
- for (size_t 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
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
diff --git a/columns.c b/columns.c
new file mode 100644
index 0000000..a729cd2
--- /dev/null
+++ b/columns.c
@@ -0,0 +1,161 @@
+/*
+ * columns.c - This file contains logic for drawing columns.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#include "columns.h"
+#include "entry.h"
+
+#define E_ISDIR(e) (S_ISDIR(S_ISLNK((e)->info.st_mode) ? (e)->linkedmode : (e)->info.st_mode))
+
+static void lpad(char *buf, int width)
+{
+ int len = strlen(buf);
+ if (len < width) {
+ int pad = width - len;
+ memmove(&buf[pad], buf, (size_t)len + 1);
+ while (pad > 0) buf[--pad] = ' ';
+ }
+}
+
+static char* stpcpy_escaped(char *buf, const char *str, const char *color)
+{
+ static const char *escapes = " abtnvfr e";
+ for (const char *c = str; *c; ++c) {
+ if (*c > 0 && *c <= '\x1b' && escapes[(int)*c] != ' ') { // "\n", etc.
+ buf += sprintf(buf, "\033[31m\\%c%s", escapes[(int)*c], color);
+ } else if (*c >= 0 && !(' ' <= *c && *c <= '~')) { // "\x02", etc.
+ buf += sprintf(buf, "\033[31m\\x%02X%s", *c, color);
+ } else {
+ *(buf++) = *c;
+ }
+ }
+ *buf = '\0';
+ return buf;
+}
+
+static void timeago(char *buf, time_t t)
+{
+ const int SECOND = 1;
+ const int MINUTE = 60 * SECOND;
+ const int HOUR = 60 * MINUTE;
+ const int DAY = 24 * HOUR;
+ const int MONTH = 30 * DAY;
+ const int YEAR = 365 * DAY;
+
+ time_t now = time(0);
+ double delta = difftime(now, t);
+
+ if (delta < 1.5)
+ sprintf(buf, "a second");
+ else if (delta < 1 * MINUTE)
+ sprintf(buf, "%d seconds", (int)delta);
+ else if (delta < 2 * MINUTE)
+ sprintf(buf, "a minute");
+ else if (delta < 1 * HOUR)
+ sprintf(buf, "%d minutes", (int)delta/MINUTE);
+ else if (delta < 2 * HOUR)
+ sprintf(buf, "an hour");
+ else if (delta < 1 * DAY)
+ sprintf(buf, "%d hours", (int)delta/HOUR);
+ else if (delta < 2 * DAY)
+ sprintf(buf, "yesterday");
+ else if (delta < 1 * MONTH)
+ sprintf(buf, "%d days", (int)delta/DAY);
+ else if (delta < 2 * MONTH)
+ sprintf(buf, "a month");
+ else if (delta < 1 * YEAR)
+ sprintf(buf, "%d months", (int)delta/MONTH);
+ else if (delta < 2 * YEAR)
+ sprintf(buf, "a year");
+ else
+ sprintf(buf, "%d years", (int)delta/YEAR);
+}
+
+void col_mreltime(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color;
+ timeago(buf, entry->info.st_mtime);
+ lpad(buf, width);
+}
+
+void col_areltime(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color;
+ timeago(buf, entry->info.st_atime);
+ lpad(buf, width);
+}
+
+void col_creltime(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color;
+ timeago(buf, entry->info.st_ctime);
+ lpad(buf, width);
+}
+
+void col_mtime(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color;
+ strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_mtime)));
+}
+
+void col_atime(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color;
+ strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_atime)));
+}
+
+void col_ctime(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color;
+ strftime(buf, (size_t)width, TIME_FMT, localtime(&(entry->info.st_ctime)));
+}
+
+void col_selected(entry_t *entry, const char *color, char *buf, int width) {
+ (void)width;
+ buf = stpcpy(buf, IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR);
+ buf = stpcpy(buf, color);
+}
+
+void col_perm(entry_t *entry, const char *color, char *buf, int width) {
+ (void)color; (void)width;
+ sprintf(buf, " %03o", entry->info.st_mode & 0777);
+}
+
+void col_random(entry_t *entry, const char *color, char *buf, int width)
+{
+ (void)color;
+ sprintf(buf, "%*d", width, entry->shufflepos);
+}
+
+void col_size(entry_t *entry, const char *color, char *buf, int width)
+{
+ (void)color; (void)width;
+ int j = 0;
+ const char* units = "BKMGTPEZY";
+ double bytes = (double)entry->info.st_size;
+ while (bytes > 1024) {
+ bytes /= 1024;
+ j++;
+ }
+ sprintf(buf, " %6.*f%c ", j > 0 ? 1 : 0, bytes, units[j]);
+}
+
+void col_name(entry_t *entry, const char *color, char *buf, int width)
+{
+ (void)width;
+ if (entry->no_esc) buf = stpcpy(buf, entry->name);
+ else buf = stpcpy_escaped(buf, entry->name, color);
+
+ if (E_ISDIR(entry)) buf = stpcpy(buf, "/");
+
+ if (!entry->linkname) return;
+
+ buf = stpcpy(buf, "\033[2m -> \033[3m");
+ buf = stpcpy(buf, color);
+ if (entry->link_no_esc) buf = stpcpy(buf, entry->linkname);
+ else buf = stpcpy_escaped(buf, entry->linkname, color);
+
+ if (S_ISDIR(entry->linkedmode))
+ buf = stpcpy(buf, "/");
+
+ buf = stpcpy(buf, "\033[22;23m");
+}
diff --git a/columns.h b/columns.h
index 297a00a..3c9c599 100644
--- a/columns.h
+++ b/columns.h
@@ -1,154 +1,15 @@
/*
- * This file contains logic for drawing columns.
+ * columns.h - This file contains definitions for bb column-drawing code.
*/
-static void lpad(char *buf, int width)
-{
- int len = strlen(buf);
- if (len < width) {
- int pad = width - len;
- memmove(&buf[pad], buf, (size_t)len + 1);
- while (pad > 0) buf[--pad] = ' ';
- }
-}
+#ifndef FILE_COLUMNS__H
+#define FILE_COLUMNS__H
-static char* stpcpy_escaped(char *buf, const char *str, const char *color)
-{
- static const char *escapes = " abtnvfr e";
- for (const char *c = str; *c; ++c) {
- if (*c > 0 && *c <= '\x1b' && escapes[(int)*c] != ' ') { // "\n", etc.
- buf += sprintf(buf, "\033[31m\\%c%s", escapes[(int)*c], color);
- } else if (*c >= 0 && !(' ' <= *c && *c <= '~')) { // "\x02", etc.
- buf += sprintf(buf, "\033[31m\\x%02X%s", *c, color);
- } else {
- *(buf++) = *c;
- }
- }
- *buf = '\0';
- return buf;
-}
+#include "entry.h"
-static void timeago(char *buf, time_t t)
-{
- const int SECOND = 1;
- const int MINUTE = 60 * SECOND;
- const int HOUR = 60 * MINUTE;
- const int DAY = 24 * HOUR;
- const int MONTH = 30 * DAY;
- const int YEAR = 365 * DAY;
-
- time_t now = time(0);
- double delta = difftime(now, t);
-
- if (delta < 1.5)
- sprintf(buf, "a second");
- else if (delta < 1 * MINUTE)
- sprintf(buf, "%d seconds", (int)delta);
- else if (delta < 2 * MINUTE)
- sprintf(buf, "a minute");
- else if (delta < 1 * HOUR)
- sprintf(buf, "%d minutes", (int)delta/MINUTE);
- else if (delta < 2 * HOUR)
- sprintf(buf, "an hour");
- else if (delta < 1 * DAY)
- sprintf(buf, "%d hours", (int)delta/HOUR);
- else if (delta < 2 * DAY)
- sprintf(buf, "yesterday");
- else if (delta < 1 * MONTH)
- sprintf(buf, "%d days", (int)delta/DAY);
- else if (delta < 2 * MONTH)
- sprintf(buf, "a month");
- else if (delta < 1 * YEAR)
- sprintf(buf, "%d months", (int)delta/MONTH);
- else if (delta < 2 * YEAR)
- sprintf(buf, "a year");
- else
- sprintf(buf, "%d years", (int)delta/YEAR);
-}
-
-static void col_mreltime(entry_t *entry, const char *color, char *buf, int width) {
- (void)color;
- timeago(buf, entry->info.st_mtime);
- lpad(buf, width);
-}
-
-static void col_areltime(entry_t *entry, const char *color, char *buf, int width) {
- (void)color;
- timeago(buf, entry->info.st_atime);
- lpad(buf, width);
-}
-
-static void col_creltime(entry_t *entry, const char *color, char *buf, int width) {
- (void)color;
- timeago(buf, entry->info.st_ctime);
- lpad(buf, width);
-}
-
-static void col_mtime(entry_t *entry, const char *color, char *buf, int width) {
- (void)color;
- strftime(buf, (size_t)width, BB_TIME_FMT, localtime(&(entry->info.st_mtime)));
-}
-
-static void col_atime(entry_t *entry, const char *color, char *buf, int width) {
- (void)color;
- strftime(buf, (size_t)width, BB_TIME_FMT, localtime(&(entry->info.st_atime)));
-}
-
-static void col_ctime(entry_t *entry, const char *color, char *buf, int width) {
- (void)color;
- strftime(buf, (size_t)width, BB_TIME_FMT, localtime(&(entry->info.st_ctime)));
-}
-
-static void col_selected(entry_t *entry, const char *color, char *buf, int width) {
- (void)width;
- buf = stpcpy(buf, IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR);
- buf = stpcpy(buf, color);
-}
-
-static void col_perm(entry_t *entry, const char *color, char *buf, int width) {
- (void)color; (void)width;
- sprintf(buf, " %03o", entry->info.st_mode & 0777);
-}
-
-static void col_random(entry_t *entry, const char *color, char *buf, int width)
-{
- (void)color;
- sprintf(buf, "%*d", width, entry->shufflepos);
-}
-
-static void col_size(entry_t *entry, const char *color, char *buf, int width)
-{
- (void)color; (void)width;
- int j = 0;
- const char* units = "BKMGTPEZY";
- double bytes = (double)entry->info.st_size;
- while (bytes > 1024) {
- bytes /= 1024;
- j++;
- }
- sprintf(buf, " %6.*f%c ", j > 0 ? 1 : 0, bytes, units[j]);
-}
-
-static void col_name(entry_t *entry, const char *color, char *buf, int width)
-{
- (void)width;
- if (entry->no_esc) buf = stpcpy(buf, entry->name);
- else buf = stpcpy_escaped(buf, entry->name, color);
-
- if (E_ISDIR(entry)) buf = stpcpy(buf, "/");
-
- if (!entry->linkname) return;
-
- buf = stpcpy(buf, "\033[2m -> \033[3m");
- buf = stpcpy(buf, color);
- if (entry->link_no_esc) buf = stpcpy(buf, entry->linkname);
- else buf = stpcpy_escaped(buf, entry->linkname, color);
-
- if (S_ISDIR(entry->linkedmode))
- buf = stpcpy(buf, "/");
-
- buf = stpcpy(buf, "\033[22;23m");
-}
+#define TIME_FMT " %T %D "
+#define SELECTED_INDICATOR " \033[31;7m \033[0m"
+#define NOT_SELECTED_INDICATOR " "
typedef struct {
const char *name;
@@ -156,16 +17,28 @@ typedef struct {
unsigned int stretchy : 1;
} column_t;
-static column_t columns[255] = {
- ['*'] = {.name = "*", .render = col_selected},
- ['n'] = {.name = "Name", .render = col_name, .stretchy = 1},
- ['s'] = {.name = " Size", .render = col_size},
- ['p'] = {.name = "Perm", .render = col_perm},
- ['m'] = {.name = " Modified", .render = col_mreltime},
- ['M'] = {.name = " Modified ", .render = col_mtime},
- ['a'] = {.name = " Accessed", .render = col_areltime},
- ['A'] = {.name = " Accessed ", .render = col_atime},
- ['c'] = {.name = " Created", .render = col_creltime},
- ['C'] = {.name = " Created ", .render = col_ctime},
- ['r'] = {.name = "Random", .render = col_random},
-};
+typedef enum {
+ COL_NONE = 0,
+ COL_NAME = 'n',
+ COL_SIZE = 's',
+ COL_PERM = 'p',
+ COL_MTIME = 'm',
+ COL_CTIME = 'c',
+ COL_ATIME = 'a',
+ COL_RANDOM = 'r',
+ COL_SELECTED = '*',
+} column_e;
+
+void col_mreltime(entry_t *entry, const char *color, char *buf, int width);
+void col_areltime(entry_t *entry, const char *color, char *buf, int width);
+void col_creltime(entry_t *entry, const char *color, char *buf, int width);
+void col_mtime(entry_t *entry, const char *color, char *buf, int width);
+void col_atime(entry_t *entry, const char *color, char *buf, int width);
+void col_ctime(entry_t *entry, const char *color, char *buf, int width);
+void col_selected(entry_t *entry, const char *color, char *buf, int width);
+void col_perm(entry_t *entry, const char *color, char *buf, int width);
+void col_random(entry_t *entry, const char *color, char *buf, int width);
+void col_size(entry_t *entry, const char *color, char *buf, int width);
+void col_name(entry_t *entry, const char *color, char *buf, int width);
+
+#endif
diff --git a/entry.h b/entry.h
new file mode 100644
index 0000000..0574341
--- /dev/null
+++ b/entry.h
@@ -0,0 +1,38 @@
+/*
+ * entry.h - Define types for file entries.
+ */
+#ifndef FILE_ENTRY__H
+#define FILE_ENTRY__H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define IS_SELECTED(p) (((p)->selected.atme) != NULL)
+#define IS_VIEWED(p) ((p)->index >= 0)
+#define IS_LOADED(p) ((p)->hash.atme != NULL)
+
+/* entry_t uses intrusive linked lists. This means entries can only belong to
+ * one list at a time, in this case the list of selected entries. 'atme' is an
+ * indirect pointer to either the 'next' field of the previous list member, or
+ * the variable that points to the first list member. In other words,
+ * item->next->atme == &item->next and firstitem->atme == &firstitem.
+ */
+typedef struct entry_s {
+ struct {
+ struct entry_s *next, **atme;
+ } selected, hash;
+ char *name, *linkname;
+ struct stat info;
+ mode_t linkedmode;
+ int no_esc : 1;
+ int link_no_esc : 1;
+ int shufflepos;
+ int index;
+ char fullname[1];
+ // ------- fullname must be last! --------------
+ // When entries are allocated, extra space on the end is reserved to fill
+ // in fullname.
+} entry_t;
+
+#endif