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.
 
 
 
 
 
 

465 lines
7.6 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. 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. }