My build of nnn with minor changes
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

479 Zeilen
7.9 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 <stdlib.h>
  10. #include <stdio.h>
  11. #include <signal.h>
  12. #include <string.h>
  13. #include <unistd.h>
  14. #ifdef LINUX
  15. #include <bsd/string.h>
  16. #endif
  17. #ifdef DEBUG
  18. #define DEBUG_FD 8
  19. #define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
  20. #define DPRINTF_U(x) dprintf(DEBUG_FD, #x "=%u\n", x)
  21. #define DPRINTF_S(x) dprintf(DEBUG_FD, #x "=%s\n", x)
  22. #define DPRINTF_P(x) dprintf(DEBUG_FD, #x "=0x%p\n", x)
  23. #else
  24. #define DPRINTF_D(x)
  25. #define DPRINTF_U(x)
  26. #define DPRINTF_S(x)
  27. #define DPRINTF_P(x)
  28. #endif /* DEBUG */
  29. #define LEN(x) (sizeof(x) / sizeof(*(x)))
  30. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  31. #define ISODD(x) ((x) & 1)
  32. struct assoc {
  33. char *ext; /* Extension */
  34. char *bin; /* Program */
  35. };
  36. /* Configuration */
  37. struct assoc assocs[] = {
  38. { ".avi", "mplayer" },
  39. { ".mp4", "mplayer" },
  40. { ".mkv", "mplayer" },
  41. { ".mp3", "mplayer" },
  42. { ".ogg", "mplayer" },
  43. { ".srt", "less" },
  44. { ".txt", "less" },
  45. { ".sh", "sh" },
  46. { "README", "less" },
  47. };
  48. #define CWD "cwd: "
  49. #define CURSR " > "
  50. #define EMPTY " "
  51. /*
  52. * Layout:
  53. * .---------
  54. * | cwd: /mnt/path
  55. * |
  56. * | file0
  57. * | file1
  58. * | > file2
  59. * | file3
  60. * | file4
  61. * ...
  62. * | filen
  63. * |
  64. * | Permission denied
  65. * '------
  66. */
  67. int die = 0;
  68. struct entry {
  69. char name[MAXNAMLEN + 1];
  70. };
  71. char *
  72. openwith(char *file)
  73. {
  74. char *ext = NULL;
  75. char *bin = NULL;
  76. int i;
  77. ext = strrchr(file, '.');
  78. if (ext == NULL)
  79. ext = file;
  80. DPRINTF_S(ext);
  81. for (i = 0; i < LEN(assocs); i++)
  82. if (strcmp(assocs[i].ext, ext) == 0)
  83. bin = assocs[i].bin;
  84. DPRINTF_S(bin);
  85. return bin;
  86. }
  87. int
  88. dentcmp(const void *va, const void *vb)
  89. {
  90. const struct dirent *a, *b;
  91. a = *(struct dirent **)va;
  92. b = *(struct dirent **)vb;
  93. return strcmp(a->d_name, b->d_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. testopen(char *path)
  175. {
  176. int fd;
  177. fd = open(path, O_RDONLY);
  178. if (fd == -1) {
  179. return 0;
  180. } else {
  181. close(fd);
  182. return 1;
  183. }
  184. }
  185. int
  186. testopendir(char *path)
  187. {
  188. DIR *dirp;
  189. dirp = opendir(path);
  190. if (dirp == NULL) {
  191. return 0;
  192. } else {
  193. closedir(dirp);
  194. return 1;
  195. }
  196. }
  197. void
  198. browse(const char *ipath)
  199. {
  200. DIR *dirp;
  201. struct dirent *dp;
  202. struct dirent **dents;
  203. int i, n, cur;
  204. int r, ret;
  205. char *path = strdup(ipath);
  206. char *cwd;
  207. begin:
  208. /* Path should be a malloc(3)-ed string at all times */
  209. n = 0;
  210. cur = 0;
  211. dents = NULL;
  212. dirp = opendir(path);
  213. if (dirp == NULL) {
  214. printwarn();
  215. goto nochange;
  216. }
  217. while ((dp = readdir(dirp)) != NULL) {
  218. /* Skip self and parent */
  219. if (strcmp(dp->d_name, ".") == 0
  220. || strcmp(dp->d_name, "..") == 0)
  221. continue;
  222. dents = realloc(dents, (n + 1) * sizeof(*dents));
  223. if (dents == NULL)
  224. printerr(1, "realloc");
  225. dents[n] = dp;
  226. n++;
  227. }
  228. qsort(dents, n, sizeof(*dents), dentcmp);
  229. for (;;) {
  230. int nlines;
  231. struct entry *tmpents;
  232. int odd;
  233. redraw:
  234. nlines = MIN(LINES - 4, n);
  235. /* Clean screen */
  236. erase();
  237. /* Strip trailing slashes */
  238. for (i = strlen(path) - 1; i > -1; i--)
  239. if (path[i] == '/')
  240. path[i] = '\0';
  241. else
  242. break;
  243. DPRINTF_D(cur);
  244. DPRINTF_S(path);
  245. /* No text wrapping in cwd line */
  246. cwd = malloc(COLS * sizeof(char));
  247. strlcpy(cwd, path, COLS * sizeof(char));
  248. cwd[COLS - strlen(CWD) - 1] = '\0';
  249. /* No text wrapping in entries */
  250. tmpents = malloc(n * sizeof(*tmpents));
  251. for (i = 0; i < n; i++) {
  252. strlcpy(tmpents[i].name, dents[i]->d_name,
  253. sizeof(tmpents[i].name));
  254. tmpents[i].name[COLS - strlen(CURSR) - 1] = '\0';
  255. }
  256. /* Print cwd. If empty we are on the root. We store it
  257. * as an empty string so that when we navigate in /mnt
  258. * is doesn't come up as //mnt. */
  259. printw(CWD "%s%s\n\n",
  260. strcmp(cwd, "") == 0 ? "/" : "",
  261. cwd);
  262. /* Print listing */
  263. odd = ISODD(nlines);
  264. if (cur < nlines / 2) {
  265. for (i = 0; i < nlines; i++)
  266. printw("%s%s\n",
  267. i == cur ? CURSR : EMPTY,
  268. tmpents[i].name);
  269. } else if (cur >= n - nlines / 2) {
  270. for (i = n - nlines; i < n; i++)
  271. printw("%s%s\n",
  272. i == cur ? CURSR : EMPTY,
  273. tmpents[i].name);
  274. } else {
  275. for (i = cur - nlines / 2;
  276. i < cur + nlines / 2 + odd; i++)
  277. printw("%s%s\n",
  278. i == cur ? CURSR : EMPTY,
  279. tmpents[i].name);
  280. }
  281. free(tmpents);
  282. nochange:
  283. ret = nextsel(&cur, n);
  284. if (ret == 1) {
  285. free(path);
  286. return;
  287. }
  288. if (ret == 2) {
  289. /* Handle root case */
  290. if (strcmp(path, "") == 0) {
  291. goto nochange;
  292. } else {
  293. char *dir, *tmp;
  294. dir = dirname(path);
  295. tmp = malloc(strlen(dir) + 1);
  296. strlcpy(tmp, dir, strlen(dir) + 1);
  297. free(path);
  298. path = tmp;
  299. goto out;
  300. }
  301. }
  302. if (ret == 3) {
  303. char *pathnew, *pathtmp;
  304. size_t pathsiz;
  305. char *name;
  306. u_int8_t type;
  307. char *bin;
  308. pid_t pid;
  309. struct stat sb;
  310. /* Cannot descend in empty directories */
  311. if (n == 0)
  312. goto nochange;
  313. name = dents[cur]->d_name;
  314. type = dents[cur]->d_type;
  315. pathsiz = strlen(path) + 1 + strlen(name) + 1;
  316. pathnew = malloc(pathsiz);
  317. snprintf(pathnew, pathsiz, "%s/%s", path, name);
  318. DPRINTF_S(name);
  319. DPRINTF_U(type);
  320. DPRINTF_S(pathnew);
  321. again:
  322. switch (type) {
  323. case DT_LNK:
  324. /* Resolve link */
  325. pathtmp = realpath(pathnew, NULL);
  326. if (pathtmp == NULL) {
  327. printwarn();
  328. free(pathnew);
  329. goto nochange;
  330. } else {
  331. r = stat(pathtmp, &sb);
  332. free(pathtmp);
  333. if (r == -1) {
  334. printwarn();
  335. free(pathnew);
  336. goto nochange;
  337. }
  338. /* Directory or file */
  339. if (S_ISDIR(sb.st_mode)) {
  340. type = DT_DIR;
  341. goto again;
  342. }
  343. if (S_ISREG(sb.st_mode)) {
  344. type = DT_REG;
  345. goto again;
  346. }
  347. /* All the rest */
  348. printmsg("Unsupported file");
  349. free(pathnew);
  350. goto nochange;
  351. }
  352. case DT_DIR:
  353. /* Change to new path */
  354. if (testopen(pathnew)) {
  355. free(path);
  356. path = pathnew;
  357. goto out;
  358. } else {
  359. printwarn();
  360. free(pathnew);
  361. goto nochange;
  362. }
  363. case DT_REG:
  364. if (!testopen(pathnew)) {
  365. printwarn();
  366. free(pathnew);
  367. goto nochange;
  368. }
  369. /* Open with */
  370. bin = openwith(name);
  371. if (bin == NULL) {
  372. printmsg("No association");
  373. free(pathnew);
  374. goto nochange;
  375. }
  376. exitcurses();
  377. /* Run program */
  378. pid = fork();
  379. if (pid == 0)
  380. execlp(bin, bin, pathnew, NULL);
  381. else
  382. waitpid(pid, NULL, 0);
  383. initcurses();
  384. free(pathnew);
  385. goto redraw;
  386. default:
  387. printmsg("Unsupported file");
  388. free(pathnew);
  389. goto nochange;
  390. }
  391. }
  392. }
  393. out:
  394. free(dents);
  395. r = closedir(dirp);
  396. if (r == -1)
  397. printerr(1, "closedir");
  398. goto begin;
  399. }
  400. int
  401. main(int argc, char *argv[])
  402. {
  403. char *ipath = argv[1] != NULL ? argv[1] : "/";
  404. /* Test initial path */
  405. if (!testopendir(ipath))
  406. printerr(1, ipath);
  407. /* Set locale before curses setup */
  408. setlocale(LC_ALL, "");
  409. initcurses();
  410. browse(ipath);
  411. exitcurses();
  412. return 0;
  413. }