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.
 
 
 
 
 
 

459 lines
7.7 KiB

  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. browse(const char *ipath)
  187. {
  188. DIR *dirp;
  189. int dfd;
  190. struct dirent *dp;
  191. struct entry *dents;
  192. int i, n, cur;
  193. int r, ret;
  194. char *path = strdup(ipath);
  195. char *cwd;
  196. struct stat sb;
  197. begin:
  198. /* Path should be a malloc(3)-ed string at all times */
  199. n = 0;
  200. cur = 0;
  201. dents = NULL;
  202. dirp = opendir(path);
  203. if (dirp == NULL) {
  204. printwarn();
  205. goto nochange;
  206. }
  207. dfd = dirfd(dirp);
  208. if (dfd == -1)
  209. printerr(1, "dirfd");
  210. while ((dp = readdir(dirp)) != NULL) {
  211. char *name;
  212. /* Skip self and parent */
  213. if (strcmp(dp->d_name, ".") == 0
  214. || strcmp(dp->d_name, "..") == 0)
  215. continue;
  216. /* Deep copy because readdir(3) reuses the entries */
  217. dents = realloc(dents, (n + 1) * sizeof(*dents));
  218. if (dents == NULL)
  219. printerr(1, "realloc");
  220. dents[n].name = strdup(dp->d_name);
  221. if (dents[n].name == NULL)
  222. printerr(1, "strdup");
  223. /* Get mode flags */
  224. r = fstatat(dfd, dents[n].name, &sb, 0);
  225. if (r == -1)
  226. printerr(1, "stat");
  227. dents[n].mode = sb.st_mode;
  228. n++;
  229. }
  230. qsort(dents, n, sizeof(*dents), entrycmp);
  231. for (;;) {
  232. int nlines;
  233. struct entry *tmpents;
  234. int maxlen;
  235. int odd;
  236. redraw:
  237. nlines = MIN(LINES - 4, n);
  238. /* Clean screen */
  239. erase();
  240. /* Strip trailing slashes */
  241. for (i = strlen(path) - 1; i > -1; i--)
  242. if (path[i] == '/')
  243. path[i] = '\0';
  244. else
  245. break;
  246. DPRINTF_D(cur);
  247. DPRINTF_S(path);
  248. /* No text wrapping in cwd line */
  249. cwd = malloc(COLS * sizeof(char));
  250. strlcpy(cwd, path, COLS * sizeof(char));
  251. cwd[COLS - strlen(CWD) - 1] = '\0';
  252. /* No text wrapping in entries */
  253. tmpents = malloc(n * sizeof(*tmpents));
  254. maxlen = COLS - strlen(CURSR) - 1;
  255. for (i = 0; i < n; i++) {
  256. struct entry *tmpent = &tmpents[i];
  257. tmpent->name = strdup(dents[i].name);
  258. if (tmpent->name == NULL)
  259. printerr(1, "strdup tmp");
  260. tmpent->mode = dents[i].mode;
  261. if (strlen(tmpent->name) > maxlen)
  262. tmpent->name[maxlen] = '\0';
  263. }
  264. /* Print cwd. If empty we are on the root. We store it
  265. * as an empty string so that when we navigate in /mnt
  266. * is doesn't come up as //mnt. */
  267. printw(CWD "%s%s\n\n",
  268. strcmp(cwd, "") == 0 ? "/" : "",
  269. cwd);
  270. /* Print listing */
  271. odd = ISODD(nlines);
  272. if (cur < nlines / 2) {
  273. for (i = 0; i < nlines; i++)
  274. printw("%s%s\n",
  275. i == cur ? CURSR : EMPTY,
  276. tmpents[i].name);
  277. } else if (cur >= n - nlines / 2) {
  278. for (i = n - nlines; i < n; i++)
  279. printw("%s%s\n",
  280. i == cur ? CURSR : EMPTY,
  281. tmpents[i].name);
  282. } else {
  283. for (i = cur - nlines / 2;
  284. i < cur + nlines / 2 + odd; i++)
  285. printw("%s%s\n",
  286. i == cur ? CURSR : EMPTY,
  287. tmpents[i].name);
  288. }
  289. for (i = 0; i < n; i++)
  290. free(tmpents[i].name);
  291. free(tmpents);
  292. nochange:
  293. ret = nextsel(&cur, n);
  294. if (ret == 1) {
  295. free(path);
  296. return;
  297. }
  298. if (ret == 2) {
  299. /* Handle root case */
  300. if (strcmp(path, "") == 0) {
  301. goto nochange;
  302. } else {
  303. char *dir, *tmp;
  304. dir = dirname(path);
  305. tmp = malloc(strlen(dir) + 1);
  306. strlcpy(tmp, dir, strlen(dir) + 1);
  307. free(path);
  308. path = tmp;
  309. goto out;
  310. }
  311. }
  312. if (ret == 3) {
  313. char *pathnew;
  314. char *name;
  315. char *bin;
  316. pid_t pid;
  317. int fd;
  318. /* Cannot descend in empty directories */
  319. if (n == 0)
  320. goto nochange;
  321. name = dents[cur].name;
  322. asprintf(&pathnew, "%s/%s", path, name);
  323. DPRINTF_S(name);
  324. DPRINTF_S(pathnew);
  325. /* Get path info */
  326. fd = open(pathnew, O_RDONLY | O_NONBLOCK);
  327. if (fd == -1) {
  328. printwarn();
  329. free(pathnew);
  330. goto nochange;
  331. }
  332. r = fstat(fd, &sb);
  333. close(fd);
  334. if (r == -1) {
  335. printwarn();
  336. free(pathnew);
  337. goto nochange;
  338. }
  339. DPRINTF_U(sb.st_mode);
  340. /* Directory */
  341. if (S_ISDIR(sb.st_mode)) {
  342. free(path);
  343. path = pathnew;
  344. goto out;
  345. }
  346. /* Regular file */
  347. if (S_ISREG(sb.st_mode)) {
  348. /* Open with */
  349. bin = openwith(name);
  350. if (bin == NULL) {
  351. printmsg("No association");
  352. free(pathnew);
  353. goto nochange;
  354. }
  355. exitcurses();
  356. /* Run program */
  357. pid = fork();
  358. if (pid == 0)
  359. execlp(bin, bin, pathnew, NULL);
  360. else
  361. waitpid(pid, NULL, 0);
  362. initcurses();
  363. free(pathnew);
  364. goto redraw;
  365. }
  366. /* All the rest */
  367. printmsg("Unsupported file");
  368. free(pathnew);
  369. goto nochange;
  370. }
  371. }
  372. out:
  373. for (i = 0; i < n; i++)
  374. free(dents[i].name);
  375. free(dents);
  376. r = closedir(dirp);
  377. if (r == -1)
  378. printerr(1, "closedir");
  379. goto begin;
  380. }
  381. int
  382. main(int argc, char *argv[])
  383. {
  384. char *ipath = argv[1] != NULL ? argv[1] : "/";
  385. /* Test initial path */
  386. if (!testopendir(ipath))
  387. printerr(1, ipath);
  388. /* Set locale before curses setup */
  389. setlocale(LC_ALL, "");
  390. initcurses();
  391. browse(ipath);
  392. exitcurses();
  393. return 0;
  394. }