sfm

Simple file manager
git clone git://git.christosmarg.xyz/sfm.git
Log | Files | Refs | README | LICENSE

commit b18087666f3ca2651839323bc03e8b8d996319ca
parent d8464186980d6faa6e11810dee73db3a8e1d1d65
Author: Christos Margiolis <christos@margiolis.net>
Date:   Mon, 22 Nov 2021 01:56:40 +0200

undergoing major cleanup and changes

Diffstat:
A.gitignore | 2++
MREADME | 7+++++--
Mconfig.h | 127+++++++++++++------------------------------------------------------------------
Msfm.c | 831++++++++++++++++++++++++++++++++-----------------------------------------------
4 files changed, 361 insertions(+), 606 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +config.h.old +scripts/ diff --git a/README b/README @@ -1,5 +1,6 @@ -sfm - simple file manager -========================= +sfm (WIP) +========= + sfm is a curses file manager for UNIX-based operating systems. Usage @@ -12,3 +13,5 @@ Configuration is done by editing `config.h` and recompiling the source code. Note ---- This project is still early in development so don't use it. + +Report any bugs to <christos@margiolis.net>. diff --git a/config.h b/config.h @@ -1,121 +1,34 @@ /* Used in `xdelay`. */ #define DELAY_MS 350 -/* Defines how many lines above the last line scrolling starts. */ -#define SCROLLOFF 4 - -/* Clumps to 1 if < 1 and to 9 if > 9 */ -static uint npanes = 4; +/* Must be between 1 and 9. */ +static int npanes = 4; /* Read date(1)'s and strftime(3)'s man pages for info on formatting. */ static char *datefmt = "%F"; -/* Fallback color in case no color is given for a given entry in `colors`. */ -static int defaultcolor = 0xff; - -/* Opening utility. */ -static char *openprog = "xdg-open"; - -/* - * c1 - Very light yellow - * e2 - Yellow - * 27 - Light blue - * 2e - Light green - * 60 - Dark orchid - * 33 - Cyan - * f7 - Grey - * c6 - Deep pink - * d6 - Orange - * ab - Violet - * c4 - Red - * - * https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg - */ -static int colors[] = { - [C_BLK] = 0x33, /* Block device */ - [C_CHR] = 0xe2, /* Character device */ - [C_DIR] = 0x27, /* Directory */ - [C_EXE] = 0xd6, /* Executable file */ - [C_FIL] = 0xff, /* Regular file */ - [C_HRD] = 0x60, /* Hard link */ - [C_LNK] = 0xab, /* Symbolic link */ - [C_MIS] = 0xc4, /* Missing file OR file details */ - [C_ORP] = 0xc1, /* Orphaned symlink */ - [C_PIP] = 0xc6, /* Named pipe (FIFO) */ - [C_SOC] = 0x2e, /* Socket */ - [C_UND] = 0xc4, /* Unknown OR 0B regular/exe file */ - [C_INF] = 0xf7, /* Information */ - [C_PAN] = 0xff, /* Pane */ - [C_SPN] = COLOR_BLUE, /* Selected pane */ - [C_PTH] = COLOR_BLUE, /* Path */ - [C_STA] = COLOR_BLUE, /* Stats */ -}; - -/* TODO: slstatus */ /* - * Available functions. Inside each function's parentheses is written - * which `arg` field has to be assigned a value. - * - * - chlevel(.i): Change directory. - * * -1: Parent - * * +1: Child - * - * - scrollpane(.i): Scroll the current pane. - * * -1: Scroll up - * * +1: Scroll down - * * 0: Go to top - * * [TODO]: Go to bottom - * * [TODO]: Half scroll up/down - * - * - cyclepane({0}): Switch panes. - * - * - setflag(.i): The name explains it. - * * F_SHOWALL: Show hidden files - * * F_INFO: Show detailed info next to each entry - * * F_REDRAW: Redraw current pane - * * F_QUIT: Exit program - * - * - sel(.i): Select an entry: - * * 0: Select only one entry - * * 1: Select all entries. Currently selected entries get de-selected. - * - * - cd(.s): Go to directory. - * - * - run(.s): Run a shell command. It accepts environmental variables. - * - * - sort({0}): Sort entries by: - * * Name - * * Size - * * Date - * * Reverse (reverses currently selected sorting function) - * - * - prompt({0}): Write something in a prompt and (probably) execute it. + * function description argument */ static struct key keys[] = { /* key func arg */ - { KEY_LEFT, chlevel, {.i = -1} }, - { 'h', chlevel, {.i = -1} }, - { KEY_RIGHT, chlevel, {.i = +1} }, - { 'l', chlevel, {.i = +1} }, - { '\n', chlevel, {.i = +1} }, - { KEY_UP, scrollpane, {.i = -1} }, - { 'k', scrollpane, {.i = -1} }, - { KEY_DOWN, scrollpane, {.i = +1} }, - { 'j', scrollpane, {.i = +1} }, - { 'g', scrollpane, {.i = 0} }, - //{ 'G', scrollpane, {.i = S_BOTTOM} }, + { KEY_LEFT, chlevel, { .i = S_LEFT } }, + { 'h', chlevel, { .i = S_LEFT } }, + { KEY_RIGHT, chlevel, { .i = S_RIGHT } }, + { 'l', chlevel, { .i = S_RIGHT } }, + { KEY_UP, scrolldir, { .i = S_UP } }, + { 'j', scrolldir, { .i = S_UP } }, + { KEY_DOWN, scrolldir, { .i = S_DOWN } }, + { 'k', scrolldir, { .i = S_DOWN } }, + //{ KEY_PPAGE, scrolldir, { .i = S_PGUP } }, + //{ KEY_NPAGE, scrolldir, { .i = S_PGDOWN } }, + { 'g', scrolldir, { .i = S_TOP } }, + { 'G', scrolldir, { .i = S_BOTTOM } }, + { '.', setflag, { .i = F_SHOWALL } }, + { 'i', setflag, { .i = F_INFO } }, + { CTRL('r'), setflag, { .i = F_REDRAW } }, + { 'q', setflag, { .i = F_QUIT } }, { '\t', cyclepane, {0} }, - { ' ', sel, {.i = 0} }, - { CTRL('a'), sel, {.i = 1} }, - { '.', setflag, {.i = F_SHOWALL} }, - { 'i', setflag, {.i = F_INFO} }, - { CTRL('r'), setflag, {.i = F_REDRAW} }, - { 'q', setflag, {.i = F_QUIT} }, - { '~', cd, {.s = "/home/christos"} }, - { CTRL('b'), cd, {.s = "/storage"} }, - { 'p', run, {.s = "PAGER"} }, - { 'e', run, {.s = "EDITOR"} }, - { 'x', run, {.s = "rm -rf"} }, { 's', sort, {0} }, - { ':', prompt, {0} }, + { '~', cd, { .s = "HOME"} }, }; diff --git a/sfm.c b/sfm.c @@ -5,6 +5,7 @@ #include <dirent.h> #include <err.h> +#include <errno.h> #include <fcntl.h> #include <limits.h> #include <locale.h> @@ -17,10 +18,6 @@ #include <curses.h> -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif /* PATH_MAX */ - #ifndef DT_DIR #define DT_DIR 4 #endif /* DT_DIR */ @@ -39,38 +36,36 @@ #define SIGWINCH 28 #endif /* SIGWINCH */ -#define CTRL(x) ((x) & 0x1f) -#define YMAX (getmaxy(stdscr)) -#define XMAX (getmaxx(stdscr)) -#define SETTIMEOUT() (timeout(1000)) -#define CLRTIMEOUT() (timeout(-1)) -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) -#define ENTSORT(e, n, f) (qsort((e), (n), sizeof(*(e)), f)) - -typedef unsigned char uchar; -typedef unsigned int uint; -typedef unsigned long ulong; +#define F_SHOWALL (1 << 1) +#define F_INFO (1 << 2) +#define F_REDRAW (1 << 3) +#define F_QUIT (1 << 4) + +#define CTRL(x) ((x) & 0x1f) +#define YMAX (getmaxy(stdscr)) +#define XMAX (getmaxx(stdscr)) +#define TIMEOUT_SET() (timeout(1000)) +#define TIMEOUT_CLEAR() (timeout(-1)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define LEN(x) (sizeof(x) / sizeof(x[0])) +#define ENTSORT(e, n, f) (qsort((e), (n), sizeof(*(e)), f)) +#define UNUSED(x) ((void)(x)) struct entry { + char name[NAME_MAX]; char date[NAME_MAX]; - char stats[36]; /* XXX: not needed I think */ - char *name; - off_t sz; + off_t size; mode_t mode; - uchar flags; - uchar selected; + int flags; + int selected; }; struct pane { struct entry *ents; - char *curdir; - uint nsel; - uint nents; + char curdir[PATH_MAX]; + int nents; int sel; - int cur; - int curscroll; int (*sortfn)(const void *, const void *); }; @@ -87,93 +82,63 @@ struct key { }; enum { - F_SHOWALL = 1 << 1, - F_INFO = 1 << 2, - F_REDRAW = 1 << 3, - F_QUIT = 1 << 4, + S_UP, + S_DOWN, + S_PGUP, + S_PGDOWN, + S_TOP, + S_BOTTOM, + S_LEFT, + S_RIGHT, }; enum { - MSG_EXEC, MSG_SORT, - MSG_PROMPT, MSG_FAIL, }; -/* Colors */ -enum { - C_BLK = 1, /* Block device */ - C_CHR, /* Character device */ - C_DIR, /* Directory */ - C_EXE, /* Executable file */ - C_FIL, /* Regular file */ - C_HRD, /* Hard link */ - C_LNK, /* Symbolic link */ - C_MIS, /* Missing file OR file details */ - C_ORP, /* Orphaned symlink */ - C_PIP, /* Named pipe (FIFO) */ - C_SOC, /* Socket */ - C_UND, /* Unknown OR 0B regular/exe file */ - C_INF, /* Information */ - C_PAN, /* Pane */ - C_SPN, /* Selected pane */ - C_PTH, /* Path */ - C_STA, /* Stats */ - C_LAST, /* Not used. Has to be last. */ -}; - -static void initcurses(void); -static struct entry *entget(const char *); -static void entprint(struct pane *); -static void spawn(char *); +static void curses_init(void); +static struct entry *entries_read(const char *); +static void pane_print(struct pane *); static void notify(const char *, ...); -static int confirmact(const char *); -static int appendbuf(char *, size_t *, const char *); +static int confirm(const char *); static char *escape(const char *); -static char *fmtsize(size_t); -static void namprint(char *); -static char *promptread(const char *); -static void selclump(struct pane *); -static void xdelay(ulong); +static char *size_human(size_t); +static void str_print(char *); +static char *prompt_read(const char *); +static void xdelay(int); +static void sel_clump(void); +static void *emalloc(size_t); +static void *erealloc(void *, size_t); +static char *estrdup(const char *); +static void sighandler(int); +static void cleanup(void); static void chlevel(const arg *); -static void scrollpane(const arg *); +static void scrolldir(const arg *); static void cyclepane(const arg *); static void setflag(const arg *); -static void _sel(struct pane *, int); -static void sel(const arg *); static void cd(const arg *); static void run(const arg *); static void sort(const arg *); -static void prompt(const arg *); -static void echdir(const char *); -static void *emalloc(size_t); -static void *erealloc(void *, size_t); -static char *estrdup(const char *); -static void sighandler(int); -static void entfree(struct pane *); -static void cleanup(void); +static char *argv0; +static struct pane *panes; +static int selpane = 0; /* current pane */ static const char *msgs[] = { - [MSG_EXEC] = "%s (y/N)?", [MSG_SORT] = "'n'ame 's'ize 'd'ate 'r'everse", - [MSG_PROMPT] = ":", [MSG_FAIL] = "action failed: %s", }; -static char *argv0; /* program name */ -static struct pane *pane; /* display */ -static int selpane = 0; /* current pane */ - /* flags */ -static uchar f_showall = 0; /* show hidden files */ -static uchar f_info = 0; /* show info about entries */ -static uchar f_noconfirm = 0; /* exec without confirmation */ -static uchar f_namesort = 0; /* sort entries by name */ -static uchar f_sizesort = 0; /* sort entries by size */ -static uchar f_datesort = 0; /* sort entries by date */ -static uchar f_revsort = 0; /* reverse current sort function */ -static volatile sig_atomic_t f_redraw = 0; /* redraw screen */ -static volatile sig_atomic_t f_running = 1; /* 0 when sfm should exit */ +static int f_showall = 0; /* show hidden files */ +static int f_info = 0; +static int f_noconfirm = 0; /* exec without confirmation */ +static int f_namesort = 0; +static int f_sizesort = 0; +static int f_datesort = 0; +static int f_revsort = 0; +static volatile sig_atomic_t f_redraw = 0; +static volatile sig_atomic_t f_running = 1; #include "config.h" @@ -186,7 +151,7 @@ namecmp(const void *x, const void *y) static inline int revnamecmp(const void *x, const void *y) { - return -namecmp(x, y); + return (-namecmp(x, y)); } static inline int @@ -198,78 +163,46 @@ datecmp(const void *x, const void *y) static inline int revdatecmp(const void *x, const void *y) { - return -datecmp(x, y); + return (-datecmp(x, y)); } static inline int sizecmp(const void *x, const void *y) { - return -(((struct entry *)x)->sz - ((struct entry *)y)->sz); + return (-(((struct entry *)x)->size - ((struct entry *)y)->size)); } static inline int revsizecmp(const void *x, const void *y) { - return -sizecmp(x, y); + return (-sizecmp(x, y)); } static void -initcurses(void) +curses_init(void) { - struct sigaction sa; - int i, l; - if (!initscr()) errx(1, "initscr"); noecho(); cbreak(); curs_set(0); keypad(stdscr, 1); - SETTIMEOUT(); + TIMEOUT_SET(); set_escdelay(0); - - start_color(); - use_default_colors(); - l = ARRLEN(colors); - for (i = 1; i < C_LAST; i++) { - /* - * This lets us use any number of elements in `colors`. - * If there's no color for a given entry, it falls back - * to `defaultcolor`. - */ - if (i < l && colors[i]) - /* FIXME: wrong. */ - (void)init_pair(i, colors[i], -1); - else - (void)init_pair(i, defaultcolor, -1); - } - - (void)memset(&sa, 0, sizeof(sa)); - (void)sigemptyset(&sa.sa_mask); - sa.sa_handler = sighandler; - sa.sa_flags = SA_RESTART; - if (sigaction(SIGINT, &sa, NULL) < 0) - err(1, "sigaction: SIGINT"); - if (sigaction(SIGTERM, &sa, NULL) < 0) - err(1, "sigaction: SIGTERM"); - if (sigaction(SIGUSR1, &sa, NULL) < 0) - err(1, "sigaction: SIGUSR1"); - if (sigaction(SIGWINCH, &sa, NULL) < 0) - err(1, "sigaction: SIGWINCH"); } static struct entry * -entget(const char *path) +entries_read(const char *path) { - struct entry *ents, *e; + struct entry *ents, *ep; struct dirent *dent; struct stat st; DIR *dir; int i = 0; - char type; + /* XXX: should we die? */ if ((dir = opendir(path)) == NULL) - err(1, "opendir: %s:", path); + err(1, "opendir: %s", path); ents = emalloc(sizeof(struct entry)); @@ -281,199 +214,176 @@ entget(const char *path) /* TODO: handle filtering */ ents = erealloc(ents, (i + 1) * sizeof(struct entry)); - e = &ents[i]; + ep = &ents[i]; - e->name = estrdup(dent->d_name); + (void)strlcpy(ep->name, dent->d_name, sizeof(ep->name)); /* XXX: use fstatat(3) */ - lstat(e->name, &st); - e->sz = st.st_size; - e->mode = st.st_mode; - strftime(e->date, sizeof(e->date), datefmt, localtime(&st.st_ctime)); - - /* XXX: resets on every redraw, keep track somehow */ - e->selected = 0; - e->flags = dent->d_type | 0; - - switch (e->mode & S_IFMT) { - case S_IFREG: - type = '-'; - break; - case S_IFDIR: - type = 'd'; - break; - case S_IFLNK: - type = 'l'; - break; - case S_IFSOCK: - type = 's'; - break; - case S_IFIFO: - type = 'p'; - break; - case S_IFBLK: - type = 'b'; - break; - case S_IFCHR: - type = 'c'; - break; - default: - type = '?'; - break; - } + lstat(ep->name, &st); + ep->size = st.st_size; + ep->mode = st.st_mode; + strftime(ep->date, sizeof(ep->date), datefmt, + localtime(&st.st_ctime)); - snprintf(e->stats, sizeof(e->stats), - "%c%c%c%c%c%c%c%c%c%c %s %s", - type, - e->mode & S_IRUSR ? 'r' : '-', - e->mode & S_IWUSR ? 'w' : '-', - e->mode & S_IXUSR ? 'x' : '-', - e->mode & S_IRGRP ? 'r' : '-', - e->mode & S_IWGRP ? 'w' : '-', - e->mode & S_IXGRP ? 'x' : '-', - e->mode & S_IROTH ? 'r' : '-', - e->mode & S_IWOTH ? 'w' : '-', - e->mode & S_IXOTH ? 'x' : '-', - fmtsize(e->sz), - e->date); + /* FIXME: resets on every redraw, keep track somehow */ + ep->selected = 0; + ep->flags = dent->d_type | 0; i++; } (void)closedir(dir); - pane[selpane].nents = i; - ENTSORT(ents, pane[selpane].nents, pane[selpane].sortfn); + panes[selpane].nents = i; + ENTSORT(ents, panes[selpane].nents, panes[selpane].sortfn); - return ents; + return (ents); } static void -entprint(struct pane *p) +pane_print(struct pane *p) { - struct entry *e; + struct entry *ep; int n, i; - uint attrs; - uchar color; + int attrs; + /*int color;*/ char ind; - attron(COLOR_PAIR(C_PAN)); + /*attron(COLOR_PAIR(C_PAN));*/ addch('['); for (i = 0; i < npanes; i++) { if (i == selpane) - attron(COLOR_PAIR(C_SPN) | A_BOLD | A_REVERSE); + attron(A_BOLD | A_REVERSE); addch(i + '0' + 1); - attroff(COLOR_PAIR(C_SPN) | A_BOLD | A_REVERSE); + attroff(A_BOLD | A_REVERSE); if (i != npanes - 1) addch(' '); } addstr("] "); - attroff(COLOR_PAIR(C_PAN)); + /*attroff(COLOR_PAIR(C_PAN));*/ - attron(A_BOLD | COLOR_PAIR(C_PTH)); - namprint(p->curdir); - attroff(A_BOLD | COLOR_PAIR(C_PTH)); + /*attron(A_BOLD | COLOR_PAIR(C_PTH));*/ + str_print(p->curdir); + /*attroff(A_BOLD | COLOR_PAIR(C_PTH));*/ - if ((n = MIN(YMAX - 4, p->nents - p->curscroll)) <= 0) + /*if ((n = MIN(YMAX - 4, p->nents - p->curscroll)) <= 0)*/ + /*return;*/ + if ((n = p->nents) <= 0) return; for (i = 0; i < n; i++) { - /* FIXME: segfault when going back if curscroll is too big */ - e = &p->ents[i + p->curscroll]; + ep = &p->ents[i]; ind = ' '; attrs = 0; - color = 0; + /*color = 0;*/ move(i + 2, 0); - - if (i == p->cur) + + /* FIXME: no. */ + if (i == p->sel) attrs |= A_REVERSE; if (f_info) { - attron(COLOR_PAIR(C_INF)); + /*attron(COLOR_PAIR(C_INF));*/ printw("%s %c%c%c %7s ", - e->date, - ((e->mode >> 6) & 7) + '0', - ((e->mode >> 3) & 7) + '0', - (e->mode & 7) + '0', - fmtsize(e->sz)); - attroff(COLOR_PAIR(C_INF)); + ep->date, + ((ep->mode >> 6) & 7) + '0', + ((ep->mode >> 3) & 7) + '0', + (ep->mode & 7) + '0', + size_human(ep->size)); + /*attroff(COLOR_PAIR(C_INF));*/ } - addch(e->selected ? '+' : ' '); + addch(ep->selected ? '+' : ' '); - switch (e->mode & S_IFMT) { + switch (ep->mode & S_IFMT) { case S_IFDIR: ind = '/'; - color = C_DIR; + /*color = C_DIR;*/ attrs |= A_BOLD; break; case S_IFREG: - color = C_FIL; - if (e->mode & 0100) { + /*color = C_FIL;*/ + if (ep->mode & 0100) { ind = '*'; - color = C_EXE; + /*color = C_EXE;*/ } break; case S_IFLNK: /* FIXME: @ is added to dirs too */ - ind = (e->flags & DT_DIR) ? '/' : '@'; - color = C_LNK; - if (S_ISDIR(e->mode)) + ind = (ep->flags & DT_DIR) ? '/' : '@'; + /*color = C_LNK;*/ + if (S_ISDIR(ep->mode)) attrs |= A_BOLD; break; case S_IFSOCK: ind = '='; - color = C_SOC; + /*color = C_SOC;*/ break; case S_IFIFO: ind = '|'; - color = C_PIP; + /*color = C_PIP;*/ break; case S_IFBLK: - color = C_BLK; + /*color = C_BLK;*/ break; case S_IFCHR: - color = C_CHR; + /*color = C_CHR;*/ break; default: ind = '?'; - color = C_UND; + /*color = C_UND;*/ break; } - attrs |= COLOR_PAIR(color); + /*attrs |= COLOR_PAIR(color);*/ attron(attrs); - namprint(e->name); + str_print(ep->name); attroff(attrs); - addch(ind); } - attron(COLOR_PAIR(C_STA)); - mvprintw(YMAX - 1, 0, "%ld/%ld %s", - p->sel + 1, p->nents, p->ents[p->sel].stats); - attroff(COLOR_PAIR(C_STA)); -} - -/* FIXME: weird stuff with select all */ -static void -spawn(char *cmd) -{ - char *args[] = {getenv("SHELL"), "-c", cmd, NULL}; - switch (fork()) { - case -1: - notify(msgs[MSG_FAIL], "fork"); - xdelay(DELAY_MS << 2); + ep = &p->ents[p->sel]; + switch (ep->mode & S_IFMT) { + case S_IFREG: + n = '-'; break; - case 0: - (void)execvp(*args, args); - _exit(0); + case S_IFDIR: + n = 'd'; + break; + case S_IFLNK: + n = 'l'; + break; + case S_IFSOCK: + n = 's'; + break; + case S_IFIFO: + n = 'p'; + break; + case S_IFBLK: + n = 'b'; + break; + case S_IFCHR: + n = 'c'; break; default: - (void)endwin(); - if (wait(NULL) < 0) - err(1, "wait"); + n = '?'; break; } + + /*attron(COLOR_PAIR(C_STA));*/ + mvprintw(YMAX - 1, 0, "%ld/%ld %c%c%c%c%c%c%c%c%c%c %s %s", + p->sel + 1, p->nents, n, + ep->mode & S_IRUSR ? 'r' : '-', + ep->mode & S_IWUSR ? 'w' : '-', + ep->mode & S_IXUSR ? 'x' : '-', + ep->mode & S_IRGRP ? 'r' : '-', + ep->mode & S_IWGRP ? 'w' : '-', + ep->mode & S_IXGRP ? 'x' : '-', + ep->mode & S_IROTH ? 'r' : '-', + ep->mode & S_IWOTH ? 'w' : '-', + ep->mode & S_IXOTH ? 'x' : '-', + size_human(ep->size), + ep->date); + /*attroff(COLOR_PAIR(C_STA));*/ } static void @@ -488,6 +398,7 @@ notify(const char *fmt, ...) (void)vsnprintf(buf, XMAX, fmt, args); va_end(args); addstr(buf); + f_redraw = 1; } static int @@ -496,33 +407,15 @@ confirmact(const char *str) char c; if (f_noconfirm) - return 1; - CLRTIMEOUT(); - notify(msgs[MSG_EXEC], str); + return (1); + TIMEOUT_CLEAR(); + /*notify(msgs[MSG_EXEC], str);*/ c = getch(); - SETTIMEOUT(); + TIMEOUT_SET(); return (c == 'y'); } -static int -appendbuf(char *buf, size_t *sz, const char *str) -{ - char *esc; - size_t l; - - esc = escape(str); - l = strlen(esc) + 2; /* +2 for the spaces in snprintf. */ - *sz += l; - if (snprintf(buf + strlen(buf), l, " %s ", esc) != l) { - free(esc); - return 0; - } - free(esc); - - return 1; -} - static char * escape(const char *str) { @@ -546,11 +439,11 @@ escape(const char *str) } buf[i] = '\0'; - return buf; + return (buf); } static char * -fmtsize(size_t sz) +size_human(size_t sz) { static char buf[12]; int i = 0; @@ -559,20 +452,27 @@ fmtsize(size_t sz) sz >>= 10; (void)snprintf(buf, sizeof(buf), "%ld%c", sz, "BKMGTPEZY"[i]); - return buf; + return (buf); } static void -namprint(char *str) +str_print(char *str) { char *s; - for (s = str; *s != '\0' && (s - str) < XMAX; s++) + for (s = str; *s != '\0' && (s - str) < XMAX; s++) { + /* Print a ~ if we have no more space left. */ + /* FIXME: put it outside the loop? */ + if (getcurx(stdscr) == XMAX - 1) { + addch('~'); + break; + } addch(*s); + } } static char * -promptread(const char *msg) +prompt_read(const char *msg) { char buf[BUFSIZ], *str; int len = 0, c; @@ -580,7 +480,7 @@ promptread(const char *msg) notify(msg); echo(); curs_set(1); - CLRTIMEOUT(); + TIMEOUT_CLEAR(); while ((c = getch()) != '\n') { switch (c) { @@ -605,82 +505,142 @@ promptread(const char *msg) exit: curs_set(0); noecho(); - SETTIMEOUT(); + TIMEOUT_SET(); - return str; + return (str); +} + +/* TODO: use nanosleep(2) */ +static void +xdelay(int delay) +{ + refresh(); + (void)usleep(delay * 1000); } static void -selclump(struct pane *p) +sel_clump(void) { + struct pane *p; + + p = &panes[selpane]; if (p->sel < 0) p->sel = 0; else if (p->sel > p->nents - 1) p->sel = p->nents - 1; - p->cur = p->sel; } -/* TODO: use nanosleep(2) */ +static void * +emalloc(size_t nb) +{ + void *p; + + if ((p = malloc(nb)) == NULL) + err(1, "malloc"); + return (p); +} + +static void * +erealloc(void *p, size_t nb) +{ + if ((p = realloc(p, nb)) == NULL) + err(1, "realloc"); + return (p); +} + +static char * +estrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + err(1, "strdup"); + return (p); +} + static void -xdelay(ulong delay) +sighandler(int sig) { - refresh(); - (void)usleep(delay * 1000); + switch (sig) { + case SIGINT: /* FALLTHROUGH */ + case SIGTERM: + case SIGUSR1: + f_running = 0; + case SIGWINCH: + f_redraw = 1; + break; + } +} + +static void +cleanup(void) +{ + int i = 0; + + for (i = 0; i < npanes; i++) + free(panes[i].ents); + free(panes); + endwin(); } -/* TODO: add sel "history" */ static void chlevel(const arg *arg) -{ +{ struct pane *p; - char buf[BUFSIZ]; - - p = &pane[selpane]; - if (arg->i < 0) { - echdir(".."); - } else if (arg->i > 0) { - if (p->ents[p->sel].flags & DT_DIR) - echdir(p->ents[p->sel].name); - - /* TODO: handle links to dirs */ - if (p->ents[p->sel].flags & DT_REG - && p->ents[p->sel].flags & ~DT_LNK) { - snprintf(buf, sizeof(buf), "%s %s", - openprog, p->ents[p->sel].name); - /* TODO: escape this buf! */ - spawn(buf); + union arg a; + + p = &panes[selpane]; + switch (arg->i) { + case S_LEFT: + a.s = ".."; + break; + case S_RIGHT: + if (p->ents[p->sel].flags & DT_DIR) { + a.s = p->ents[p->sel].name; + } else { + /* TODO: file */ + return; } + break; + default: + return; } - f_redraw = 1; + cd(&a); } -/* TODO: handle bottom and half scrolls */ static void -scrollpane(const arg *arg) +scrolldir(const arg *arg) { struct pane *p; - p = &pane[selpane]; - if (arg->i < 0) + p = &panes[selpane]; + switch (arg->i) { + case S_UP: p->sel--; - else if (arg->i > 0) + break; + case S_DOWN: p->sel++; - else + break; + /*case S_PGUP:*/ + /*p->sel >>= 1;*/ + /*break;*/ + /*case S_PGDOWN:*/ + /*p->sel <<= 1;*/ + /*break;*/ + case S_TOP: p->sel = 0; - selclump(p); - if (p->sel > YMAX - 4 - SCROLLOFF) { - p->cur = YMAX - 4 - SCROLLOFF; - if (p->cur + p->curscroll + arg->i < p->nents) - p->curscroll += arg->i; - } else { - p->cur = p->sel; - p->curscroll = 0; + break; + case S_BOTTOM: + p->sel = p->nents - 1; + break; } + sel_clump(); } static void cyclepane(const arg *arg) { + UNUSED(arg); selpane++; selpane %= npanes; f_redraw = 1; @@ -694,80 +654,40 @@ setflag(const arg *arg) if (arg->i & F_INFO) f_info ^= 1; if (arg->i & F_REDRAW) - ; + ; /* nothing */ if (arg->i & F_QUIT) f_running = 0; f_redraw = 1; } -static void -_sel(struct pane *p, int i) -{ - p->ents[i].selected ^= 1; - if (p->ents[i].selected) - p->nsel++; - else - p->nsel--; -} - -static void -sel(const arg *arg) -{ - struct pane *p; - int i; - - p = &pane[selpane]; - if (arg->i == 0) - _sel(p, p->sel); - else - for (i = 0; i < p->nents; i++) - _sel(p, i); -} - +/* FIXME: have to wait until xdelay returns */ static void cd(const arg *arg) { - echdir(arg->s); + char *path; + char apath[PATH_MAX]; + + if ((path = getenv(arg->s)) == NULL) + path = (char *)arg->s; + /* + * Using this `apath` temporary variable we avoid `entries_read` + * crashing when the path is wrong. + */ + if (realpath(path, apath) == NULL) + goto fail; + if (chdir(path) < 0) + goto fail; + (void)strlcpy(panes[selpane].curdir, apath, sizeof(apath)); f_redraw = 1; + return; +fail: + notify(msgs[MSG_FAIL], strerror(errno)); + xdelay(DELAY_MS << 2); } -/* FIXME: only runs on sels */ static void run(const arg *arg) { - struct pane *p; - char buf[BUFSIZ], *prog; - size_t sz; - int i; - - /* - * Check if the argument is an environmental variable so we - * don't have to implement seperate functions to handle just - * that. If the argument isn't an environmental variable, getenv(3) - * will return NULL and we'll use the actual argument as a program. - */ - if ((prog = getenv(arg->s)) == NULL) - prog = (char *)arg->s; /* Remove `const`ness. */ - sz = strlen(prog); - (void)strncpy(buf, prog, ++sz); - p = &pane[selpane]; - - if (p->nsel > 0) { - for (i = 0; i < p->nents; i++) - if (p->ents[i].selected - && !appendbuf(buf, &sz, p->ents[i].name)) - return; - } else - if (!appendbuf(buf, &sz, p->ents[p->sel].name)) - return; - buf[--sz] = '\0'; - - f_noconfirm = 0; - if (confirmact(buf)) { - spawn(buf); - f_redraw = 1; - p->nsel = 0; - } } static void @@ -775,7 +695,8 @@ sort(const arg *arg) { struct pane *p; - p = &pane[selpane]; + UNUSED(arg); + p = &panes[selpane]; notify(msgs[MSG_SORT]); switch (getch()) { @@ -813,109 +734,16 @@ sort(const arg *arg) ENTSORT(p->ents, p->nents, p->sortfn); } -static void -prompt(const arg *arg) -{ - char *buf; - char *str; - - if ((str = promptread(msgs[MSG_PROMPT])) == NULL) - return; - buf = estrdup(str); - if (confirmact(buf)) { - spawn(buf); - f_redraw = 1; - } - free(buf); -} - -/* FIXME: always have to wait until xdelay returns */ -static void -echdir(const char *path) -{ - if (chdir(path) < 0) { - notify(msgs[MSG_FAIL], "chdir"); - xdelay(DELAY_MS << 2); - } -} - -static void * -emalloc(size_t nb) -{ - void *p; - - if ((p = malloc(nb)) == NULL) - err(1, "malloc"); - - return p; -} - -static void * -erealloc(void *p, size_t nb) -{ - if ((p = realloc(p, nb)) == NULL) - err(1, "realloc"); - - return p; -} - -static char * -estrdup(const char *s) -{ - char *p; - - if ((p = strdup(s)) == NULL) - err(1, "strdup"); - return p; -} - -static void -sighandler(int sig) -{ - switch (sig) { - case SIGINT: /* FALLTHROUGH */ - case SIGTERM: - case SIGUSR1: - f_running = 0; - case SIGWINCH: - f_redraw = 1; - break; - } -} - -static void -entfree(struct pane *p) -{ - int i= 0; - - for (; i < p->nents; i++) - free(p->ents[i].name); - free(p->ents); - p->nents = 0; - p->nsel = 0; -} - -static void -cleanup(void) -{ - int i = 0; - - for (i = 0; i < npanes; i++) - entfree(&pane[i]); - free(pane); - endwin(); -} - int main(int argc, char *argv[]) { struct pane *p; + struct sigaction sa; char cwd[PATH_MAX] = {0}; int ch, i; argv0 = *argv; - if (!setlocale(LC_ALL, "")) - errx(1, "setlocale"); + (void)setlocale(LC_ALL, ""); if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) err(1, "isatty"); @@ -938,6 +766,19 @@ main(int argc, char *argv[]) } argc -= optind; argv += optind; + /* TODO: add [dir] */ + + (void)memset(&sa, 0, sizeof(sa)); + (void)sigfillset(&sa.sa_mask); + sa.sa_handler = sighandler; + if (sigaction(SIGINT, &sa, NULL) < 0) + err(1, "sigaction(SIGINT)"); + if (sigaction(SIGTERM, &sa, NULL) < 0) + err(1, "sigaction(SIGTERM)"); + if (sigaction(SIGWINCH, &sa, NULL) < 0) + err(1, "sigaction(SIGWINCH)"); + + curses_init(); if (npanes < 1) npanes = 1; @@ -946,38 +787,34 @@ main(int argc, char *argv[]) f_redraw = 1; f_namesort = 1; - pane = emalloc(npanes * sizeof(struct pane)); + panes = emalloc(npanes * sizeof(struct pane)); for (i = 0; i < npanes; i++) { - pane[i].ents = NULL; - pane[i].curdir = NULL; - pane[i].nents = 0; - pane[i].sel = pane[i].nsel = 0; - pane[i].cur = pane[i].curscroll = 0; - pane[i].sortfn = namecmp; + panes[i].ents = NULL; + panes[i].nents = 0; + panes[i].sel = 0; + panes[i].sortfn = namecmp; + (void)strlcpy(panes[i].curdir, getcwd(cwd, sizeof(cwd)), + sizeof(panes[i].curdir)); } - /* TODO: add [dir] */ - initcurses(); while (f_running) { - p = &pane[selpane]; + p = &panes[selpane]; erase(); if (f_redraw) { - /* FIXME: getcwd(3) is not suitable anymore... */ - if ((p->curdir = getcwd(cwd, sizeof(cwd))) == NULL) - err(1, "getcwd"); - entfree(p); - if ((p->ents = entget(p->curdir)) == NULL) - errx(1, "entget: %s", p->curdir); - selclump(p); + free(p->ents); + if ((p->ents = entries_read(p->curdir)) == NULL) + errx(1, "entries_read: %s", p->curdir); + sel_clump(); f_redraw = 0; + /* XXX: what? */ refresh(); } - entprint(p); + pane_print(p); /* TODO: use kqueue(2) and inotify */ if ((ch = getch()) != ERR) { - for (i = 0; i < ARRLEN(keys); i++) + for (i = 0; i < LEN(keys); i++) if (ch == keys[i].key) - keys[i].func(&(keys[i].arg)); + keys[i].func(&keys[i].arg); ch -= '0'; if (ch > 0 && ch <= npanes) { selpane = ch - 1; @@ -987,5 +824,5 @@ main(int argc, char *argv[]) } cleanup(); - return 0; + return (0); }