[ref] listdir, filter, empty dir
[sfm.git] / sfm.c
blobe01b3c46387756debc0b54b6994cd2ba4351a40e
1 /* See LICENSE file for copyright and license details. */
3 #include <dirent.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <grp.h>
7 #include <pwd.h>
8 #include <stdarg.h>
9 #include <stdint.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/resource.h>
14 #include <sys/stat.h>
15 #include <sys/time.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <time.h>
19 #include <unistd.h>
21 #ifdef __linux__
22 #include <sys/inotify.h>
23 #define INOTIFY
24 #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || \
25 defined(__APPLE__)
26 #define KQUEUE
27 #include <sys/event.h>
28 #endif /* filesystem events */
30 #include "termbox.h"
31 #include "util.h"
33 /* macros */
34 #define MAX_P 4095
35 #define MAX_N 255
36 #define MAX_USRI 32
37 #define EVENTS 32
38 #define EV_BUF_LEN (EVENTS * (sizeof(struct inotify_event) + MAX_N + 1))
40 /* enums */
41 enum { AskDel, DAskDel }; /* delete directory */
42 enum { AddHi, NoHi }; /* add highlight in listdir */
43 enum { AllP, PtP }; /* set pane items */
45 /* typedef */
46 typedef struct {
47 char name[MAX_N];
48 gid_t group;
49 mode_t mode;
50 off_t size;
51 time_t td;
52 uid_t user;
53 } Entry;
55 typedef struct {
56 uint16_t fg;
57 uint16_t bg;
58 } Cpair;
60 typedef struct {
61 char dirn[MAX_P]; // dir name cwd
62 Entry *direntr; // dir entries
63 int dirx; // pane cwd x pos
64 size_t dirc; // dir entries sum
65 size_t hdir; // highlighted dir
66 size_t firstrow;
67 Cpair dircol;
68 } Pane;
70 typedef struct {
71 uint32_t ch;
72 char path[MAX_P];
73 } Bookmark;
75 typedef struct {
76 char *soft;
77 const char **ext;
78 size_t len;
79 } Rule;
81 typedef union {
82 uint16_t key; /* one of the TB_KEY_* constants */
83 uint32_t ch; /* unicode character */
84 } Evkey;
86 typedef struct {
87 const Evkey evkey;
88 void (*func)(void);
89 } Key;
91 /* function declarations */
92 static void print_tb(const char*, int, int, uint16_t, uint16_t);
93 static void printf_tb(int, int, Cpair, const char*, ...);
94 static void print_status(Cpair, const char*, ...);
95 static void print_xstatus(char, int);
96 static void print_error(char*);
97 static void print_prompt(char*);
98 static void print_info(void);
99 static void print_row(Pane*, size_t, Cpair);
100 static void clear(int, int, int, uint16_t);
101 static void clear_status(void);
102 static void clear_pane(int);
103 static void add_hi(Pane*, size_t);
104 static void rm_hi(Pane*, size_t);
105 static void float_to_string(float, char*);
106 static int check_dir(char*);
107 static mode_t chech_execf(mode_t);
108 static int sort_name(const void *const, const void *const);
109 static char *get_ext(char*);
110 static int get_fdt(char*, size_t, time_t);
111 static char *get_fgrp(gid_t, size_t);
112 static char *get_finfo(Entry*);
113 static char *get_fperm(mode_t);
114 static char *get_fsize(off_t);
115 static char *get_fullpath(char*, char*);
116 static char *get_fusr(uid_t, size_t);
117 static void get_dirsize(char*, off_t*);
118 static void get_hicol(Cpair*, mode_t);
119 static int deldir(char*, int);
120 static int delent(char *);
121 static int delf(char*);
122 static void calcdir(void);
123 static void crnd(void);
124 static void crnf(void);
125 static void delfd(void);
126 static void mvbk(void);
127 static void mvbtm(void);
128 static void mvdwn(void);
129 static void mvdwns(void);
130 static void mvfor(void);
131 static void mvmid(void);
132 static void mvtop(void);
133 static void mvup(void);
134 static void mvups(void);
135 static void scrdwn(void);
136 static void scrdwns(void);
137 static void scrup(void);
138 static void scrups(void);
139 static int get_usrinput(char*, size_t, char*);
140 static char *frules(char *);
141 static char *getsw(char*);
142 static int opnf(char*);
143 static ssize_t findbm(uint32_t);
144 static void filter(void);
145 static void switch_pane(void);
146 static void quit(void);
147 static void grabkeys(struct tb_event*);
148 static void start_ev(void);
149 static void refresh_pane(void);
150 static int listdir(int, char*);
151 static void t_resize(void);
152 static void set_panes(int);
153 static void draw_frame(void);
154 static void start(void);
156 /* global variables */
157 static Pane pane_r, pane_l, *cpane;
158 static int parent_row = 1; // FIX
159 static size_t scrheight;
160 static char fed[] = "vi";
162 static const uint32_t INOTIFY_MASK = IN_CREATE | IN_DELETE | IN_DELETE_SELF \
163 | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO;
165 /* configuration, allows nested code to access above variables */
166 #include "config.h"
168 /* function implementations */
169 static void
170 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
172 while (*str != '\0') {
173 uint32_t uni = 0;
174 str += tb_utf8_char_to_unicode(&uni, str);
175 tb_change_cell(x, y, uni, fg, bg);
176 x++;
180 static void
181 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
183 char buf[4096];
184 va_list vl;
185 va_start(vl, fmt);
186 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
187 va_end(vl);
188 print_tb(buf, x, y, col.fg, col.bg);
191 static void
192 print_status(Cpair col, const char *fmt, ...)
194 int height;
195 height = tb_height();
197 char buf[256];
198 va_list vl;
199 va_start(vl, fmt);
200 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
201 va_end(vl);
202 clear_status();
203 print_tb(buf, 1, height-1, col.fg, col.bg);
207 static void
208 print_xstatus(char c, int x)
210 int height;
211 uint32_t uni = 0;
212 height = tb_height();
213 (void)tb_utf8_char_to_unicode(&uni, &c);
214 tb_change_cell(x, height-1, uni, cstatus.fg , cstatus.bg);
217 static void
218 print_error(char *errmsg)
220 print_status(cerr, errmsg);
223 static void
224 print_prompt(char *prompt)
226 print_status(cprompt, prompt);
229 static void
230 print_info(void)
232 char *fileinfo;
233 fileinfo = get_finfo(&cpane->direntr[cpane->hdir-1]);
234 print_status(cstatus, "%lu/%lu %s",
235 cpane->hdir, cpane->dirc, fileinfo);
236 free(fileinfo);
239 static void
240 print_row(Pane *pane, size_t entpos, Cpair col)
242 int x, y;
243 char *result;
244 char buf[MAX_P];
245 char lnk_full[MAX_P];
246 int width;
248 width = (tb_width() / 2) - 4;
249 result = pane->direntr[entpos].name;
250 x = pane->dirx;
251 y = entpos - cpane->firstrow + 1;
253 if (S_ISLNK(pane->direntr[entpos].mode) &&
254 realpath(pane->direntr[entpos].name, buf) != NULL) {
255 strncpy(lnk_full, pane->direntr[entpos].name, MAX_N);
256 strcat(lnk_full, " -> ");
257 strncat(lnk_full, buf, MAX_N);
258 result = lnk_full;
261 printf_tb(x, y, col, "%*.*s", ~width, width, result);
264 static void
265 clear(int sx, int ex, int y, uint16_t bg)
267 /* clear line from to */
268 /* x = line number vertical */
269 /* y = column number horizontal */
270 int i;
271 for (i = sx; i < ex; i++) {
272 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
277 static void
278 clear_status(void)
280 int width, height;
281 width = tb_width();
282 height = tb_height();
283 clear(1, width-1, height-1, cstatus.bg);
286 static void
287 clear_pane(int pane)
289 int i, x, ex, y, width, height;
290 width = tb_width();
291 height = tb_height();
292 x = 0, y = 0, i = 0, ex = 0;
294 if (pane == 2) {
295 x = 2;
296 ex = (width/2) - 1;
297 } else if (pane == (width/2) + 2) {
298 x = (width/2) + 1;
299 ex = width -1;
302 while (i < height-2) {
303 clear(x, ex, y, TB_DEFAULT);
304 i++;
305 y++;
307 /* draw top line */
308 for (y = x; y < ex ; ++y) {
309 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
314 static void
315 add_hi(Pane *pane, size_t entpos)
317 Cpair col;
318 get_hicol(&col, pane->direntr[entpos].mode);
319 col.fg |= TB_REVERSE|TB_BOLD;
320 col.bg |= TB_REVERSE;
321 print_row(pane, entpos, col);
324 static void
325 rm_hi(Pane *pane, size_t entpos)
327 Cpair col;
328 get_hicol(&col, pane->direntr[entpos].mode);
329 print_row(pane, entpos, col);
332 static void
333 float_to_string(float f, char *r)
335 int length, length2, i, number, position, tenth; /* length is size of decimal part, length2 is size of tenth part */
336 float number2;
338 f = (float)(int)(f * 10) / 10;
340 number2 = f;
341 number = (int)f;
342 length2 = 0;
343 tenth = 1;
345 /* Calculate length2 tenth part */
346 while ((number2 - (float)number) != 0.0 && !((number2 - (float)number) < 0.0))
348 tenth *= 10.0;
349 number2 = f * (float)tenth;
350 number = (int)number2;
352 length2++;
355 /* Calculate length decimal part */
356 for (length = (f > 1.0) ? 0 : 1; f > 1.0; length++)
357 f /= 10.0;
359 position = length;
360 length = length + 1 + length2;
361 number = (int)number2;
363 if (length2 > 0)
365 for (i = length; i >= 0; i--)
367 if (i == (length))
368 r[i] = '\0';
369 else if (i == (position))
370 r[i] = '.';
371 else
373 r[i] = (char)(number % 10) + '0';
374 number /= 10;
378 else
380 length--;
381 for (i = length; i >= 0; i--)
383 if (i == (length))
384 r[i] = '\0';
385 else
387 r[i] = (char)(number % 10) + '0';
388 number /= 10;
394 static int
395 check_dir(char *path)
397 DIR *dir;
398 dir = opendir(path);
400 if (dir == NULL) {
401 if (errno == ENOTDIR) {
402 return 1;
403 } else {
404 return -1;
408 if (closedir(dir) < 0)
409 return -1;
411 return 0;
414 static mode_t
415 chech_execf(mode_t mode)
417 if (S_ISREG(mode))
418 return (((S_IXUSR | S_IXGRP | S_IXOTH) & mode));
419 return 0;
422 static int
423 sort_name(const void *const A, const void *const B)
425 int result;
426 mode_t data1 = (*(Entry*) A).mode;
427 mode_t data2 = (*(Entry*) B).mode;
429 if (data1 < data2) {
430 return -1;
431 } else if (data1 == data2) {
432 result = strcmp((*(Entry*) A).name,(*(Entry*) B).name);
433 return result;
434 } else {
435 return 1;
439 static char *
440 get_ext(char *str)
442 char *ext;
443 char dot;
444 size_t counter, len, i;
446 dot = '.';
447 counter = 0;
448 len = strlen(str);
450 for (i = len-1; i > 0; i--) {
451 if (str[i] == dot) {
452 break;
453 } else {
454 counter++;
458 ext = ecalloc(counter+1, sizeof(char));
459 strncpy(ext, &str[len-counter], counter);
460 return ext;
463 static int
464 get_fdt(char *result, size_t reslen, time_t status)
466 struct tm lt;
467 localtime_r(&status, &lt);
468 return strftime(result, reslen, dtfmt, &lt);
471 static char *
472 get_fgrp(gid_t status, size_t len)
474 char *result;
475 struct group *gr;
477 result = ecalloc(len, sizeof(char));
478 gr = getgrgid(status);
479 if (gr == NULL)
480 (void)snprintf(result, len-1, "%d", (int)status);
481 else
482 strncpy(result, gr->gr_name, len-1);
484 return result;
487 static char *
488 get_finfo(Entry *cursor)
490 char *sz, *rst, *ur, *gr, *td, *prm;
491 size_t szlen, prmlen, urlen, grlen, tdlen, rstlen;
493 szlen = 9;
494 prmlen = 11;
495 urlen = grlen = tdlen = 32;
496 rstlen = szlen + prmlen + urlen + grlen + tdlen;
497 rst = ecalloc(rstlen, sizeof(char));
499 if (show_perm == 1) {
500 prm = get_fperm(cursor->mode);
501 strncpy(rst, prm, prmlen);
502 strcat(rst, " ");
503 free(prm);
506 if (show_ug == 1) {
507 ur = get_fusr(cursor->user, urlen);
508 gr = get_fgrp(cursor->group, grlen);
509 strncat(rst, ur, urlen);
510 strcat(rst, ":");
511 strncat(rst, gr, grlen);
512 strcat(rst, " ");
513 free(ur);
514 free(gr);
517 if (show_dt == 1) {
518 td = ecalloc(tdlen, sizeof(char));
519 if (get_fdt(td, tdlen, cursor->td) > 0) {
520 strncat(rst, td, tdlen);
521 strcat(rst, " ");
523 free(td);
526 if (show_size == 1 && S_ISREG(cursor->mode)) {
527 sz = get_fsize(cursor->size);
528 strncat(rst, sz, szlen);
529 free(sz);
532 return rst;
535 static char *
536 get_fperm(mode_t mode)
538 char *buf;
539 size_t i;
541 const char chars[] = "rwxrwxrwx";
542 buf = ecalloc((size_t)11, sizeof(char));
544 if (S_ISDIR(mode))
545 buf[0] = 'd';
546 else if (S_ISREG(mode))
547 buf[0] = '-';
548 else if (S_ISLNK(mode))
549 buf[0] = 'l';
550 else if (S_ISBLK(mode))
551 buf[0] = 'b';
552 else if (S_ISCHR(mode))
553 buf[0] = 'c';
554 else if (S_ISFIFO(mode))
555 buf[0] = 'p';
556 else if (S_ISSOCK(mode))
557 buf[0] = 's';
558 else
559 buf[0] = '?';
561 for (i = 1; i < 10; i++) {
562 buf[i] = (mode & (1 << (9-i))) ? chars[i-1] : '-';
564 buf[10] = '\0';
566 return buf;
569 static char *
570 get_fsize(off_t size)
573 /* need to be freed */
574 char *Rsize;
575 float lsize;
576 int counter;
577 counter = 0;
579 Rsize = ecalloc((size_t)10, sizeof(char));
580 lsize = (float)size;
582 while (lsize >= 1000.0)
584 lsize /= 1024.0;
585 ++counter;
588 float_to_string(lsize, Rsize);
590 switch (counter)
592 case 0:
593 strcat(Rsize, "B");
594 break;
595 case 1:
596 strcat(Rsize, "K");
597 break;
598 case 2:
599 strcat(Rsize, "M");
600 break;
601 case 3:
602 strcat(Rsize, "G");
603 break;
604 case 4:
605 strcat(Rsize, "T");
606 break;
609 return Rsize;
612 static char *
613 get_fullpath(char *first, char *second)
615 char *full_path;
616 size_t full_path_len;
618 full_path_len = strlen(first) + strlen(second) + 2;
619 full_path = ecalloc(full_path_len, sizeof(char));
621 if (strcmp(first, "/") == 0) {
622 (void)snprintf(full_path, full_path_len, "/%s", second);
624 } else {
625 (void)snprintf(
626 full_path, full_path_len, "%s/%s", first, second);
629 return full_path;
632 static char *
633 get_fusr(uid_t status, size_t len)
635 char *result;
636 struct passwd *pw;
638 result = ecalloc(len, sizeof(char));
639 pw = getpwuid(status);
640 if (pw == NULL)
641 (void)snprintf(result, len-1, "%d", (int)status);
642 else
643 strncpy(result, pw->pw_name, len-1);
645 return result;
648 static void
649 get_dirsize(char *fullpath, off_t *fullsize)
651 DIR *dir;
652 char *ent_full;
653 mode_t mode;
654 struct dirent *entry;
655 struct stat status;
657 dir = opendir(fullpath);
658 if (dir == NULL)
660 return;
663 while ((entry = readdir(dir)) != 0)
665 if ((strcmp(entry->d_name, ".") == 0 ||
666 strcmp(entry->d_name, "..") == 0))
667 continue;
669 ent_full = get_fullpath(fullpath, entry->d_name);
670 if (lstat(ent_full, &status) == 0) {
671 mode = status.st_mode;
672 if (S_ISDIR(mode)) {
673 get_dirsize(ent_full, fullsize);
674 free(ent_full);
675 } else {
677 *fullsize += status.st_size;
678 free(ent_full);
683 closedir(dir);
684 clear_status();
687 static void
688 get_hicol(Cpair *col, mode_t mode)
690 *col = cfile;
691 if (S_ISDIR(mode))
692 *col = cdir;
693 else if (S_ISLNK(mode))
694 *col = cother;
695 else if (chech_execf(mode) > 0)
696 *col = cexec;
699 static int
700 deldir(char *fullpath, int delchoice)
702 if (delchoice == (int)AskDel) {
703 char *confirmation;
704 confirmation = ecalloc((size_t)2, sizeof(char));
705 if ((get_usrinput(
706 confirmation, (size_t)2,"delete directory (Y) ?") < 0) ||
707 (strcmp(confirmation, "Y") != 0)) {
708 free(confirmation);
709 return 1; /* canceled by user or wrong confirmation */
711 free(confirmation);
714 if (rmdir(fullpath) == 0)
715 return 0; /* empty directory */
717 DIR *dir;
718 char *ent_full;
719 mode_t mode;
720 struct dirent *entry;
721 struct stat status;
723 dir = opendir(fullpath);
724 if (dir == NULL) {
725 return -1;
728 while ((entry = readdir(dir)) != 0) {
729 if ((strcmp(entry->d_name, ".") == 0 ||
730 strcmp(entry->d_name, "..") == 0))
731 continue;
733 ent_full = get_fullpath(fullpath, entry->d_name);
734 if (lstat(ent_full, &status) == 0) {
735 mode = status.st_mode;
736 if (S_ISDIR(mode)) {
737 if (deldir(ent_full, (int)DAskDel) < 0) {
738 free(ent_full);
739 return -1;
741 } else if (S_ISREG(mode)) {
742 if (unlink(ent_full) < 0) {
743 free(ent_full);
744 return -1;
748 free(ent_full);
751 print_status(cstatus, "gotit");
752 if (closedir(dir) < 0)
753 return -1;
755 return rmdir(fullpath); /* directory after delete all entries */
758 static int
759 delent(char *fullpath)
761 struct stat status;
762 mode_t mode;
764 if (lstat(fullpath, &status) < 0)
765 return -1;
767 mode = status.st_mode;
768 if (S_ISDIR(mode)) {
769 return deldir(fullpath, (int)AskDel);
770 } else {
771 return delf(fullpath);
776 static int
777 delf(char *fullpath)
779 char *confirmation;
780 confirmation = ecalloc((size_t)2, sizeof(char));
782 if ((get_usrinput(confirmation, (size_t)2, "delete file (Y) ?") < 0) ||
783 (strcmp(confirmation, "Y") != 0)) {
784 free(confirmation);
785 return 1; /* canceled by user or wrong confirmation */
788 free(confirmation);
789 return unlink(fullpath);
792 static void
793 calcdir(void)
795 off_t *fullsize;
796 char *csize;
797 char *result;
799 if (S_ISDIR(cpane->direntr[cpane->hdir-1].mode)) {
800 fullsize = ecalloc(50, sizeof(off_t));
801 get_dirsize(cpane->direntr[cpane->hdir-1].name, fullsize);
802 csize = get_fsize(*fullsize);
803 result = get_finfo(&cpane->direntr[cpane->hdir-1]);
805 clear_status();
806 print_status(cstatus, "%lu/%lu %s%s",
807 cpane->hdir, cpane->dirc, result, csize);
808 free(csize);
809 free(fullsize);
810 free(result);
814 static void
815 crnd(void)
817 char *user_input, *path;
818 size_t pathlen;
820 user_input = ecalloc(MAX_USRI, sizeof(char));
821 if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
822 free(user_input);
823 return;
826 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
827 path = ecalloc(pathlen, sizeof(char));
828 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
829 free(user_input);
830 free(path);
831 return;
834 if (mkdir(path, ndir_perm) < 0)
835 print_error(strerror(errno));
836 else
837 if (listdir(AddHi, NULL) < 0)
838 print_error(strerror(errno));
840 free(user_input);
841 free(path);
844 static void
845 crnf(void)
847 char *user_input, *path;
848 size_t pathlen;
849 int rf;
851 user_input = ecalloc(MAX_USRI, sizeof(char));
852 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
853 free(user_input);
854 return;
857 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
858 path = ecalloc(pathlen, sizeof(char));
859 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
860 free(user_input);
861 free(path);
862 return;
865 rf = open(path, O_CREAT|O_EXCL, nf_perm);
867 if (rf < 0) {
868 print_error(strerror(errno));
869 } else {
870 if (close(rf) < 0)
871 print_error(strerror(errno));
872 else
873 if (listdir(AddHi, NULL) < 0)
874 print_error(strerror(errno));
877 free(user_input);
878 free(path);
881 static void
882 delfd(void)
884 switch (delent(cpane->direntr[cpane->hdir-1].name)) {
885 case -1:
886 print_error(strerror(errno));
887 break;
888 case 0:
889 if (BETWEEN(cpane->hdir-1, 1, cpane->dirc)) /* last entry */
890 cpane->hdir--;
891 if (listdir(AddHi, NULL) < 0)
892 print_error(strerror(errno));
893 break;
897 static void
898 mvbk(void)
900 chdir("..");
901 getcwd(cpane->dirn, MAX_P);
902 cpane->firstrow = 0;
903 cpane->hdir = parent_row;
904 if (listdir(AddHi, NULL) < 0)
905 print_error(strerror(errno));
906 parent_row = 1;
909 static void
910 mvbtm(void)
912 if (cpane->dirc < 1)
913 return;
914 if (cpane->dirc > scrheight) {
915 clear_pane(cpane->dirx);
916 rm_hi(cpane, cpane->hdir-1);
917 cpane->hdir = cpane->dirc;
918 cpane->firstrow = cpane->dirc - scrheight + 1;
919 refresh_pane();
920 add_hi(cpane, cpane->hdir-1);
921 } else {
922 rm_hi(cpane, cpane->hdir-1);
923 cpane->hdir = cpane->dirc;
924 add_hi(cpane, cpane->hdir-1);
926 print_info();
929 static void
930 mvdwn(void)
932 if (cpane->dirc < 1)
933 return;
934 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
935 rm_hi(cpane, cpane->hdir-1);
936 cpane->hdir++;
937 add_hi(cpane, cpane->hdir-1);
938 } else {
939 mvdwns(); /* scroll */
941 print_info();
944 static void
945 mvdwns(void)
947 size_t real;
948 real= cpane->hdir - 1 - cpane->firstrow;
950 if (real > scrheight - 3 - scrsp && cpane->hdir + scrsp < cpane->dirc) {
951 cpane->firstrow++;
952 clear_pane(cpane->dirx);
953 rm_hi(cpane, cpane->hdir-1);
954 cpane->hdir++;
955 refresh_pane();
956 add_hi(cpane, cpane->hdir-1);
957 } else if (cpane->hdir < cpane->dirc) {
958 rm_hi(cpane, cpane->hdir-1);
959 cpane->hdir++;
960 add_hi(cpane, cpane->hdir-1);
964 static void
965 mvfor(void)
967 if (cpane->dirc < 1)
968 return;
969 int s;
971 switch (check_dir(cpane->direntr[cpane->hdir-1].name)) {
972 case 0:
973 chdir(cpane->direntr[cpane->hdir-1].name);
974 getcwd(cpane->dirn, MAX_P);
975 parent_row = (int)cpane->hdir;
976 cpane->hdir = 1;
977 cpane->firstrow = 0;
978 if (listdir(AddHi, NULL) < 0)
979 print_error(strerror(errno));
980 break;
981 case 1: /* not a directory open file */
982 tb_shutdown();
983 s = opnf(cpane->direntr[cpane->hdir-1].name);
984 if (tb_init() != 0)
985 die("tb_init");
986 t_resize();
987 if (s < 0)
988 print_error("process failed non-zero exit");
989 break;
990 case -1: /* failed to open directory */
991 print_error(strerror(errno));
995 static void
996 mvmid(void)
998 if (cpane->dirc < 1)
999 return;
1000 rm_hi(cpane, cpane->hdir - 1);
1001 cpane->hdir = (scrheight / 2) + cpane->firstrow;
1002 add_hi(cpane, cpane->hdir - 1);
1003 print_info();
1006 static void
1007 mvtop(void)
1009 if (cpane->dirc < 1)
1010 return;
1011 if (cpane->dirc > scrheight) {
1012 clear_pane(cpane->dirx);
1013 rm_hi(cpane, cpane->hdir-1);
1014 cpane->hdir = 1;
1015 cpane->firstrow = 0;
1016 refresh_pane();
1017 add_hi(cpane, cpane->hdir-1);
1018 } else {
1019 rm_hi(cpane, cpane->hdir-1);
1020 cpane->hdir = 1;
1021 add_hi(cpane, cpane->hdir-1);
1022 print_info();
1026 static void
1027 mvup(void)
1029 if (cpane->dirc < 1)
1030 return;
1031 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1032 rm_hi(cpane, cpane->hdir-1);
1033 cpane->hdir--;
1034 add_hi(cpane, cpane->hdir-1);
1035 } else {
1036 mvups(); /* scroll */
1038 print_info();
1041 static void
1042 mvups(void)
1044 size_t real;
1045 real= cpane->hdir - 1 - cpane->firstrow;
1047 if (cpane->firstrow > 0 && real < 1 + scrsp) {
1048 cpane->firstrow--;
1049 clear_pane(cpane->dirx);
1050 rm_hi(cpane, cpane->hdir-1);
1051 cpane->hdir--;
1052 refresh_pane();
1053 add_hi(cpane, cpane->hdir-1);
1054 } else if (cpane->hdir > 1) {
1055 rm_hi(cpane, cpane->hdir-1);
1056 cpane->hdir--;
1057 add_hi(cpane, cpane->hdir-1);
1061 static void
1062 scrdwn(void)
1064 if (cpane->dirc < 1)
1065 return;
1066 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
1067 if (cpane->hdir < cpane->dirc - scrmv) {
1068 rm_hi(cpane, cpane->hdir-1);
1069 cpane->hdir += scrmv;
1070 add_hi(cpane, cpane->hdir-1);
1071 } else {
1072 mvbtm();
1074 } else {
1075 scrdwns();
1077 print_info();
1080 static void
1081 scrdwns(void)
1083 size_t real;
1084 int dynmv;
1085 real = cpane->hdir - cpane->firstrow;
1086 dynmv = MIN(cpane->dirc - cpane->hdir - cpane->firstrow , scrmv);
1088 if (real + scrmv + 1 > scrheight &&
1089 cpane->hdir + scrsp + scrmv < cpane->dirc) { /* scroll */
1090 cpane->firstrow += dynmv;
1091 clear_pane(cpane->dirx);
1092 rm_hi(cpane, cpane->hdir-1);
1093 cpane->hdir += scrmv;
1094 refresh_pane();
1095 add_hi(cpane, cpane->hdir-1);
1096 } else {
1097 if (cpane->hdir < cpane->dirc - scrmv) {
1098 rm_hi(cpane, cpane->hdir-1);
1099 cpane->hdir += scrmv;
1100 add_hi(cpane, cpane->hdir-1);
1101 } else {
1102 mvbtm();
1107 static void
1108 scrup(void)
1110 if (cpane->dirc < 1)
1111 return;
1112 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1113 if (cpane->hdir > scrmv) {
1114 rm_hi(cpane, cpane->hdir-1);
1115 cpane->hdir = cpane->hdir - scrmv;
1116 add_hi(cpane, cpane->hdir-1);
1117 print_info();
1118 } else {
1119 mvtop();
1121 } else {
1122 scrups();
1126 static void
1127 scrups(void)
1129 size_t real;
1130 int dynmv;
1131 real = cpane->hdir - cpane->firstrow;
1132 dynmv = MIN(cpane->firstrow , scrmv);
1134 if (cpane->firstrow > 0 && real < scrmv + scrsp) {
1135 cpane->firstrow -= dynmv;
1136 clear_pane(cpane->dirx);
1137 rm_hi(cpane, cpane->hdir-1);
1138 cpane->hdir -= scrmv;
1139 refresh_pane();
1140 add_hi(cpane, cpane->hdir-1);
1141 } else {
1142 if (cpane->hdir > scrmv + 1) {
1143 rm_hi(cpane, cpane->hdir-1);
1144 cpane->hdir -= scrmv;
1145 add_hi(cpane, cpane->hdir-1);
1146 } else {
1147 mvtop();
1152 static int
1153 get_usrinput(char *out, size_t sout, char *prompt)
1155 int height = tb_height();
1156 size_t startat;
1157 struct tb_event fev;
1158 size_t counter = (size_t)1;
1159 char empty = ' ';
1160 int x = 0;
1162 clear_status();
1163 startat = strlen(prompt) + 1;
1164 print_prompt(prompt);
1165 tb_set_cursor((int)(startat + 1), height-1);
1166 tb_present();
1168 while (tb_poll_event(&fev) != 0) {
1169 switch (fev.type) {
1170 case TB_EVENT_KEY:
1171 if (fev.key == (uint16_t)TB_KEY_ESC) {
1172 tb_set_cursor(-1, -1);
1173 clear_status();
1174 return -1;
1177 if (fev.key == (uint16_t)TB_KEY_BACKSPACE ||
1178 fev.key == (uint16_t)TB_KEY_BACKSPACE2) {
1179 if (BETWEEN(counter, (size_t)2, sout)) {
1180 out[x-1] = '\0';
1181 counter--;
1182 x--;
1183 print_xstatus(empty, startat + counter);
1184 tb_set_cursor(
1185 (int)startat + counter, height - 1);
1188 } else if (fev.key == (uint16_t)TB_KEY_ENTER) {
1189 tb_set_cursor(-1, -1);
1190 out[counter-1] = '\0';
1191 return 0;
1193 } else {
1194 if (counter < sout) {
1195 print_xstatus((char)fev.ch, (int)(startat+counter));
1196 out[x] = (char)fev.ch;
1197 tb_set_cursor((int)(startat + counter + 1),height-1);
1198 counter++;
1199 x++;
1203 tb_present();
1204 break;
1206 default:
1207 return -1;
1211 return -1;
1215 static char *
1216 frules(char *ex)
1218 size_t c, d;
1220 for (c = 0; c < LEN(rules); c++)
1221 for (d = 0; d < rules[c].len; d++)
1222 if (strcmp(rules[c].ext[d], ex) == 0)
1223 return rules[c].soft;
1224 return NULL;
1227 static char *
1228 getsw(char *fn)
1230 char *ed, *ex, *sw;
1232 ed = getenv("EDITOR");
1233 if (ed == NULL)
1234 ed = fed;
1235 ex = get_ext(fn);
1236 sw = frules(ex);
1237 if (sw == NULL)
1238 sw = ed;
1239 free(ex);
1240 return sw;
1243 static int
1244 opnf(char *fn)
1246 char *sw;
1247 int ws;
1248 pid_t pid, r;
1250 sw = getsw(fn);
1251 char *filex[] = {sw, fn, NULL};
1252 pid = fork();
1254 switch (pid) {
1255 case -1:
1256 return -1;
1257 case 0:
1258 execvp(filex[0], filex);
1259 exit(EXIT_SUCCESS);
1260 default:
1261 while ((r = waitpid(pid, &ws, 0)) == -1 && errno == EINTR)
1262 continue;
1263 if (r == -1)
1264 return -1;
1265 if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
1266 return -1;
1268 return 0;
1271 static ssize_t
1272 findbm(uint32_t event)
1274 ssize_t i;
1276 for (i = 0; i < (ssize_t)LEN(bmarks); i++) {
1277 if (event == bmarks[i].ch) {
1278 if (check_dir(bmarks[i].path) != 0) {
1279 print_error(strerror(errno));
1280 return -1;
1282 return i;
1285 return -1;
1288 static void
1289 filter(void)
1291 if (cpane->dirc < 1)
1292 return;
1293 char *user_input;
1294 user_input = ecalloc(MAX_USRI, sizeof(char));
1295 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1296 free(user_input);
1297 return;
1299 if (listdir(AddHi, user_input) < 0)
1300 print_error("no match");
1301 free(user_input);
1304 static void
1305 switch_pane(void)
1307 if (cpane->dirc > 0)
1308 rm_hi(cpane, cpane->hdir-1);
1309 if (cpane == &pane_l)
1310 cpane = &pane_r;
1311 else if (cpane == &pane_r)
1312 cpane = &pane_l;
1313 chdir(cpane->dirn);
1314 if (cpane->dirc > 0) {
1315 add_hi(cpane, cpane->hdir-1);
1316 print_info();
1317 } else {
1318 clear_status();
1322 static void
1323 quit(void)
1325 free(pane_l.direntr);
1326 free(pane_r.direntr);
1327 tb_shutdown();
1328 exit(EXIT_SUCCESS);
1331 static void
1332 grabkeys(struct tb_event *event)
1334 size_t i;
1335 ssize_t b;
1337 for (i = 0; i < LEN(keys); i++) {
1338 if (event->ch != 0) {
1339 if (event->ch == keys[i].evkey.ch) {
1340 keys[i].func();
1341 return;
1343 } else if (event->key != 0) {
1344 if (event->key == keys[i].evkey.key) {
1345 keys[i].func();
1346 return;
1351 /* bookmarks */
1352 b = findbm(event->ch);
1353 if (b < 0)
1354 return;
1355 strcpy(cpane->dirn, bmarks[b].path);
1356 cpane->firstrow = 0;
1357 cpane->hdir = 1;
1358 if (listdir(AddHi, NULL) < 0)
1359 print_error(strerror(errno));
1362 static void
1363 start_ev(void)
1365 struct tb_event ev;
1367 while (tb_poll_event(&ev) != 0) {
1368 switch (ev.type) {
1369 case TB_EVENT_KEY:
1370 grabkeys(&ev);
1371 tb_present();
1372 break;
1373 case TB_EVENT_RESIZE:
1374 t_resize();
1375 break;
1376 default:
1377 break;
1380 tb_shutdown();
1383 static void
1384 refresh_pane(void)
1386 size_t y, dyn_max, start_from;
1387 int width;
1388 width = (tb_width() / 2) - 4;
1389 Cpair col;
1391 y = 1;
1392 start_from = cpane->firstrow;
1393 dyn_max = MIN(cpane->dirc, (scrheight - 1) + cpane->firstrow);
1395 /* print each entry in directory */
1396 while (start_from < dyn_max) {
1397 get_hicol(&col, cpane->direntr[start_from].mode);
1398 print_row(cpane, start_from, col);
1399 start_from++;
1400 y++;
1403 print_info();
1405 /* print current directory title */
1406 cpane->dircol.fg |= TB_BOLD;
1407 printf_tb(cpane->dirx, 0, cpane->dircol," %.*s ", width, cpane->dirn);
1410 static int
1411 listdir(int hi, char *filter)
1413 DIR *dir;
1414 struct dirent *entry;
1415 struct stat status;
1416 int width;
1417 size_t i;
1418 int filtercount = 0;
1419 size_t oldc = cpane->dirc;
1421 width = (tb_width() / 2) - 4;
1422 cpane->dirc = 0;
1423 i = 0;
1425 if (chdir(cpane->dirn) < 0)
1426 return -1;
1428 dir = opendir(cpane->dirn);
1429 if (dir == NULL)
1430 return -1;
1432 /* get content and filter sum */
1433 while ((entry = readdir(dir)) != 0) {
1434 if (filter != NULL) {
1435 if (strstr(entry->d_name, filter) != NULL)
1436 filtercount++;
1437 } else { /* no filter */
1438 cpane->dirc++;
1442 if (filter == NULL) {
1443 clear_pane(cpane->dirx);
1444 cpane->dirc -=2;
1447 if (filter != NULL) {
1448 if (filtercount > 0) {
1449 cpane->dirc -=2;
1450 cpane->dirc = filtercount;
1451 clear_pane(cpane->dirx);
1452 cpane->hdir = 1;
1453 } else if (filtercount == 0) {
1454 if (closedir(dir) < 0)
1455 return -1;
1456 cpane->dirc = oldc;
1457 return -1;
1461 /* print current directory title */
1462 cpane->dircol.fg |= TB_BOLD;
1463 printf_tb(cpane->dirx, 0, cpane->dircol," %.*s ", width, cpane->dirn);
1465 /* empty directory */
1466 if (cpane->dirc == 0) {
1467 clear_status();
1468 if (closedir(dir) < 0)
1469 return -1;
1470 return 0;
1473 rewinddir(dir); /* reset position */
1475 /* create array of entries */
1476 i = 0;
1477 cpane->direntr = erealloc(cpane->direntr, cpane->dirc * sizeof(Entry));
1478 while ((entry = readdir(dir)) != 0) {
1479 if ((strcmp(entry->d_name, ".") == 0 ||
1480 strcmp(entry->d_name, "..") == 0))
1481 continue;
1483 /* list found filter */
1484 if (filter != NULL) {
1485 if (strstr(entry->d_name, filter) != NULL) {
1486 strcpy(cpane->direntr[i].name, entry->d_name);
1487 if (lstat(entry->d_name, &status) == 0) {
1488 cpane->direntr[i].size = status.st_size;
1489 cpane->direntr[i].mode = status.st_mode;
1490 cpane->direntr[i].group = status.st_gid;
1491 cpane->direntr[i].user = status.st_uid;
1492 cpane->direntr[i].td = status.st_mtime;
1494 i++;
1497 } else {
1498 strcpy(cpane->direntr[i].name, entry->d_name);
1499 if (lstat(entry->d_name, &status) == 0) {
1500 cpane->direntr[i].size = status.st_size;
1501 cpane->direntr[i].mode = status.st_mode;
1502 cpane->direntr[i].group = status.st_gid;
1503 cpane->direntr[i].user = status.st_uid;
1504 cpane->direntr[i].td = status.st_mtime;
1506 i++;
1510 cpane->dirc = i;
1511 qsort(cpane->direntr, cpane->dirc, sizeof(Entry), sort_name);
1512 refresh_pane();
1514 if (hi == AddHi)
1515 add_hi(cpane, cpane->hdir-1);
1517 if (closedir(dir) < 0)
1518 return -1;
1519 return 0;
1522 static void
1523 t_resize(void)
1525 /* TODO need refactoring */
1526 tb_clear();
1527 draw_frame();
1528 set_panes(PtP);
1530 if (cpane == &pane_l) {
1531 chdir(pane_r.dirn);
1532 cpane = &pane_r;
1533 refresh_pane();
1534 chdir(pane_l.dirn);
1535 cpane = &pane_l;
1536 refresh_pane();
1537 add_hi(&pane_l, pane_l.hdir-1);
1538 } else if (cpane == &pane_r) {
1539 chdir(pane_l.dirn);
1540 cpane = &pane_l;
1541 refresh_pane();
1542 chdir(pane_r.dirn);
1543 cpane = &pane_r;
1544 refresh_pane();
1545 add_hi(&pane_r, pane_r.hdir-1);
1548 tb_present();
1551 static void
1552 set_panes(int paneitem)
1554 int width;
1555 char *home;
1556 char cwd[MAX_P];
1557 scrheight = tb_height() - 2;
1559 home = getenv("HOME");
1560 width = tb_width();
1561 if ((getcwd(cwd, sizeof(cwd)) == NULL))
1562 return;
1563 if (home == NULL)
1564 home = "/";
1566 pane_l.dirx = 2;
1567 pane_l.dircol = cpanell;
1568 pane_l.firstrow = 0;
1569 if (paneitem == AllP) {
1570 pane_l.direntr = ecalloc(0, sizeof(Entry));
1571 strcpy(pane_l.dirn, cwd);
1572 pane_l.hdir = 1;
1575 pane_r.dirx = (width / 2) + 2;
1576 pane_r.dircol = cpanelr;
1577 pane_r.firstrow = 0;
1578 if (paneitem == AllP) {
1579 pane_r.direntr = ecalloc(0, sizeof(Entry));
1580 strcpy(pane_r.dirn, home);
1581 pane_r.hdir = 1;
1585 static void
1586 draw_frame(void)
1588 int height, width, i;
1590 width = tb_width();
1591 height = tb_height();
1593 /* 2 horizontal lines */
1594 for (i = 1; i < width-1 ; ++i) {
1595 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1596 tb_change_cell(i, height-2, u_hl, cframe.fg, cframe.bg);
1599 /* 3 vertical lines */
1600 for (i = 1; i < height-1 ; ++i) {
1601 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1602 tb_change_cell((width-1)/2, i-1, u_vl, cframe.fg, cframe.bg);
1603 tb_change_cell(width-1, i, u_vl, cframe.fg, cframe.bg);
1606 /* 4 corners */
1607 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1608 tb_change_cell(width-1, 0, u_cne, cframe.fg, cframe.bg);
1609 tb_change_cell(0, height-2, u_csw, cframe.fg, cframe.bg);
1610 tb_change_cell(width-1, height-2, u_cse, cframe.fg, cframe.bg);
1612 /* 2 middel top and bottom */
1613 tb_change_cell((width-1)/2, 0, u_mn, cframe.fg, cframe.bg);
1614 tb_change_cell((width-1)/2, height-2, u_ms, cframe.fg, cframe.bg);
1617 static void
1618 start(void)
1620 if (tb_init()!= 0)
1621 die("tb_init");
1622 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
1623 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
1624 die("output error");
1626 draw_frame();
1627 set_panes(AllP);
1628 cpane = &pane_r;
1629 if (listdir(NoHi, NULL) < 0)
1630 print_error(strerror(errno));
1631 cpane = &pane_l;
1632 if (listdir(AddHi, NULL) < 0)
1633 print_error(strerror(errno));
1634 tb_present();
1635 start_ev();
1639 main(int argc, char *argv[])
1641 #ifdef __OpenBSD__
1642 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath", NULL) == -1)
1643 die("pledge");
1644 #endif /* __OpenBSD__ */
1645 if (argc == 1) {
1646 start();
1647 } else if (
1648 argc == 2 && strlen(argv[1]) == (size_t)2 &&
1649 strcmp("-v", argv[1]) == 0) {
1650 die("sfm-"VERSION);
1651 } else {
1652 die("usage: sfm [-v]");
1654 return 0;