noice

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

noice.c (17337B)


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