My build of nnn with minor changes
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

921 строка
15 KiB

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