Introduction
Introduction Statistics Contact Development Disclaimer Help
noice.c - noice - small file browser (mirror / fork from 2f30.org)
git clone git://git.codemadness.org/noice
Log
Files
Refs
README
LICENSE
---
noice.c (15447B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <sys/stat.h>
3 #include <sys/types.h>
4
5 #include <curses.h>
6 #include <dirent.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <libgen.h>
10 #include <limits.h>
11 #include <locale.h>
12 #include <regex.h>
13 #include <signal.h>
14 #include <stdarg.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "arg.h"
21 #include "util.h"
22
23 #define ISODD(x) ((x) & 1)
24 #define CONTROL(c) ((c) ^ 0x40)
25 #define META(c) ((c) ^ 0x80)
26
27 struct cpair {
28 int fg;
29 int bg;
30 };
31
32 /* Supported actions */
33 enum action {
34 SEL_QUIT = 1,
35 SEL_BACK,
36 SEL_GOIN,
37 SEL_FLTR,
38 SEL_NEXT,
39 SEL_PREV,
40 SEL_PGDN,
41 SEL_PGUP,
42 SEL_HOME,
43 SEL_END,
44 SEL_CD,
45 SEL_CDHOME,
46 SEL_TOGGLEDOT,
47 SEL_DSORT,
48 SEL_MTIME,
49 SEL_ICASE,
50 SEL_VERS,
51 SEL_REDRAW,
52 SEL_RUN,
53 SEL_RUNARG,
54 };
55
56 struct key {
57 int sym; /* Key pressed */
58 enum action act; /* Action */
59 char *run; /* Program to run */
60 char *env; /* Environment variable override */
61 };
62
63 #include "noiceconf.h"
64
65 struct entry {
66 char name[PATH_MAX];
67 mode_t mode;
68 time_t t;
69 };
70
71 /* Global context */
72 struct entry *dents;
73 char *argv0;
74 int ndents, cur;
75 int idle;
76
77 /*
78 * Layout:
79 * .---------
80 * | /mnt/path
81 * |
82 * | file0
83 * | file1
84 * | > file2
85 * | file3
86 * | file4
87 * ...
88 * | filen
89 * |
90 * | Permission denied
91 * '------
92 */
93
94 void info(char *, ...);
95 void warn(char *, ...);
96 void fatal(char *, ...);
97
98 void *
99 xrealloc(void *p, size_t size)
100 {
101 p = realloc(p, size);
102 if (p == NULL)
103 fatal("realloc");
104 return p;
105 }
106
107 /* Some implementations of dirname(3) may modify `path' and some
108 * return a pointer inside `path'. */
109 char *
110 xdirname(const char *path)
111 {
112 static char out[PATH_MAX];
113 char tmp[PATH_MAX], *p;
114
115 strlcpy(tmp, path, sizeof(tmp));
116 p = dirname(tmp);
117 if (p == NULL)
118 fatal("dirname");
119 strlcpy(out, p, sizeof(out));
120 return out;
121 }
122
123 char *
124 xgetenv(char *name, char *fallback)
125 {
126 char *value;
127
128 if (name == NULL)
129 return fallback;
130 value = getenv(name);
131 return value && value[0] ? value : fallback;
132 }
133
134 int
135 setfilter(regex_t *regex, char *filter)
136 {
137 char errbuf[LINE_MAX];
138 size_t len;
139 int r;
140
141 r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED | REG_ICASE);
142 if (r != 0) {
143 len = COLS;
144 if (len > sizeof(errbuf))
145 len = sizeof(errbuf);
146 regerror(r, regex, errbuf, len);
147 info("%s", errbuf);
148 }
149 return r;
150 }
151
152 void
153 freefilter(regex_t *regex)
154 {
155 regfree(regex);
156 }
157
158 void
159 initfilter(int dot, char **ifilter)
160 {
161 *ifilter = dot ? "." : "^[^.]";
162 }
163
164 int
165 visible(regex_t *regex, char *file)
166 {
167 return regexec(regex, file, 0, NULL, 0) == 0;
168 }
169
170 int
171 dircmp(mode_t a, mode_t b)
172 {
173 if (S_ISDIR(a) && S_ISDIR(b))
174 return 0;
175 if (!S_ISDIR(a) && !S_ISDIR(b))
176 return 0;
177 if (S_ISDIR(a))
178 return -1;
179 else
180 return 1;
181 }
182
183 int
184 entrycmp(const void *va, const void *vb)
185 {
186 const struct entry *a = va, *b = vb;
187
188 if (dirorder) {
189 if (dircmp(a->mode, b->mode) != 0)
190 return dircmp(a->mode, b->mode);
191 }
192
193 if (mtimeorder)
194 return b->t - a->t;
195 if (icaseorder)
196 return strcasecmp(a->name, b->name);
197 if (versorder)
198 return strverscmp(a->name, b->name);
199 return strcmp(a->name, b->name);
200 }
201
202 void
203 initcolor(void)
204 {
205 int i;
206
207 start_color();
208 use_default_colors();
209 for (i = 1; i < LEN(pairs); i++)
210 init_pair(i, pairs[i].fg, pairs[i].bg);
211 }
212
213 void
214 initcurses(void)
215 {
216 char *term;
217
218 if (initscr() == NULL) {
219 term = getenv("TERM");
220 if (term != NULL)
221 fprintf(stderr, "error opening terminal: %s\n", …
222 else
223 fprintf(stderr, "failed to initialize curses\n");
224 exit(1);
225 }
226 if (usecolor && has_colors())
227 initcolor();
228 cbreak();
229 noecho();
230 nonl();
231 intrflush(stdscr, FALSE);
232 keypad(stdscr, TRUE);
233 curs_set(FALSE); /* Hide cursor */
234 timeout(1000); /* One second */
235 }
236
237 void
238 exitcurses(void)
239 {
240 endwin(); /* Restore terminal */
241 }
242
243 /* Messages show up at the bottom */
244 void
245 info(char *fmt, ...)
246 {
247 char buf[LINE_MAX];
248 va_list ap;
249
250 va_start(ap, fmt);
251 vsnprintf(buf, sizeof(buf), fmt, ap);
252 va_end(ap);
253 move(LINES - 1, 0);
254 printw("%s\n", buf);
255 }
256
257 /* Display warning as a message */
258 void
259 warn(char *fmt, ...)
260 {
261 char buf[LINE_MAX];
262 va_list ap;
263
264 va_start(ap, fmt);
265 vsnprintf(buf, sizeof(buf), fmt, ap);
266 va_end(ap);
267 move(LINES - 1, 0);
268 printw("%s: %s\n", buf, strerror(errno));
269 }
270
271 /* Kill curses and display error before exiting */
272 void
273 fatal(char *fmt, ...)
274 {
275 va_list ap;
276
277 exitcurses();
278 va_start(ap, fmt);
279 vfprintf(stderr, fmt, ap);
280 fprintf(stderr, ": %s\n", strerror(errno));
281 va_end(ap);
282 exit(1);
283 }
284
285 /* Clear the last line */
286 void
287 clearprompt(void)
288 {
289 info("");
290 }
291
292 /* Print prompt on the last line */
293 void
294 printprompt(char *str)
295 {
296 clearprompt();
297 info("%s", str);
298 }
299
300 int
301 xgetch(void)
302 {
303 int c;
304
305 c = getch();
306 if (c == -1)
307 idle++;
308 else
309 idle = 0;
310 return c;
311 }
312
313 /* Returns SEL_* if key is bound and 0 otherwise.
314 * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */
315 int
316 nextsel(char **run, char **env)
317 {
318 int c, i;
319
320 c = xgetch();
321 if (c == 033)
322 c = META(xgetch());
323
324 for (i = 0; i < LEN(bindings); i++)
325 if (c == bindings[i].sym) {
326 *run = bindings[i].run;
327 *env = bindings[i].env;
328 return bindings[i].act;
329 }
330 return 0;
331 }
332
333 char *
334 readln(void)
335 {
336 static char ln[LINE_MAX];
337
338 timeout(-1);
339 echo();
340 curs_set(TRUE);
341 memset(ln, 0, sizeof(ln));
342 wgetnstr(stdscr, ln, sizeof(ln) - 1);
343 noecho();
344 curs_set(FALSE);
345 timeout(1000);
346 return ln[0] ? ln : NULL;
347 }
348
349 int
350 canopendir(char *path)
351 {
352 DIR *dirp;
353
354 dirp = opendir(path);
355 if (dirp == NULL)
356 return 0;
357 closedir(dirp);
358 return 1;
359 }
360
361 char *
362 mkpath(char *dir, char *name, char *out, size_t n)
363 {
364 /* Handle absolute path */
365 if (name[0] == '/') {
366 strlcpy(out, name, n);
367 } else {
368 /* Handle root case */
369 if (strcmp(dir, "/") == 0) {
370 strlcpy(out, "/", n);
371 strlcat(out, name, n);
372 } else {
373 strlcpy(out, dir, n);
374 strlcat(out, "/", n);
375 strlcat(out, name, n);
376 }
377 }
378 return out;
379 }
380
381 void
382 printent(struct entry *ent, int active)
383 {
384 char name[PATH_MAX];
385 unsigned int len = COLS - strlen(CURSR) - 1;
386 char cm = 0;
387 int attr = 0;
388
389 /* Copy name locally */
390 strlcpy(name, ent->name, sizeof(name));
391
392 /* No text wrapping in entries */
393 if (strlen(name) < len)
394 len = strlen(name) + 1;
395
396 if (S_ISDIR(ent->mode)) {
397 cm = '/';
398 attr |= DIR_ATTR;
399 } else if (S_ISLNK(ent->mode)) {
400 cm = '@';
401 attr |= LINK_ATTR;
402 } else if (S_ISSOCK(ent->mode)) {
403 cm = '=';
404 attr |= SOCK_ATTR;
405 } else if (S_ISFIFO(ent->mode)) {
406 cm = '|';
407 attr |= FIFO_ATTR;
408 } else if (ent->mode & S_IXUSR) {
409 cm = '*';
410 attr |= EXEC_ATTR;
411 }
412
413 if (active)
414 attr |= CURSR_ATTR;
415
416 if (cm) {
417 name[len - 1] = cm;
418 name[len] = '\0';
419 }
420
421 attron(attr);
422 printw("%s%s\n", active ? CURSR : EMPTY, name);
423 attroff(attr);
424 }
425
426 int
427 dentfill(char *path, struct entry **dents,
428 int (*filter)(regex_t *, char *), regex_t *re)
429 {
430 char newpath[PATH_MAX];
431 DIR *dirp;
432 struct dirent *dp;
433 struct stat sb;
434 int r, n = 0;
435
436 dirp = opendir(path);
437 if (dirp == NULL)
438 return 0;
439
440 while ((dp = readdir(dirp)) != NULL) {
441 /* Skip self and parent */
442 if (strcmp(dp->d_name, ".") == 0 ||
443 strcmp(dp->d_name, "..") == 0)
444 continue;
445 if (filter(re, dp->d_name) == 0)
446 continue;
447 *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
448 strlcpy((*dents)[n].name, dp->d_name, sizeof((*dents)[n]…
449 /* Get mode flags */
450 mkpath(path, dp->d_name, newpath, sizeof(newpath));
451 r = lstat(newpath, &sb);
452 if (r == -1)
453 fatal("lstat");
454 (*dents)[n].mode = sb.st_mode;
455 (*dents)[n].t = sb.st_mtime;
456 n++;
457 }
458
459 /* Should never be null */
460 r = closedir(dirp);
461 if (r == -1)
462 fatal("closedir");
463 return n;
464 }
465
466 void
467 dentfree(struct entry *dents)
468 {
469 free(dents);
470 }
471
472 /* Return the position of the matching entry or 0 otherwise */
473 int
474 dentfind(struct entry *dents, int n, char *cwd, char *path)
475 {
476 char tmp[PATH_MAX];
477 int i;
478
479 if (path == NULL)
480 return 0;
481 for (i = 0; i < n; i++) {
482 mkpath(cwd, dents[i].name, tmp, sizeof(tmp));
483 DPRINTF_S(path);
484 DPRINTF_S(tmp);
485 if (strcmp(tmp, path) == 0)
486 return i;
487 }
488 return 0;
489 }
490
491 int
492 populate(char *path, char *oldpath, char *fltr)
493 {
494 regex_t re;
495 int r;
496
497 /* Can fail when permissions change while browsing */
498 if (canopendir(path) == 0)
499 return -1;
500
501 /* Search filter */
502 r = setfilter(&re, fltr);
503 if (r != 0)
504 return -1;
505
506 dentfree(dents);
507
508 ndents = 0;
509 dents = NULL;
510
511 ndents = dentfill(path, &dents, visible, &re);
512 freefilter(&re);
513 if (ndents == 0)
514 return 0; /* Empty result */
515
516 qsort(dents, ndents, sizeof(*dents), entrycmp);
517
518 /* Find cur from history */
519 cur = dentfind(dents, ndents, path, oldpath);
520 return 0;
521 }
522
523 void
524 redraw(char *path)
525 {
526 char cwd[PATH_MAX], cwdresolved[PATH_MAX];
527 size_t ncols;
528 int nlines, odd;
529 int i;
530
531 nlines = MIN(LINES - 4, ndents);
532
533 /* Clean screen */
534 erase();
535
536 /* Strip trailing slashes */
537 for (i = strlen(path) - 1; i > 0; i--)
538 if (path[i] == '/')
539 path[i] = '\0';
540 else
541 break;
542
543 DPRINTF_D(cur);
544 DPRINTF_S(path);
545
546 /* No text wrapping in cwd line */
547 ncols = COLS;
548 if (ncols > PATH_MAX)
549 ncols = PATH_MAX;
550 strlcpy(cwd, path, ncols);
551 cwd[ncols - strlen(CWD) - 1] = '\0';
552 realpath(cwd, cwdresolved);
553
554 printw(CWD "%s\n\n", cwdresolved);
555
556 /* Print listing */
557 odd = ISODD(nlines);
558 if (cur < nlines / 2) {
559 for (i = 0; i < nlines; i++)
560 printent(&dents[i], i == cur);
561 } else if (cur >= ndents - nlines / 2) {
562 for (i = ndents - nlines; i < ndents; i++)
563 printent(&dents[i], i == cur);
564 } else {
565 for (i = cur - nlines / 2;
566 i < cur + nlines / 2 + odd; i++)
567 printent(&dents[i], i == cur);
568 }
569 }
570
571 void
572 browse(char *ipath, char *ifilter)
573 {
574 char path[PATH_MAX], oldpath[PATH_MAX], newpath[PATH_MAX];
575 char fltr[LINE_MAX];
576 char *dir, *tmp, *run, *env;
577 struct stat sb;
578 regex_t re;
579 int r, fd;
580
581 strlcpy(path, ipath, sizeof(path));
582 strlcpy(fltr, ifilter, sizeof(fltr));
583 oldpath[0] = '\0';
584 begin:
585 r = populate(path, oldpath, fltr);
586 if (r == -1) {
587 warn("populate");
588 goto nochange;
589 }
590
591 for (;;) {
592 redraw(path);
593 nochange:
594 switch (nextsel(&run, &env)) {
595 case SEL_QUIT:
596 dentfree(dents);
597 return;
598 case SEL_BACK:
599 /* There is no going back */
600 if (strcmp(path, "/") == 0 ||
601 strcmp(path, ".") == 0 ||
602 strchr(path, '/') == NULL)
603 goto nochange;
604 dir = xdirname(path);
605 if (canopendir(dir) == 0) {
606 warn("canopendir");
607 goto nochange;
608 }
609 /* Save history */
610 strlcpy(oldpath, path, sizeof(oldpath));
611 strlcpy(path, dir, sizeof(path));
612 /* Reset filter */
613 strlcpy(fltr, ifilter, sizeof(fltr));
614 goto begin;
615 case SEL_GOIN:
616 /* Cannot descend in empty directories */
617 if (ndents == 0)
618 goto nochange;
619
620 mkpath(path, dents[cur].name, newpath, sizeof(ne…
621 DPRINTF_S(newpath);
622
623 /* Get path info */
624 fd = open(newpath, O_RDONLY | O_NONBLOCK);
625 if (fd == -1) {
626 warn("open");
627 goto nochange;
628 }
629 r = fstat(fd, &sb);
630 if (r == -1) {
631 warn("fstat");
632 close(fd);
633 goto nochange;
634 }
635 close(fd);
636 DPRINTF_U(sb.st_mode);
637
638 switch (sb.st_mode & S_IFMT) {
639 case S_IFDIR:
640 if (canopendir(newpath) == 0) {
641 warn("canopendir");
642 goto nochange;
643 }
644 strlcpy(path, newpath, sizeof(path));
645 /* Reset filter */
646 strlcpy(fltr, ifilter, sizeof(fltr));
647 goto begin;
648 case S_IFREG:
649 exitcurses();
650 run = xgetenv("NOPEN", NOPEN);
651 r = spawnlp(path, run, run, newpath, (vo…
652 initcurses();
653 if (r == -1) {
654 info("Failed to execute plumber"…
655 goto nochange;
656 }
657 continue;
658 default:
659 info("Unsupported file");
660 goto nochange;
661 }
662 case SEL_FLTR:
663 /* Read filter */
664 printprompt("/");
665 tmp = readln();
666 if (tmp == NULL)
667 tmp = ifilter;
668 /* Check and report regex errors */
669 r = setfilter(&re, tmp);
670 if (r != 0)
671 goto nochange;
672 freefilter(&re);
673 strlcpy(fltr, tmp, sizeof(fltr));
674 DPRINTF_S(fltr);
675 /* Save current */
676 if (ndents > 0)
677 mkpath(path, dents[cur].name, oldpath, s…
678 goto begin;
679 case SEL_NEXT:
680 if (cur < ndents - 1)
681 cur++;
682 break;
683 case SEL_PREV:
684 if (cur > 0)
685 cur--;
686 break;
687 case SEL_PGDN:
688 if (cur < ndents - 1)
689 cur += MIN((LINES - 4) / 2, ndents - 1 -…
690 break;
691 case SEL_PGUP:
692 if (cur > 0)
693 cur -= MIN((LINES - 4) / 2, cur);
694 break;
695 case SEL_HOME:
696 cur = 0;
697 break;
698 case SEL_END:
699 cur = ndents - 1;
700 break;
701 case SEL_CD:
702 /* Read target dir */
703 printprompt("chdir: ");
704 tmp = readln();
705 if (tmp == NULL) {
706 clearprompt();
707 goto nochange;
708 }
709 mkpath(path, tmp, newpath, sizeof(newpath));
710 if (canopendir(newpath) == 0) {
711 warn("canopendir");
712 goto nochange;
713 }
714 strlcpy(path, newpath, sizeof(path));
715 /* Reset filter */
716 strlcpy(fltr, ifilter, sizeof(fltr));
717 DPRINTF_S(path);
718 goto begin;
719 case SEL_CDHOME:
720 tmp = getenv("HOME");
721 if (tmp == NULL) {
722 clearprompt();
723 goto nochange;
724 }
725 if (canopendir(tmp) == 0) {
726 warn("canopendir");
727 goto nochange;
728 }
729 strlcpy(path, tmp, sizeof(path));
730 /* Reset filter */
731 strlcpy(fltr, ifilter, sizeof(fltr));
732 DPRINTF_S(path);
733 goto begin;
734 case SEL_TOGGLEDOT:
735 showhidden ^= 1;
736 initfilter(showhidden, &ifilter);
737 strlcpy(fltr, ifilter, sizeof(fltr));
738 goto begin;
739 case SEL_MTIME:
740 mtimeorder = !mtimeorder;
741 /* Save current */
742 if (ndents > 0)
743 mkpath(path, dents[cur].name, oldpath, s…
744 goto begin;
745 case SEL_DSORT:
746 dirorder = !dirorder;
747 /* Save current */
748 if (ndents > 0)
749 mkpath(path, dents[cur].name, oldpath, s…
750 goto begin;
751 case SEL_ICASE:
752 icaseorder = !icaseorder;
753 /* Save current */
754 if (ndents > 0)
755 mkpath(path, dents[cur].name, oldpath, s…
756 goto begin;
757 case SEL_VERS:
758 versorder = !versorder;
759 /* Save current */
760 if (ndents > 0)
761 mkpath(path, dents[cur].name, oldpath, s…
762 goto begin;
763 case SEL_REDRAW:
764 /* Save current */
765 if (ndents > 0)
766 mkpath(path, dents[cur].name, oldpath, s…
767 goto begin;
768 case SEL_RUN:
769 /* Save current */
770 if (ndents > 0)
771 mkpath(path, dents[cur].name, oldpath, s…
772 run = xgetenv(env, run);
773 exitcurses();
774 spawnlp(path, run, run, (void *)0);
775 initcurses();
776 goto begin;
777 case SEL_RUNARG:
778 /* Save current */
779 if (ndents > 0)
780 mkpath(path, dents[cur].name, oldpath, s…
781 run = xgetenv(env, run);
782 exitcurses();
783 spawnlp(path, run, run, dents[cur].name, (void *…
784 initcurses();
785 goto begin;
786 }
787 /* Screensaver */
788 if (idletimeout != 0 && idle == idletimeout) {
789 idle = 0;
790 exitcurses();
791 spawnlp(NULL, idlecmd, idlecmd, (void *)0);
792 initcurses();
793 }
794 }
795 }
796
797 void
798 usage(void)
799 {
800 fprintf(stderr, "usage: %s [-c] [dir]\n", argv0);
801 exit(1);
802 }
803
804 int
805 main(int argc, char *argv[])
806 {
807 char cwd[PATH_MAX], *ipath;
808 char *ifilter;
809
810 ARGBEGIN {
811 case 'c':
812 usecolor = 1;
813 break;
814 default:
815 usage();
816 } ARGEND
817
818 if (argc > 1)
819 usage();
820
821 /* Confirm we are in a terminal */
822 if (!isatty(0) || !isatty(1)) {
823 fprintf(stderr, "stdin or stdout is not a tty\n");
824 exit(1);
825 }
826
827 if (getuid() == 0)
828 showhidden = 1;
829 initfilter(showhidden, &ifilter);
830
831 if (argv[0] != NULL) {
832 ipath = argv[0];
833 } else {
834 ipath = getcwd(cwd, sizeof(cwd));
835 if (ipath == NULL)
836 ipath = "/";
837 }
838
839 signal(SIGINT, SIG_IGN);
840
841 /* Test initial path */
842 if (canopendir(ipath) == 0) {
843 fprintf(stderr, "%s: %s\n", ipath, strerror(errno));
844 exit(1);
845 }
846
847 /* Set locale before curses setup */
848 setlocale(LC_ALL, "");
849 initcurses();
850 browse(ipath, ifilter);
851 exitcurses();
852 exit(0);
853 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.