[feat] toggle dotfiles with . key (#17)
[sfm.git] / sfm.c
blob63260ee3d9c15cbc080ad8a63ccf274896b28c59
1 /* See LICENSE file for copyright and license details. */
3 #if defined(__linux__)
4 #define _GNU_SOURCE
5 #elif defined(__APPLE__)
6 #define _DARWIN_C_SOURCE
7 #elif defined(__FreeBSD__)
8 #define __BSD_VISIBLE 1
9 #endif
10 #include <sys/types.h>
11 #include <sys/resource.h>
12 #include <sys/stat.h>
13 #include <sys/time.h>
14 #include <sys/wait.h>
15 #if defined(__linux__)
16 #include <sys/inotify.h>
17 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
18 defined(__APPLE__)
19 #include <sys/event.h>
20 #endif
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <grp.h>
26 #include <libgen.h>
27 #include <pthread.h>
28 #include <pwd.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
38 #include "termbox.h"
39 #include "util.h"
41 /* macros */
42 #define MAX_P 4096
43 #define MAX_N 255
44 #define MAX_USRI 32
45 #define MAX_EXT 4
46 #define MAX_STATUS 255
47 #define MAX_LINE 4096
48 #define MAX_USRN 32
49 #define MAX_GRPN 32
50 #define MAX_DTF 32
51 #define CURSOR(x) (x)->direntr[(x)->hdir - 1]
53 /* typedef */
54 typedef struct {
55 char name[MAX_N];
56 gid_t group;
57 mode_t mode;
58 off_t size;
59 time_t dt;
60 uid_t user;
61 } Entry;
63 typedef struct {
64 uint16_t fg;
65 uint16_t bg;
66 } Cpair;
68 typedef struct {
69 int pane_id;
70 char dirn[MAX_P]; // dir name cwd
71 char *filter;
72 Entry *direntr; // dir entries
73 int dirx; // pane cwd x pos
74 int dirc; // dir entries sum
75 int hdir; // highlighted dir
76 int firstrow;
77 int parent_firstrow;
78 int parent_row; // FIX
79 Cpair dircol;
80 int inotify_wd;
81 int event_fd;
82 } Pane;
84 typedef struct {
85 const char **ext;
86 size_t exlen;
87 const void *v;
88 size_t vlen;
89 } Rule;
91 typedef union {
92 uint16_t key; /* one of the TB_KEY_* constants */
93 uint32_t ch; /* unicode character */
94 } Evkey;
96 typedef union {
97 int i;
98 const void *v;
99 } Arg;
101 typedef struct {
102 const Evkey evkey;
103 void (*func)(const Arg *);
104 const Arg arg;
105 } Key;
107 /* function declarations */
108 static void print_tb(const char *, int, int, uint16_t, uint16_t);
109 static void printf_tb(int, int, Cpair, const char *, ...);
110 static void print_status(Cpair, const char *, ...);
111 static void print_xstatus(char, int);
112 static void print_error(char *);
113 static void print_prompt(char *);
114 static void print_info(Pane *, char *);
115 static void print_row(Pane *, size_t, Cpair);
116 static void clear(int, int, int, uint16_t);
117 static void clear_status(void);
118 static void clear_pane(Pane *);
119 static void add_hi(Pane *, size_t);
120 static void rm_hi(Pane *, size_t);
121 static int check_dir(char *);
122 static mode_t chech_execf(mode_t);
123 static int sort_name(const void *const, const void *const);
124 static void get_dirp(char *);
125 static char *get_ext(char *);
126 static int get_fdt(char *, time_t);
127 static char *get_fgrp(gid_t);
128 static char *get_fperm(mode_t);
129 static char *get_fsize(off_t);
130 static char *get_fullpath(char *, char *);
131 static char *get_fusr(uid_t);
132 static void get_dirsize(char *, off_t *);
133 static void get_hicol(Cpair *, mode_t);
134 static void delent(const Arg *arg);
135 static void calcdir(const Arg *arg);
136 static void crnd(const Arg *arg);
137 static void crnf(const Arg *arg);
138 static void mv_ver(const Arg *arg);
139 static void mvbk(const Arg *arg);
140 static void mvbtm(const Arg *arg);
141 static void mvfwd(const Arg *arg);
142 static void mvmid(const Arg *arg);
143 static void mvtop(const Arg *arg);
144 static void bkmrk(const Arg *arg);
145 static int get_usrinput(char *, size_t, const char *, ...);
146 static int frules(char *);
147 static int spawn(const void *, size_t, const void *, size_t, char *);
148 static int opnf(char *);
149 static int fsev_init(void);
150 static int addwatch(Pane *);
151 static int read_events(void);
152 static void rmwatch(Pane *);
153 static void fsev_shdn(void);
154 static void toggle_df(const Arg *arg);
155 static void start_filter(const Arg *arg);
156 static void start_vmode(const Arg *arg);
157 static void exit_vmode(const Arg *arg);
158 static void selup(const Arg *arg);
159 static void seldwn(const Arg *arg);
160 static void selall(const Arg *arg);
161 static void selref(void);
162 static void selynk(const Arg *arg);
163 static void selcalc(void);
164 static void paste(const Arg *arg);
165 static void selmv(const Arg *arg);
166 static void seldel(const Arg *arg);
167 static void init_files(void);
168 static void free_files(void);
169 static void yank(const Arg *arg);
170 static void rname(const Arg *arg);
171 static void switch_pane(const Arg *arg);
172 static void quit(const Arg *arg);
173 static void grabkeys(struct tb_event *, Key *, size_t);
174 static void *read_th(void *arg);
175 static void start_ev(void);
176 static void refresh_pane(Pane *);
177 static void set_direntr(Pane *, struct dirent *, DIR *, char *);
178 static int listdir(Pane *);
179 static void t_resize(void);
180 static void set_panes(void);
181 static void draw_frame(void);
182 static void start(void);
184 /* global variables */
185 static pthread_t fsev_thread;
186 static Pane pane_r, pane_l, *cpane;
187 static char *editor[2];
188 static char fed[] = "vi";
189 static int theight, twidth, scrheight;
190 static int *sel_indexes;
191 static size_t sel_len = 0;
192 static char **sel_files;
193 static int cont_vmode = 0;
194 pid_t main_pid;
195 #if defined(_SYS_INOTIFY_H)
196 #define READEVSZ 16
197 static int inotify_fd;
198 #elif defined(_SYS_EVENT_H_)
199 #define READEVSZ 0
200 static int kq;
201 struct kevent evlist[2]; /* events we want to monitor */
202 struct kevent chlist[2]; /* events that were triggered */
203 static struct timespec gtimeout;
204 #endif
205 #if defined(__linux__) || defined(__FreeBSD__)
206 #define OFF_T "%ld"
207 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
208 #define OFF_T "%lld"
209 #endif
211 /* configuration, allows nested code to access above variables */
212 #include "config.h"
214 /* function implementations */
215 static void
216 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
218 while (*str != '\0') {
219 uint32_t uni = 0;
220 str += tb_utf8_char_to_unicode(&uni, str);
221 tb_change_cell(x, y, uni, fg, bg);
222 x++;
226 static void
227 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
229 char buf[MAX_LINE];
230 va_list vl;
231 va_start(vl, fmt);
232 (void)vsnprintf(buf, MAX_LINE, fmt, vl);
233 va_end(vl);
234 print_tb(buf, x, y, col.fg, col.bg);
237 static void
238 print_status(Cpair col, const char *fmt, ...)
240 char buf[MAX_STATUS];
241 va_list vl;
242 va_start(vl, fmt);
243 (void)vsnprintf(buf, MAX_STATUS, fmt, vl);
244 va_end(vl);
245 clear_status();
246 print_tb(buf, 1, theight - 1, col.fg, col.bg);
249 static void
250 print_xstatus(char c, int x)
252 uint32_t uni = 0;
253 (void)tb_utf8_char_to_unicode(&uni, &c);
254 tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg);
257 static void
258 print_error(char *errmsg)
260 print_status(cerr, errmsg);
263 static void
264 print_prompt(char *prompt)
266 print_status(cprompt, prompt);
269 static void
270 print_info(Pane *pane, char *dirsize)
272 char *sz, *ur, *gr, *dt, *prm;
274 dt = ecalloc(MAX_DTF, sizeof(char));
276 prm = get_fperm(CURSOR(pane).mode);
277 ur = get_fusr(CURSOR(pane).user);
278 gr = get_fgrp(CURSOR(pane).group);
280 if (get_fdt(dt, CURSOR(pane).dt) < 0)
281 *dt = '\0';
283 if (S_ISREG(CURSOR(pane).mode)) {
284 sz = get_fsize(CURSOR(pane).size);
285 } else {
286 if (dirsize == NULL) {
287 sz = ecalloc(1, sizeof(char));
288 *sz = '\0';
289 } else {
290 sz = dirsize;
294 print_status(cstatus, "%02d/%02d %s %s:%s %s %s", pane->hdir,
295 pane->dirc, prm, ur, gr, dt, sz);
297 free(prm);
298 free(ur);
299 free(gr);
300 free(dt);
301 free(sz);
304 static void
305 print_row(Pane *pane, size_t entpos, Cpair col)
307 int x, y;
308 char *result;
309 char buf[MAX_P];
310 char lnk_full[MAX_P];
311 int width;
313 width = (twidth / 2) - 4;
314 result = basename(pane->direntr[entpos].name);
315 x = pane->dirx;
316 y = entpos - pane->firstrow + 1;
318 if (S_ISLNK(pane->direntr[entpos].mode) != 0) {
319 if (realpath(pane->direntr[entpos].name, buf) != NULL) {
320 (void)snprintf(
321 lnk_full, MAX_N, "%s -> %s", result, buf);
322 result = lnk_full;
326 printf_tb(x, y, col, "%*.*s", ~width, width, result);
329 static void
330 clear(int sx, int ex, int y, uint16_t bg)
332 /* clear line from to */
333 /* x = line number vertical */
334 /* y = column number horizontal */
335 int i;
336 for (i = sx; i < ex; i++) {
337 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
341 static void
342 clear_status(void)
344 clear(1, twidth - 1, theight - 1, cstatus.bg);
347 static void
348 clear_pane(Pane *pane)
350 int i, ex, y;
351 y = 0, i = 0, ex = 0;
353 if (pane == &pane_l)
354 ex = (twidth / 2) - 1;
355 else if (pane == &pane_r)
356 ex = twidth - 1;
358 while (i < scrheight) {
359 clear(pane->dirx, ex, y, TB_DEFAULT);
360 i++;
361 y++;
364 /* draw top line */
365 for (y = pane->dirx; y < ex; ++y) {
366 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
370 static void
371 add_hi(Pane *pane, size_t entpos)
373 Cpair col;
374 get_hicol(&col, pane->direntr[entpos].mode);
375 col.fg |= TB_REVERSE | TB_BOLD;
376 col.bg |= TB_REVERSE;
377 print_row(pane, entpos, col);
380 static void
381 rm_hi(Pane *pane, size_t entpos)
383 Cpair col;
384 get_hicol(&col, pane->direntr[entpos].mode);
385 print_row(pane, entpos, col);
388 static int
389 check_dir(char *path)
391 DIR *dir;
392 dir = opendir(path);
394 if (dir == NULL) {
395 if (errno == ENOTDIR) {
396 return 1;
397 } else {
398 return -1;
402 if (closedir(dir) < 0)
403 return -1;
405 return 0;
408 static mode_t
409 chech_execf(mode_t mode)
411 if (S_ISREG(mode))
412 return (((S_IXUSR | S_IXGRP | S_IXOTH) & mode));
413 return 0;
416 static int
417 sort_name(const void *const A, const void *const B)
419 int result;
420 mode_t data1 = (*(Entry *)A).mode;
421 mode_t data2 = (*(Entry *)B).mode;
423 if (data1 < data2) {
424 return -1;
425 } else if (data1 == data2) {
426 result = strncmp((*(Entry *)A).name, (*(Entry *)B).name, MAX_N);
427 return result;
428 } else {
429 return 1;
433 static void
434 get_dirp(char *cdir)
436 int counter, len, i;
438 counter = 0;
439 len = strnlen(cdir, MAX_P);
440 if (len == 1)
441 return;
443 for (i = len - 1; i > 1; i--) {
444 if (cdir[i] == '/')
445 break;
446 else
447 counter++;
450 cdir[len - counter - 1] = '\0';
453 static char *
454 get_ext(char *str)
456 char *ext;
457 char dot;
458 size_t counter, len, i;
460 dot = '.';
461 counter = 0;
462 len = strnlen(str, MAX_N);
464 for (i = len - 1; i > 0; i--) {
465 if (str[i] == dot) {
466 break;
467 } else {
468 counter++;
472 ext = ecalloc(MAX_EXT + 1, sizeof(char));
473 strncpy(ext, &str[len - counter], MAX_EXT);
474 ext[MAX_EXT] = '\0';
475 return ext;
478 static int
479 get_fdt(char *result, time_t status)
481 struct tm lt;
482 localtime_r(&status, &lt);
483 return strftime(result, MAX_DTF, dtfmt, &lt);
486 static char *
487 get_fgrp(gid_t status)
489 char *result;
490 struct group *gr;
492 result = ecalloc(MAX_GRPN, sizeof(char));
493 gr = getgrgid(status);
494 if (gr == NULL)
495 (void)snprintf(result, MAX_GRPN, "%u", status);
496 else
497 strncpy(result, gr->gr_name, MAX_GRPN);
499 result[MAX_GRPN - 1] = '\0';
500 return result;
503 static char *
504 get_fperm(mode_t mode)
506 char *buf;
507 size_t i;
509 const char chars[] = "rwxrwxrwx";
510 buf = ecalloc(11, sizeof(char));
512 if (S_ISDIR(mode))
513 buf[0] = 'd';
514 else if (S_ISREG(mode))
515 buf[0] = '-';
516 else if (S_ISLNK(mode))
517 buf[0] = 'l';
518 else if (S_ISBLK(mode))
519 buf[0] = 'b';
520 else if (S_ISCHR(mode))
521 buf[0] = 'c';
522 else if (S_ISFIFO(mode))
523 buf[0] = 'p';
524 else if (S_ISSOCK(mode))
525 buf[0] = 's';
526 else
527 buf[0] = '?';
529 for (i = 1; i < 10; i++) {
530 buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-';
532 buf[10] = '\0';
534 return buf;
537 static char *
538 get_fsize(off_t size)
540 char *result; /* need to be freed */
541 char unit;
542 int result_len;
543 int counter;
545 counter = 0;
546 result_len = 6; /* 9999X/0 */
547 result = ecalloc(result_len, sizeof(char));
549 while (size >= 1000) {
550 size /= 1024;
551 ++counter;
554 switch (counter) {
555 case 0:
556 unit = 'B';
557 break;
558 case 1:
559 unit = 'K';
560 break;
561 case 2:
562 unit = 'M';
563 break;
564 case 3:
565 unit = 'G';
566 break;
567 case 4:
568 unit = 'T';
569 break;
570 default:
571 unit = '?';
574 if (snprintf(result, result_len, OFF_T "%c", size, unit) < 0)
575 strncat(result, "???", result_len);
577 return result;
580 static char *
581 get_fullpath(char *first, char *second)
583 char *full_path;
585 full_path = ecalloc(MAX_P, sizeof(char));
587 if (strncmp(first, "/", MAX_P) == 0)
588 (void)snprintf(full_path, MAX_P, "/%s", second);
589 else
590 (void)snprintf(full_path, MAX_P, "%s/%s", first, second);
592 return full_path;
595 static char *
596 get_fusr(uid_t status)
598 char *result;
599 struct passwd *pw;
601 result = ecalloc(MAX_USRN, sizeof(char));
602 pw = getpwuid(status);
603 if (pw == NULL)
604 (void)snprintf(result, MAX_USRN, "%u", status);
605 else
606 strncpy(result, pw->pw_name, MAX_USRN);
608 result[MAX_USRN - 1] = '\0';
609 return result;
612 static void
613 get_dirsize(char *fullpath, off_t *fullsize)
615 DIR *dir;
616 char *ent_full;
617 mode_t mode;
618 struct dirent *entry;
619 struct stat status;
621 dir = opendir(fullpath);
622 if (dir == NULL) {
623 return;
626 while ((entry = readdir(dir)) != 0) {
627 if ((strncmp(entry->d_name, ".", 2) == 0 ||
628 strncmp(entry->d_name, "..", 3) == 0))
629 continue;
631 ent_full = get_fullpath(fullpath, entry->d_name);
632 if (lstat(ent_full, &status) == 0) {
633 mode = status.st_mode;
634 if (S_ISDIR(mode)) {
635 get_dirsize(ent_full, fullsize);
636 free(ent_full);
637 } else {
638 *fullsize += status.st_size;
639 free(ent_full);
644 closedir(dir);
645 clear_status();
648 static void
649 get_hicol(Cpair *col, mode_t mode)
651 *col = cfile;
652 if (S_ISDIR(mode))
653 *col = cdir;
654 else if (S_ISLNK(mode))
655 *col = cother;
656 else if (chech_execf(mode) > 0)
657 *col = cexec;
660 static void
661 delent(const Arg *arg)
663 if (cpane->dirc < 1)
664 return;
665 char *inp_conf;
666 int conf_len = 4;
667 char conf[] = "yes";
669 inp_conf = ecalloc(conf_len, sizeof(char));
670 if ((get_usrinput(inp_conf, conf_len, "delete file (yes) ?") < 0) ||
671 (strncmp(inp_conf, conf, conf_len) != 0)) {
672 free(inp_conf);
673 return; /* canceled by user or wrong inp_conf */
675 free(inp_conf);
677 char *tmp[1];
678 tmp[0] = CURSOR(cpane).name;
679 if (spawn(rm_cmd, rm_cmd_len, tmp, 1, NULL) < 0) {
680 print_error(strerror(errno));
681 return;
685 static void
686 calcdir(const Arg *arg)
688 if (cpane->dirc < 1)
689 return;
690 if (!S_ISDIR(CURSOR(cpane).mode))
691 return;
693 off_t *fullsize;
694 char *csize;
696 fullsize = ecalloc(1, sizeof(off_t));
697 get_dirsize(CURSOR(cpane).name, fullsize);
698 csize = get_fsize(*fullsize);
700 CURSOR(cpane).size = *fullsize;
701 print_info(cpane, csize);
702 free(fullsize);
705 static void
706 crnd(const Arg *arg)
708 char *user_input, *path;
710 user_input = ecalloc(MAX_USRI, sizeof(char));
711 if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
712 free(user_input);
713 return;
716 path = ecalloc(MAX_P, sizeof(char));
717 if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
718 free(user_input);
719 free(path);
720 return;
723 if (mkdir(path, ndir_perm) < 0)
724 print_error(strerror(errno));
726 free(user_input);
727 free(path);
730 static void
731 crnf(const Arg *arg)
733 char *user_input, *path;
734 int rf;
736 user_input = ecalloc(MAX_USRI, sizeof(char));
737 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
738 free(user_input);
739 return;
742 path = ecalloc(MAX_P, sizeof(char));
743 if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
744 free(user_input);
745 free(path);
746 return;
749 rf = open(path, O_CREAT | O_EXCL, nf_perm);
751 if (rf < 0)
752 print_error(strerror(errno));
753 else if (close(rf) < 0)
754 print_error(strerror(errno));
756 free(user_input);
757 free(path);
759 static void
760 mv_ver(const Arg *arg)
763 if (cpane->dirc < 1)
764 return;
765 if (cpane->hdir - cpane->firstrow - arg->i < 1) { /* move to the top */
766 if (arg->i > 1)
767 mvtop(arg);
768 return;
770 if (cpane->hdir - arg->i > cpane->dirc || /* move to the bottom */
771 cpane->hdir - cpane->firstrow - arg->i > scrheight - 1) {
772 if (arg->i < 1)
773 mvbtm(arg);
774 return;
777 if (cpane->firstrow > 1 && arg->i > 0 &&
778 cpane->hdir < (cpane->firstrow + arg->i)) { /* scroll up */
779 cpane->firstrow = cpane->firstrow - arg->i;
780 rm_hi(cpane, cpane->hdir - 1);
781 cpane->hdir = cpane->hdir - arg->i;
782 refresh_pane(cpane);
783 add_hi(cpane, cpane->hdir - 1);
784 return;
787 if (cpane->hdir - cpane->firstrow >= scrheight - 1 &&
788 arg->i < 0) { /* scroll down */
789 cpane->firstrow = cpane->firstrow - arg->i;
790 rm_hi(cpane, cpane->hdir - 1);
791 cpane->hdir = cpane->hdir - arg->i;
792 refresh_pane(cpane);
793 add_hi(cpane, cpane->hdir - 1);
794 return;
797 rm_hi(cpane, cpane->hdir - 1);
798 cpane->hdir = cpane->hdir - arg->i;
799 add_hi(cpane, cpane->hdir - 1);
800 print_info(cpane, NULL);
803 static void
804 mvbk(const Arg *arg)
806 if (cpane->dirn[0] == '/' && cpane->dirn[1] == '\0') { /* cwd = / */
807 return;
810 get_dirp(cpane->dirn);
811 if (check_dir(cpane->dirn) < 0) {
812 print_error(strerror(errno));
813 return;
816 rmwatch(cpane);
817 cpane->firstrow = cpane->parent_firstrow;
818 cpane->hdir = cpane->parent_row;
819 if (listdir(cpane) < 0)
820 print_error(strerror(errno));
821 cpane->parent_firstrow = 0;
822 cpane->parent_row = 1;
825 static void
826 mvbtm(const Arg *arg)
828 if (cpane->dirc < 1)
829 return;
830 if (cpane->dirc > scrheight) {
831 rm_hi(cpane, cpane->hdir - 1);
832 cpane->hdir = cpane->dirc;
833 cpane->firstrow = cpane->dirc - scrheight + 1;
834 refresh_pane(cpane);
835 add_hi(cpane, cpane->hdir - 1);
836 } else {
837 rm_hi(cpane, cpane->hdir - 1);
838 cpane->hdir = cpane->dirc;
839 add_hi(cpane, cpane->hdir - 1);
841 print_info(cpane, NULL);
844 static void
845 mvfwd(const Arg *arg)
847 if (cpane->dirc < 1)
848 return;
849 int s;
851 switch (check_dir(CURSOR(cpane).name)) {
852 case 0:
853 strncpy(cpane->dirn, CURSOR(cpane).name, MAX_P);
854 cpane->parent_row = cpane->hdir;
855 cpane->parent_firstrow = cpane->firstrow;
856 cpane->hdir = 1;
857 cpane->firstrow = 0;
858 rmwatch(cpane);
859 if (listdir(cpane) < 0)
860 print_error(strerror(errno));
861 break;
862 case 1: /* not a directory open file */
863 rmwatch(cpane);
864 tb_shutdown();
865 s = opnf(CURSOR(cpane).name);
866 if (tb_init() != 0)
867 die("tb_init");
868 t_resize();
869 if (s < 0)
870 print_error("process failed non-zero exit");
871 break;
872 case -1: /* failed to open directory */
873 print_error(strerror(errno));
877 static void
878 mvmid(const Arg *arg)
880 if (cpane->dirc < 1)
881 return;
882 rm_hi(cpane, cpane->hdir - 1);
883 if (cpane->dirc < scrheight / 2)
884 cpane->hdir = (cpane->dirc + 1) / 2;
885 else
886 cpane->hdir = (scrheight / 2) + cpane->firstrow;
887 add_hi(cpane, cpane->hdir - 1);
888 print_info(cpane, NULL);
891 static void
892 mvtop(const Arg *arg)
894 if (cpane->dirc < 1)
895 return;
896 if (cpane->dirc > scrheight) {
897 rm_hi(cpane, cpane->hdir - 1);
898 cpane->hdir = 1;
899 cpane->firstrow = 0;
900 refresh_pane(cpane);
901 add_hi(cpane, cpane->hdir - 1);
902 } else {
903 rm_hi(cpane, cpane->hdir - 1);
904 cpane->hdir = 1;
905 add_hi(cpane, cpane->hdir - 1);
906 print_info(cpane, NULL);
910 static void
911 bkmrk(const Arg *arg)
913 if (check_dir((char *)arg->v) != 0) {
914 print_error(strerror(errno));
915 return;
918 rmwatch(cpane);
919 strncpy(cpane->dirn, (char *)arg->v, MAX_P);
920 cpane->firstrow = 0;
921 cpane->parent_row = 1;
922 cpane->hdir = 1;
923 if (listdir(cpane) < 0)
924 print_error(strerror(errno));
927 static int
928 get_usrinput(char *out, size_t sout, const char *fmt, ...)
930 int height = tb_height();
931 size_t startat;
932 struct tb_event fev;
933 size_t counter = (size_t)1;
934 char empty = ' ';
935 int x = 0;
936 int name_size = 0;
937 char buf[256];
939 clear_status();
941 va_list vl;
942 Cpair col;
943 col = cprompt;
944 va_start(vl, fmt);
945 name_size = vsnprintf(buf, sizeof(buf), fmt, vl);
946 va_end(vl);
947 print_tb(buf, 1, height - 1, col.fg, col.bg);
948 startat = name_size + 1;
949 tb_set_cursor((int)(startat + 1), height - 1);
950 tb_present();
952 while (tb_poll_event(&fev) != 0) {
953 switch (fev.type) {
954 case TB_EVENT_KEY:
955 if (fev.key == TB_KEY_ESC) {
956 tb_set_cursor(-1, -1);
957 clear_status();
958 return -1;
961 if (fev.key == TB_KEY_BACKSPACE ||
962 fev.key == TB_KEY_BACKSPACE2) {
963 if (BETWEEN(counter, 2, sout)) {
964 out[x - 1] = '\0';
965 counter--;
966 x--;
967 print_xstatus(empty, startat + counter);
968 tb_set_cursor(
969 startat + counter, theight - 1);
972 } else if (fev.key == TB_KEY_ENTER) {
973 tb_set_cursor(-1, -1);
974 out[counter - 1] = '\0';
975 return 0;
977 } else {
978 if (counter < sout) {
979 print_xstatus((char)fev.ch,
980 (startat + counter));
981 out[x] = (char)fev.ch;
982 tb_set_cursor((startat + counter + 1),
983 theight - 1);
984 counter++;
985 x++;
989 tb_present();
990 break;
992 default:
993 return -1;
997 return -1;
1000 static int
1001 frules(char *ex)
1003 size_t c, d;
1005 for (c = 0; c < LEN(rules); c++)
1006 for (d = 0; d < rules[c].exlen; d++)
1007 if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0)
1008 return c;
1009 return -1;
1012 static int
1013 spawn(const void *com_argv, size_t com_argc, const void *f_argv, size_t f_argc,
1014 char *fn)
1016 int ws;
1017 size_t argc;
1018 pid_t pid, r;
1020 argc = com_argc + f_argc + 2;
1021 char *argv[argc];
1023 memcpy(argv, com_argv, com_argc * sizeof(char *)); /* command */
1024 memcpy(&argv[com_argc], f_argv, f_argc * sizeof(char *)); /* files */
1026 argv[argc - 2] = fn;
1027 argv[argc - 1] = NULL;
1029 pid = fork();
1030 switch (pid) {
1031 case -1:
1032 return -1;
1033 case 0:
1034 execvp(argv[0], argv);
1035 exit(EXIT_SUCCESS);
1036 default:
1037 while ((r = waitpid(pid, &ws, 0)) == -1 && errno == EINTR)
1038 continue;
1039 if (r == -1)
1040 return -1;
1041 if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
1042 return -1;
1044 return 0;
1047 static int
1048 opnf(char *fn)
1050 char *ex;
1051 int c;
1053 ex = get_ext(fn);
1054 c = frules(ex);
1055 free(ex);
1057 if (c < 0) /* extension not found open in editor */
1058 return spawn(editor, 1, NULL, 0, fn);
1059 else
1060 return spawn((char **)rules[c].v, rules[c].vlen, NULL, 0, fn);
1063 static int
1064 fsev_init(void)
1066 #if defined(_SYS_INOTIFY_H)
1067 inotify_fd = inotify_init();
1068 if (inotify_fd < 0)
1069 return -1;
1070 #elif defined(_SYS_EVENT_H_)
1071 gtimeout.tv_sec = 1;
1072 kq = kqueue();
1073 if (kq < 0)
1074 return -1;
1075 #endif
1076 return 0;
1079 static int
1080 addwatch(Pane *pane)
1082 #if defined(_SYS_INOTIFY_H)
1083 return pane->inotify_wd = inotify_add_watch(inotify_fd, pane->dirn,
1084 IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
1085 IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF);
1086 #elif defined(_SYS_EVENT_H_)
1087 pane->event_fd = open(pane->dirn, O_RDONLY);
1088 if (pane->event_fd < 0)
1089 return pane->event_fd;
1090 EV_SET(&evlist[pane->pane_id], pane->event_fd, EVFILT_VNODE,
1091 EV_ADD | EV_CLEAR,
1092 NOTE_DELETE | NOTE_EXTEND | NOTE_LINK | NOTE_RENAME |
1093 NOTE_REVOKE | NOTE_WRITE,
1094 0, NULL);
1095 return 0;
1096 #endif
1099 static int
1100 read_events(void)
1102 #if defined(_SYS_INOTIFY_H)
1103 char *p;
1104 ssize_t r;
1105 struct inotify_event *event;
1106 const size_t events = 32;
1107 const size_t evbuflen =
1108 events * (sizeof(struct inotify_event) + MAX_N + 1);
1109 char buf[evbuflen];
1111 if (cpane->inotify_wd < 0)
1112 return -1;
1113 r = read(inotify_fd, buf, evbuflen);
1114 if (r <= 0)
1115 return r;
1117 for (p = buf; p < buf + r;) {
1118 event = (struct inotify_event *)p;
1119 if (!event->wd)
1120 break;
1121 if (event->mask) {
1122 return r;
1125 p += sizeof(struct inotify_event) + event->len;
1127 #elif defined(_SYS_EVENT_H_)
1128 return kevent(kq, evlist, 2, chlist, 2, &gtimeout);
1129 #endif
1130 return -1;
1133 static void
1134 rmwatch(Pane *pane)
1136 #if defined(_SYS_INOTIFY_H)
1137 if (pane->inotify_wd >= 0)
1138 inotify_rm_watch(inotify_fd, pane->inotify_wd);
1139 #elif defined(_SYS_EVENT_H_)
1140 close(pane->event_fd);
1141 #endif
1144 static void
1145 fsev_shdn(void)
1147 pthread_cancel(fsev_thread);
1148 #if defined(__linux__)
1149 pthread_join(fsev_thread, NULL);
1150 #endif
1151 rmwatch(&pane_l);
1152 rmwatch(&pane_r);
1153 #if defined(_SYS_INOTIFY_H)
1154 close(inotify_fd);
1155 #elif defined(_SYS_EVENT_H_)
1156 close(kq);
1157 #endif
1160 static void
1161 toggle_df(const Arg *arg)
1163 show_dotfiles = !show_dotfiles;
1164 if (cpane == &pane_l) {
1165 if (listdir(&pane_r) < 0)
1166 print_error(strerror(errno));
1167 if (listdir(&pane_l) < 0)
1168 print_error(strerror(errno));
1169 } else if (cpane == &pane_r) {
1170 if (listdir(&pane_l) < 0)
1171 print_error(strerror(errno));
1172 if (listdir(&pane_r) < 0)
1173 print_error(strerror(errno));
1177 static void
1178 start_filter(const Arg *arg)
1180 if (cpane->dirc < 1)
1181 return;
1182 char *user_input;
1183 user_input = ecalloc(MAX_USRI, sizeof(char));
1184 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1185 free(user_input);
1186 return;
1188 cpane->filter = user_input;
1189 if (listdir(cpane) < 0)
1190 print_error("no match");
1191 cpane->filter = NULL;
1192 free(user_input);
1195 static void
1196 start_vmode(const Arg *arg)
1198 if (cpane->dirc < 1)
1199 return;
1200 struct tb_event fev;
1201 if (sel_indexes != NULL) {
1202 free(sel_indexes);
1203 sel_indexes = NULL;
1206 sel_indexes = ecalloc(cpane->dirc, sizeof(size_t));
1207 sel_indexes[0] = cpane->hdir;
1208 cont_vmode = 0;
1209 print_prompt("-- VISUAL --");
1210 tb_present();
1211 while (tb_poll_event(&fev) != 0) {
1212 switch (fev.type) {
1213 case TB_EVENT_KEY:
1214 grabkeys(&fev, vkeys, vkeyslen);
1215 if (cont_vmode == -1)
1216 return;
1217 tb_present();
1218 break;
1223 static void
1224 exit_vmode(const Arg *arg)
1226 refresh_pane(cpane);
1227 add_hi(cpane, cpane->hdir - 1);
1228 cont_vmode = -1;
1231 static void
1232 selup(const Arg *arg)
1234 mv_ver(arg);
1235 print_prompt("-- VISUAL --");
1236 int index = abs(cpane->hdir - sel_indexes[0]);
1238 if (cpane->hdir < sel_indexes[0]) {
1239 sel_indexes[index] = cpane->hdir;
1240 add_hi(cpane, sel_indexes[index]);
1241 } else if (index < cpane->dirc) {
1242 sel_indexes[index + 1] = 0;
1244 if (cpane->dirc >= scrheight ||
1245 cpane->hdir <= 1) { /* rehighlight all if scrolling */
1246 selref();
1250 static void
1251 seldwn(const Arg *arg)
1253 mv_ver(arg);
1254 print_prompt("-- VISUAL --");
1255 int index = abs(cpane->hdir - sel_indexes[0]);
1257 if (cpane->hdir > sel_indexes[0]) {
1258 sel_indexes[index] = cpane->hdir;
1259 add_hi(cpane, sel_indexes[index] - 2);
1260 } else {
1261 sel_indexes[index + 1] = 0;
1263 if (cpane->dirc >= scrheight ||
1264 cpane->hdir >= cpane->dirc) { /* rehighlight all if scrolling */
1265 selref();
1269 static void
1270 selall(const Arg *arg)
1272 int i;
1273 for (i = 0; i < cpane->dirc; i++) {
1274 sel_indexes[i] = i + 1;
1276 selref();
1279 static void
1280 selref(void)
1282 int i;
1283 for (i = 0; i < cpane->dirc; i++) {
1284 if (sel_indexes[i] < (scrheight + cpane->firstrow) &&
1285 sel_indexes[i] >
1286 cpane->firstrow) { /* checks if in the frame of the directories */
1287 add_hi(cpane, sel_indexes[i] - 1);
1292 static void
1293 selcalc(void)
1295 int j;
1296 sel_len = 0;
1298 for (j = 0; j < cpane->dirc; j++) { /* calculate used selection size */
1299 if (sel_indexes[j] != 0)
1300 sel_len++;
1301 else
1302 break;
1306 static void
1307 free_files(void)
1309 size_t i;
1311 if (sel_files != NULL) {
1312 for (i = 0; i < sel_len; i++) {
1313 free(sel_files[i]);
1314 sel_files[i] = NULL;
1316 free(sel_files);
1317 sel_files = NULL;
1321 static void
1322 init_files(void)
1324 size_t i;
1325 free_files();
1327 selcalc();
1328 sel_files = ecalloc(sel_len, sizeof(char *));
1330 for (i = 0; i < sel_len; i++) {
1331 sel_files[i] = ecalloc(MAX_P, sizeof(char));
1332 strncpy(sel_files[i], cpane->direntr[sel_indexes[i] - 1].name,
1333 MAX_P);
1337 static void
1338 selynk(const Arg *arg)
1340 init_files();
1341 refresh_pane(cpane);
1342 add_hi(cpane, cpane->hdir - 1);
1343 print_status(cprompt, "%zu files are yanked", sel_len);
1344 cont_vmode = -1;
1347 static void
1348 seldel(const Arg *arg)
1350 char *inp_conf;
1352 inp_conf = ecalloc(delconf_len, sizeof(char));
1353 if ((get_usrinput(inp_conf, delconf_len, "delete file (yes) ?") < 0) ||
1354 (strncmp(inp_conf, delconf, delconf_len) != 0)) {
1355 free(inp_conf);
1356 return; /* canceled by user or wrong inp_conf */
1358 free(inp_conf);
1360 init_files();
1362 if (spawn(rm_cmd, rm_cmd_len, sel_files, sel_len, NULL) < 0)
1363 print_error(strerror(errno));
1364 else
1365 print_status(cprompt, "%zu files are deleted", sel_len);
1367 free_files();
1368 cont_vmode = -1;
1371 static void
1372 paste(const Arg *arg)
1374 if (sel_files == NULL) {
1375 print_error("nothing to paste");
1376 return;
1379 if (spawn(cp_cmd, cp_cmd_len, sel_files, sel_len, cpane->dirn) < 0)
1380 print_error(strerror(errno));
1381 else
1382 print_status(cprompt, "%zu files are copied", sel_len);
1384 free_files();
1387 static void
1388 selmv(const Arg *arg)
1390 if (sel_files == NULL) {
1391 print_error("nothing to move");
1392 return;
1395 if (spawn(mv_cmd, mv_cmd_len, sel_files, sel_len, cpane->dirn) < 0)
1396 print_error(strerror(errno));
1397 else
1398 print_status(cprompt, "%zu files are moved", sel_len);
1400 free_files();
1403 static void
1404 rname(const Arg *arg)
1406 if (cpane->dirc < 1)
1407 return;
1408 char new_name[MAX_P];
1409 char *input_name;
1411 input_name = ecalloc(MAX_N, sizeof(char));
1413 if (get_usrinput(input_name, MAX_N, "rename: %s",
1414 basename(CURSOR(cpane).name)) < 0) {
1415 free(input_name);
1416 return;
1419 if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
1420 free(input_name);
1421 print_error(strerror(errno));
1422 return;
1425 char *rename_cmd[] = { "mv", CURSOR(cpane).name, new_name };
1426 if (spawn(rename_cmd, 3, NULL, 0, NULL) < 0)
1427 print_error(strerror(errno));
1429 free(input_name);
1432 static void
1433 yank(const Arg *arg)
1435 if (cpane->dirc < 1)
1436 return;
1438 free_files();
1439 sel_len = 1;
1440 sel_files = ecalloc(sel_len, sizeof(char *));
1441 sel_files[0] = ecalloc(MAX_P, sizeof(char));
1442 strncpy(sel_files[0], CURSOR(cpane).name, MAX_P);
1443 print_status(cprompt, "1 file is yanked", sel_len);
1446 static void
1447 switch_pane(const Arg *arg)
1449 if (cpane->dirc > 0)
1450 rm_hi(cpane, cpane->hdir - 1);
1451 if (cpane == &pane_l)
1452 cpane = &pane_r;
1453 else if (cpane == &pane_r)
1454 cpane = &pane_l;
1455 if (cpane->dirc > 0) {
1456 add_hi(cpane, cpane->hdir - 1);
1457 print_info(cpane, NULL);
1458 } else {
1459 clear_status();
1463 static void
1464 quit(const Arg *arg)
1466 if (cont_vmode == -1) { /* check if selection was allocated */
1467 free(sel_indexes);
1468 if (sel_files != NULL)
1469 free_files();
1471 free(pane_l.direntr);
1472 free(pane_r.direntr);
1473 fsev_shdn();
1474 tb_shutdown();
1475 exit(EXIT_SUCCESS);
1478 static void
1479 grabkeys(struct tb_event *event, Key *key, size_t max_keys)
1481 size_t i;
1483 for (i = 0; i < max_keys; i++) {
1484 if (event->ch != 0) {
1485 if (event->ch == key[i].evkey.ch) {
1486 key[i].func(&key[i].arg);
1487 return;
1489 } else if (event->key != 0) {
1490 if (event->key == key[i].evkey.key) {
1491 key[i].func(&key[i].arg);
1492 return;
1498 void *
1499 read_th(void *arg)
1501 struct timespec tim;
1502 tim.tv_sec = 0;
1503 tim.tv_nsec = 5000000L; /* 0.005 sec */
1505 while (1)
1506 if (read_events() > READEVSZ) {
1507 kill(main_pid, SIGUSR1);
1508 nanosleep(&tim, NULL);
1510 return arg;
1513 static void
1514 start_ev(void)
1516 struct tb_event ev;
1518 while (tb_poll_event(&ev) != 0) {
1519 switch (ev.type) {
1520 case TB_EVENT_KEY:
1521 grabkeys(&ev, nkeys, nkeyslen);
1522 tb_present();
1523 break;
1524 case TB_EVENT_RESIZE:
1525 t_resize();
1526 break;
1527 default:
1528 break;
1531 tb_shutdown();
1534 static void
1535 refresh_pane(Pane *pane)
1537 size_t y, dyn_max, start_from;
1538 int width;
1539 width = (twidth / 2) - 4;
1540 Cpair col;
1542 y = 1;
1543 start_from = pane->firstrow;
1544 dyn_max = MIN(pane->dirc, (scrheight - 1) + pane->firstrow);
1546 /* print each entry in directory */
1547 while (start_from < dyn_max) {
1548 get_hicol(&col, pane->direntr[start_from].mode);
1549 print_row(pane, start_from, col);
1550 start_from++;
1551 y++;
1554 if (pane->dirc > 0)
1555 print_info(pane, NULL);
1556 else
1557 clear_status();
1559 /* print current directory title */
1560 pane->dircol.fg |= TB_BOLD;
1561 printf_tb(pane->dirx, 0, pane->dircol, " %.*s ", width, pane->dirn);
1564 static void
1565 set_direntr(Pane *pane, struct dirent *entry, DIR *dir, char *filter)
1567 int i;
1568 char *tmpfull;
1569 struct stat status;
1571 #define ADD_ENTRY \
1572 tmpfull = get_fullpath(pane->dirn, entry->d_name); \
1573 strncpy(pane->direntr[i].name, tmpfull, MAX_N); \
1574 if (lstat(tmpfull, &status) == 0) { \
1575 pane->direntr[i].size = status.st_size; \
1576 pane->direntr[i].mode = status.st_mode; \
1577 pane->direntr[i].group = status.st_gid; \
1578 pane->direntr[i].user = status.st_uid; \
1579 pane->direntr[i].dt = status.st_mtime; \
1581 i++; \
1582 free(tmpfull);
1584 i = 0;
1585 pane->direntr =
1586 erealloc(pane->direntr, (10 + pane->dirc) * sizeof(Entry));
1587 while ((entry = readdir(dir)) != 0) {
1588 if (show_dotfiles == 1) {
1589 if (entry->d_name[0] == '.' &&
1590 (entry->d_name[1] == '\0' || entry->d_name[1] == '.'))
1591 continue;
1592 } else {
1593 if (entry->d_name[0] == '.')
1594 continue;
1598 if (filter == NULL) {
1599 ADD_ENTRY
1600 } else if (filter != NULL) {
1601 if (strcasestr(entry->d_name, filter) != NULL) {
1602 ADD_ENTRY
1607 pane->dirc = i;
1610 static int
1611 listdir(Pane *pane)
1613 DIR *dir;
1614 struct dirent *entry;
1615 int width;
1616 int filtercount = 0;
1617 size_t oldc = pane->dirc;
1619 width = (twidth / 2) - 4;
1620 pane->dirc = 0;
1622 dir = opendir(pane->dirn);
1623 if (dir == NULL)
1624 return -1;
1626 /* get content and filter sum */
1627 while ((entry = readdir(dir)) != 0) {
1628 if (pane->filter != NULL) {
1629 if (strcasestr(entry->d_name, pane->filter) != NULL)
1630 filtercount++;
1631 } else { /* no filter */
1632 pane->dirc++;
1636 if (pane->filter == NULL) {
1637 clear_pane(pane);
1638 pane->dirc -= 2;
1641 if (pane->filter != NULL) {
1642 if (filtercount > 0) {
1643 pane->dirc = filtercount;
1644 clear_pane(pane);
1645 pane->hdir = 1;
1646 } else if (filtercount == 0) {
1647 if (closedir(dir) < 0)
1648 return -1;
1649 pane->dirc = oldc;
1650 return -1;
1654 /* print current directory title */
1655 pane->dircol.fg |= TB_BOLD;
1656 printf_tb(pane->dirx, 0, pane->dircol, " %.*s ", width, pane->dirn);
1658 if (pane->filter == NULL) /* dont't watch when filtering */
1659 if (addwatch(pane) < 0)
1660 print_error("can't add watch");
1662 /* empty directory */
1663 if (pane->dirc == 0) {
1664 clear_status();
1665 if (closedir(dir) < 0)
1666 return -1;
1667 return 0;
1670 rewinddir(dir); /* reset position */
1671 set_direntr(
1672 pane, entry, dir, pane->filter); /* create array of entries */
1673 qsort(pane->direntr, pane->dirc, sizeof(Entry), sort_name);
1674 refresh_pane(pane);
1676 if (pane->hdir > pane->dirc)
1677 pane->hdir = pane->dirc;
1679 if (pane == cpane && pane->dirc > 0)
1680 add_hi(pane, pane->hdir - 1);
1682 if (closedir(dir) < 0)
1683 return -1;
1684 return 0;
1687 static void
1688 t_resize(void)
1690 /* TODO need refactoring */
1691 tb_clear();
1692 draw_frame();
1693 pane_r.dirx = (twidth / 2) + 2;
1695 if (cpane == &pane_l) {
1696 refresh_pane(&pane_r);
1697 refresh_pane(&pane_l);
1698 if (cpane->dirc > 0)
1699 add_hi(&pane_l, pane_l.hdir - 1);
1700 } else if (cpane == &pane_r) {
1701 refresh_pane(&pane_l);
1702 refresh_pane(&pane_r);
1703 if (cpane->dirc > 0)
1704 add_hi(&pane_r, pane_r.hdir - 1);
1707 tb_present();
1710 static void
1711 get_editor(void)
1713 editor[0] = getenv("EDITOR");
1714 editor[1] = NULL;
1716 if (editor[0] == NULL)
1717 editor[0] = fed;
1720 static void
1721 set_panes(void)
1723 char *home;
1724 char cwd[MAX_P];
1726 home = getenv("HOME");
1727 if (home == NULL)
1728 home = "/";
1729 if ((getcwd(cwd, sizeof(cwd)) == NULL))
1730 strncpy(cwd, home, MAX_P);
1732 pane_l.pane_id = 0;
1733 pane_l.dirx = 2;
1734 pane_l.dircol = cpanell;
1735 pane_l.firstrow = 0;
1736 pane_l.direntr = ecalloc(0, sizeof(Entry));
1737 strncpy(pane_l.dirn, cwd, MAX_P);
1738 pane_l.hdir = 1;
1739 pane_l.inotify_wd = -1;
1740 pane_l.parent_row = 1;
1742 pane_r.pane_id = 1;
1743 pane_r.dirx = (twidth / 2) + 2;
1744 pane_r.dircol = cpanelr;
1745 pane_r.firstrow = 0;
1746 pane_r.direntr = ecalloc(0, sizeof(Entry));
1747 strncpy(pane_r.dirn, home, MAX_P);
1748 pane_r.hdir = 1;
1749 pane_r.inotify_wd = -1;
1750 pane_r.parent_row = 1;
1753 static void
1754 draw_frame(void)
1756 int i;
1757 theight = tb_height();
1758 twidth = tb_width();
1759 scrheight = theight - 2;
1761 /* 2 horizontal lines */
1762 for (i = 1; i < twidth - 1; ++i) {
1763 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1764 tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg);
1767 /* 4 vertical lines */
1768 for (i = 1; i < theight - 1; ++i) {
1769 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1770 tb_change_cell(
1771 (twidth - 1) / 2, i - 1, u_vl, cframe.fg, cframe.bg);
1772 tb_change_cell(((twidth - 1) / 2) + 1, i - 1, u_vl, cframe.fg,
1773 cframe.bg);
1774 tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg);
1777 /* 4 corners */
1778 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1779 tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg);
1780 tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg);
1781 tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg);
1783 /* 2 middel top and bottom */
1784 tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg);
1785 tb_change_cell(
1786 (twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg);
1789 void
1790 th_handler(int num)
1792 (void)num;
1793 if (cpane == &pane_l) {
1794 if (listdir(&pane_r) < 0)
1795 print_error(strerror(errno));
1796 if (listdir(&pane_l) < 0)
1797 print_error(strerror(errno));
1798 } else if (cpane == &pane_r) {
1799 if (listdir(&pane_l) < 0)
1800 print_error(strerror(errno));
1801 if (listdir(&pane_r) < 0)
1802 print_error(strerror(errno));
1804 tb_present();
1807 static int
1808 start_signal(void)
1810 struct sigaction sa;
1812 main_pid = getpid();
1813 sa.sa_handler = th_handler;
1814 sigemptyset(&sa.sa_mask);
1815 sa.sa_flags = SA_RESTART;
1816 return sigaction(SIGUSR1, &sa, NULL);
1819 static void
1820 start(void)
1822 if (tb_init() != 0)
1823 die("tb_init");
1824 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
1825 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
1826 die("output error");
1828 draw_frame();
1829 set_panes();
1830 get_editor();
1831 if (start_signal() < 0)
1832 print_error(strerror(errno));
1833 if (fsev_init() < 0)
1834 print_error(strerror(errno));
1835 cpane = &pane_l;
1836 if (listdir(&pane_r) < 0)
1837 print_error(strerror(errno));
1838 if (listdir(&pane_l) < 0)
1839 print_error(strerror(errno));
1840 tb_present();
1842 pthread_create(&fsev_thread, NULL, read_th, NULL);
1843 start_ev();
1847 main(int argc, char *argv[])
1849 #if defined(__OpenBSD__)
1850 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
1851 NULL) == -1)
1852 die("pledge");
1853 #endif /* __OpenBSD__ */
1854 if (argc == 1)
1855 start();
1856 else if (argc == 2 && strncmp("-v", argv[1], 2) == 0)
1857 die("sfm-" VERSION);
1858 else
1859 die("usage: sfm [-v]");
1860 return 0;