[style] remove casting
[sfm.git] / sfm.c
blob89689d3fcf259535721e118fb82e81fa21927b35
1 /* See LICENSE file for copyright and license details. */
3 #include <sys/types.h>
4 #include <sys/resource.h>
5 #include <sys/stat.h>
6 #include <sys/time.h>
7 #include <sys/wait.h>
8 #if defined(__linux__)
9 #include <sys/inotify.h>
10 #elif defined(__FreeBSD__) || defined(__NetBSD__) ||\
11 defined(__OpenBSD__) || defined(__APPLE__)
12 #include <sys/event.h>
13 #endif
15 #include <dirent.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <grp.h>
19 #include <pwd.h>
20 #include <stdarg.h>
21 #include <stdint.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
28 #include "termbox.h"
29 #include "util.h"
31 /* macros */
32 #define MAX_P 4095
33 #define MAX_N 255
34 #define MAX_USRI 32
35 #define MAX_EXT 4
37 /* enums */
38 enum { AskDel, DAskDel }; /* delete directory */
39 enum { AddHi, NoHi }; /* add highlight in listdir */
41 /* typedef */
42 typedef struct {
43 char name[MAX_N];
44 gid_t group;
45 mode_t mode;
46 off_t size;
47 time_t td;
48 uid_t user;
49 } Entry;
51 typedef struct {
52 uint16_t fg;
53 uint16_t bg;
54 } Cpair;
56 typedef struct {
57 int pane_id;
58 char dirn[MAX_P]; // dir name cwd
59 Entry *direntr; // dir entries
60 int dirx; // pane cwd x pos
61 int dirc; // dir entries sum
62 int hdir; // highlighted dir
63 int firstrow;
64 int parent_row; // FIX
65 Cpair dircol;
66 int inotify_wd;
67 int event_fd;
68 } Pane;
70 typedef struct {
71 uint32_t ch;
72 char path[MAX_P];
73 } Bookmark;
75 typedef struct {
76 const char **ext;
77 size_t exlen;
78 const void *v;
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 int frules(char *);
141 static int spawn(const void *, char *);
142 static int opnf(char *);
143 static int fsev_init(void);
144 static int addwatch(void);
145 static int read_events(void);
146 static void rmwatch(Pane *);
147 static void fsev_shdn(void);
148 static ssize_t findbm(uint32_t);
149 static void filter(void);
150 static void switch_pane(void);
151 static void quit(void);
152 static void grabkeys(struct tb_event *);
153 static void start_ev(void);
154 static void refresh_pane(void);
155 static int listdir(int, char *);
156 static void t_resize(void);
157 static void get_editor(void);
158 static void set_panes(void);
159 static void draw_frame(void);
160 static void start(void);
162 /* global variables */
163 static Pane pane_r, pane_l, *cpane;
164 static char *editor[2];
165 static char fed[] = "vi";
166 static int theight, twidth, scrheight;
167 #if defined _SYS_INOTIFY_H
168 static int inotify_fd;
169 #elif defined _SYS_EVENT_H_
170 static int kq;
171 struct kevent evlist[2]; /* events we want to monitor */
172 struct kevent chlist[2]; /* events that were triggered */
173 static struct timespec gtimeout;
174 #endif
176 /* configuration, allows nested code to access above variables */
177 #include "config.h"
179 /* function implementations */
180 static void
181 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
183 while (*str != '\0') {
184 uint32_t uni = 0;
185 str += tb_utf8_char_to_unicode(&uni, str);
186 tb_change_cell(x, y, uni, fg, bg);
187 x++;
191 static void
192 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
194 char buf[4096];
195 va_list vl;
196 va_start(vl, fmt);
197 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
198 va_end(vl);
199 print_tb(buf, x, y, col.fg, col.bg);
202 static void
203 print_status(Cpair col, const char *fmt, ...)
205 char buf[256];
206 va_list vl;
207 va_start(vl, fmt);
208 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
209 va_end(vl);
210 clear_status();
211 print_tb(buf, 1, theight - 1, col.fg, col.bg);
214 static void
215 print_xstatus(char c, int x)
217 uint32_t uni = 0;
218 (void)tb_utf8_char_to_unicode(&uni, &c);
219 tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg);
222 static void
223 print_error(char *errmsg)
225 print_status(cerr, errmsg);
228 static void
229 print_prompt(char *prompt)
231 print_status(cprompt, prompt);
234 static void
235 print_info(void)
237 char *fileinfo;
238 fileinfo = get_finfo(&cpane->direntr[cpane->hdir - 1]);
239 print_status(cstatus, "%d/%d %s", cpane->hdir, cpane->dirc, fileinfo);
240 free(fileinfo);
243 static void
244 print_row(Pane *pane, size_t entpos, Cpair col)
246 int x, y;
247 char *result;
248 char buf[MAX_P];
249 char lnk_full[MAX_P];
250 int width;
252 width = (twidth / 2) - 4;
253 result = pane->direntr[entpos].name;
254 x = pane->dirx;
255 y = entpos - cpane->firstrow + 1;
257 if (S_ISLNK(pane->direntr[entpos].mode) &&
258 realpath(pane->direntr[entpos].name, buf) != NULL) {
259 strncpy(lnk_full, pane->direntr[entpos].name, MAX_N);
260 strcat(lnk_full, " -> ");
261 strncat(lnk_full, buf, MAX_N);
262 result = lnk_full;
265 printf_tb(x, y, col, "%*.*s", ~width, width, result);
268 static void
269 clear(int sx, int ex, int y, uint16_t bg)
271 /* clear line from to */
272 /* x = line number vertical */
273 /* y = column number horizontal */
274 int i;
275 for (i = sx; i < ex; i++) {
276 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
280 static void
281 clear_status(void)
283 clear(1, twidth - 1, theight - 1, cstatus.bg);
286 static void
287 clear_pane(int pane)
289 int i, x, ex, y;
290 x = 0, y = 0, i = 0, ex = 0;
292 if (pane == 2) {
293 x = 2;
294 ex = (twidth / 2) - 1;
295 } else if (pane == (twidth / 2) + 2) {
296 x = (twidth / 2) + 1;
297 ex = twidth - 1;
300 while (i < scrheight) {
301 clear(x, ex, y, TB_DEFAULT);
302 i++;
303 y++;
305 /* draw top line */
306 for (y = x; y < ex; ++y) {
307 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
311 static void
312 add_hi(Pane *pane, size_t entpos)
314 Cpair col;
315 get_hicol(&col, pane->direntr[entpos].mode);
316 col.fg |= TB_REVERSE | TB_BOLD;
317 col.bg |= TB_REVERSE;
318 print_row(pane, entpos, col);
321 static void
322 rm_hi(Pane *pane, size_t entpos)
324 Cpair col;
325 get_hicol(&col, pane->direntr[entpos].mode);
326 print_row(pane, entpos, col);
329 static void
330 float_to_string(float f, char *r)
332 int length, length2, i, number, position,
333 tenth; /* length is size of decimal part, length2 is size of tenth part */
334 float number2;
336 f = (float)(int)(f * 10) / 10;
338 number2 = f;
339 number = (int)f;
340 length2 = 0;
341 tenth = 1;
343 /* Calculate length2 tenth part */
344 while ((number2 - (float)number) != 0.0 &&
345 !((number2 - (float)number) < 0.0)) {
346 tenth *= 10.0;
347 number2 = f * (float)tenth;
348 number = (int)number2;
350 length2++;
353 /* Calculate length decimal part */
354 for (length = (f > 1.0) ? 0 : 1; f > 1.0; length++)
355 f /= 10.0;
357 position = length;
358 length = length + 1 + length2;
359 number = (int)number2;
361 if (length2 > 0) {
362 for (i = length; i >= 0; i--) {
363 if (i == (length))
364 r[i] = '\0';
365 else if (i == (position))
366 r[i] = '.';
367 else {
368 r[i] = (char)(number % 10) + '0';
369 number /= 10;
372 } else {
373 length--;
374 for (i = length; i >= 0; i--) {
375 if (i == (length))
376 r[i] = '\0';
377 else {
378 r[i] = (char)(number % 10) + '0';
379 number /= 10;
385 static int
386 check_dir(char *path)
388 DIR *dir;
389 dir = opendir(path);
391 if (dir == NULL) {
392 if (errno == ENOTDIR) {
393 return 1;
394 } else {
395 return -1;
399 if (closedir(dir) < 0)
400 return -1;
402 return 0;
405 static mode_t
406 chech_execf(mode_t mode)
408 if (S_ISREG(mode))
409 return (((S_IXUSR | S_IXGRP | S_IXOTH) & mode));
410 return 0;
413 static int
414 sort_name(const void *const A, const void *const B)
416 int result;
417 mode_t data1 = (*(Entry *)A).mode;
418 mode_t data2 = (*(Entry *)B).mode;
420 if (data1 < data2) {
421 return -1;
422 } else if (data1 == data2) {
423 result = strcmp((*(Entry *)A).name, (*(Entry *)B).name);
424 return result;
425 } else {
426 return 1;
430 static char *
431 get_ext(char *str)
433 char *ext;
434 char dot;
435 size_t counter, len, i;
437 dot = '.';
438 counter = 0;
439 len = strlen(str);
441 for (i = len - 1; i > 0; i--) {
442 if (str[i] == dot) {
443 break;
444 } else {
445 counter++;
449 ext = ecalloc(counter + 1, sizeof(char));
450 strncpy(ext, &str[len - counter], counter);
451 return ext;
454 static int
455 get_fdt(char *result, size_t reslen, time_t status)
457 struct tm lt;
458 localtime_r(&status, &lt);
459 return strftime(result, reslen, dtfmt, &lt);
462 static char *
463 get_fgrp(gid_t status, size_t len)
465 char *result;
466 struct group *gr;
468 result = ecalloc(len, sizeof(char));
469 gr = getgrgid(status);
470 if (gr == NULL)
471 (void)snprintf(result, len - 1, "%u", status);
472 else
473 strncpy(result, gr->gr_name, len - 1);
475 return result;
478 static char *
479 get_finfo(Entry *cursor)
481 char *sz, *rst, *ur, *gr, *td, *prm;
482 size_t szlen, prmlen, urlen, grlen, tdlen, rstlen;
484 szlen = 9;
485 prmlen = 11;
486 urlen = grlen = tdlen = 32;
487 rstlen = szlen + prmlen + urlen + grlen + tdlen;
488 rst = ecalloc(rstlen, sizeof(char));
490 if (show_perm == 1) {
491 prm = get_fperm(cursor->mode);
492 strncpy(rst, prm, prmlen);
493 strcat(rst, " ");
494 free(prm);
497 if (show_ug == 1) {
498 ur = get_fusr(cursor->user, urlen);
499 gr = get_fgrp(cursor->group, grlen);
500 strncat(rst, ur, urlen);
501 strcat(rst, ":");
502 strncat(rst, gr, grlen);
503 strcat(rst, " ");
504 free(ur);
505 free(gr);
508 if (show_dt == 1) {
509 td = ecalloc(tdlen, sizeof(char));
510 if (get_fdt(td, tdlen, cursor->td) > 0) {
511 strncat(rst, td, tdlen);
512 strcat(rst, " ");
514 free(td);
517 if (show_size == 1 && S_ISREG(cursor->mode)) {
518 sz = get_fsize(cursor->size);
519 strncat(rst, sz, szlen);
520 free(sz);
523 return rst;
526 static char *
527 get_fperm(mode_t mode)
529 char *buf;
530 size_t i;
532 const char chars[] = "rwxrwxrwx";
533 buf = ecalloc(11, sizeof(char));
535 if (S_ISDIR(mode))
536 buf[0] = 'd';
537 else if (S_ISREG(mode))
538 buf[0] = '-';
539 else if (S_ISLNK(mode))
540 buf[0] = 'l';
541 else if (S_ISBLK(mode))
542 buf[0] = 'b';
543 else if (S_ISCHR(mode))
544 buf[0] = 'c';
545 else if (S_ISFIFO(mode))
546 buf[0] = 'p';
547 else if (S_ISSOCK(mode))
548 buf[0] = 's';
549 else
550 buf[0] = '?';
552 for (i = 1; i < 10; i++) {
553 buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-';
555 buf[10] = '\0';
557 return buf;
560 static char *
561 get_fsize(off_t size)
563 /* need to be freed */
564 char *Rsize;
565 float lsize;
566 int counter;
567 counter = 0;
569 Rsize = ecalloc(10, sizeof(char));
570 lsize = (float)size;
572 while (lsize >= 1000.0) {
573 lsize /= 1024.0;
574 ++counter;
577 float_to_string(lsize, Rsize);
579 switch (counter) {
580 case 0:
581 strcat(Rsize, "B");
582 break;
583 case 1:
584 strcat(Rsize, "K");
585 break;
586 case 2:
587 strcat(Rsize, "M");
588 break;
589 case 3:
590 strcat(Rsize, "G");
591 break;
592 case 4:
593 strcat(Rsize, "T");
594 break;
597 return Rsize;
600 static char *
601 get_fullpath(char *first, char *second)
603 char *full_path;
604 size_t full_path_len;
606 full_path_len = strlen(first) + strlen(second) + 2;
607 full_path = ecalloc(full_path_len, sizeof(char));
609 if (strcmp(first, "/") == 0) {
610 (void)snprintf(full_path, full_path_len, "/%s", second);
612 } else {
613 (void)snprintf(full_path, full_path_len, "%s/%s", first,
614 second);
617 return full_path;
620 static char *
621 get_fusr(uid_t status, size_t len)
623 char *result;
624 struct passwd *pw;
626 result = ecalloc(len, sizeof(char));
627 pw = getpwuid(status);
628 if (pw == NULL)
629 (void)snprintf(result, len - 1, "%u", status);
630 else
631 strncpy(result, pw->pw_name, len - 1);
633 return result;
636 static void
637 get_dirsize(char *fullpath, off_t *fullsize)
639 DIR *dir;
640 char *ent_full;
641 mode_t mode;
642 struct dirent *entry;
643 struct stat status;
645 dir = opendir(fullpath);
646 if (dir == NULL) {
647 return;
650 while ((entry = readdir(dir)) != 0) {
651 if ((strcmp(entry->d_name, ".") == 0 ||
652 strcmp(entry->d_name, "..") == 0))
653 continue;
655 ent_full = get_fullpath(fullpath, entry->d_name);
656 if (lstat(ent_full, &status) == 0) {
657 mode = status.st_mode;
658 if (S_ISDIR(mode)) {
659 get_dirsize(ent_full, fullsize);
660 free(ent_full);
661 } else {
662 *fullsize += status.st_size;
663 free(ent_full);
668 closedir(dir);
669 clear_status();
672 static void
673 get_hicol(Cpair *col, mode_t mode)
675 *col = cfile;
676 if (S_ISDIR(mode))
677 *col = cdir;
678 else if (S_ISLNK(mode))
679 *col = cother;
680 else if (chech_execf(mode) > 0)
681 *col = cexec;
684 static int
685 deldir(char *fullpath, int delchoice)
687 if (delchoice == AskDel) {
688 char *confirmation;
689 confirmation = ecalloc(2, sizeof(char));
690 if ((get_usrinput(confirmation, 2,
691 "delete directory (Y) ?") < 0) ||
692 (strcmp(confirmation, "Y") != 0)) {
693 free(confirmation);
694 return 1; /* canceled by user or wrong confirmation */
696 free(confirmation);
699 if (rmdir(fullpath) == 0)
700 return 0; /* empty directory */
702 DIR *dir;
703 char *ent_full;
704 mode_t mode;
705 struct dirent *entry;
706 struct stat status;
708 dir = opendir(fullpath);
709 if (dir == NULL) {
710 return -1;
713 while ((entry = readdir(dir)) != 0) {
714 if ((strcmp(entry->d_name, ".") == 0 ||
715 strcmp(entry->d_name, "..") == 0))
716 continue;
718 ent_full = get_fullpath(fullpath, entry->d_name);
719 if (lstat(ent_full, &status) == 0) {
720 mode = status.st_mode;
721 if (S_ISDIR(mode)) {
722 if (deldir(ent_full, DAskDel) < 0) {
723 free(ent_full);
724 return -1;
726 } else if (S_ISREG(mode)) {
727 if (unlink(ent_full) < 0) {
728 free(ent_full);
729 return -1;
733 free(ent_full);
736 print_status(cstatus, "gotit");
737 if (closedir(dir) < 0)
738 return -1;
740 return rmdir(fullpath); /* directory after delete all entries */
743 static int
744 delent(char *fullpath)
746 struct stat status;
747 mode_t mode;
749 if (lstat(fullpath, &status) < 0)
750 return -1;
752 mode = status.st_mode;
753 if (S_ISDIR(mode)) {
754 return deldir(fullpath, AskDel);
755 } else {
756 return delf(fullpath);
760 static int
761 delf(char *fullpath)
763 char *confirmation;
764 confirmation = ecalloc(2, sizeof(char));
766 if ((get_usrinput(confirmation, 2, "delete file (Y) ?") < 0) ||
767 (strcmp(confirmation, "Y") != 0)) {
768 free(confirmation);
769 return 1; /* canceled by user or wrong confirmation */
772 free(confirmation);
773 return unlink(fullpath);
776 static void
777 calcdir(void)
779 off_t *fullsize;
780 char *csize;
781 char *result;
783 if (S_ISDIR(cpane->direntr[cpane->hdir - 1].mode)) {
784 fullsize = ecalloc(50, sizeof(off_t));
785 get_dirsize(cpane->direntr[cpane->hdir - 1].name, fullsize);
786 csize = get_fsize(*fullsize);
787 result = get_finfo(&cpane->direntr[cpane->hdir - 1]);
789 clear_status();
790 print_status(cstatus, "%d/%d %s%s", cpane->hdir, cpane->dirc,
791 result, csize);
792 free(csize);
793 free(fullsize);
794 free(result);
798 static void
799 crnd(void)
801 char *user_input, *path;
802 size_t pathlen;
804 user_input = ecalloc(MAX_USRI, sizeof(char));
805 if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
806 free(user_input);
807 return;
810 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
811 path = ecalloc(pathlen, sizeof(char));
812 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
813 free(user_input);
814 free(path);
815 return;
818 if (mkdir(path, ndir_perm) < 0)
819 print_error(strerror(errno));
820 else if (listdir(AddHi, NULL) < 0)
821 print_error(strerror(errno));
823 free(user_input);
824 free(path);
827 static void
828 crnf(void)
830 char *user_input, *path;
831 size_t pathlen;
832 int rf;
834 user_input = ecalloc(MAX_USRI, sizeof(char));
835 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
836 free(user_input);
837 return;
840 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
841 path = ecalloc(pathlen, sizeof(char));
842 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
843 free(user_input);
844 free(path);
845 return;
848 rf = open(path, O_CREAT | O_EXCL, nf_perm);
850 if (rf < 0) {
851 print_error(strerror(errno));
852 } else {
853 if (close(rf) < 0)
854 print_error(strerror(errno));
855 else if (listdir(AddHi, NULL) < 0)
856 print_error(strerror(errno));
859 free(user_input);
860 free(path);
863 static void
864 delfd(void)
866 switch (delent(cpane->direntr[cpane->hdir - 1].name)) {
867 case -1:
868 print_error(strerror(errno));
869 break;
870 case 0:
871 if (BETWEEN(cpane->hdir - 1, 1, cpane->dirc)) /* last entry */
872 cpane->hdir--;
873 if (listdir(AddHi, NULL) < 0)
874 print_error(strerror(errno));
875 break;
879 static void
880 mvbk(void)
882 rmwatch(cpane);
883 chdir("..");
884 getcwd(cpane->dirn, MAX_P);
885 cpane->firstrow = 0;
886 cpane->hdir = cpane->parent_row;
887 if (listdir(AddHi, NULL) < 0)
888 print_error(strerror(errno));
889 cpane->parent_row = 1;
892 static void
893 mvbtm(void)
895 if (cpane->dirc < 1)
896 return;
897 if (cpane->dirc > scrheight) {
898 clear_pane(cpane->dirx);
899 rm_hi(cpane, cpane->hdir - 1);
900 cpane->hdir = cpane->dirc;
901 cpane->firstrow = cpane->dirc - scrheight + 1;
902 refresh_pane();
903 add_hi(cpane, cpane->hdir - 1);
904 } else {
905 rm_hi(cpane, cpane->hdir - 1);
906 cpane->hdir = cpane->dirc;
907 add_hi(cpane, cpane->hdir - 1);
909 print_info();
912 static void
913 mvdwn(void)
915 if (cpane->dirc < 1)
916 return;
917 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
918 rm_hi(cpane, cpane->hdir - 1);
919 cpane->hdir++;
920 add_hi(cpane, cpane->hdir - 1);
921 } else {
922 mvdwns(); /* scroll */
924 print_info();
927 static void
928 mvdwns(void)
930 int real;
931 real = cpane->hdir - 1 - cpane->firstrow;
933 if (real > scrheight - 3 - scrsp && cpane->hdir + scrsp < cpane->dirc) {
934 cpane->firstrow++;
935 clear_pane(cpane->dirx);
936 rm_hi(cpane, cpane->hdir - 1);
937 cpane->hdir++;
938 refresh_pane();
939 add_hi(cpane, cpane->hdir - 1);
940 } else if (cpane->hdir < cpane->dirc) {
941 rm_hi(cpane, cpane->hdir - 1);
942 cpane->hdir++;
943 add_hi(cpane, cpane->hdir - 1);
947 static void
948 mvfor(void)
950 rmwatch(cpane);
951 if (cpane->dirc < 1)
952 return;
953 int s;
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 cpane->parent_row = cpane->hdir;
960 cpane->hdir = 1;
961 cpane->firstrow = 0;
962 if (listdir(AddHi, NULL) < 0)
963 print_error(strerror(errno));
964 break;
965 case 1: /* not a directory open file */
966 tb_shutdown();
967 s = opnf(cpane->direntr[cpane->hdir - 1].name);
968 if (tb_init() != 0)
969 die("tb_init");
970 t_resize();
971 if (s < 0)
972 print_error("process failed non-zero exit");
973 break;
974 case -1: /* failed to open directory */
975 print_error(strerror(errno));
979 static void
980 mvmid(void)
982 if (cpane->dirc < 1)
983 return;
984 rm_hi(cpane, cpane->hdir - 1);
985 if (cpane->dirc < scrheight / 2)
986 cpane->hdir = (cpane->dirc + 1) / 2;
987 else
988 cpane->hdir = (scrheight / 2) + cpane->firstrow;
989 add_hi(cpane, cpane->hdir - 1);
990 print_info();
993 static void
994 mvtop(void)
996 if (cpane->dirc < 1)
997 return;
998 if (cpane->dirc > scrheight) {
999 clear_pane(cpane->dirx);
1000 rm_hi(cpane, cpane->hdir - 1);
1001 cpane->hdir = 1;
1002 cpane->firstrow = 0;
1003 refresh_pane();
1004 add_hi(cpane, cpane->hdir - 1);
1005 } else {
1006 rm_hi(cpane, cpane->hdir - 1);
1007 cpane->hdir = 1;
1008 add_hi(cpane, cpane->hdir - 1);
1009 print_info();
1013 static void
1014 mvup(void)
1016 if (cpane->dirc < 1)
1017 return;
1018 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1019 rm_hi(cpane, cpane->hdir - 1);
1020 cpane->hdir--;
1021 add_hi(cpane, cpane->hdir - 1);
1022 } else {
1023 mvups(); /* scroll */
1025 print_info();
1028 static void
1029 mvups(void)
1031 size_t real;
1032 real = cpane->hdir - 1 - cpane->firstrow;
1034 if (cpane->firstrow > 0 && real < 1 + scrsp) {
1035 cpane->firstrow--;
1036 clear_pane(cpane->dirx);
1037 rm_hi(cpane, cpane->hdir - 1);
1038 cpane->hdir--;
1039 refresh_pane();
1040 add_hi(cpane, cpane->hdir - 1);
1041 } else if (cpane->hdir > 1) {
1042 rm_hi(cpane, cpane->hdir - 1);
1043 cpane->hdir--;
1044 add_hi(cpane, cpane->hdir - 1);
1048 static void
1049 scrdwn(void)
1051 if (cpane->dirc < 1)
1052 return;
1053 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
1054 if (cpane->hdir < cpane->dirc - scrmv) {
1055 rm_hi(cpane, cpane->hdir - 1);
1056 cpane->hdir += scrmv;
1057 add_hi(cpane, cpane->hdir - 1);
1058 } else {
1059 mvbtm();
1061 } else {
1062 scrdwns();
1064 print_info();
1067 static void
1068 scrdwns(void)
1070 int real, dynmv;
1072 real = cpane->hdir - cpane->firstrow;
1073 dynmv = MIN(cpane->dirc - cpane->hdir - cpane->firstrow, scrmv);
1075 if (real + scrmv + 1 > scrheight &&
1076 cpane->hdir + scrsp + scrmv < cpane->dirc) { /* scroll */
1077 cpane->firstrow += dynmv;
1078 clear_pane(cpane->dirx);
1079 rm_hi(cpane, cpane->hdir - 1);
1080 cpane->hdir += scrmv;
1081 refresh_pane();
1082 add_hi(cpane, cpane->hdir - 1);
1083 } else {
1084 if (cpane->hdir < cpane->dirc - scrmv - 1) {
1085 rm_hi(cpane, cpane->hdir - 1);
1086 cpane->hdir += scrmv;
1087 add_hi(cpane, cpane->hdir - 1);
1088 } else {
1089 mvbtm();
1094 static void
1095 scrup(void)
1097 if (cpane->dirc < 1)
1098 return;
1099 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1100 if (cpane->hdir > scrmv) {
1101 rm_hi(cpane, cpane->hdir - 1);
1102 cpane->hdir = cpane->hdir - scrmv;
1103 add_hi(cpane, cpane->hdir - 1);
1104 print_info();
1105 } else {
1106 mvtop();
1108 } else {
1109 scrups();
1113 static void
1114 scrups(void)
1116 int real, dynmv;
1117 real = cpane->hdir - cpane->firstrow;
1118 dynmv = MIN(cpane->firstrow, scrmv);
1120 if (cpane->firstrow > 0 && real < scrmv + scrsp) {
1121 cpane->firstrow -= dynmv;
1122 clear_pane(cpane->dirx);
1123 rm_hi(cpane, cpane->hdir - 1);
1124 cpane->hdir -= scrmv;
1125 refresh_pane();
1126 add_hi(cpane, cpane->hdir - 1);
1127 } else {
1128 if (cpane->hdir > scrmv + 1) {
1129 rm_hi(cpane, cpane->hdir - 1);
1130 cpane->hdir -= scrmv;
1131 add_hi(cpane, cpane->hdir - 1);
1132 } else {
1133 mvtop();
1138 static int
1139 get_usrinput(char *out, size_t sout, char *prompt)
1141 struct tb_event fev;
1142 size_t startat, counter;
1143 char empty;
1144 int x;
1146 startat = strlen(prompt) + 1;
1147 counter = 1;
1148 empty = ' ';
1149 x = 0;
1151 clear_status();
1152 print_prompt(prompt);
1153 tb_set_cursor((startat + 1), theight - 1);
1154 tb_present();
1156 while (tb_poll_event(&fev) != 0) {
1157 switch (fev.type) {
1158 case TB_EVENT_KEY:
1159 if (fev.key == TB_KEY_ESC) {
1160 tb_set_cursor(-1, -1);
1161 clear_status();
1162 return -1;
1165 if (fev.key == TB_KEY_BACKSPACE ||
1166 fev.key == TB_KEY_BACKSPACE2) {
1167 if (BETWEEN(counter, 2, sout)) {
1168 out[x - 1] = '\0';
1169 counter--;
1170 x--;
1171 print_xstatus(empty, startat + counter);
1172 tb_set_cursor(startat + counter,
1173 theight - 1);
1176 } else if (fev.key == TB_KEY_ENTER) {
1177 tb_set_cursor(-1, -1);
1178 out[counter - 1] = '\0';
1179 return 0;
1181 } else {
1182 if (counter < sout) {
1183 print_xstatus((char)fev.ch,
1184 (startat + counter));
1185 out[x] = (char)fev.ch;
1186 tb_set_cursor((startat + counter + 1),
1187 theight - 1);
1188 counter++;
1189 x++;
1193 tb_present();
1194 break;
1196 default:
1197 return -1;
1201 return -1;
1204 static int
1205 frules(char *ex)
1207 size_t c, d;
1209 for (c = 0; c < LEN(rules); c++)
1210 for (d = 0; d < rules[c].exlen; d++)
1211 if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0)
1212 return c;
1213 return -1;
1216 static int
1217 spawn(const void *v, char *fn)
1219 int ws, x, argc;
1220 pid_t pid, r;
1222 x = 0;
1223 argc = 0;
1225 /* count args */
1226 while (((char **)v)[x++] != NULL)
1227 argc++;
1229 char *argv[argc + 2];
1230 for ( x = 0; x < argc; x++)
1231 argv[x] = ((char **)v)[x];
1233 argv[argc] = fn;
1234 argv[argc + 1] = NULL;
1236 pid = fork();
1237 switch (pid) {
1238 case -1:
1239 return -1;
1240 case 0:
1241 execvp(argv[0], argv);
1242 exit(EXIT_SUCCESS);
1243 default:
1244 while ((r = waitpid(pid, &ws, 0)) == -1 && errno == EINTR)
1245 continue;
1246 if (r == -1)
1247 return -1;
1248 if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
1249 return -1;
1251 return 0;
1254 static int
1255 opnf(char *fn)
1257 char *ex;
1258 int c;
1260 ex = get_ext(fn);
1261 c = frules(ex);
1262 free(ex);
1264 if (c < 0) /* extension not found open in editor */
1265 return spawn(editor, fn);
1266 else
1267 return spawn((char **)rules[c].v, fn);
1270 static int
1271 fsev_init(void)
1273 #if defined _SYS_INOTIFY_H
1274 inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
1275 if (inotify_fd < 0)
1276 return -1;
1277 #elif defined _SYS_EVENT_H_
1278 kq = kqueue();
1279 if (kq < 0)
1280 return -1;
1281 #endif
1282 return 0;
1285 static int
1286 addwatch(void)
1288 #if defined _SYS_INOTIFY_H
1289 return cpane->inotify_wd = inotify_add_watch(inotify_fd, cpane->dirn,
1290 IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
1291 IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF);
1292 #elif defined _SYS_EVENT_H_
1293 cpane->event_fd = open(cpane->dirn, O_RDONLY);
1294 if (cpane->event_fd < 0)
1295 return cpane->event_fd;
1296 EV_SET(&evlist[cpane->pane_id], cpane->event_fd,
1297 EVFILT_VNODE, EV_ADD | EV_CLEAR,
1298 NOTE_DELETE | NOTE_EXTEND | NOTE_LINK |
1299 NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE, 0, NULL);
1300 return 0;
1301 #endif
1304 static int
1305 read_events(void)
1307 #if defined _SYS_INOTIFY_H
1308 char *p;
1309 ssize_t r;
1310 struct inotify_event *event;
1311 const size_t events = 32;
1312 const size_t evbuflen =
1313 events * (sizeof(struct inotify_event) + MAX_N + 1);
1314 char buf[evbuflen];
1316 if (cpane->inotify_wd < 0)
1317 return -1;
1318 r = read(inotify_fd, buf, evbuflen);
1319 if (r <= 0)
1320 return r;
1322 for (p = buf; p < buf + r;) {
1323 event = (struct inotify_event *)p;
1324 if (!event->wd)
1325 break;
1326 if (event->mask) {
1327 return r;
1330 p += sizeof(struct inotify_event) + event->len;
1332 #elif defined _SYS_EVENT_H_
1333 return kevent(kq, evlist, 2, chlist, 2, &gtimeout);
1334 #endif
1335 return -1;
1338 static void
1339 rmwatch(Pane *pane)
1341 #if defined _SYS_INOTIFY_H
1342 if (pane->inotify_wd >= 0)
1343 inotify_rm_watch(inotify_fd, pane->inotify_wd);
1344 #elif defined _SYS_EVENT_H_
1345 close(pane->event_fd);
1346 return;
1347 #endif
1350 static void
1351 fsev_shdn(void)
1353 rmwatch(&pane_l);
1354 rmwatch(&pane_r);
1355 #if defined _SYS_INOTIFY_H
1356 close(inotify_fd);
1357 #elif defined _SYS_EVENT_H_
1358 close(kq);
1359 #endif
1362 static ssize_t
1363 findbm(uint32_t event)
1365 ssize_t i;
1367 for (i = 0; i < (ssize_t)LEN(bmarks); i++) {
1368 if (event == bmarks[i].ch) {
1369 if (check_dir(bmarks[i].path) != 0) {
1370 print_error(strerror(errno));
1371 return -1;
1373 return i;
1376 return -1;
1379 static void
1380 filter(void)
1382 if (cpane->dirc < 1)
1383 return;
1384 char *user_input;
1385 user_input = ecalloc(MAX_USRI, sizeof(char));
1386 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1387 free(user_input);
1388 return;
1390 if (listdir(AddHi, user_input) < 0)
1391 print_error("no match");
1392 free(user_input);
1395 static void
1396 switch_pane(void)
1398 if (cpane->dirc > 0)
1399 rm_hi(cpane, cpane->hdir - 1);
1400 if (cpane == &pane_l)
1401 cpane = &pane_r;
1402 else if (cpane == &pane_r)
1403 cpane = &pane_l;
1404 chdir(cpane->dirn);
1405 if (cpane->dirc > 0) {
1406 add_hi(cpane, cpane->hdir - 1);
1407 print_info();
1408 } else {
1409 clear_status();
1413 static void
1414 quit(void)
1416 free(pane_l.direntr);
1417 free(pane_r.direntr);
1418 fsev_shdn();
1419 tb_shutdown();
1420 exit(EXIT_SUCCESS);
1423 static void
1424 grabkeys(struct tb_event *event)
1426 size_t i;
1427 ssize_t b;
1429 for (i = 0; i < LEN(keys); i++) {
1430 if (event->ch != 0) {
1431 if (event->ch == keys[i].evkey.ch) {
1432 keys[i].func();
1433 return;
1435 } else if (event->key != 0) {
1436 if (event->key == keys[i].evkey.key) {
1437 keys[i].func();
1438 return;
1443 /* bookmarks */
1444 b = findbm(event->ch);
1445 if (b < 0)
1446 return;
1447 rmwatch(cpane);
1448 strcpy(cpane->dirn, bmarks[b].path);
1449 cpane->firstrow = 0;
1450 cpane->parent_row = 1;
1451 cpane->hdir = 1;
1452 if (listdir(AddHi, NULL) < 0)
1453 print_error(strerror(errno));
1456 static void
1457 start_ev(void)
1459 struct tb_event ev;
1461 for (;;) {
1462 int t = tb_peek_event(&ev, 2000);
1463 if (t < 0) {
1464 tb_shutdown();
1465 return;
1468 if (t == 1) /* keyboard event */
1469 grabkeys(&ev);
1470 else if (t == 2) /* resize event */
1471 t_resize();
1472 else if (t == 0) /* filesystem event */
1473 if (read_events() > 0)
1474 if (listdir(AddHi, NULL) < 0)
1475 print_error(strerror(errno));
1477 tb_present();
1478 continue;
1480 tb_shutdown();
1483 static void
1484 refresh_pane(void)
1486 size_t y, dyn_max, start_from;
1487 int width;
1488 width = (twidth / 2) - 4;
1489 Cpair col;
1491 y = 1;
1492 start_from = cpane->firstrow;
1493 dyn_max = MIN(cpane->dirc, (scrheight - 1) + cpane->firstrow);
1495 /* print each entry in directory */
1496 while (start_from < dyn_max) {
1497 get_hicol(&col, cpane->direntr[start_from].mode);
1498 print_row(cpane, start_from, col);
1499 start_from++;
1500 y++;
1503 if (cpane->dirc > 0)
1504 print_info();
1505 else
1506 clear_status();
1508 /* print current directory title */
1509 cpane->dircol.fg |= TB_BOLD;
1510 printf_tb(cpane->dirx, 0, cpane->dircol, " %.*s ", width, cpane->dirn);
1513 static int
1514 listdir(int hi, char *filter)
1516 DIR *dir;
1517 struct dirent *entry;
1518 struct stat status;
1519 int width;
1520 size_t i;
1521 int filtercount = 0;
1522 size_t oldc = cpane->dirc;
1524 width = (twidth / 2) - 4;
1525 cpane->dirc = 0;
1526 i = 0;
1528 if (chdir(cpane->dirn) < 0)
1529 return -1;
1531 dir = opendir(cpane->dirn);
1532 if (dir == NULL)
1533 return -1;
1535 /* get content and filter sum */
1536 while ((entry = readdir(dir)) != 0) {
1537 if (filter != NULL) {
1538 if (strstr(entry->d_name, filter) != NULL)
1539 filtercount++;
1540 } else { /* no filter */
1541 cpane->dirc++;
1545 if (filter == NULL) {
1546 clear_pane(cpane->dirx);
1547 cpane->dirc -= 2;
1550 if (filter != NULL) {
1551 if (filtercount > 0) {
1552 cpane->dirc -= 2;
1553 cpane->dirc = filtercount;
1554 clear_pane(cpane->dirx);
1555 cpane->hdir = 1;
1556 } else if (filtercount == 0) {
1557 if (closedir(dir) < 0)
1558 return -1;
1559 cpane->dirc = oldc;
1560 return -1;
1564 /* print current directory title */
1565 cpane->dircol.fg |= TB_BOLD;
1566 printf_tb(cpane->dirx, 0, cpane->dircol, " %.*s ", width, cpane->dirn);
1568 if (addwatch() < 0)
1569 print_error("can't add watch");
1571 /* empty directory */
1572 if (cpane->dirc == 0) {
1573 clear_status();
1574 if (closedir(dir) < 0)
1575 return -1;
1576 return 0;
1579 rewinddir(dir); /* reset position */
1581 /* create array of entries */
1582 i = 0;
1583 cpane->direntr = erealloc(cpane->direntr, cpane->dirc * sizeof(Entry));
1584 while ((entry = readdir(dir)) != 0) {
1585 if ((strcmp(entry->d_name, ".") == 0 ||
1586 strcmp(entry->d_name, "..") == 0))
1587 continue;
1589 /* list found filter */
1590 if (filter != NULL) {
1591 if (strstr(entry->d_name, filter) != NULL) {
1592 strcpy(cpane->direntr[i].name, entry->d_name);
1593 if (lstat(entry->d_name, &status) == 0) {
1594 cpane->direntr[i].size = status.st_size;
1595 cpane->direntr[i].mode = status.st_mode;
1596 cpane->direntr[i].group = status.st_gid;
1597 cpane->direntr[i].user = status.st_uid;
1598 cpane->direntr[i].td = status.st_mtime;
1600 i++;
1603 } else {
1604 strcpy(cpane->direntr[i].name, entry->d_name);
1605 if (lstat(entry->d_name, &status) == 0) {
1606 cpane->direntr[i].size = status.st_size;
1607 cpane->direntr[i].mode = status.st_mode;
1608 cpane->direntr[i].group = status.st_gid;
1609 cpane->direntr[i].user = status.st_uid;
1610 cpane->direntr[i].td = status.st_mtime;
1612 i++;
1616 cpane->dirc = i;
1617 qsort(cpane->direntr, cpane->dirc, sizeof(Entry), sort_name);
1618 refresh_pane();
1620 if (hi == AddHi)
1621 add_hi(cpane, cpane->hdir - 1);
1623 if (closedir(dir) < 0)
1624 return -1;
1625 return 0;
1628 static void
1629 t_resize(void)
1631 /* TODO need refactoring */
1632 tb_clear();
1633 draw_frame();
1634 pane_r.dirx = (twidth / 2) + 2;
1636 if (cpane == &pane_l) {
1637 chdir(pane_r.dirn);
1638 cpane = &pane_r;
1639 refresh_pane();
1640 chdir(pane_l.dirn);
1641 cpane = &pane_l;
1642 refresh_pane();
1643 if (cpane->dirc > 0)
1644 add_hi(&pane_l, pane_l.hdir - 1);
1645 } else if (cpane == &pane_r) {
1646 chdir(pane_l.dirn);
1647 cpane = &pane_l;
1648 refresh_pane();
1649 chdir(pane_r.dirn);
1650 cpane = &pane_r;
1651 refresh_pane();
1652 if (cpane->dirc > 0)
1653 add_hi(&pane_r, pane_r.hdir - 1);
1656 tb_present();
1659 static void
1660 get_editor(void)
1662 editor[0] = getenv("EDITOR");
1663 editor[1] = NULL;
1665 if (editor[0] == NULL)
1666 editor[0] = fed;
1669 static void
1670 set_panes(void)
1672 char *home;
1673 char cwd[MAX_P];
1675 home = getenv("HOME");
1676 if (home == NULL)
1677 home = "/";
1678 if ((getcwd(cwd, sizeof(cwd)) == NULL))
1679 strncpy(cwd, home, MAX_P);
1681 pane_l.pane_id = 0;
1682 pane_l.dirx = 2;
1683 pane_l.dircol = cpanell;
1684 pane_l.firstrow = 0;
1685 pane_l.direntr = ecalloc(0, sizeof(Entry));
1686 strcpy(pane_l.dirn, cwd);
1687 pane_l.hdir = 1;
1688 pane_l.inotify_wd = -1;
1689 pane_l.parent_row = 1;
1691 pane_r.pane_id = 1;
1692 pane_r.dirx = (twidth / 2) + 2;
1693 pane_r.dircol = cpanelr;
1694 pane_r.firstrow = 0;
1695 pane_r.direntr = ecalloc(0, sizeof(Entry));
1696 strcpy(pane_r.dirn, home);
1697 pane_r.hdir = 1;
1698 pane_r.inotify_wd = -1;
1699 pane_r.parent_row = 1;
1702 static void
1703 draw_frame(void)
1705 int i;
1706 theight = tb_height();
1707 twidth = tb_width();
1708 scrheight = theight - 2;
1710 /* 2 horizontal lines */
1711 for (i = 1; i < twidth - 1; ++i) {
1712 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1713 tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg);
1716 /* 3 vertical lines */
1717 for (i = 1; i < theight - 1; ++i) {
1718 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1719 tb_change_cell((twidth - 1) / 2, i - 1, u_vl, cframe.fg,
1720 cframe.bg);
1721 tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg);
1724 /* 4 corners */
1725 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1726 tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg);
1727 tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg);
1728 tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg);
1730 /* 2 middel top and bottom */
1731 tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg);
1732 tb_change_cell((twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg);
1735 static void
1736 start(void)
1738 if (tb_init() != 0)
1739 die("tb_init");
1740 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
1741 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
1742 die("output error");
1744 draw_frame();
1745 set_panes();
1746 get_editor();
1747 if (fsev_init() < 0)
1748 print_error(strerror(errno));
1749 cpane = &pane_r;
1750 if (listdir(NoHi, NULL) < 0)
1751 print_error(strerror(errno));
1752 cpane = &pane_l;
1753 if (listdir(AddHi, NULL) < 0)
1754 print_error(strerror(errno));
1755 tb_present();
1756 start_ev();
1760 main(int argc, char *argv[])
1762 #ifdef __OpenBSD__
1763 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
1764 NULL) == -1)
1765 die("pledge");
1766 #endif /* __OpenBSD__ */
1767 if (argc == 1)
1768 start();
1769 else if (argc == 2 && strncmp("-v", argv[1], 2) == 0)
1770 die("sfm-" VERSION);
1771 else
1772 die("usage: sfm [-v]");
1773 return 0;