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.
 
 
 
 
 
 

423 lines
6.8 KiB

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