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.

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