noice

small file browser
git clone git://git.2f30.org/noice.git
Log | Files | Refs | README | LICENSE

noice.c (15447B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <sys/stat.h>
      3 #include <sys/types.h>
      4 
      5 #include <curses.h>
      6 #include <dirent.h>
      7 #include <errno.h>
      8 #include <fcntl.h>
      9 #include <libgen.h>
     10 #include <limits.h>
     11 #include <locale.h>
     12 #include <regex.h>
     13 #include <signal.h>
     14 #include <stdarg.h>
     15 #include <stdio.h>
     16 #include <stdlib.h>
     17 #include <string.h>
     18 #include <unistd.h>
     19 
     20 #include "arg.h"
     21 #include "util.h"
     22 
     23 #define ISODD(x) ((x) & 1)
     24 #define CONTROL(c) ((c) ^ 0x40)
     25 #define META(c) ((c) ^ 0x80)
     26 
     27 struct cpair {
     28 	int fg;
     29 	int bg;
     30 };
     31 
     32 /* Supported actions */
     33 enum action {
     34 	SEL_QUIT = 1,
     35 	SEL_BACK,
     36 	SEL_GOIN,
     37 	SEL_FLTR,
     38 	SEL_NEXT,
     39 	SEL_PREV,
     40 	SEL_PGDN,
     41 	SEL_PGUP,
     42 	SEL_HOME,
     43 	SEL_END,
     44 	SEL_CD,
     45 	SEL_CDHOME,
     46 	SEL_TOGGLEDOT,
     47 	SEL_DSORT,
     48 	SEL_MTIME,
     49 	SEL_ICASE,
     50 	SEL_VERS,
     51 	SEL_REDRAW,
     52 	SEL_RUN,
     53 	SEL_RUNARG,
     54 };
     55 
     56 struct key {
     57 	int sym;         /* Key pressed */
     58 	enum action act; /* Action */
     59 	char *run;       /* Program to run */
     60 	char *env;       /* Environment variable override */
     61 };
     62 
     63 #include "noiceconf.h"
     64 
     65 struct entry {
     66 	char name[PATH_MAX];
     67 	mode_t mode;
     68 	time_t t;
     69 };
     70 
     71 /* Global context */
     72 struct entry *dents;
     73 char *argv0;
     74 int ndents, cur;
     75 int idle;
     76 
     77 /*
     78  * Layout:
     79  * .---------
     80  * | /mnt/path
     81  * |
     82  * |    file0
     83  * |    file1
     84  * |  > file2
     85  * |    file3
     86  * |    file4
     87  *      ...
     88  * |    filen
     89  * |
     90  * | Permission denied
     91  * '------
     92  */
     93 
     94 void info(char *, ...);
     95 void warn(char *, ...);
     96 void fatal(char *, ...);
     97 
     98 void *
     99 xrealloc(void *p, size_t size)
    100 {
    101 	p = realloc(p, size);
    102 	if (p == NULL)
    103 		fatal("realloc");
    104 	return p;
    105 }
    106 
    107 /* Some implementations of dirname(3) may modify `path' and some
    108  * return a pointer inside `path'. */
    109 char *
    110 xdirname(const char *path)
    111 {
    112 	static char out[PATH_MAX];
    113 	char tmp[PATH_MAX], *p;
    114 
    115 	strlcpy(tmp, path, sizeof(tmp));
    116 	p = dirname(tmp);
    117 	if (p == NULL)
    118 		fatal("dirname");
    119 	strlcpy(out, p, sizeof(out));
    120 	return out;
    121 }
    122 
    123 char *
    124 xgetenv(char *name, char *fallback)
    125 {
    126 	char *value;
    127 
    128 	if (name == NULL)
    129 		return fallback;
    130 	value = getenv(name);
    131 	return value && value[0] ? value : fallback;
    132 }
    133 
    134 int
    135 setfilter(regex_t *regex, char *filter)
    136 {
    137 	char errbuf[LINE_MAX];
    138 	size_t len;
    139 	int r;
    140 
    141 	r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED | REG_ICASE);
    142 	if (r != 0) {
    143 		len = COLS;
    144 		if (len > sizeof(errbuf))
    145 			len = sizeof(errbuf);
    146 		regerror(r, regex, errbuf, len);
    147 		info("%s", errbuf);
    148 	}
    149 	return r;
    150 }
    151 
    152 void
    153 freefilter(regex_t *regex)
    154 {
    155 	regfree(regex);
    156 }
    157 
    158 void
    159 initfilter(int dot, char **ifilter)
    160 {
    161 	*ifilter = dot ? "." : "^[^.]";
    162 }
    163 
    164 int
    165 visible(regex_t *regex, char *file)
    166 {
    167 	return regexec(regex, file, 0, NULL, 0) == 0;
    168 }
    169 
    170 int
    171 dircmp(mode_t a, mode_t b)
    172 {
    173 	if (S_ISDIR(a) && S_ISDIR(b))
    174 		return 0;
    175 	if (!S_ISDIR(a) && !S_ISDIR(b))
    176 		return 0;
    177 	if (S_ISDIR(a))
    178 		return -1;
    179 	else
    180 		return 1;
    181 }
    182 
    183 int
    184 entrycmp(const void *va, const void *vb)
    185 {
    186 	const struct entry *a = va, *b = vb;
    187 
    188 	if (dirorder) {
    189 		if (dircmp(a->mode, b->mode) != 0)
    190 			return dircmp(a->mode, b->mode);
    191 	}
    192 
    193 	if (mtimeorder)
    194 		return b->t - a->t;
    195 	if (icaseorder)
    196 		return strcasecmp(a->name, b->name);
    197 	if (versorder)
    198 		return strverscmp(a->name, b->name);
    199 	return strcmp(a->name, b->name);
    200 }
    201 
    202 void
    203 initcolor(void)
    204 {
    205 	int i;
    206 
    207 	start_color();
    208 	use_default_colors();
    209 	for (i = 1; i < LEN(pairs); i++)
    210 		init_pair(i, pairs[i].fg, pairs[i].bg);
    211 }
    212 
    213 void
    214 initcurses(void)
    215 {
    216 	char *term;
    217 
    218 	if (initscr() == NULL) {
    219 		term = getenv("TERM");
    220 		if (term != NULL)
    221 			fprintf(stderr, "error opening terminal: %s\n", term);
    222 		else
    223 			fprintf(stderr, "failed to initialize curses\n");
    224 		exit(1);
    225 	}
    226 	if (usecolor && has_colors())
    227 		initcolor();
    228 	cbreak();
    229 	noecho();
    230 	nonl();
    231 	intrflush(stdscr, FALSE);
    232 	keypad(stdscr, TRUE);
    233 	curs_set(FALSE); /* Hide cursor */
    234 	timeout(1000); /* One second */
    235 }
    236 
    237 void
    238 exitcurses(void)
    239 {
    240 	endwin(); /* Restore terminal */
    241 }
    242 
    243 /* Messages show up at the bottom */
    244 void
    245 info(char *fmt, ...)
    246 {
    247 	char buf[LINE_MAX];
    248 	va_list ap;
    249 
    250 	va_start(ap, fmt);
    251 	vsnprintf(buf, sizeof(buf), fmt, ap);
    252 	va_end(ap);
    253 	move(LINES - 1, 0);
    254 	printw("%s\n", buf);
    255 }
    256 
    257 /* Display warning as a message */
    258 void
    259 warn(char *fmt, ...)
    260 {
    261 	char buf[LINE_MAX];
    262 	va_list ap;
    263 
    264 	va_start(ap, fmt);
    265 	vsnprintf(buf, sizeof(buf), fmt, ap);
    266 	va_end(ap);
    267 	move(LINES - 1, 0);
    268 	printw("%s: %s\n", buf, strerror(errno));
    269 }
    270 
    271 /* Kill curses and display error before exiting */
    272 void
    273 fatal(char *fmt, ...)
    274 {
    275 	va_list ap;
    276 
    277 	exitcurses();
    278 	va_start(ap, fmt);
    279 	vfprintf(stderr, fmt, ap);
    280 	fprintf(stderr, ": %s\n", strerror(errno));
    281 	va_end(ap);
    282 	exit(1);
    283 }
    284 
    285 /* Clear the last line */
    286 void
    287 clearprompt(void)
    288 {
    289 	info("");
    290 }
    291 
    292 /* Print prompt on the last line */
    293 void
    294 printprompt(char *str)
    295 {
    296 	clearprompt();
    297 	info("%s", str);
    298 }
    299 
    300 int
    301 xgetch(void)
    302 {
    303 	int c;
    304 
    305 	c = getch();
    306 	if (c == -1)
    307 		idle++;
    308 	else
    309 		idle = 0;
    310 	return c;
    311 }
    312 
    313 /* Returns SEL_* if key is bound and 0 otherwise.
    314  * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */
    315 int
    316 nextsel(char **run, char **env)
    317 {
    318 	int c, i;
    319 
    320 	c = xgetch();
    321 	if (c == 033)
    322 		c = META(xgetch());
    323 
    324 	for (i = 0; i < LEN(bindings); i++)
    325 		if (c == bindings[i].sym) {
    326 			*run = bindings[i].run;
    327 			*env = bindings[i].env;
    328 			return bindings[i].act;
    329 		}
    330 	return 0;
    331 }
    332 
    333 char *
    334 readln(void)
    335 {
    336 	static char ln[LINE_MAX];
    337 
    338 	timeout(-1);
    339 	echo();
    340 	curs_set(TRUE);
    341 	memset(ln, 0, sizeof(ln));
    342 	wgetnstr(stdscr, ln, sizeof(ln) - 1);
    343 	noecho();
    344 	curs_set(FALSE);
    345 	timeout(1000);
    346 	return ln[0] ? ln : NULL;
    347 }
    348 
    349 int
    350 canopendir(char *path)
    351 {
    352 	DIR *dirp;
    353 
    354 	dirp = opendir(path);
    355 	if (dirp == NULL)
    356 		return 0;
    357 	closedir(dirp);
    358 	return 1;
    359 }
    360 
    361 char *
    362 mkpath(char *dir, char *name, char *out, size_t n)
    363 {
    364 	/* Handle absolute path */
    365 	if (name[0] == '/') {
    366 		strlcpy(out, name, n);
    367 	} else {
    368 		/* Handle root case */
    369 		if (strcmp(dir, "/") == 0) {
    370 			strlcpy(out, "/", n);
    371 			strlcat(out, name, n);
    372 		} else {
    373 			strlcpy(out, dir, n);
    374 			strlcat(out, "/", n);
    375 			strlcat(out, name, n);
    376 		}
    377 	}
    378 	return out;
    379 }
    380 
    381 void
    382 printent(struct entry *ent, int active)
    383 {
    384 	char name[PATH_MAX];
    385 	unsigned int len = COLS - strlen(CURSR) - 1;
    386 	char cm = 0;
    387 	int attr = 0;
    388 
    389 	/* Copy name locally */
    390 	strlcpy(name, ent->name, sizeof(name));
    391 
    392 	/* No text wrapping in entries */
    393 	if (strlen(name) < len)
    394 		len = strlen(name) + 1;
    395 
    396 	if (S_ISDIR(ent->mode)) {
    397 		cm = '/';
    398 		attr |= DIR_ATTR;
    399 	} else if (S_ISLNK(ent->mode)) {
    400 		cm = '@';
    401 		attr |= LINK_ATTR;
    402 	} else if (S_ISSOCK(ent->mode)) {
    403 		cm = '=';
    404 		attr |= SOCK_ATTR;
    405 	} else if (S_ISFIFO(ent->mode)) {
    406 		cm = '|';
    407 		attr |= FIFO_ATTR;
    408 	} else if (ent->mode & S_IXUSR) {
    409 		cm = '*';
    410 		attr |= EXEC_ATTR;
    411 	}
    412 
    413 	if (active)
    414 		attr |= CURSR_ATTR;
    415 
    416 	if (cm) {
    417 		name[len - 1] = cm;
    418 		name[len] = '\0';
    419 	}
    420 
    421 	attron(attr);
    422 	printw("%s%s\n", active ? CURSR : EMPTY, name);
    423 	attroff(attr);
    424 }
    425 
    426 int
    427 dentfill(char *path, struct entry **dents,
    428 	 int (*filter)(regex_t *, char *), regex_t *re)
    429 {
    430 	char newpath[PATH_MAX];
    431 	DIR *dirp;
    432 	struct dirent *dp;
    433 	struct stat sb;
    434 	int r, n = 0;
    435 
    436 	dirp = opendir(path);
    437 	if (dirp == NULL)
    438 		return 0;
    439 
    440 	while ((dp = readdir(dirp)) != NULL) {
    441 		/* Skip self and parent */
    442 		if (strcmp(dp->d_name, ".") == 0 ||
    443 		    strcmp(dp->d_name, "..") == 0)
    444 			continue;
    445 		if (filter(re, dp->d_name) == 0)
    446 			continue;
    447 		*dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
    448 		strlcpy((*dents)[n].name, dp->d_name, sizeof((*dents)[n].name));
    449 		/* Get mode flags */
    450 		mkpath(path, dp->d_name, newpath, sizeof(newpath));
    451 		r = lstat(newpath, &sb);
    452 		if (r == -1)
    453 			fatal("lstat");
    454 		(*dents)[n].mode = sb.st_mode;
    455 		(*dents)[n].t = sb.st_mtime;
    456 		n++;
    457 	}
    458 
    459 	/* Should never be null */
    460 	r = closedir(dirp);
    461 	if (r == -1)
    462 		fatal("closedir");
    463 	return n;
    464 }
    465 
    466 void
    467 dentfree(struct entry *dents)
    468 {
    469 	free(dents);
    470 }
    471 
    472 /* Return the position of the matching entry or 0 otherwise */
    473 int
    474 dentfind(struct entry *dents, int n, char *cwd, char *path)
    475 {
    476 	char tmp[PATH_MAX];
    477 	int i;
    478 
    479 	if (path == NULL)
    480 		return 0;
    481 	for (i = 0; i < n; i++) {
    482 		mkpath(cwd, dents[i].name, tmp, sizeof(tmp));
    483 		DPRINTF_S(path);
    484 		DPRINTF_S(tmp);
    485 		if (strcmp(tmp, path) == 0)
    486 			return i;
    487 	}
    488 	return 0;
    489 }
    490 
    491 int
    492 populate(char *path, char *oldpath, char *fltr)
    493 {
    494 	regex_t re;
    495 	int r;
    496 
    497 	/* Can fail when permissions change while browsing */
    498 	if (canopendir(path) == 0)
    499 		return -1;
    500 
    501 	/* Search filter */
    502 	r = setfilter(&re, fltr);
    503 	if (r != 0)
    504 		return -1;
    505 
    506 	dentfree(dents);
    507 
    508 	ndents = 0;
    509 	dents = NULL;
    510 
    511 	ndents = dentfill(path, &dents, visible, &re);
    512 	freefilter(&re);
    513 	if (ndents == 0)
    514 		return 0; /* Empty result */
    515 
    516 	qsort(dents, ndents, sizeof(*dents), entrycmp);
    517 
    518 	/* Find cur from history */
    519 	cur = dentfind(dents, ndents, path, oldpath);
    520 	return 0;
    521 }
    522 
    523 void
    524 redraw(char *path)
    525 {
    526 	char cwd[PATH_MAX], cwdresolved[PATH_MAX];
    527 	size_t ncols;
    528 	int nlines, odd;
    529 	int i;
    530 
    531 	nlines = MIN(LINES - 4, ndents);
    532 
    533 	/* Clean screen */
    534 	erase();
    535 
    536 	/* Strip trailing slashes */
    537 	for (i = strlen(path) - 1; i > 0; i--)
    538 		if (path[i] == '/')
    539 			path[i] = '\0';
    540 		else
    541 			break;
    542 
    543 	DPRINTF_D(cur);
    544 	DPRINTF_S(path);
    545 
    546 	/* No text wrapping in cwd line */
    547 	ncols = COLS;
    548 	if (ncols > PATH_MAX)
    549 		ncols = PATH_MAX;
    550 	strlcpy(cwd, path, ncols);
    551 	cwd[ncols - strlen(CWD) - 1] = '\0';
    552 	realpath(cwd, cwdresolved);
    553 
    554 	printw(CWD "%s\n\n", cwdresolved);
    555 
    556 	/* Print listing */
    557 	odd = ISODD(nlines);
    558 	if (cur < nlines / 2) {
    559 		for (i = 0; i < nlines; i++)
    560 			printent(&dents[i], i == cur);
    561 	} else if (cur >= ndents - nlines / 2) {
    562 		for (i = ndents - nlines; i < ndents; i++)
    563 			printent(&dents[i], i == cur);
    564 	} else {
    565 		for (i = cur - nlines / 2;
    566 		     i < cur + nlines / 2 + odd; i++)
    567 			printent(&dents[i], i == cur);
    568 	}
    569 }
    570 
    571 void
    572 browse(char *ipath, char *ifilter)
    573 {
    574 	char path[PATH_MAX], oldpath[PATH_MAX], newpath[PATH_MAX];
    575 	char fltr[LINE_MAX];
    576 	char *dir, *tmp, *run, *env;
    577 	struct stat sb;
    578 	regex_t re;
    579 	int r, fd;
    580 
    581 	strlcpy(path, ipath, sizeof(path));
    582 	strlcpy(fltr, ifilter, sizeof(fltr));
    583 	oldpath[0] = '\0';
    584 begin:
    585 	r = populate(path, oldpath, fltr);
    586 	if (r == -1) {
    587 		warn("populate");
    588 		goto nochange;
    589 	}
    590 
    591 	for (;;) {
    592 		redraw(path);
    593 nochange:
    594 		switch (nextsel(&run, &env)) {
    595 		case SEL_QUIT:
    596 			dentfree(dents);
    597 			return;
    598 		case SEL_BACK:
    599 			/* There is no going back */
    600 			if (strcmp(path, "/") == 0 ||
    601 			    strcmp(path, ".") == 0 ||
    602 			    strchr(path, '/') == NULL)
    603 				goto nochange;
    604 			dir = xdirname(path);
    605 			if (canopendir(dir) == 0) {
    606 				warn("canopendir");
    607 				goto nochange;
    608 			}
    609 			/* Save history */
    610 			strlcpy(oldpath, path, sizeof(oldpath));
    611 			strlcpy(path, dir, sizeof(path));
    612 			/* Reset filter */
    613 			strlcpy(fltr, ifilter, sizeof(fltr));
    614 			goto begin;
    615 		case SEL_GOIN:
    616 			/* Cannot descend in empty directories */
    617 			if (ndents == 0)
    618 				goto nochange;
    619 
    620 			mkpath(path, dents[cur].name, newpath, sizeof(newpath));
    621 			DPRINTF_S(newpath);
    622 
    623 			/* Get path info */
    624 			fd = open(newpath, O_RDONLY | O_NONBLOCK);
    625 			if (fd == -1) {
    626 				warn("open");
    627 				goto nochange;
    628 			}
    629 			r = fstat(fd, &sb);
    630 			if (r == -1) {
    631 				warn("fstat");
    632 				close(fd);
    633 				goto nochange;
    634 			}
    635 			close(fd);
    636 			DPRINTF_U(sb.st_mode);
    637 
    638 			switch (sb.st_mode & S_IFMT) {
    639 			case S_IFDIR:
    640 				if (canopendir(newpath) == 0) {
    641 					warn("canopendir");
    642 					goto nochange;
    643 				}
    644 				strlcpy(path, newpath, sizeof(path));
    645 				/* Reset filter */
    646 				strlcpy(fltr, ifilter, sizeof(fltr));
    647 				goto begin;
    648 			case S_IFREG:
    649 				exitcurses();
    650 				run = xgetenv("NOPEN", NOPEN);
    651 				r = spawnlp(path, run, run, newpath, (void *)0);
    652 				initcurses();
    653 				if (r == -1) {
    654 					info("Failed to execute plumber");
    655 					goto nochange;
    656 				}
    657 				continue;
    658 			default:
    659 				info("Unsupported file");
    660 				goto nochange;
    661 			}
    662 		case SEL_FLTR:
    663 			/* Read filter */
    664 			printprompt("/");
    665 			tmp = readln();
    666 			if (tmp == NULL)
    667 				tmp = ifilter;
    668 			/* Check and report regex errors */
    669 			r = setfilter(&re, tmp);
    670 			if (r != 0)
    671 				goto nochange;
    672 			freefilter(&re);
    673 			strlcpy(fltr, tmp, sizeof(fltr));
    674 			DPRINTF_S(fltr);
    675 			/* Save current */
    676 			if (ndents > 0)
    677 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    678 			goto begin;
    679 		case SEL_NEXT:
    680 			if (cur < ndents - 1)
    681 				cur++;
    682 			break;
    683 		case SEL_PREV:
    684 			if (cur > 0)
    685 				cur--;
    686 			break;
    687 		case SEL_PGDN:
    688 			if (cur < ndents - 1)
    689 				cur += MIN((LINES - 4) / 2, ndents - 1 - cur);
    690 			break;
    691 		case SEL_PGUP:
    692 			if (cur > 0)
    693 				cur -= MIN((LINES - 4) / 2, cur);
    694 			break;
    695 		case SEL_HOME:
    696 			cur = 0;
    697 			break;
    698 		case SEL_END:
    699 			cur = ndents - 1;
    700 			break;
    701 		case SEL_CD:
    702 			/* Read target dir */
    703 			printprompt("chdir: ");
    704 			tmp = readln();
    705 			if (tmp == NULL) {
    706 				clearprompt();
    707 				goto nochange;
    708 			}
    709 			mkpath(path, tmp, newpath, sizeof(newpath));
    710 			if (canopendir(newpath) == 0) {
    711 				warn("canopendir");
    712 				goto nochange;
    713 			}
    714 			strlcpy(path, newpath, sizeof(path));
    715 			/* Reset filter */
    716 			strlcpy(fltr, ifilter, sizeof(fltr));
    717 			DPRINTF_S(path);
    718 			goto begin;
    719 		case SEL_CDHOME:
    720 			tmp = getenv("HOME");
    721 			if (tmp == NULL) {
    722 				clearprompt();
    723 				goto nochange;
    724 			}
    725 			if (canopendir(tmp) == 0) {
    726 				warn("canopendir");
    727 				goto nochange;
    728 			}
    729 			strlcpy(path, tmp, sizeof(path));
    730 			/* Reset filter */
    731 			strlcpy(fltr, ifilter, sizeof(fltr));
    732 			DPRINTF_S(path);
    733 			goto begin;
    734 		case SEL_TOGGLEDOT:
    735 			showhidden ^= 1;
    736 			initfilter(showhidden, &ifilter);
    737 			strlcpy(fltr, ifilter, sizeof(fltr));
    738 			goto begin;
    739 		case SEL_MTIME:
    740 			mtimeorder = !mtimeorder;
    741 			/* Save current */
    742 			if (ndents > 0)
    743 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    744 			goto begin;
    745 		case SEL_DSORT:
    746 			dirorder = !dirorder;
    747 			/* Save current */
    748 			if (ndents > 0)
    749 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    750 			goto begin;
    751 		case SEL_ICASE:
    752 			icaseorder = !icaseorder;
    753 			/* Save current */
    754 			if (ndents > 0)
    755 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    756 			goto begin;
    757 		case SEL_VERS:
    758 			versorder = !versorder;
    759 			/* Save current */
    760 			if (ndents > 0)
    761 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    762 			goto begin;
    763 		case SEL_REDRAW:
    764 			/* Save current */
    765 			if (ndents > 0)
    766 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    767 			goto begin;
    768 		case SEL_RUN:
    769 			/* Save current */
    770 			if (ndents > 0)
    771 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    772 			run = xgetenv(env, run);
    773 			exitcurses();
    774 			spawnlp(path, run, run, (void *)0);
    775 			initcurses();
    776 			goto begin;
    777 		case SEL_RUNARG:
    778 			/* Save current */
    779 			if (ndents > 0)
    780 				mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
    781 			run = xgetenv(env, run);
    782 			exitcurses();
    783 			spawnlp(path, run, run, dents[cur].name, (void *)0);
    784 			initcurses();
    785 			goto begin;
    786 		}
    787 		/* Screensaver */
    788 		if (idletimeout != 0 && idle == idletimeout) {
    789 			idle = 0;
    790 			exitcurses();
    791 			spawnlp(NULL, idlecmd, idlecmd, (void *)0);
    792 			initcurses();
    793 		}
    794 	}
    795 }
    796 
    797 void
    798 usage(void)
    799 {
    800 	fprintf(stderr, "usage: %s [-c] [dir]\n", argv0);
    801 	exit(1);
    802 }
    803 
    804 int
    805 main(int argc, char *argv[])
    806 {
    807 	char cwd[PATH_MAX], *ipath;
    808 	char *ifilter;
    809 
    810 	ARGBEGIN {
    811 	case 'c':
    812 		usecolor = 1;
    813 		break;
    814 	default:
    815 		usage();
    816 	} ARGEND
    817 
    818 	if (argc > 1)
    819 		usage();
    820 
    821 	/* Confirm we are in a terminal */
    822 	if (!isatty(0) || !isatty(1)) {
    823 		fprintf(stderr, "stdin or stdout is not a tty\n");
    824 		exit(1);
    825 	}
    826 
    827 	if (getuid() == 0)
    828 		showhidden = 1;
    829 	initfilter(showhidden, &ifilter);
    830 
    831 	if (argv[0] != NULL) {
    832 		ipath = argv[0];
    833 	} else {
    834 		ipath = getcwd(cwd, sizeof(cwd));
    835 		if (ipath == NULL)
    836 			ipath = "/";
    837 	}
    838 
    839 	signal(SIGINT, SIG_IGN);
    840 
    841 	/* Test initial path */
    842 	if (canopendir(ipath) == 0) {
    843 		fprintf(stderr, "%s: %s\n", ipath, strerror(errno));
    844 		exit(1);
    845 	}
    846 
    847 	/* Set locale before curses setup */
    848 	setlocale(LC_ALL, "");
    849 	initcurses();
    850 	browse(ipath, ifilter);
    851 	exitcurses();
    852 	exit(0);
    853 }