[ref] create Cpair fg bg colors
[sfm.git] / sfm.c
blobf48bdc2c460498d6cfd5a9b1e49a03de1285759a
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 */
43 /* typedef */
44 typedef struct {
45 char name[MAX_N];
46 gid_t group;
47 mode_t mode;
48 off_t size;
49 time_t td;
50 uid_t user;
51 } Entry;
53 typedef struct {
54 uint16_t fg;
55 uint16_t bg;
56 } Cpair;
58 typedef struct {
59 char dirn[MAX_P]; // dir name cwd
60 Entry *direntr; // dir entries
61 int dirx; // pane cwd x pos
62 size_t dirc; // dir entries sum
63 size_t hdir; // highlighted dir
64 size_t firstrow;
65 Cpair dircol;
66 } Pane;
68 typedef struct {
69 char key;
70 char path[MAX_P];
71 } Bookmark;
73 typedef struct {
74 char *soft;
75 const char **ext;
76 size_t len;
77 } Rule;
79 typedef union {
80 uint16_t key; /* one of the TB_KEY_* constants */
81 uint32_t ch; /* unicode character */
82 } Evkey;
84 typedef struct {
85 const Evkey evkey;
86 void (*func)(void);
87 } Key;
89 /* function declarations */
90 static void print_tb(const char*, int, int, uint16_t, uint16_t);
91 static void printf_tb(int, int, Cpair, const char*, ...);
92 static void print_status(Cpair, const char*, ...);
93 static void print_xstatus(char, int);
94 static void print_error(char*);
95 static void print_prompt(char*);
96 static void print_info(void);
97 static void print_row(Pane*, size_t, Cpair);
98 static void clear(int, int, int, uint16_t);
99 static void clear_status(void);
100 static void clear_pane(int);
101 static void add_hi(Pane*, size_t);
102 static void rm_hi(Pane*, size_t);
103 static void float_to_string(float, char*);
104 static int check_dir(char*);
105 static mode_t chech_execf(mode_t);
106 static int sort_name(const void *const, const void *const);
107 static char *get_ext(char*);
108 static int get_fdt(char*, size_t, time_t);
109 static char *get_fgrp(gid_t, size_t);
110 static char *get_finfo(Entry*);
111 static char *get_fperm(mode_t);
112 static char *get_fsize(off_t);
113 static char *get_fullpath(char*, char*);
114 static char *get_fusr(uid_t, size_t);
115 static void get_dirsize(char*, off_t*);
116 static void get_hicol(Cpair*, mode_t);
117 static int deldir(char*, int);
118 static int delent(char *);
119 static int delf(char*);
120 static void calcdir(void);
121 static void crnd(void);
122 static void crnf(void);
123 static void delfd(void);
124 static void mvbk(void);
125 static void mvbtm(void);
126 static void mvdwn(void);
127 static void mvdwns(void);
128 static void mvfor(void);
129 static void mvmid(void);
130 static void mvtop(void);
131 static void mvup(void);
132 static void mvups(void);
133 static void scrdwn(void);
134 static void scrdwns(void);
135 static void scrup(void);
136 static void scrups(void);
137 static int get_usrinput(char*, size_t, char*);
138 static int open_files(char*);
139 static ssize_t findbm(char);
140 static void filter(void);
141 static void switch_pane(void);
142 static void quit(void);
143 static void grabkeys(struct tb_event*);
144 static void start_ev(void);
145 static void refresh_pane(void);
146 static int listdir(char*);
147 static void t_resize(void);
148 static int set_panes(int);
149 static void draw_frame(void);
150 static int start(void);
152 /* global variables */
153 static Pane pane_r, pane_l, *cpane;
154 static int parent_row = 1; // FIX
155 static size_t scrheight;
157 static const uint32_t INOTIFY_MASK = IN_CREATE | IN_DELETE | IN_DELETE_SELF \
158 | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO;
160 /* configuration, allows nested code to access above variables */
161 #include "config.h"
163 /* function implementations */
164 static void
165 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
167 while (*str != '\0') {
168 uint32_t uni = 0;
169 str += tb_utf8_char_to_unicode(&uni, str);
170 tb_change_cell(x, y, uni, fg, bg);
171 x++;
175 static void
176 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
178 char buf[4096];
179 va_list vl;
180 va_start(vl, fmt);
181 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
182 va_end(vl);
183 print_tb(buf, x, y, col.fg, col.bg);
186 static void
187 print_status(Cpair col, const char *fmt, ...)
189 int height;
190 height = tb_height();
192 char buf[256];
193 va_list vl;
194 va_start(vl, fmt);
195 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
196 va_end(vl);
197 clear_status();
198 print_tb(buf, 1, height-1, col.fg, col.bg);
202 static void
203 print_xstatus(char c, int x)
205 int height;
206 uint32_t uni = 0;
207 height = tb_height();
208 (void)tb_utf8_char_to_unicode(&uni, &c);
209 tb_change_cell(x, height-1, uni, cstatus.fg , cstatus.bg);
212 static void
213 print_error(char *errmsg)
215 print_status(cerr, errmsg);
218 static void
219 print_prompt(char *prompt)
221 print_status(cprompt, prompt);
224 static void
225 print_info(void)
227 char *fileinfo;
228 fileinfo = get_finfo(&cpane->direntr[cpane->hdir-1]);
229 print_status(cstatus, "%lu/%lu %s",
230 cpane->hdir, cpane->dirc, fileinfo);
231 free(fileinfo);
234 static void
235 print_row(Pane *pane, size_t entpos, Cpair col)
237 int x, y;
238 char *result;
239 char buf[MAX_P];
240 char lnk_full[MAX_P];
241 int width;
243 width = (tb_width() / 2) - 4;
244 result = pane->direntr[entpos].name;
245 x = pane->dirx;
246 y = entpos - cpane->firstrow + 1;
248 if (S_ISLNK(pane->direntr[entpos].mode) &&
249 realpath(pane->direntr[entpos].name, buf) != NULL) {
250 strncpy(lnk_full, pane->direntr[entpos].name, MAX_N);
251 strcat(lnk_full, " -> ");
252 strncat(lnk_full, buf, MAX_N);
253 result = lnk_full;
256 printf_tb(x, y, col, "%*.*s", ~width, width, result);
259 static void
260 clear(int sx, int ex, int y, uint16_t bg)
262 /* clear line from to */
263 /* x = line number vertical */
264 /* y = column number horizontal */
265 int i;
266 for (i = sx; i < ex; i++) {
267 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
272 static void
273 clear_status(void)
275 int width, height;
276 width = tb_width();
277 height = tb_height();
278 clear(1, width-1, height-1, cstatus.bg);
281 static void
282 clear_pane(int pane)
284 int i, x, ex, y, width, height;
285 width = tb_width();
286 height = tb_height();
287 x = 0, y = 0, i = 0, ex = 0;
289 if (pane == 2) {
290 x = 2;
291 ex = (width/2) - 1;
292 } else if (pane == (width/2) + 2) {
293 x = (width/2) + 1;
294 ex = width -1;
297 while (i < height-2) {
298 clear(x, ex, y, TB_DEFAULT);
299 i++;
300 y++;
302 /* draw top line */
303 for (y = x; y < ex ; ++y) {
304 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
309 static void
310 add_hi(Pane *pane, size_t entpos)
312 Cpair col;
313 get_hicol(&col, pane->direntr[entpos].mode);
314 col.fg |= TB_REVERSE|TB_BOLD;
315 col.bg |= TB_REVERSE;
316 print_row(pane, entpos, col);
319 static void
320 rm_hi(Pane *pane, size_t entpos)
322 Cpair col;
323 get_hicol(&col, pane->direntr[entpos].mode);
324 print_row(pane, entpos, col);
327 static void
328 float_to_string(float f, char *r)
330 int length, length2, i, number, position, tenth; /* length is size of decimal part, length2 is size of tenth part */
331 float number2;
333 f = (float)(int)(f * 10) / 10;
335 number2 = f;
336 number = (int)f;
337 length2 = 0;
338 tenth = 1;
340 /* Calculate length2 tenth part */
341 while ((number2 - (float)number) != 0.0 && !((number2 - (float)number) < 0.0))
343 tenth *= 10.0;
344 number2 = f * (float)tenth;
345 number = (int)number2;
347 length2++;
350 /* Calculate length decimal part */
351 for (length = (f > 1.0) ? 0 : 1; f > 1.0; length++)
352 f /= 10.0;
354 position = length;
355 length = length + 1 + length2;
356 number = (int)number2;
358 if (length2 > 0)
360 for (i = length; i >= 0; i--)
362 if (i == (length))
363 r[i] = '\0';
364 else if (i == (position))
365 r[i] = '.';
366 else
368 r[i] = (char)(number % 10) + '0';
369 number /= 10;
373 else
375 length--;
376 for (i = length; i >= 0; i--)
378 if (i == (length))
379 r[i] = '\0';
380 else
382 r[i] = (char)(number % 10) + '0';
383 number /= 10;
389 static int
390 check_dir(char *path)
392 DIR *dir;
393 dir = opendir(path);
395 if (dir == NULL) {
396 if (errno == ENOTDIR) {
397 return 1;
398 } else {
399 return -1;
403 if (closedir(dir) < 0)
404 return -1;
406 return 0;
409 static mode_t
410 chech_execf(mode_t mode)
412 if (S_ISREG(mode))
413 return (((S_IXUSR | S_IXGRP | S_IXOTH) & mode));
414 return 0;
417 static int
418 sort_name(const void *const A, const void *const B)
420 int result;
421 mode_t data1 = (*(Entry*) A).mode;
422 mode_t data2 = (*(Entry*) B).mode;
424 if (data1 < data2) {
425 return -1;
426 } else if (data1 == data2) {
427 result = strcmp((*(Entry*) A).name,(*(Entry*) B).name);
428 return result;
429 } else {
430 return 1;
434 static char *
435 get_ext(char *str)
437 char *ext;
438 char dot;
439 size_t counter, len, i;
441 dot = '.';
442 counter = 0;
443 len = strlen(str);
445 for (i = len-1; i > 0; i--) {
446 if (str[i] == dot) {
447 break;
448 } else {
449 counter++;
453 ext = ecalloc(counter+1, sizeof(char));
454 strncpy(ext, &str[len-counter], counter);
455 return ext;
458 static int
459 get_fdt(char *result, size_t reslen, time_t status)
461 struct tm lt;
462 localtime_r(&status, &lt);
463 return strftime(result, reslen, dtfmt, &lt);
466 static char *
467 get_fgrp(gid_t status, size_t len)
469 char *result;
470 struct group *gr;
472 result = ecalloc(len, sizeof(char));
473 gr = getgrgid(status);
474 if (gr == NULL)
475 (void)snprintf(result, len-1, "%d", (int)status);
476 else
477 strncpy(result, gr->gr_name, len-1);
479 return result;
482 static char *
483 get_finfo(Entry *cursor)
485 char *sz, *rst, *ur, *gr, *td, *prm;
486 size_t szlen, prmlen, urlen, grlen, tdlen, rstlen;
488 szlen = 9;
489 prmlen = 11;
490 urlen = grlen = tdlen = 32;
491 rstlen = szlen + prmlen + urlen + grlen + tdlen;
492 rst = ecalloc(rstlen, sizeof(char));
494 if (show_perm == 1) {
495 prm = get_fperm(cursor->mode);
496 strncpy(rst, prm, prmlen);
497 strcat(rst, " ");
498 free(prm);
501 if (show_ug == 1) {
502 ur = get_fusr(cursor->user, urlen);
503 gr = get_fgrp(cursor->group, grlen);
504 strncat(rst, ur, urlen);
505 strcat(rst, ":");
506 strncat(rst, gr, grlen);
507 strcat(rst, " ");
508 free(ur);
509 free(gr);
512 if (show_dt == 1) {
513 td = ecalloc(tdlen, sizeof(char));
514 if (get_fdt(td, tdlen, cursor->td) > 0) {
515 strncat(rst, td, tdlen);
516 strcat(rst, " ");
518 free(td);
521 if (show_size == 1 && S_ISREG(cursor->mode)) {
522 sz = get_fsize(cursor->size);
523 strncat(rst, sz, szlen);
524 free(sz);
527 return rst;
530 static char *
531 get_fperm(mode_t mode)
533 char *buf;
534 size_t i;
536 const char chars[] = "rwxrwxrwx";
537 buf = ecalloc((size_t)11, sizeof(char));
539 if (S_ISDIR(mode))
540 buf[0] = 'd';
541 else if (S_ISREG(mode))
542 buf[0] = '-';
543 else if (S_ISLNK(mode))
544 buf[0] = 'l';
545 else if (S_ISBLK(mode))
546 buf[0] = 'b';
547 else if (S_ISCHR(mode))
548 buf[0] = 'c';
549 else if (S_ISFIFO(mode))
550 buf[0] = 'p';
551 else if (S_ISSOCK(mode))
552 buf[0] = 's';
553 else
554 buf[0] = '?';
556 for (i = 1; i < 10; i++) {
557 buf[i] = (mode & (1 << (9-i))) ? chars[i-1] : '-';
559 buf[10] = '\0';
561 return buf;
564 static char *
565 get_fsize(off_t size)
568 /* need to be freed */
569 char *Rsize;
570 float lsize;
571 int counter;
572 counter = 0;
574 Rsize = ecalloc((size_t)10, sizeof(char));
575 lsize = (float)size;
577 while (lsize >= 1000.0)
579 lsize /= 1024.0;
580 ++counter;
583 float_to_string(lsize, Rsize);
585 switch (counter)
587 case 0:
588 strcat(Rsize, "B");
589 break;
590 case 1:
591 strcat(Rsize, "K");
592 break;
593 case 2:
594 strcat(Rsize, "M");
595 break;
596 case 3:
597 strcat(Rsize, "G");
598 break;
599 case 4:
600 strcat(Rsize, "T");
601 break;
604 return Rsize;
607 static char *
608 get_fullpath(char *first, char *second)
610 char *full_path;
611 size_t full_path_len;
613 full_path_len = strlen(first) + strlen(second) + 2;
614 full_path = ecalloc(full_path_len, sizeof(char));
616 if (strcmp(first, "/") == 0) {
617 (void)snprintf(full_path, full_path_len, "/%s", second);
619 } else {
620 (void)snprintf(
621 full_path, full_path_len, "%s/%s", first, second);
624 return full_path;
627 static char *
628 get_fusr(uid_t status, size_t len)
630 char *result;
631 struct passwd *pw;
633 result = ecalloc(len, sizeof(char));
634 pw = getpwuid(status);
635 if (pw == NULL)
636 (void)snprintf(result, len-1, "%d", (int)status);
637 else
638 strncpy(result, pw->pw_name, len-1);
640 return result;
643 static void
644 get_dirsize(char *fullpath, off_t *fullsize)
646 DIR *dir;
647 char *ent_full;
648 mode_t mode;
649 struct dirent *entry;
650 struct stat status;
652 dir = opendir(fullpath);
653 if (dir == NULL)
655 return;
658 while ((entry = readdir(dir)) != 0)
660 if ((strcmp(entry->d_name, ".") == 0 ||
661 strcmp(entry->d_name, "..") == 0))
662 continue;
664 ent_full = get_fullpath(fullpath, entry->d_name);
665 if (lstat(ent_full, &status) == 0) {
666 mode = status.st_mode;
667 if (S_ISDIR(mode)) {
668 get_dirsize(ent_full, fullsize);
669 free(ent_full);
670 } else {
672 *fullsize += status.st_size;
673 free(ent_full);
678 closedir(dir);
679 clear_status();
682 static void
683 get_hicol(Cpair *col, mode_t mode)
685 *col = cfile;
686 if (S_ISDIR(mode))
687 *col = cdir;
688 else if (S_ISLNK(mode))
689 *col = cother;
690 else if (chech_execf(mode) > 0)
691 *col = cexec;
694 static int
695 deldir(char *fullpath, int delchoice)
697 if (delchoice == (int)AskDel) {
698 char *confirmation;
699 confirmation = ecalloc((size_t)2, sizeof(char));
700 if ((get_usrinput(
701 confirmation, (size_t)2,"delete directory (Y) ?") < 0) ||
702 (strcmp(confirmation, "Y") != 0)) {
703 free(confirmation);
704 return 1; /* canceled by user or wrong confirmation */
706 free(confirmation);
709 if (rmdir(fullpath) == 0)
710 return 0; /* empty directory */
712 DIR *dir;
713 char *ent_full;
714 mode_t mode;
715 struct dirent *entry;
716 struct stat status;
718 dir = opendir(fullpath);
719 if (dir == NULL) {
720 return -1;
723 while ((entry = readdir(dir)) != 0) {
724 if ((strcmp(entry->d_name, ".") == 0 ||
725 strcmp(entry->d_name, "..") == 0))
726 continue;
728 ent_full = get_fullpath(fullpath, entry->d_name);
729 if (lstat(ent_full, &status) == 0) {
730 mode = status.st_mode;
731 if (S_ISDIR(mode)) {
732 if (deldir(ent_full, (int)DAskDel) < 0) {
733 free(ent_full);
734 return -1;
736 } else if (S_ISREG(mode)) {
737 if (unlink(ent_full) < 0) {
738 free(ent_full);
739 return -1;
743 free(ent_full);
746 print_status(cstatus, "gotit");
747 if (closedir(dir) < 0)
748 return -1;
750 return rmdir(fullpath); /* directory after delete all entries */
753 static int
754 delent(char *fullpath)
756 struct stat status;
757 mode_t mode;
759 if (lstat(fullpath, &status) < 0)
760 return -1;
762 mode = status.st_mode;
763 if (S_ISDIR(mode)) {
764 return deldir(fullpath, (int)AskDel);
765 } else {
766 return delf(fullpath);
771 static int
772 delf(char *fullpath)
774 char *confirmation;
775 confirmation = ecalloc((size_t)2, sizeof(char));
777 if ((get_usrinput(confirmation, (size_t)2, "delete file (Y) ?") < 0) ||
778 (strcmp(confirmation, "Y") != 0)) {
779 free(confirmation);
780 return 1; /* canceled by user or wrong confirmation */
783 free(confirmation);
784 return unlink(fullpath);
787 static void
788 calcdir(void)
790 off_t *fullsize;
791 char *csize;
792 char *result;
794 if (S_ISDIR(cpane->direntr[cpane->hdir-1].mode)) {
795 fullsize = ecalloc(50, sizeof(off_t));
796 get_dirsize(cpane->direntr[cpane->hdir-1].name, fullsize);
797 csize = get_fsize(*fullsize);
798 result = get_finfo(&cpane->direntr[cpane->hdir-1]);
800 clear_status();
801 print_status(cstatus, "%lu/%lu %s%s",
802 cpane->hdir, cpane->dirc, result, csize);
803 free(csize);
804 free(fullsize);
805 free(result);
809 static void
810 crnd(void)
812 char *user_input, *path;
813 size_t pathlen;
815 user_input = ecalloc(MAX_USRI, sizeof(char));
816 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
817 free(user_input);
818 return;
821 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
822 path = ecalloc(pathlen, sizeof(char));
823 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
824 free(user_input);
825 free(path);
826 return;
829 if (mkdir(path, new_dir_perm) < 0)
830 print_error(strerror(errno));
831 else
832 listdir(NULL);
834 free(user_input);
835 free(path);
838 static void
839 crnf(void)
841 char *user_input, *path;
842 size_t pathlen;
843 int rf;
845 user_input = ecalloc(MAX_USRI, sizeof(char));
846 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
847 free(user_input);
848 return;
851 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
852 path = ecalloc(pathlen, sizeof(char));
853 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
854 free(user_input);
855 free(path);
856 return;
859 rf = open(path, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
861 if (rf < 0) {
862 print_error(strerror(errno));
863 } else {
864 if (close(rf) < 0)
865 print_error(strerror(errno));
866 else
867 listdir(NULL);
870 free(user_input);
871 free(path);
874 static void
875 delfd(void)
877 switch (delent(cpane->direntr[cpane->hdir-1].name)) {
878 case -1:
879 print_error(strerror(errno));
880 break;
881 case 0:
882 if (cpane->hdir == cpane->dirc) /* last entry */
883 cpane->hdir--;
884 listdir(NULL);
885 break;
889 static void
890 mvbk(void)
892 chdir("..");
893 getcwd(cpane->dirn, MAX_P);
894 cpane->firstrow = 0;
895 cpane->hdir = parent_row;
896 listdir(NULL);
897 parent_row = 1;
898 add_hi(cpane, cpane->hdir-1);
901 static void
902 mvbtm(void)
904 if (cpane->dirc > scrheight) {
905 clear_pane(cpane->dirx);
906 rm_hi(cpane, cpane->hdir-1);
907 cpane->hdir = cpane->dirc;
908 cpane->firstrow = cpane->dirc - scrheight + 1;
909 refresh_pane();
910 add_hi(cpane, cpane->hdir-1);
911 } else {
912 rm_hi(cpane, cpane->hdir-1);
913 cpane->hdir = cpane->dirc;
914 add_hi(cpane, cpane->hdir-1);
916 print_info();
919 static void
920 mvdwn(void)
922 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
923 rm_hi(cpane, cpane->hdir-1);
924 cpane->hdir++;
925 add_hi(cpane, cpane->hdir-1);
926 } else {
927 mvdwns(); /* scroll */
929 print_info();
932 static void
933 mvdwns(void)
935 size_t real;
936 real= cpane->hdir - 1 - cpane->firstrow;
938 if (real > scrheight - 3 - scrsp && cpane->hdir + scrsp < cpane->dirc) {
939 cpane->firstrow++;
940 clear_pane(cpane->dirx);
941 rm_hi(cpane, cpane->hdir-1);
942 cpane->hdir++;
943 refresh_pane();
944 add_hi(cpane, cpane->hdir-1);
945 } else if (cpane->hdir < cpane->dirc) {
946 rm_hi(cpane, cpane->hdir-1);
947 cpane->hdir++;
948 add_hi(cpane, cpane->hdir-1);
952 static void
953 mvfor(void)
955 switch (check_dir(cpane->direntr[cpane->hdir-1].name)) {
956 case 0:
957 chdir(cpane->direntr[cpane->hdir-1].name);
958 getcwd(cpane->dirn, MAX_P);
959 parent_row = (int)cpane->hdir;
960 cpane->hdir = 1;
961 cpane->firstrow = 0;
962 (void)listdir(NULL);
963 add_hi(cpane, cpane->hdir-1);
964 break;
965 case 1:
966 /* is not a directory open file */
967 if (open_files(cpane->direntr[cpane->hdir-1].name) < 0) {
968 print_error("procces failed");
969 return;
971 if (tb_init() != 0)
972 die("tb_init");
973 t_resize();
974 break;
975 case -1:
976 /* failed to open directory */
977 print_error(strerror(errno));
981 static void
982 mvmid(void)
984 rm_hi(cpane, cpane->hdir - 1);
985 cpane->hdir = (scrheight / 2) + cpane->firstrow;
986 add_hi(cpane, cpane->hdir - 1);
987 print_info();
990 static void
991 mvtop(void)
993 if (cpane->dirc > scrheight) {
994 clear_pane(cpane->dirx);
995 rm_hi(cpane, cpane->hdir-1);
996 cpane->hdir = 1;
997 cpane->firstrow = 0;
998 refresh_pane();
999 add_hi(cpane, cpane->hdir-1);
1000 } else {
1001 rm_hi(cpane, cpane->hdir-1);
1002 cpane->hdir = 1;
1003 add_hi(cpane, cpane->hdir-1);
1004 print_info();
1008 static void
1009 mvup(void)
1011 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1012 rm_hi(cpane, cpane->hdir-1);
1013 cpane->hdir--;
1014 add_hi(cpane, cpane->hdir-1);
1015 } else {
1016 mvups(); /* scroll */
1018 print_info();
1021 static void
1022 mvups(void)
1024 size_t real;
1025 real= cpane->hdir - 1 - cpane->firstrow;
1027 if (cpane->firstrow > 0 && real < 1 + scrsp) {
1028 cpane->firstrow--;
1029 clear_pane(cpane->dirx);
1030 rm_hi(cpane, cpane->hdir-1);
1031 cpane->hdir--;
1032 refresh_pane();
1033 add_hi(cpane, cpane->hdir-1);
1034 } else if (cpane->hdir > 1) {
1035 rm_hi(cpane, cpane->hdir-1);
1036 cpane->hdir--;
1037 add_hi(cpane, cpane->hdir-1);
1041 static void
1042 scrdwn(void)
1044 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
1045 if (cpane->hdir < cpane->dirc - scrmv) {
1046 rm_hi(cpane, cpane->hdir-1);
1047 cpane->hdir += scrmv;
1048 add_hi(cpane, cpane->hdir-1);
1049 } else {
1050 mvbtm();
1052 } else {
1053 scrdwns();
1055 print_info();
1058 static void
1059 scrdwns(void)
1061 size_t real;
1062 int dynmv;
1063 real = cpane->hdir - cpane->firstrow;
1064 dynmv = MIN(cpane->dirc - cpane->hdir - cpane->firstrow , scrmv);
1066 if (real + scrmv + 1 > scrheight &&
1067 cpane->hdir + scrsp + scrmv < cpane->dirc) { /* scroll */
1068 cpane->firstrow += dynmv;
1069 clear_pane(cpane->dirx);
1070 rm_hi(cpane, cpane->hdir-1);
1071 cpane->hdir += scrmv;
1072 refresh_pane();
1073 add_hi(cpane, cpane->hdir-1);
1074 } else {
1075 if (cpane->hdir < cpane->dirc - scrmv) {
1076 rm_hi(cpane, cpane->hdir-1);
1077 cpane->hdir += scrmv;
1078 add_hi(cpane, cpane->hdir-1);
1079 } else {
1080 mvbtm();
1085 static void
1086 scrup(void)
1088 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1089 if (cpane->hdir > scrmv) {
1090 rm_hi(cpane, cpane->hdir-1);
1091 cpane->hdir = cpane->hdir - scrmv;
1092 add_hi(cpane, cpane->hdir-1);
1093 print_info();
1094 } else {
1095 mvtop();
1097 } else {
1098 scrups();
1102 static void
1103 scrups(void)
1105 size_t real;
1106 int dynmv;
1107 real = cpane->hdir - cpane->firstrow;
1108 dynmv = MIN(cpane->firstrow , scrmv);
1110 if (cpane->firstrow > 0 && real < scrmv + scrsp) {
1111 cpane->firstrow -= dynmv;
1112 clear_pane(cpane->dirx);
1113 rm_hi(cpane, cpane->hdir-1);
1114 cpane->hdir -= scrmv;
1115 refresh_pane();
1116 add_hi(cpane, cpane->hdir-1);
1117 } else {
1118 if (cpane->hdir > scrmv + 1) {
1119 rm_hi(cpane, cpane->hdir-1);
1120 cpane->hdir -= scrmv;
1121 add_hi(cpane, cpane->hdir-1);
1122 } else {
1123 mvtop();
1128 static int
1129 get_usrinput(char *out, size_t sout, char *prompt)
1131 int height = tb_height();
1132 size_t startat;
1133 struct tb_event fev;
1134 size_t counter = (size_t)1;
1135 char empty = ' ';
1136 int x = 0;
1138 clear_status();
1139 startat = strlen(prompt) + 3;
1140 print_prompt(prompt);
1141 tb_set_cursor((int)(startat + 1), height-1);
1142 tb_present();
1144 while (tb_poll_event(&fev) != 0) {
1145 switch (fev.type) {
1146 case TB_EVENT_KEY:
1147 if (fev.key == (uint16_t)TB_KEY_ESC) {
1148 tb_set_cursor(-1, -1);
1149 clear_status();
1150 return -1;
1153 if (fev.key == (uint16_t)TB_KEY_BACKSPACE ||
1154 fev.key == (uint16_t)TB_KEY_BACKSPACE2) {
1155 if (BETWEEN(counter, (size_t)2, sout)) {
1156 out[x-1] = '\0';
1157 counter--;
1158 x--;
1159 print_xstatus(empty, startat + counter);
1160 tb_set_cursor(
1161 (int)startat + counter, height - 1);
1164 } else if (fev.key == (uint16_t)TB_KEY_ENTER) {
1165 tb_set_cursor(-1, -1);
1166 out[counter-1] = '\0';
1167 return 0;
1169 } else {
1170 if (counter < sout) {
1171 print_xstatus((char)fev.ch, (int)(startat+counter));
1172 out[x] = (char)fev.ch;
1173 tb_set_cursor((int)(startat + counter + 1),height-1);
1174 counter++;
1175 x++;
1179 tb_present();
1180 break;
1182 default:
1183 return -1;
1187 return -1;
1191 static int
1192 open_files(char *filename)
1194 // TODO
1195 /* open editor in other window */
1196 /* wait option */
1197 char *editor, *file_ex, *software, *term;
1198 int status;
1199 size_t d, c;
1200 pid_t pid, r;
1202 editor = getenv("EDITOR");
1203 term = getenv("TERM");
1204 file_ex = get_ext(filename);
1205 software = NULL;
1207 /* find software in rules */
1208 for (c = 0; c < LEN(rules); c++) {
1209 for (d = 0; d < rules[c].len; d++) {
1210 if (strcmp(rules[c].ext[d], file_ex) == 0) {
1211 software = rules[c].soft;
1216 /* default softwares */
1217 if (term == NULL)
1218 term = "xterm-256color";
1219 if (editor == NULL)
1220 editor = "vi";
1221 if (software == NULL)
1222 software = editor;
1224 free(file_ex);
1225 char *filex[] = {software, filename, NULL};
1226 tb_shutdown();
1227 pid = fork();
1229 switch (pid) {
1230 case -1:
1231 return -1;
1232 case 0:
1233 (void)execvp(filex[0], filex);
1234 exit(EXIT_SUCCESS);
1235 default:
1236 while ((r = waitpid(pid, &status, 0)) == (pid_t)-1 && errno == EINTR)
1237 continue;
1238 if (r == (pid_t)-1)
1239 return -1;
1240 if ((WIFEXITED(status) != 0) && (WEXITSTATUS(status) != 0))
1241 return -1;
1244 return 0;
1247 static ssize_t
1248 findbm(char event)
1250 ssize_t i;
1252 for (i = 0; i < (ssize_t)LEN(bmarks); i++) {
1253 if (event == bmarks[i].key) {
1254 if (check_dir(bmarks[i].path) != 0) {
1255 print_error(strerror(errno));
1256 return -1;
1258 return i;
1261 return -1;
1264 static void
1265 filter(void)
1267 char *user_input;
1268 user_input = ecalloc(MAX_USRI, sizeof(char));
1269 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1270 free(user_input);
1271 return;
1273 if (listdir(user_input) < 0) {
1274 print_error("no match");
1276 free(user_input);
1279 static void
1280 switch_pane(void)
1282 if (cpane == &pane_l) {
1283 rm_hi(&pane_l, pane_l.hdir-1);
1284 add_hi(&pane_r, pane_r.hdir-1);
1285 chdir(pane_r.dirn);
1286 cpane = &pane_r;
1287 } else if (cpane == &pane_r) {
1288 rm_hi(&pane_r, pane_r.hdir-1);
1289 add_hi(&pane_l, pane_l.hdir-1);
1290 chdir(pane_l.dirn);
1291 cpane = &pane_l;
1293 print_info();
1296 static void
1297 quit(void)
1299 free(pane_l.direntr);
1300 free(pane_r.direntr);
1301 tb_shutdown();
1302 exit(EXIT_SUCCESS);
1305 static void
1306 grabkeys(struct tb_event *event)
1308 size_t i;
1310 for (i = 0; i < LEN(keys); i++) {
1311 if (event->ch != 0) {
1312 if (event->ch == keys[i].evkey.ch) {
1313 keys[i].func();
1314 return;
1316 } else if (event->key != 0) {
1317 if (event->key == keys[i].evkey.key) {
1318 keys[i].func();
1319 return;
1325 static void
1326 start_ev(void)
1328 struct tb_event ev;
1330 while (tb_poll_event(&ev) != 0) {
1331 switch (ev.type) {
1332 case TB_EVENT_KEY:
1333 grabkeys(&ev);
1334 tb_present();
1335 break;
1336 case TB_EVENT_RESIZE:
1337 t_resize();
1338 break;
1339 default:
1340 break;
1343 tb_shutdown();
1346 static void
1347 refresh_pane(void)
1349 size_t y, dyn_max, start_from;
1350 int width;
1351 width = (tb_width() / 2) - 4;
1352 Cpair col;
1354 y = 1;
1355 start_from = cpane->firstrow;
1356 dyn_max = MIN(cpane->dirc, (scrheight - 1) + cpane->firstrow);
1358 /* print each entry in directory */
1359 while (start_from < dyn_max) {
1360 get_hicol(&col, cpane->direntr[start_from].mode);
1361 print_row(cpane, start_from, col);
1362 start_from++;
1363 y++;
1366 print_info();
1368 /* print current directory title */
1369 cpane->dircol.fg |= TB_BOLD;
1370 printf_tb(cpane->dirx, 0, cpane->dircol," %.*s ", width, cpane->dirn);
1373 static int
1374 listdir(char *filter)
1376 DIR *dir;
1377 struct dirent *entry;
1378 struct stat status;
1379 int width;
1380 size_t i;
1381 int filtercount = 0;
1382 size_t oldc = cpane->dirc;
1384 width = (tb_width() / 2) - 4;
1385 cpane->dirc = 0;
1386 i = 0;
1388 if (chdir(cpane->dirn) < 0)
1389 return -1;
1391 dir = opendir(cpane->dirn);
1392 if (dir == NULL)
1393 return -1;
1395 /* get content and filter sum */
1396 while ((entry = readdir(dir)) != 0) {
1397 if (filter != NULL) {
1398 if (strstr(entry->d_name, filter) != NULL)
1399 filtercount++;
1400 } else { /* no filter */
1401 cpane->dirc++;
1405 if (filter == NULL) {
1406 clear_pane(cpane->dirx);
1407 cpane->dirc -=2;
1410 if (filter != NULL) {
1411 if (filtercount > 0) {
1412 cpane->dirc -=2;
1413 cpane->dirc = filtercount;
1414 clear_pane(cpane->dirx);
1415 cpane->hdir = 1;
1416 } else if (filtercount == 0) {
1417 if (closedir(dir) < 0)
1418 return -1;
1419 cpane->dirc = oldc;
1420 return -1;
1424 /* print current directory title */
1425 cpane->dircol.fg |= TB_BOLD;
1426 printf_tb(cpane->dirx, 0, cpane->dircol," %.*s ", width, cpane->dirn);
1428 /* empty directory */
1429 if (cpane->dirc == 0) {
1430 clear_status();
1431 if (closedir(dir) < 0)
1432 return -1;
1433 return 0;
1436 rewinddir(dir); /* reset position */
1438 /* create array of entries */
1439 i = 0;
1440 cpane->direntr = erealloc(cpane->direntr, cpane->dirc * sizeof(Entry));
1441 while ((entry = readdir(dir)) != 0) {
1442 if ((strcmp(entry->d_name, ".") == 0 ||
1443 strcmp(entry->d_name, "..") == 0))
1444 continue;
1446 /* list found filter */
1447 if (filter != NULL) {
1448 if (strstr(entry->d_name, filter) != NULL) {
1449 strcpy(cpane->direntr[i].name, entry->d_name);
1450 if (lstat(entry->d_name, &status) == 0) {
1451 cpane->direntr[i].size = status.st_size;
1452 cpane->direntr[i].mode = status.st_mode;
1453 cpane->direntr[i].group = status.st_gid;
1454 cpane->direntr[i].user = status.st_uid;
1455 cpane->direntr[i].td = status.st_mtime;
1457 i++;
1460 } else {
1461 strcpy(cpane->direntr[i].name, entry->d_name);
1462 if (lstat(entry->d_name, &status) == 0) {
1463 cpane->direntr[i].size = status.st_size;
1464 cpane->direntr[i].mode = status.st_mode;
1465 cpane->direntr[i].group = status.st_gid;
1466 cpane->direntr[i].user = status.st_uid;
1467 cpane->direntr[i].td = status.st_mtime;
1469 i++;
1473 cpane->dirc = i;
1474 qsort(cpane->direntr, cpane->dirc, sizeof(Entry), sort_name);
1475 refresh_pane();
1477 if (closedir(dir) < 0)
1478 return -1;
1479 return 0;
1482 static void
1483 t_resize(void)
1485 /* TODO need refactoring */
1486 tb_clear();
1487 draw_frame();
1488 (void)set_panes(1);
1490 if (cpane == &pane_l) {
1491 chdir(pane_r.dirn);
1492 cpane = &pane_r;
1493 refresh_pane();
1494 chdir(pane_l.dirn);
1495 cpane = &pane_l;
1496 refresh_pane();
1497 add_hi(&pane_l, pane_l.hdir-1);
1498 } else if (cpane == &pane_r) {
1499 chdir(pane_l.dirn);
1500 cpane = &pane_l;
1501 refresh_pane();
1502 chdir(pane_r.dirn);
1503 cpane = &pane_r;
1504 refresh_pane();
1505 add_hi(&pane_r, pane_r.hdir-1);
1508 tb_present();
1511 static int
1512 set_panes(int resize)
1514 int width;
1515 char *home;
1516 char cwd[MAX_P];
1517 scrheight = tb_height() - 2;
1519 home = getenv("HOME");
1520 width = tb_width();
1521 if ((getcwd(cwd, sizeof(cwd)) == NULL))
1522 return -1;
1523 if (home == NULL)
1524 home = "/";
1526 pane_l.dirx = 2;
1527 pane_l.dircol = cpanell;
1528 pane_l.firstrow = 0;
1529 if (resize == 0) {
1530 pane_l.direntr = ecalloc(0, sizeof(Entry));
1531 strcpy(pane_l.dirn, cwd);
1532 pane_l.hdir = 1;
1535 pane_r.dirx = (width / 2) + 2;
1536 pane_r.dircol = cpanelr;
1537 pane_r.firstrow = 0;
1538 if (resize == 0) {
1539 pane_r.direntr = ecalloc(0, sizeof(Entry));
1540 strcpy(pane_r.dirn, home);
1541 pane_r.hdir = 1;
1543 return 0;
1546 static void
1547 draw_frame(void)
1549 int height, width, i;
1551 width = tb_width();
1552 height = tb_height();
1554 /* 2 horizontal lines */
1555 for (i = 1; i < width-1 ; ++i) {
1556 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1557 tb_change_cell(i, height-2, u_hl, cframe.fg, cframe.bg);
1560 /* 3 vertical lines */
1561 for (i = 1; i < height-1 ; ++i) {
1562 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1563 tb_change_cell((width-1)/2, i-1, u_vl, cframe.fg, cframe.bg);
1564 tb_change_cell(width-1, i, u_vl, cframe.fg, cframe.bg);
1567 /* 4 corners */
1568 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1569 tb_change_cell(width-1, 0, u_cne, cframe.fg, cframe.bg);
1570 tb_change_cell(0, height-2, u_csw, cframe.fg, cframe.bg);
1571 tb_change_cell(width-1, height-2, u_cse, cframe.fg, cframe.bg);
1573 /* 2 middel top and bottom */
1574 tb_change_cell((width-1)/2, 0, u_mn, cframe.fg, cframe.bg);
1575 tb_change_cell((width-1)/2, height-2, u_ms, cframe.fg, cframe.bg);
1578 static int
1579 start(void)
1581 if (tb_init()!= 0)
1582 die("tb_init");
1583 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
1584 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
1585 die("output error");
1587 draw_frame();
1588 set_panes(0);
1589 cpane = &pane_r;
1590 listdir(NULL);
1591 cpane = &pane_l;
1592 listdir(NULL);
1593 add_hi(&pane_l, pane_l.hdir-1);
1594 tb_present();
1595 start_ev();
1596 return 0;
1600 main(int argc, char *argv[])
1602 #ifdef __OpenBSD__
1603 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath", NULL) == -1)
1604 die("pledge");
1605 #endif /* __OpenBSD__ */
1606 if (argc == 1) {
1607 if (start() != 0)
1608 die("start failed");
1609 } else if (
1610 argc == 2 && strlen(argv[1]) == (size_t)2 &&
1611 strcmp("-v", argv[1]) == 0) {
1612 die("sfm-"VERSION);
1613 } else {
1614 die("usage: sfm [-v]");
1616 return 0;