sfm

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

commit 8868b3cf35b3dde3c97cf6222976786757f65bb9
parent eaf954058a70cfd162a35e473ab3653df8369462
Author: Christos Margiolis <christos@margiolis.net>
Date:   Sat,  1 May 2021 00:02:53 +0300

added a basic signal handler, improved config.h

Diffstat:
MMakefile | 6+++---
MREADME | 8+++-----
Mconfig.h | 91++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Asfm.1 | 15+++++++++++++++
Msfm.c | 210+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
5 files changed, 217 insertions(+), 113 deletions(-)

diff --git a/Makefile b/Makefile @@ -41,10 +41,10 @@ install: all ${MKDIR} ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN_DIR} ${MKDIR} ${DESTDIR}${BIN_DIR} ${CP} ${BIN} ${BIN_DIR} - #${CP} ${MAN1} ${DESTDIR}${MAN_DIR} - #sed "s/VERSION/${VERSION}/g" < ${MAN1} > ${DESTDIR}${MAN_DIR}/${MAN1} + ${CP} ${MAN1} ${DESTDIR}${MAN_DIR} + sed "s/VERSION/${VERSION}/g" < ${MAN1} > ${DESTDIR}${MAN_DIR}/${MAN1} chmod 755 ${DESTDIR}${BIN_DIR}/${BIN} - #chmod 644 ${DESTDIR}${MAN_DIR}/${MAN1} + chmod 644 ${DESTDIR}${MAN_DIR}/${MAN1} uninstall: ${RM} ${DESTDIR}${BIN_DIR}/${BIN} diff --git a/README b/README @@ -1,15 +1,13 @@ sfm - simple file manager ========================= - -sfm is a minimal file manager for UNIX-based operating systems -that follows the suckless style of configuration. +sfm is a curses file manager for UNIX-based operating systems. Usage ----- # make install clean -sfm will be installed in /usr/local/bin by default. -Configuration is done by editing config.h and recompiling the source code. +sfm will be installed in `/usr/local/bin` by default. +Configuration is done by editing `config.h` and recompiling the source code. Note ---- diff --git a/config.h b/config.h @@ -1,34 +1,96 @@ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef _SFM_CONFIG_H_ +#define _SFM_CONFIG_H_ +/* Used in `xdelay`. */ #define DELAY_MS 350 -#define SCROLLOFF 4 + +/* Defines how many lines above the last line scrolling starts. */ +#define SCROLLOFF (YMAX >> 1) /* Clumps to 1 if < 1 and to 9 if > 9 */ static uint npanes = 4; +/* Read date(1)'s and strftime(3)'s man pages for info on formatting. */ static char *datefmt = "%F"; -/* c1 e2 27 2e 00 60 33 f7 c6 d6 ab c4 */ -/* https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg */ +/* 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"; -/* TODO: Handle any number of colors */ +/* + * 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] = 0x00, /* TODO: Block device */ + [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] = 0x00, /* TODO: Hard link */ - [C_LNK] = 0x33, /* Symbolic link */ - [C_MIS] = 0x00, /* TODO: Missing file OR file details */ - [C_ORP] = 0x00, /* TODO: Orphaned symlink */ - [C_PIP] = 0x00, /* TODO: Named pipe (FIFO) */ + [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] = 0x00, /* TODO: Unknown OR 0B regular/exe file */ + [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 */ }; +/* + * 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(.i): 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 + * + * - select(.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) + */ static struct key keys[] = { /* key func arg */ { KEY_LEFT, chlevel, {.i = -1} }, @@ -51,7 +113,6 @@ static struct key keys[] = { { 'q', setflag, {.i = F_QUIT} }, { '~', cd, {.s = "/home/christos"} }, { CTRL('b'), cd, {.s = "/storage"} }, - { CTRL('l'), cd, {.s = "/root/.local"} }, { 'p', run, {.s = "PAGER"} }, { 'e', run, {.s = "EDITOR"} }, { 'x', run, {.s = "rm -rf"} }, @@ -59,4 +120,4 @@ static struct key keys[] = { { ':', prompt, {0} }, }; -#endif /* CONFIG_H */ +#endif /* _SFM_CONFIG_H_ */ diff --git a/sfm.1 b/sfm.1 @@ -0,0 +1,15 @@ +.Dd sfm\-VERSION +.Dt SFM 1 +.Os +.Sh NAME +.Nm sfm +.Nd the simple file manager +.Sh SYNOPSIS +.Nm +.Op Fl ai +.Sh DESCRIPTION +.Pp +.Nm +does stuff. I will write this manual later. +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@christosmarg.xyz diff --git a/sfm.c b/sfm.c @@ -19,12 +19,6 @@ #ifndef PATH_MAX #define PATH_MAX 1024 #endif /* PATH_MAX */ -#ifndef HOST_NAME_MAX -#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX -#endif /* HOST_NAME_MAX */ -#ifndef LOGIN_NAME_MAX -#define LOGIN_NAME_MAX _POSIX_LOGIN_NAME_MAX -#endif /* LOGIN_NAME_MAX */ #ifndef DT_DIR #define DT_DIR 4 @@ -50,6 +44,8 @@ #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])) @@ -61,12 +57,12 @@ typedef unsigned int uint; typedef unsigned long ulong; struct entry { - struct stat stat; + char date[NAME_MAX]; char statstr[36]; - char date[12]; char sizestr[12]; char *name; - uint nlen; + off_t sz; + mode_t mode; uchar flags; uchar selected; }; @@ -100,10 +96,6 @@ enum { }; enum { - CMD_OPEN, -}; - -enum { MSG_EXEC, MSG_SORT, MSG_PROMPT, @@ -125,6 +117,11 @@ enum { 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, }; static void initcurses(void); @@ -136,6 +133,7 @@ static int confirmact(const char *); static int appendbuf(char *, size_t *, 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); @@ -153,14 +151,11 @@ 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 void die(const char *, ...); -static const char *cmds[] = { - [CMD_OPEN] = "xdg-open", -}; - static const char *msgs[] = { [MSG_EXEC] = "%s (y/N)?", [MSG_SORT] = "'n'ame 's'ize 'd'ate 'r'everse", @@ -214,8 +209,7 @@ revdatecmp(const void *x, const void *y) static inline int sizecmp(const void *x, const void *y) { - return -(((struct entry *)x)->stat.st_size - - ((struct entry *)y)->stat.st_size); + return -(((struct entry *)x)->sz - ((struct entry *)y)->sz); } static inline int @@ -228,7 +222,7 @@ static void initcurses(void) { struct sigaction sa; - int i; + int i, l; if (!initscr()) die("initscr:"); @@ -236,19 +230,30 @@ initcurses(void) cbreak(); curs_set(0); keypad(stdscr, 1); - /*timeout(1000);*/ + SETTIMEOUT(); set_escdelay(0); + if (has_colors() && can_change_color()) { start_color(); use_default_colors(); - for (i = 1; i < ARRLEN(colors); i++) - init_pair(i, colors[i], COLOR_BLACK); + 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]) + (void)init_pair(i, colors[i], COLOR_BLACK); + else + (void)init_pair(i, defaultcolor, COLOR_BLACK); + } } memset(&sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); + sa.sa_handler = sighandler; sa.sa_flags = SA_RESTART; - /*sa.sa_handler = sighandler;*/ + sigemptyset(&sa.sa_mask); sigaction(SIGTERM, &sa, NULL); sigaction(SIGWINCH, &sa, NULL); } @@ -257,14 +262,14 @@ static struct entry * entget(const char *path) { struct entry *ents, *e; + struct stat st; struct dirent *dent; - struct tm *tm; DIR *dir; int i = 0; char type; if ((dir = opendir(path)) == NULL) - die("opendir: %s", path); + die("opendir: %s:", path); ents = emalloc(sizeof(struct entry)); @@ -278,22 +283,19 @@ entget(const char *path) ents = erealloc(ents, (i + 1) * sizeof(struct entry)); e = &ents[i]; - e->nlen = strlen(dent->d_name); e->name = estrdup(dent->d_name); - stat(e->name, &e->stat); - tm = localtime(&e->stat.st_ctime); - strftime(e->date, sizeof(e->date), datefmt, tm); - (void)strncpy(e->sizestr, fmtsize(e->stat.st_size), - sizeof(e->sizestr)); + /* 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)); + (void)strncpy(e->sizestr, fmtsize(e->sz), sizeof(e->sizestr)); - e->flags = 0; /* XXX: resets on every redraw, keep track somehow */ e->selected = 0; - e->flags |= dent->d_type; + e->flags = dent->d_type | 0; - /* TODO: use fstatat(3) */ - /* FIXME: links don't work */ - switch (e->stat.st_mode & S_IFMT) { + switch (e->mode & S_IFMT) { case S_IFREG: type = '-'; break; @@ -323,15 +325,15 @@ entget(const char *path) snprintf(e->statstr, sizeof(e->statstr), "%c%c%c%c%c%c%c%c%c%c %s %s", type, - e->stat.st_mode & S_IRUSR ? 'r' : '-', - e->stat.st_mode & S_IWUSR ? 'w' : '-', - e->stat.st_mode & S_IXUSR ? 'x' : '-', - e->stat.st_mode & S_IRGRP ? 'r' : '-', - e->stat.st_mode & S_IWGRP ? 'w' : '-', - e->stat.st_mode & S_IXGRP ? 'x' : '-', - e->stat.st_mode & S_IROTH ? 'r' : '-', - e->stat.st_mode & S_IWOTH ? 'w' : '-', - e->stat.st_mode & S_IXOTH ? 'x' : '-', + 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' : '-', e->sizestr, e->date); @@ -354,20 +356,22 @@ entprint(struct pane *p) uchar color; char ind; + attron(COLOR_PAIR(C_PAN)); addch('['); for (i = 0; i < npanes; i++) { if (i == selpane) - attron(A_BOLD | A_REVERSE); + attron(COLOR_PAIR(C_SPN) | A_BOLD | A_REVERSE); addch(i + '0' + 1); - attroff(A_BOLD | A_REVERSE); + attroff(COLOR_PAIR(C_SPN) | A_BOLD | A_REVERSE); if (i != npanes - 1) addch(' '); } addstr("] "); + attroff(COLOR_PAIR(C_PAN)); - attron(A_BOLD | COLOR_PAIR(C_DIR)); - addstr(p->curdir); - attroff(A_BOLD | COLOR_PAIR(C_DIR)); + attron(A_BOLD | COLOR_PAIR(C_PTH)); + namprint(p->curdir); + attroff(A_BOLD | COLOR_PAIR(C_PTH)); if ((n = MIN(YMAX - 4, p->nents - curscroll)) <= 0) return; @@ -387,16 +391,16 @@ entprint(struct pane *p) attron(COLOR_PAIR(C_INF)); printw("%s %c%c%c %7s ", e->date, - '0' + ((e->stat.st_mode >> 6) & 7), - '0' + ((e->stat.st_mode >> 3) & 7), - '0' + (e->stat.st_mode & 7), + '0' + ((e->mode >> 6) & 7), + '0' + ((e->mode >> 3) & 7), + '0' + (e->mode & 7), e->sizestr); attroff(COLOR_PAIR(C_INF)); } addch(e->selected ? '+' : ' '); - switch (e->stat.st_mode & S_IFMT) { + switch (e->mode & S_IFMT) { case S_IFDIR: ind = '/'; color = C_DIR; @@ -404,15 +408,16 @@ entprint(struct pane *p) break; case S_IFREG: color = C_FIL; - if (e->stat.st_mode & 0100) { + if (e->mode & 0100) { ind = '*'; 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->stat.st_mode)) + if (S_ISDIR(e->mode)) attrs |= A_BOLD; break; case S_IFSOCK: @@ -437,38 +442,37 @@ entprint(struct pane *p) attrs |= COLOR_PAIR(color); attron(attrs); - addstr(e->name); + namprint(e->name); attroff(attrs); addch(ind); } - - mvprintw(YMAX - 1, 0, "%ld/%ld %s", p->sel + 1, p->nents, - p->ents[p->sel].statstr); + attron(COLOR_PAIR(C_STA)); + mvprintw(YMAX - 1, 0, "%ld/%ld %s", + p->sel + 1, p->nents, p->ents[p->sel].statstr); + attroff(COLOR_PAIR(C_STA)); } static int spawn(char *cmd) { char *args[] = {getenv("SHELL"), "-c", cmd, NULL}; - struct sigaction oldsighup; - struct sigaction oldsigtstp; pid_t pid; int status; switch (pid = fork()) { case -1: + notify(msgs[MSG_FAIL]); + xdelay(DELAY_MS << 2); return 1; case 0: - execvp(*args, args); + (void)execvp(*args, args); _exit(EXIT_SUCCESS); break; default: endwin(); while (wait(&status) != pid) ; - sigaction(SIGHUP, &oldsighup, NULL); - sigaction(SIGTSTP, &oldsigtstp, NULL); break; } return 0; @@ -483,7 +487,7 @@ notify(const char *fmt, ...) move(YMAX - 1, 0); clrtoeol(); va_start(args, fmt); - vsnprintf(buf, XMAX, fmt, args); + (void)vsnprintf(buf, XMAX, fmt, args); va_end(args); addstr(buf); } @@ -491,10 +495,15 @@ notify(const char *fmt, ...) static int confirmact(const char *str) { + char c; + if (f_noconfirm) return 1; + CLRTIMEOUT(); notify(msgs[MSG_EXEC], str); - return (getch() == 'y'); + c = getch(); + SETTIMEOUT(); + return (c == 'y'); } static int @@ -548,12 +557,24 @@ fmtsize(size_t sz) for (; sz > 1024; i++) sz >>= 10; - /* XXX: s/sprintf/snprintf/ */ - sprintf(buf, "%ld%c", sz, "BKMGTPEZY"[i]); + snprintf(buf, sizeof(buf), "%ld%c", sz, "BKMGTPEZY"[i]); return buf; } +static void +namprint(char *str) +{ + char *s; + + for (s = str; *s != '\0'; s++) { + if ((s - str) > XMAX - 1) + break; + addch(*s); + } + return; +} + static char * promptread(const char *msg) { @@ -604,7 +625,7 @@ static void xdelay(ulong delay) { refresh(); - usleep(delay * 1000); + (void)usleep(delay * 1000); } /* TODO: add sel "history" */ @@ -624,14 +645,10 @@ chlevel(const arg *arg) /* TODO: handle links to dirs */ if (p->ents[p->sel].flags & DT_REG && p->ents[p->sel].flags & ~DT_LNK) { - /* XXX: s/sprintf/snprintf/ */ - sprintf(buf, "%s %s", cmds[CMD_OPEN], - p->ents[p->sel].name); + snprintf(buf, sizeof(buf), "%s %s", + openprog, p->ents[p->sel].name); /* TODO: escape this buf! */ - if (!spawn(buf)) { - notify(msgs[MSG_FAIL]); - xdelay(DELAY_MS << 2); - } + spawn(buf); } } f_redraw = 1; @@ -724,7 +741,7 @@ run(const arg *arg) int i; /* - * Always check if the argument is an environmental variable so we + * 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. @@ -733,13 +750,13 @@ run(const arg *arg) prog = (char *)arg->s; /* Remove `const`ness. */ p = &pane[selpane]; sz = strlen(prog); - strncpy(buf, prog, ++sz); + (void)strncpy(buf, prog, ++sz); if (p->nsel > 0) { for (i = 0; i < p->nents; i++) - if (p->ents[i].selected) - if (!appendbuf(buf, &sz, p->ents[i].name)) - return; + if (p->ents[i].selected + && !appendbuf(buf, &sz, p->ents[i].name)) + return; } else if (!appendbuf(buf, &sz, p->ents[p->sel].name)) return; @@ -851,6 +868,19 @@ estrdup(const char *s) } static void +sighandler(int sig) +{ + switch (sig) { + case SIGTERM: + cleanup(); + _exit(128 + sig); + case SIGWINCH: + f_redraw = 1; + break; + } +} + +static void entfree(struct pane *p) { int i= 0; @@ -880,7 +910,7 @@ die(const char *fmt, ...) (void)fprintf(stderr, "%s: ", argv0); va_start(args, fmt); - vfprintf(stderr, fmt, args); + (void)vfprintf(stderr, fmt, args); va_end(args); if (fmt[0] && fmt[strlen(fmt)-1] == ':') { @@ -906,9 +936,9 @@ main(int argc, char *argv[]) if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) die("isatty:"); - while ((ch = getopt(argc, argv, "Hi")) != -1) { + while ((ch = getopt(argc, argv, "ai")) != -1) { switch (ch) { - case 'H': + case 'a': f_showall = 1; break; case 'i': @@ -916,7 +946,7 @@ main(int argc, char *argv[]) break; case '?': /* FALLTHROUGH */ default: - (void)fprintf(stderr, "usage: %s [-Hi]\n", argv0); + (void)fprintf(stderr, "usage: %s [-ai]\n", argv0); exit(EXIT_FAILURE); } } @@ -949,13 +979,13 @@ main(int argc, char *argv[]) die("getcwd:"); entfree(p); if ((p->ents = entget(p->curdir)) == NULL) - die("entget: %s: ", p->curdir); + die("entget: %s:", p->curdir); selclump(p); f_redraw = 0; refresh(); } entprint(p); - /*TODO: signal/timeout, handle SIGTERM to clean up or warn first */ + /* TODO: use kqueue(2) and inotify */ if ((ch = getch()) != ERR) { for (i = 0; i < ARRLEN(keys); i++) if (ch == keys[i].key)