My build of nnn with minor changes
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

430 lignes
7.1 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. char *
  69. openwith(char *file)
  70. {
  71. char *ext = NULL;
  72. char *bin = NULL;
  73. int i;
  74. ext = strrchr(file, '.');
  75. if (ext == NULL)
  76. ext = file;
  77. DPRINTF_S(ext);
  78. for (i = 0; i < LEN(assocs); i++)
  79. if (strcmp(assocs[i].ext, ext) == 0)
  80. bin = assocs[i].bin;
  81. DPRINTF_S(bin);
  82. return bin;
  83. }
  84. int
  85. dentcmp(const void *va, const void *vb)
  86. {
  87. const struct dirent *a, *b;
  88. a = (struct dirent *)va;
  89. b = (struct dirent *)vb;
  90. return strcmp(a->d_name, b->d_name);
  91. }
  92. void
  93. initcurses(void)
  94. {
  95. initscr();
  96. cbreak();
  97. noecho();
  98. nonl();
  99. intrflush(stdscr, FALSE);
  100. keypad(stdscr, TRUE);
  101. curs_set(FALSE); /* Hide cursor */
  102. }
  103. void
  104. exitcurses(void)
  105. {
  106. endwin(); /* Restore terminal */
  107. }
  108. /* Messages show up at the bottom */
  109. void
  110. printmsg(char *msg)
  111. {
  112. move(LINES - 1, 0);
  113. printw("%s\n", msg);
  114. }
  115. /* Display warning as a message */
  116. void
  117. printwarn(void)
  118. {
  119. printmsg(strerror(errno));
  120. }
  121. /* Kill curses and display error before exiting */
  122. void
  123. printerr(int ret, char *prefix)
  124. {
  125. exitcurses();
  126. printf("%s: %s\n", prefix, strerror(errno));
  127. exit(ret);
  128. }
  129. /*
  130. * Returns 0 normally
  131. * On movement it updates *cur
  132. * Returns 1 on quit
  133. * Returns 2 on go in
  134. * Returns 3 on go up
  135. */
  136. int
  137. nextsel(int *cur, int max)
  138. {
  139. int c;
  140. c = getch();
  141. switch (c) {
  142. case 'q':
  143. return 1;
  144. /* go up */
  145. case KEY_BACKSPACE:
  146. case KEY_LEFT:
  147. case 'h':
  148. return 2;
  149. /* go in */
  150. case KEY_ENTER:
  151. case '\r':
  152. case KEY_RIGHT:
  153. case 'l':
  154. return 3;
  155. /* next */
  156. case 'j':
  157. case KEY_DOWN:
  158. if (*cur < max - 1)
  159. (*cur)++;
  160. break;
  161. /* prev */
  162. case 'k':
  163. case KEY_UP:
  164. if (*cur > 0)
  165. (*cur)--;
  166. break;
  167. }
  168. return 0;
  169. }
  170. int
  171. testopendir(char *path)
  172. {
  173. DIR *dirp;
  174. dirp = opendir(path);
  175. if (dirp == NULL) {
  176. return 0;
  177. } else {
  178. closedir(dirp);
  179. return 1;
  180. }
  181. }
  182. void
  183. browse(const char *ipath)
  184. {
  185. DIR *dirp;
  186. struct dirent *dp;
  187. struct dirent *dents;
  188. int i, n, cur;
  189. int r, ret;
  190. char *path = strdup(ipath);
  191. char *cwd;
  192. begin:
  193. /* Path should be a malloc(3)-ed string at all times */
  194. n = 0;
  195. cur = 0;
  196. dents = NULL;
  197. dirp = opendir(path);
  198. if (dirp == NULL) {
  199. printwarn();
  200. goto nochange;
  201. }
  202. while ((dp = readdir(dirp)) != NULL) {
  203. /* Skip self and parent */
  204. if (strcmp(dp->d_name, ".") == 0
  205. || strcmp(dp->d_name, "..") == 0)
  206. continue;
  207. /* Deep copy because readdir(3) reuses the entries */
  208. dents = realloc(dents, (n + 1) * sizeof(*dents));
  209. if (dents == NULL)
  210. printerr(1, "realloc");
  211. memcpy(&dents[n], dp, sizeof(*dents));
  212. n++;
  213. }
  214. qsort(dents, n, sizeof(*dents), dentcmp);
  215. for (;;) {
  216. int nlines;
  217. struct dirent *tmpents;
  218. int odd;
  219. redraw:
  220. nlines = MIN(LINES - 4, n);
  221. /* Clean screen */
  222. erase();
  223. /* Strip trailing slashes */
  224. for (i = strlen(path) - 1; i > -1; i--)
  225. if (path[i] == '/')
  226. path[i] = '\0';
  227. else
  228. break;
  229. DPRINTF_D(cur);
  230. DPRINTF_S(path);
  231. /* No text wrapping in cwd line */
  232. cwd = malloc(COLS * sizeof(char));
  233. strlcpy(cwd, path, COLS * sizeof(char));
  234. cwd[COLS - strlen(CWD) - 1] = '\0';
  235. /* No text wrapping in entries */
  236. tmpents = malloc(n * sizeof(*tmpents));
  237. memcpy(tmpents, dents, n * sizeof(*tmpents));
  238. for (i = 0; i < n; i++)
  239. tmpents[i].d_name[COLS - strlen(CURSR) - 1] = '\0';
  240. /* Print cwd. If empty we are on the root. We store it
  241. * as an empty string so that when we navigate in /mnt
  242. * is doesn't come up as //mnt. */
  243. printw(CWD "%s%s\n\n",
  244. strcmp(cwd, "") == 0 ? "/" : "",
  245. cwd);
  246. /* Print listing */
  247. odd = ISODD(nlines);
  248. if (cur < nlines / 2) {
  249. for (i = 0; i < nlines; i++)
  250. printw("%s%s\n",
  251. i == cur ? CURSR : EMPTY,
  252. tmpents[i].d_name);
  253. } else if (cur >= n - nlines / 2) {
  254. for (i = n - nlines; i < n; i++)
  255. printw("%s%s\n",
  256. i == cur ? CURSR : EMPTY,
  257. tmpents[i].d_name);
  258. } else {
  259. for (i = cur - nlines / 2;
  260. i < cur + nlines / 2 + odd; i++)
  261. printw("%s%s\n",
  262. i == cur ? CURSR : EMPTY,
  263. tmpents[i].d_name);
  264. }
  265. free(tmpents);
  266. nochange:
  267. ret = nextsel(&cur, n);
  268. if (ret == 1) {
  269. free(path);
  270. return;
  271. }
  272. if (ret == 2) {
  273. /* Handle root case */
  274. if (strcmp(path, "") == 0) {
  275. goto nochange;
  276. } else {
  277. char *dir, *tmp;
  278. dir = dirname(path);
  279. tmp = malloc(strlen(dir) + 1);
  280. strlcpy(tmp, dir, strlen(dir) + 1);
  281. free(path);
  282. path = tmp;
  283. goto out;
  284. }
  285. }
  286. if (ret == 3) {
  287. char *pathnew;
  288. char *name;
  289. char *bin;
  290. pid_t pid;
  291. int fd;
  292. struct stat sb;
  293. /* Cannot descend in empty directories */
  294. if (n == 0)
  295. goto nochange;
  296. name = dents[cur].d_name;
  297. asprintf(&pathnew, "%s/%s", path, name);
  298. DPRINTF_S(name);
  299. DPRINTF_S(pathnew);
  300. /* Get path info */
  301. fd = open(pathnew, O_RDONLY | O_NONBLOCK);
  302. if (fd == -1) {
  303. printwarn();
  304. free(pathnew);
  305. goto nochange;
  306. }
  307. r = fstat(fd, &sb);
  308. close(fd);
  309. if (r == -1) {
  310. printwarn();
  311. free(pathnew);
  312. goto nochange;
  313. }
  314. DPRINTF_U(sb.st_mode);
  315. /* Directory */
  316. if (S_ISDIR(sb.st_mode)) {
  317. free(path);
  318. path = pathnew;
  319. goto out;
  320. }
  321. /* Regular file */
  322. if (S_ISREG(sb.st_mode)) {
  323. /* Open with */
  324. bin = openwith(name);
  325. if (bin == NULL) {
  326. printmsg("No association");
  327. free(pathnew);
  328. goto nochange;
  329. }
  330. exitcurses();
  331. /* Run program */
  332. pid = fork();
  333. if (pid == 0)
  334. execlp(bin, bin, pathnew, NULL);
  335. else
  336. waitpid(pid, NULL, 0);
  337. initcurses();
  338. free(pathnew);
  339. goto redraw;
  340. }
  341. /* All the rest */
  342. printmsg("Unsupported file");
  343. free(pathnew);
  344. goto nochange;
  345. }
  346. }
  347. out:
  348. free(dents);
  349. r = closedir(dirp);
  350. if (r == -1)
  351. printerr(1, "closedir");
  352. goto begin;
  353. }
  354. int
  355. main(int argc, char *argv[])
  356. {
  357. char *ipath = argv[1] != NULL ? argv[1] : "/";
  358. /* Test initial path */
  359. if (!testopendir(ipath))
  360. printerr(1, ipath);
  361. /* Set locale before curses setup */
  362. setlocale(LC_ALL, "");
  363. initcurses();
  364. browse(ipath);
  365. exitcurses();
  366. return 0;
  367. }