My build of nnn with minor changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

noice.c 7.6 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #include <sys/stat.h>
  2. #include <sys/types.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <dirent.h>
  6. #include <curses.h>
  7. #include <libgen.h>
  8. #include <locale.h>
  9. #include <regex.h>
  10. #include <stdlib.h>
  11. #include <stdio.h>
  12. #include <signal.h>
  13. #include <string.h>
  14. #include <unistd.h>
  15. #ifdef LINUX
  16. #include <bsd/string.h>
  17. #endif
  18. #ifdef DEBUG
  19. #define DEBUG_FD 8
  20. #define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
  21. #define DPRINTF_U(x) dprintf(DEBUG_FD, #x "=%u\n", x)
  22. #define DPRINTF_S(x) dprintf(DEBUG_FD, #x "=%s\n", x)
  23. #define DPRINTF_P(x) dprintf(DEBUG_FD, #x "=0x%p\n", x)
  24. #else
  25. #define DPRINTF_D(x)
  26. #define DPRINTF_U(x)
  27. #define DPRINTF_S(x)
  28. #define DPRINTF_P(x)
  29. #endif /* DEBUG */
  30. #define LEN(x) (sizeof(x) / sizeof(*(x)))
  31. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  32. #define ISODD(x) ((x) & 1)
  33. struct assoc {
  34. char *regex; /* Regex to match on filename */
  35. char *bin; /* Program */
  36. };
  37. /* Configuration */
  38. struct assoc assocs[] = {
  39. { ".(avi|mp4|mkv|mp3|ogg)$", "mplayer" },
  40. { ".srt$", "less" },
  41. { ".txt$", "less" },
  42. { ".sh$", "sh" },
  43. { "^README$", "less" },
  44. };
  45. struct entry {
  46. char *name;
  47. mode_t mode;
  48. };
  49. #define CWD "cwd: "
  50. #define CURSR " > "
  51. #define EMPTY " "
  52. /*
  53. * Layout:
  54. * .---------
  55. * | cwd: /mnt/path
  56. * |
  57. * | file0
  58. * | file1
  59. * | > file2
  60. * | file3
  61. * | file4
  62. * ...
  63. * | filen
  64. * |
  65. * | Permission denied
  66. * '------
  67. */
  68. int die = 0;
  69. char *
  70. openwith(char *file)
  71. {
  72. regex_t regex;
  73. char *bin = NULL;
  74. int i;
  75. for (i = 0; i < LEN(assocs); i++) {
  76. if (regcomp(&regex, assocs[i].regex,
  77. REG_NOSUB | REG_EXTENDED) != 0)
  78. continue;
  79. if (regexec(&regex, file, 0, NULL, 0) != REG_NOMATCH) {
  80. bin = assocs[i].bin;
  81. break;
  82. }
  83. }
  84. DPRINTF_S(bin);
  85. return bin;
  86. }
  87. int
  88. entrycmp(const void *va, const void *vb)
  89. {
  90. const struct entry *a, *b;
  91. a = (struct entry *)va;
  92. b = (struct entry *)vb;
  93. return strcmp(a->name, b->name);
  94. }
  95. void
  96. initcurses(void)
  97. {
  98. initscr();
  99. cbreak();
  100. noecho();
  101. nonl();
  102. intrflush(stdscr, FALSE);
  103. keypad(stdscr, TRUE);
  104. curs_set(FALSE); /* Hide cursor */
  105. }
  106. void
  107. exitcurses(void)
  108. {
  109. endwin(); /* Restore terminal */
  110. }
  111. /* Messages show up at the bottom */
  112. void
  113. printmsg(char *msg)
  114. {
  115. move(LINES - 1, 0);
  116. printw("%s\n", msg);
  117. }
  118. /* Display warning as a message */
  119. void
  120. printwarn(void)
  121. {
  122. printmsg(strerror(errno));
  123. }
  124. /* Kill curses and display error before exiting */
  125. void
  126. printerr(int ret, char *prefix)
  127. {
  128. exitcurses();
  129. printf("%s: %s\n", prefix, strerror(errno));
  130. exit(ret);
  131. }
  132. /*
  133. * Returns 0 normally
  134. * On movement it updates *cur
  135. * Returns 1 on quit
  136. * Returns 2 on go in
  137. * Returns 3 on go up
  138. */
  139. int
  140. nextsel(int *cur, int max)
  141. {
  142. int c;
  143. c = getch();
  144. switch (c) {
  145. case 'q':
  146. return 1;
  147. /* go up */
  148. case KEY_BACKSPACE:
  149. case KEY_LEFT:
  150. case 'h':
  151. return 2;
  152. /* go in */
  153. case KEY_ENTER:
  154. case '\r':
  155. case KEY_RIGHT:
  156. case 'l':
  157. return 3;
  158. /* next */
  159. case 'j':
  160. case KEY_DOWN:
  161. if (*cur < max - 1)
  162. (*cur)++;
  163. break;
  164. /* prev */
  165. case 'k':
  166. case KEY_UP:
  167. if (*cur > 0)
  168. (*cur)--;
  169. break;
  170. }
  171. return 0;
  172. }
  173. int
  174. testopendir(char *path)
  175. {
  176. DIR *dirp;
  177. dirp = opendir(path);
  178. if (dirp == NULL) {
  179. return 0;
  180. } else {
  181. closedir(dirp);
  182. return 1;
  183. }
  184. }
  185. void
  186. printent(struct entry *ent, int active)
  187. {
  188. char *name;
  189. unsigned int maxlen = COLS - strlen(CURSR) - 1;
  190. char cm = 0;
  191. /* Copy name locally */
  192. name = strdup(ent->name);
  193. if (name == NULL)
  194. printerr(1, "strdup name");
  195. if (S_ISDIR(ent->mode)) {
  196. cm = '/';
  197. maxlen--;
  198. } else if (S_ISLNK(ent->mode)) {
  199. cm = '@';
  200. maxlen--;
  201. }
  202. /* No text wrapping in entries */
  203. if (strlen(name) > maxlen)
  204. name[maxlen] = '\0';
  205. if (cm == 0)
  206. printw("%s%s\n", active ? CURSR : EMPTY, name);
  207. else
  208. printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
  209. free(name);
  210. }
  211. void
  212. browse(const char *ipath)
  213. {
  214. DIR *dirp;
  215. int dfd;
  216. struct dirent *dp;
  217. struct entry *dents;
  218. int i, n, cur;
  219. int r, ret;
  220. char *path = strdup(ipath);
  221. char *cwd;
  222. struct stat sb;
  223. begin:
  224. /* Path should be a malloc(3)-ed string at all times */
  225. n = 0;
  226. cur = 0;
  227. dents = NULL;
  228. dirp = opendir(path);
  229. if (dirp == NULL) {
  230. printwarn();
  231. goto nochange;
  232. }
  233. while ((dp = readdir(dirp)) != NULL) {
  234. char *name;
  235. /* Skip self and parent */
  236. if (strcmp(dp->d_name, ".") == 0
  237. || strcmp(dp->d_name, "..") == 0)
  238. continue;
  239. /* Deep copy because readdir(3) reuses the entries */
  240. dents = realloc(dents, (n + 1) * sizeof(*dents));
  241. if (dents == NULL)
  242. printerr(1, "realloc");
  243. dents[n].name = strdup(dp->d_name);
  244. if (dents[n].name == NULL)
  245. printerr(1, "strdup");
  246. /* Get mode flags */
  247. asprintf(&name, "%s/%s", path, dents[n].name);
  248. r = lstat(name, &sb);
  249. free(name);
  250. if (r == -1)
  251. printerr(1, "stat");
  252. dents[n].mode = sb.st_mode;
  253. n++;
  254. }
  255. qsort(dents, n, sizeof(*dents), entrycmp);
  256. for (;;) {
  257. int nlines;
  258. int maxlen;
  259. int odd;
  260. redraw:
  261. nlines = MIN(LINES - 4, n);
  262. /* Clean screen */
  263. erase();
  264. /* Strip trailing slashes */
  265. for (i = strlen(path) - 1; i > -1; i--)
  266. if (path[i] == '/')
  267. path[i] = '\0';
  268. else
  269. break;
  270. DPRINTF_D(cur);
  271. DPRINTF_S(path);
  272. /* No text wrapping in cwd line */
  273. cwd = malloc(COLS * sizeof(char));
  274. strlcpy(cwd, path, COLS * sizeof(char));
  275. cwd[COLS - strlen(CWD) - 1] = '\0';
  276. /* Print cwd. If empty we are on the root. We store it
  277. * as an empty string so that when we navigate in /mnt
  278. * is doesn't come up as //mnt. */
  279. printw(CWD "%s%s\n\n",
  280. strcmp(cwd, "") == 0 ? "/" : "",
  281. cwd);
  282. /* Print listing */
  283. odd = ISODD(nlines);
  284. if (cur < nlines / 2) {
  285. for (i = 0; i < nlines; i++)
  286. printent(&dents[i], i == cur);
  287. } else if (cur >= n - nlines / 2) {
  288. for (i = n - nlines; i < n; i++)
  289. printent(&dents[i], i == cur);
  290. } else {
  291. for (i = cur - nlines / 2;
  292. i < cur + nlines / 2 + odd; i++)
  293. printent(&dents[i], i == cur);
  294. }
  295. nochange:
  296. ret = nextsel(&cur, n);
  297. if (ret == 1) {
  298. free(path);
  299. return;
  300. }
  301. if (ret == 2) {
  302. /* Handle root case */
  303. if (strcmp(path, "") == 0) {
  304. goto nochange;
  305. } else {
  306. char *dir, *tmp;
  307. dir = dirname(path);
  308. tmp = malloc(strlen(dir) + 1);
  309. strlcpy(tmp, dir, strlen(dir) + 1);
  310. free(path);
  311. path = tmp;
  312. goto out;
  313. }
  314. }
  315. if (ret == 3) {
  316. char *pathnew;
  317. char *name;
  318. char *bin;
  319. pid_t pid;
  320. int fd;
  321. /* Cannot descend in empty directories */
  322. if (n == 0)
  323. goto nochange;
  324. name = dents[cur].name;
  325. asprintf(&pathnew, "%s/%s", path, name);
  326. DPRINTF_S(name);
  327. DPRINTF_S(pathnew);
  328. /* Get path info */
  329. fd = open(pathnew, O_RDONLY | O_NONBLOCK);
  330. if (fd == -1) {
  331. printwarn();
  332. free(pathnew);
  333. goto nochange;
  334. }
  335. r = fstat(fd, &sb);
  336. close(fd);
  337. if (r == -1) {
  338. printwarn();
  339. free(pathnew);
  340. goto nochange;
  341. }
  342. DPRINTF_U(sb.st_mode);
  343. /* Directory */
  344. if (S_ISDIR(sb.st_mode)) {
  345. free(path);
  346. path = pathnew;
  347. goto out;
  348. }
  349. /* Regular file */
  350. if (S_ISREG(sb.st_mode)) {
  351. /* Open with */
  352. bin = openwith(name);
  353. if (bin == NULL) {
  354. printmsg("No association");
  355. free(pathnew);
  356. goto nochange;
  357. }
  358. exitcurses();
  359. /* Run program */
  360. pid = fork();
  361. if (pid == 0)
  362. execlp(bin, bin, pathnew, NULL);
  363. else
  364. waitpid(pid, NULL, 0);
  365. initcurses();
  366. free(pathnew);
  367. goto redraw;
  368. }
  369. /* All the rest */
  370. printmsg("Unsupported file");
  371. free(pathnew);
  372. goto nochange;
  373. }
  374. }
  375. out:
  376. for (i = 0; i < n; i++)
  377. free(dents[i].name);
  378. free(dents);
  379. r = closedir(dirp);
  380. if (r == -1)
  381. printerr(1, "closedir");
  382. goto begin;
  383. }
  384. int
  385. main(int argc, char *argv[])
  386. {
  387. char *ipath = argv[1] != NULL ? argv[1] : "/";
  388. /* Test initial path */
  389. if (!testopendir(ipath))
  390. printerr(1, ipath);
  391. /* Set locale before curses setup */
  392. setlocale(LC_ALL, "");
  393. initcurses();
  394. browse(ipath);
  395. exitcurses();
  396. return 0;
  397. }