Refactoring into multiple files better

This commit is contained in:
Bruce Hill 2020-12-30 22:47:49 -08:00
parent 18681fa449
commit 5371a49ce0
8 changed files with 646 additions and 573 deletions

View File

@ -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)
$(NAME): $(NAME).c bterm.h bb.h columns.h
$(CC) $(NAME).c $(CFLAGS) $(CWARN) $(G) $(O) -o $@
.c.o:
$(CC) -c $(CFLAGS) $(CWARN) $(G) $(O) -o $@ $<
$(NAME): $(OBJFILES) $(NAME).c
$(CC) $(CFLAGS) $(CWARN) $(G) $(O) -o $@ $(OBJFILES) $(NAME).c
install: $(NAME)
@prefix="$(PREFIX)"; \

158
bb.c
View File

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

130
bb.h
View File

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

268
bterm.c Normal file
View File

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

257
bterm.h
View File

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

161
columns.c Normal file
View File

@ -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");
}

191
columns.h
View File

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

38
entry.h Normal file
View File

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