";
case F_LNK: return "";
case F_UNKN: return ">";
}
return "<->";
}
static inline void printenttime(const time_t *timep)
{
struct tm t;
localtime_r(timep, &t);
printw("%s-%02d-%02d %02d:%02d ", xitoa(t.tm_year + 1900), t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min);
}
static inline void printentname(const Entry *ent, int sel)
{
int attr = COLOR_PAIR(ent->type)
| (ent->flag & E_DIR_DIRLNK ? A_BOLD : 0)
| ((ent->flag & E_SEL) || (sel && !ptab->cfg.selmode) ? A_REVERSE : 0)
| ((sel && ptab->cfg.selmode) ? A_UNDERLINE : 0);
attron(attr);
if (ptab->hp->stat->flag != S_ROOT)
addwstr(fitnamecols(ent->name, ncols));
else
addwstr(fitpathcols(ent->name, ncols));
attroff(attr);
}
static void printent(const Entry *ent, int sel, int mark)
{
int attr = COLOR_PAIR(C_DETAIL) | (mark || (sel && ptab->cfg.selmode) ? A_REVERSE : 0);
if (sel)
addch('>' | A_BOLD);
else
addch(' ');
attron(attr);
if (ptab->cfg.showtime)
printenttime(&ent->sec);
if (ptab->cfg.showowner)
printw("%7.6s:%-7.6s", getpwname(ent->uid), getgrname(ent->gid));
if (ptab->cfg.showperm)
printw(" %c%c%c ", '0' + ((ent->mode >> 6) & 7), '0' + ((ent->mode >> 3) & 7), '0' + (ent->mode & 7));
if (ptab->cfg.showsize)
printw("%7s ", (ent->flag & E_REG_FILE) ? tohumansize(ent->size) : filetypechar(ent->type));
attron(gcfg.newent && (ent->flag & E_NEW) ? (COLOR_PAIR(C_NEWFILE) | A_REVERSE) : 0);
if (sel)
addch('>' | A_BOLD);
else
addch(' ');
attroff(gcfg.newent && (ent->flag & E_NEW) ? (COLOR_PAIR(C_NEWFILE) | A_REVERSE) : 0);
attroff(attr);
if (ncols > 0)
printentname(ent, sel);
}
static void redraw(char *path)
{
getmaxyx(stdscr, xlines, xcols);
int pcols = xcols - (TABS_MAX + 1) * 2;
int dcols = (ptab->cfg.showtime ? 17 : 0) + (ptab->cfg.showowner ? 15 : 0)
+ (ptab->cfg.showperm ? 5 : 0) + (ptab->cfg.showsize ? 8 : 0) + 2;
int btm, j = 0;
struct selstat *ss = ptab->ss;
erase();
shiftcursor(0, 0);
onscr = xlines - 4;
ncols = xcols - dcols - 1;
// Print tabs tag
for (int i = 0; i <= TABS_MAX; ++i) {
if (gtab[i].cfg.enabled == 1)
addch((i < TABS_MAX ? i + '1' : '#')
| (COLOR_PAIR(C_TABTAG) | (gcfg.ct == i ? A_REVERSE : 0) | A_BOLD));
else
addch(i < TABS_MAX ? '*' : '#');
addch(' ');
}
// Print path
attron(COLOR_PAIR(C_PATHBAR) | A_UNDERLINE);
if (pcols > 0)
addwstr(fitpathcols(path, pcols));
attroff(COLOR_PAIR(C_PATHBAR) | A_UNDERLINE);
// Print entries
move(++j, dcols);
if (curscroll > 0 && ncols > 0)
addstr("<<");
btm = MIN(onscr + curscroll, ndents);
for (int i = curscroll; i < btm; ++i) {
if (ptab->cfg.havesel && !(pdents[i].flag & E_SEL_SCANED)) {
if (findinbuf(ss->nbuf, ss->endp - ss->nbuf, pdents[i].name, pdents[i].nlen))
pdents[i].flag |= E_SEL;
pdents[i].flag |= E_SEL_SCANED;
}
move(++j, 0);
printent(&pdents[i], i == cursel, i == markent);
}
move(++j, dcols);
if (btm < ndents && ncols > 0)
addstr(">>");
// Print filter
if (ptab->ftlen != 0) {
move(xlines - 2, 0);
attron(COLOR_PAIR(F_SOCK));
addstr("Filter: ");
mbstowcs((wchar_t *)gmbuf, ptab->filt, xcols - 12);
addwstr((wchar_t *)gmbuf);
attroff(COLOR_PAIR(F_SOCK));
addch(' ' | (ptab->ftlen > 0 ? A_REVERSE : 0));
}
// Print quick find
if (ptab->fdlen > 0) {
move(xlines - 2, 0);
attron(COLOR_PAIR(F_CHR));
addstr("Quick find: ");
mbstowcs((wchar_t *)gmbuf, ptab->find, xcols - 12);
addwstr((wchar_t *)gmbuf);
attroff(COLOR_PAIR(F_CHR));
addch(' ' | A_REVERSE);
}
// Draw scroll indicator
j = (ndents > 0) ? ndents : 1;
btm = (j <= onscr) ? onscr
: MAX(1, ((onscr * onscr << 1) / j + 1) >> 1); // indicator height, round a/b by (a*2/b+1)/2
j = (curscroll == 0 || j <= onscr) ? 2
: 2 + (((curscroll * (onscr - btm) << 1) / (j - onscr) + 1) >> 1); // starting row to drawing
attron(COLOR_PAIR(C_DETAIL));
mvaddch(1, xcols -1, '=');
for (int i = 0; i < btm; ++i, ++j)
mvaddch(j, xcols - 1, ' ' | A_REVERSE);
mvaddch(xlines - 2, xcols - 1 , '=');
attroff(COLOR_PAIR(C_DETAIL));
xcols = -xcols;
}
static void fastredraw(void)
{
if (xcols < 0) { // skip fastredraw if redraw() has already done
xcols = -xcols;
return;
} else if (ndents == 0)
return;
move(2 + lastsel - curscroll, 0);
printent(&pdents[lastsel], FALSE, lastsel == markent);
move(2 + cursel - curscroll, 0);
printent(&pdents[cursel], TRUE, cursel == markent);
}
static void statusbar(void)
{
int n, x;
move(xlines - 1, 0);
clrtoeol();
if (gcfg.mode == 1 || gcfg.mode == 2) {
attron(COLOR_PAIR(C_WARN) | A_REVERSE | A_BOLD);
addstr(" S ");
attroff(COLOR_PAIR(C_WARN) | A_REVERSE | A_BOLD);
addch(' ');
} else if (gcfg.mode > 2) {
attron(COLOR_PAIR(F_EXEC) | A_REVERSE | A_BOLD);
addstr(" B ");
attroff(COLOR_PAIR(F_EXEC) | A_REVERSE | A_BOLD);
addch(' ');
}
if (errline != 0) {
attron(COLOR_PAIR(C_WARN));
printw("Failed (%s): %s", xitoa(errline), strerror(errnum));
attroff(COLOR_PAIR(C_WARN));
errline = 0;
return;
}
attron(COLOR_PAIR(C_STATBAR));
printw("%d/%d ", ndents > 0 ? cursel + 1 : 0, ndents);
attron(A_REVERSE);
printw(" %d ", (ndents > 0 && !ptab->cfg.selmode) ? 1 : ptab->nsel);
attroff(A_REVERSE);
addch(' ');
if (ndents > 0) {
Entry *ent = &pdents[cursel];
addch(filetypechar(ent->type)[1]);
addstr(strperms(ent->mode));
addch(' ');
addstr(getpwname(ent->uid));
addch(':');
addstr(getgrname(ent->gid));
printw(" %s ", tohumansize(ent->size));
printenttime(&ent->sec);
getyx(stdscr, n, x);
n = xcols - x;
if (ent->type == F_LNK && n > 1) {
if ((x = readlink(ent->name, gpbuf, PATH_MAX)) > 1) {
gpbuf[x] = '\0';
addstr("->");
addwstr(fitpathcols(gpbuf, n - 2)); // Show symlink target
}
} else if ((ent->flag & E_REG_FILE) && n > 1) {
char *p = getextension(ent->name, ent->nlen);
if (p)
addnstr(p , n); // Show file extension
}
}
getyx(stdscr, n, x);
if (xcols - x >= 8)
mvaddstr(n, xcols - 8, " [?]help");
attroff(COLOR_PAIR(C_STATBAR));
}
static void filterentry(void)
{
Entry tmpent;
if (ptab->ftlen == 0 || setfilter(2) == GO_REDRAW)
return;
for (int i = 0; i < ndents; ++i) {
if (!strcasestr(pdents[i].name, ptab->filt) && i != --ndents) {
tmpent = pdents[i];
pdents[i] = pdents[ndents];
pdents[ndents] = tmpent;
--i;
}
}
}
static int filterinput(int c)
{
wchar_t *wbuf = (wchar_t *)gmbuf;
if (ptab->ftlen <= 0) // ftlen=0 no filter, ftlen<0 inactive, ftlen>0 active
return GO_NONE;
if (c == '/') { // turn off filter
setfilter(0);
return refreshview(2);
} else if (c == '\r' || c == ESC){ // set to inactive
ptab->ftlen = (ptab->filt[0] == '\0') ? 0 : -ptab->ftlen;
return GO_REDRAW;
} else if (c == KEY_BACKSPACE || c == KEY_DC) {
if (ptab->ftlen <= 1)
return GO_REDRAW;
size_t len = mbstowcs(wbuf, ptab->filt, ptab->ftlen);
wbuf[len - 1] = L'\0';
ptab->ftlen = wcstombs(ptab->filt, wbuf, ptab->ftlen) + 1;
ptab->filt[ptab->ftlen - 1] = '\0';
ndents = ptab->nde;
} else if (c > 31 && c < 256) {
ptab->filt[ptab->ftlen - 1] = c;
ptab->filt[ptab->ftlen == FILT_MAX - 1 ? ptab->ftlen : ++ptab->ftlen - 1] = '\0';
} else
return GO_NONE;
return refreshview(2);
}
static int qfindinput(int c)
{
int cd = FALSE;
wchar_t *wbuf = (wchar_t *)gmbuf;
if (ptab->fdlen <= 0) // fdlen=0 no quick find, fdlen<0 invisible, fdlen>0 active
return GO_NONE;
if (c == '\r' || c == ESC) { // turn of or set to invisible
ptab->fdlen = (ptab->find[0] == '\0') ? 0 : -ptab->fdlen;
return GO_REDRAW;
} else if (c == '/' && ptab->find[0] == '\0') { // go to root dir
ptab->find[0] = '\0';
ptab->fdlen = 1;
newhistpath("/");
return GO_RELOAD;
} else if (c == '\t' || c == '/') { // enter dir
ptab->find[0] = '\0';
ptab->fdlen = 1;
cd = TRUE;
} else if (c == KEY_BACKSPACE || c == KEY_DC) {
if (ptab->fdlen <= 1)
return GO_REDRAW;
size_t len = mbstowcs(wbuf, ptab->find, ptab->fdlen);
wbuf[len - 1] = L'\0';
ptab->fdlen = wcstombs(ptab->find, wbuf, ptab->fdlen) + 1;
ptab->find[ptab->fdlen - 1] = '\0';
} else if (c > 31 && c < 256) {
ptab->find[ptab->fdlen - 1] = c;
ptab->find[ptab->fdlen == FILT_MAX - 1 ? ptab->fdlen : ++ptab->fdlen - 1] = '\0';
} else
return GO_NONE;
if (cd) {
if (enterdir(0) == GO_NONE)
return GO_REDRAW;
return GO_RELOAD;
}
if (ptab->find[0] == '\0')
return GO_REDRAW;
for (int i = 0; i < ndents; ++i) {
if (strncasecmp(pdents[i].name, ptab->find, ptab->fdlen - 1) == 0) {
cursel = i;
curscroll = MAX(i - (onscr * 3 >> 2), MIN(i - (onscr >> 2), curscroll));
return GO_REDRAW;
}
}
return qfindnext(0);
}
static void browse(void)
{
int c, ctl = GO_RELOAD;
for (;;) {
switch (ctl) {
case GO_RELOAD:
ptab = >ab[gcfg.ct];
loadentries(ptab->hp->path);
// fallthrough
case GO_SORT:
filterentry();
qsort(pdents, ndents, sizeof(*pdents), ptab->cfg.reverse ? &reventrycmp : &entrycmp);
setcurrentstat(ptab->hp, ptab->ss);
// fallthrough
case GO_REDRAW:
redraw(ptab->hp->path);
// fallthrough
case GO_FASTDRAW:
fastredraw();
setpreview(1);
// fallthrough
case GO_STATBAR:
statusbar();
// fallthrough
case GO_NONE:
c = getinput(stdscr);
ctl = GO_NONE;
if (c == KEY_RESIZE) {
ctl = GO_REDRAW;
break;
} else if (c == 0)
break;
if ((ctl = filterinput(c)) != GO_NONE)
break;
if ((ctl = qfindinput(c)) != GO_NONE)
break;
for (int i = 0; i < (int)LENGTH(keys); ++i)
if ((c == keys[i].keysym1 || c == keys[i].keysym2) && keys[i].func)
ctl = keys[i].func(keys[i].arg);
if (c < 0 && gcfg.mode < 3)
ctl = callextfunc(-c);
break;
case GO_QUIT:
return;
}
}
}
static void exitsighandler(int sig __attribute__((unused)))
{
endwin();
exit(EXIT_SUCCESS);
}
static int initsff(char *arg0, char *argx)
{
char *path, *xdgcfg = getenv("XDG_CONFIG_HOME");
struct sigaction act = {.sa_handler = exitsighandler};
// Handle/ignore certain signals
sigaction(SIGHUP, &act, NULL);
sigaction(SIGTERM, &act, NULL);
act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, NULL);
sigaction(SIGQUIT, &act, NULL);
sigaction(SIGPIPE, &act, NULL);
// Reset standard input
if (!freopen("/dev/null", "r", stdin)
|| !freopen("/dev/tty", "r", stdin)) {
perror(xitoa(__LINE__));
return FALSE;
}
// Get environment variables
home = getenv("HOME");
if (!home || !home[0] || access(home, R_OK | W_OK | X_OK) == -1)
home = NULL;
editor = getenv("EDITOR");
if (!editor || !editor[0])
editor = EDITOR;
// Set config path: xdgcfg+"/sff" or home+"/.config/sff"
if ((xdgcfg && xdgcfg[0] && makepath(xdgcfg, "sff", gpbuf))
|| (home && makepath(home, ".config/sff", gpbuf)))
cfgpath = gpbuf;
// Set extfunc path, and check sff-extfunc file
if (cfgpath && makepath(cfgpath, EXTFNNAME, gmbuf)
&& access(gmbuf, R_OK | X_OK) == 0) // check it in config path
extfunc = gmbuf;
if (!extfunc && realpath(arg0, gmbuf)
&& makepath(xdirname(gmbuf), EXTFNNAME, gmbuf)
&& access(gmbuf, R_OK | X_OK) == 0) // check it in where sff is located
extfunc = gmbuf;
if (!extfunc && makepath(EXTFNPREFIX, EXTFNNAME, gmbuf)
&& access(gmbuf, R_OK | X_OK) == 0) // check it in EXTFNPREFIX
extfunc = gmbuf;
// Allocate memory for cfgpath + extfunc + pipepath and set these paths
if (cfgpath && extfunc
&& (cfgpath = malloc(strlen(gpbuf) * 3 + strlen(gmbuf) + 64))) {
extfunc = memccpy(cfgpath, gpbuf, '\0', PATH_MAX);
pipepath = memccpy(extfunc, gmbuf, '\0', PATH_MAX);
strcat(strcat(gpbuf, "/.sff-pipe."), xitoa(getpid()));
pvfifo = memccpy(pipepath, gpbuf, '\0', PATH_MAX);
strcpy(pvfifo, gpbuf);
strcat(pvfifo, ".pv");
} else {
cfgpath = NULL;
seterrnum(__LINE__, errno);
}
// Initialize first tab
path = argx ? abspath(argx, gpbuf) : getcwd(gpbuf, PATH_MAX);
if (!path || !inittab(path, 0)) {
perror(xitoa(__LINE__));
return FALSE;
}
if (getuid() == 0)
gcfg.mode = 2;
if (!cfgpath)
gcfg.mode = 4;
return TRUE;
}
static void setupcurses(void)
{
cbreak();
noecho();
nonl();
curs_set(FALSE);
keypad(stdscr, TRUE);
set_escdelay(50);
define_key("\033[1;5A", CTRL_UP);
define_key("\033[1;5B", CTRL_DOWN);
define_key("\033[1;5C", CTRL_RIGHT);
define_key("\033[1;5D", CTRL_LEFT);
define_key("\033[1;2A", SHIFT_UP);
define_key("\033[1;2B", SHIFT_DOWN);
start_color();
use_default_colors();
if (COLORS >= 256)
setcolorpair256();
else
setcolorpair8();
getmaxyx(stdscr, xlines, xcols);
onscr = xlines - 4;
}
static void cleanup(void)
{
setpreview(2);
for (int i = 0; i <= TABS_MAX; ++i) {
free(ghpath[i * 2].hs);
free(ghpath[i * 2 + 1].hs);
deleteallselstat(gtab[i].ss);
}
free(cfgpath);
free(pdents);
free(pnamebuf);
free(pfindbuf);
}
int main(int argc, char *argv[])
{
int opt;
while ((opt = getopt(argc, argv, "bcd:Hmvh")) != -1) {
switch (opt) {
case 'b': gcfg.mode = 4;
break;
case 'c': gcfg.caseinsen = 0;
break;
case 'd': gcfg.showtime = gcfg.showowner = gcfg.showperm = gcfg.showsize = 0;
for (int i = 0; optarg[i]; ++i) {
if (optarg[i] == 't')
gcfg.showtime = 1;
if (optarg[i] == 'o')
gcfg.showowner = 1;
if (optarg[i] == 'p')
gcfg.showperm = 1;
if (optarg[i] == 's')
gcfg.showsize = 1;
}
break;
case 'H': gcfg.showhidden = 1;
break;
case 'm': gcfg.dirontop = 0;
break;
case 'v': printf("sff "VERSION"\n");
return EXIT_SUCCESS;
case 'h': usage();
return EXIT_SUCCESS;
default: dprintf(STDOUT_FILENO, "Try 'sff -h' for available options.\n");
return EXIT_FAILURE;
}
}
atexit(cleanup);
if (!initsff(argv[0], argc == optind ? NULL : argv[optind]))
return EXIT_FAILURE;
if (!initscr())
return EXIT_FAILURE;
setupcurses();
setlocale(LC_ALL, "");
browse();
endwin();
return EXIT_SUCCESS;
}