My build of nnn with minor changes
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

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