My build of nnn with minor changes
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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