1 /* See LICENSE file for copyright and license details. */
5 #elif defined(__APPLE__)
6 #define _DARWIN_C_SOURCE
7 #elif defined(__FreeBSD__)
8 #define __BSD_VISIBLE 1
10 #include <sys/types.h>
11 #include <sys/resource.h>
15 #if defined(__linux__)
16 #include <sys/inotify.h>
17 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
19 #include <sys/event.h>
46 #define MAX_STATUS 255
51 #define CURSOR(x) (x)->direntr[(x)->hdir - 1]
70 char dirn
[MAX_P
]; // dir name cwd
72 Entry
*direntr
; // dir entries
73 int dirx
; // pane cwd x pos
74 int dirc
; // dir entries sum
75 int hdir
; // highlighted dir
79 int parent_row
; // FIX
93 uint16_t key
; /* one of the TB_KEY_* constants */
94 uint32_t ch
; /* unicode character */
104 void (*func
)(const Arg
*);
108 /* function declarations */
109 static void print_tb(const char *, int, int, uint16_t, uint16_t);
110 static void printf_tb(int, int, Cpair
, const char *, ...);
111 static void print_status(Cpair
, const char *, ...);
112 static void print_xstatus(char, int);
113 static void print_error(char *);
114 static void print_prompt(char *);
115 static void print_info(Pane
*, char *);
116 static void print_row(Pane
*, size_t, Cpair
);
117 static void clear(int, int, int, uint16_t);
118 static void clear_status(void);
119 static void clear_pane(Pane
*);
120 static void add_hi(Pane
*, size_t);
121 static void rm_hi(Pane
*, size_t);
122 static int check_dir(char *);
123 static mode_t
chech_execf(mode_t
);
124 static int sort_name(const void *const, const void *const);
125 static void get_dirp(char *);
126 static char *get_ext(char *);
127 static int get_fdt(char *, time_t);
128 static char *get_fgrp(gid_t
);
129 static char *get_fperm(mode_t
);
130 static char *get_fsize(off_t
);
131 static char *get_fullpath(char *, char *);
132 static char *get_fusr(uid_t
);
133 static void get_dirsize(char *, off_t
*);
134 static void get_hicol(Cpair
*, mode_t
);
135 static void delent(const Arg
*arg
);
136 static void calcdir(const Arg
*arg
);
137 static void crnd(const Arg
*arg
);
138 static void crnf(const Arg
*arg
);
139 static void mv_ver(const Arg
*arg
);
140 static void mvbk(const Arg
*arg
);
141 static void mvbtm(const Arg
*arg
);
142 static void mvfwd(const Arg
*arg
);
143 static void mvmid(const Arg
*arg
);
144 static void mvtop(const Arg
*arg
);
145 static void bkmrk(const Arg
*arg
);
146 static int get_usrinput(char *, size_t, const char *, ...);
147 static int frules(char *);
148 static int spawn(const void *, size_t, const void *, size_t, char *);
149 static int opnf(char *);
150 static int fsev_init(void);
151 static int addwatch(Pane
*);
152 static int read_events(void);
153 static void rmwatch(Pane
*);
154 static void fsev_shdn(void);
155 static void toggle_df(const Arg
*arg
);
156 static void start_filter(const Arg
*arg
);
157 static void start_vmode(const Arg
*arg
);
158 static void exit_vmode(const Arg
*arg
);
159 static void selup(const Arg
*arg
);
160 static void seldwn(const Arg
*arg
);
161 static void selall(const Arg
*arg
);
162 static void selref(void);
163 static void selynk(const Arg
*arg
);
164 static void selcalc(void);
165 static void paste(const Arg
*arg
);
166 static void selmv(const Arg
*arg
);
167 static void seldel(const Arg
*arg
);
168 static void init_files(void);
169 static void free_files(void);
170 static void yank(const Arg
*arg
);
171 static void rname(const Arg
*arg
);
172 static void switch_pane(const Arg
*arg
);
173 static void quit(const Arg
*arg
);
174 static void grabkeys(struct tb_event
*, Key
*, size_t);
175 static void *read_th(void *arg
);
176 static void start_ev(void);
177 static void refresh_pane(Pane
*);
178 static void set_direntr(Pane
*, struct dirent
*, DIR *, char *);
179 static int listdir(Pane
*);
180 static void t_resize(void);
181 static void set_panes(void);
182 static void draw_frame(void);
183 static void start(void);
185 /* global variables */
186 static pthread_t fsev_thread
;
187 static Pane panes
[2];
190 static char *editor
[2];
191 static char fed
[] = "vi";
192 static int theight
, twidth
, scrheight
;
193 static int *sel_indexes
;
194 static size_t sel_len
= 0;
195 static char **sel_files
;
196 static int cont_vmode
= 0;
198 #if defined(_SYS_INOTIFY_H)
200 static int inotify_fd
;
201 #elif defined(_SYS_EVENT_H_)
204 struct kevent evlist
[2]; /* events we want to monitor */
205 struct kevent chlist
[2]; /* events that were triggered */
206 static struct timespec gtimeout
;
208 #if defined(__linux__) || defined(__FreeBSD__)
210 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
213 enum { Left
, Right
}; /* panes */
215 /* configuration, allows nested code to access above variables */
218 /* function implementations */
220 print_tb(const char *str
, int x
, int y
, uint16_t fg
, uint16_t bg
)
222 while (*str
!= '\0') {
224 str
+= tb_utf8_char_to_unicode(&uni
, str
);
225 tb_change_cell(x
, y
, uni
, fg
, bg
);
231 printf_tb(int x
, int y
, Cpair col
, const char *fmt
, ...)
236 (void)vsnprintf(buf
, MAX_LINE
, fmt
, vl
);
238 print_tb(buf
, x
, y
, col
.fg
, col
.bg
);
242 print_status(Cpair col
, const char *fmt
, ...)
244 char buf
[MAX_STATUS
];
247 (void)vsnprintf(buf
, MAX_STATUS
, fmt
, vl
);
250 print_tb(buf
, 1, theight
- 1, col
.fg
, col
.bg
);
254 print_xstatus(char c
, int x
)
257 (void)tb_utf8_char_to_unicode(&uni
, &c
);
258 tb_change_cell(x
, theight
- 1, uni
, cstatus
.fg
, cstatus
.bg
);
262 print_error(char *errmsg
)
264 print_status(cerr
, errmsg
);
268 print_prompt(char *prompt
)
270 print_status(cprompt
, prompt
);
274 print_info(Pane
*pane
, char *dirsize
)
276 char *sz
, *ur
, *gr
, *dt
, *prm
;
278 dt
= ecalloc(MAX_DTF
, sizeof(char));
280 prm
= get_fperm(CURSOR(pane
).mode
);
281 ur
= get_fusr(CURSOR(pane
).user
);
282 gr
= get_fgrp(CURSOR(pane
).group
);
284 if (get_fdt(dt
, CURSOR(pane
).dt
) < 0)
287 if (S_ISREG(CURSOR(pane
).mode
)) {
288 sz
= get_fsize(CURSOR(pane
).size
);
290 if (dirsize
== NULL
) {
291 sz
= ecalloc(1, sizeof(char));
298 print_status(cstatus
, "%02d/%02d %s %s:%s %s %s", pane
->hdir
,
299 pane
->dirc
, prm
, ur
, gr
, dt
, sz
);
309 print_row(Pane
*pane
, size_t entpos
, Cpair col
)
314 char lnk_full
[MAX_P
];
317 width
= (twidth
/ 2) - 4;
318 result
= basename(pane
->direntr
[entpos
].name
);
320 y
= entpos
- pane
->firstrow
+ 1;
322 if (S_ISLNK(pane
->direntr
[entpos
].mode
) != 0) {
323 if (realpath(pane
->direntr
[entpos
].name
, buf
) != NULL
) {
325 lnk_full
, MAX_N
, "%s -> %s", result
, buf
);
330 printf_tb(x
, y
, col
, "%*.*s", ~width
, width
, result
);
334 clear(int sx
, int ex
, int y
, uint16_t bg
)
336 /* clear line from to */
337 /* x = line number vertical */
338 /* y = column number horizontal */
340 for (i
= sx
; i
< ex
; i
++) {
341 tb_change_cell(i
, y
, 0x0000, TB_DEFAULT
, bg
);
348 clear(1, twidth
- 1, theight
- 1, cstatus
.bg
);
352 clear_pane(Pane
*pane
)
357 while (i
< scrheight
) {
358 clear(pane
->dirx
, pane
->ex
, y
, TB_DEFAULT
);
364 for (y
= pane
->dirx
; y
< pane
->ex
; ++y
) {
365 tb_change_cell(y
, 0, u_hl
, cframe
.fg
, cframe
.bg
);
370 add_hi(Pane
*pane
, size_t entpos
)
373 get_hicol(&col
, pane
->direntr
[entpos
].mode
);
374 col
.fg
|= TB_REVERSE
| TB_BOLD
;
375 col
.bg
|= TB_REVERSE
;
376 print_row(pane
, entpos
, col
);
380 rm_hi(Pane
*pane
, size_t entpos
)
383 get_hicol(&col
, pane
->direntr
[entpos
].mode
);
384 print_row(pane
, entpos
, col
);
388 check_dir(char *path
)
394 if (errno
== ENOTDIR
) {
401 if (closedir(dir
) < 0)
408 chech_execf(mode_t mode
)
411 return (((S_IXUSR
| S_IXGRP
| S_IXOTH
) & mode
));
416 sort_name(const void *const A
, const void *const B
)
419 mode_t data1
= (*(Entry
*)A
).mode
;
420 mode_t data2
= (*(Entry
*)B
).mode
;
424 } else if (data1
== data2
) {
425 result
= strncmp((*(Entry
*)A
).name
, (*(Entry
*)B
).name
, MAX_N
);
438 len
= strnlen(cdir
, MAX_P
);
442 for (i
= len
- 1; i
> 1; i
--) {
449 cdir
[len
- counter
- 1] = '\0';
457 size_t counter
, len
, i
;
461 len
= strnlen(str
, MAX_N
);
463 for (i
= len
- 1; i
> 0; i
--) {
471 ext
= ecalloc(MAX_EXT
+ 1, sizeof(char));
472 strncpy(ext
, &str
[len
- counter
], MAX_EXT
);
478 get_fdt(char *result
, time_t status
)
481 localtime_r(&status
, <
);
482 return strftime(result
, MAX_DTF
, dtfmt
, <
);
486 get_fgrp(gid_t status
)
491 result
= ecalloc(MAX_GRPN
, sizeof(char));
492 gr
= getgrgid(status
);
494 (void)snprintf(result
, MAX_GRPN
, "%u", status
);
496 strncpy(result
, gr
->gr_name
, MAX_GRPN
);
498 result
[MAX_GRPN
- 1] = '\0';
503 get_fperm(mode_t mode
)
508 const char chars
[] = "rwxrwxrwx";
509 buf
= ecalloc(11, sizeof(char));
513 else if (S_ISREG(mode
))
515 else if (S_ISLNK(mode
))
517 else if (S_ISBLK(mode
))
519 else if (S_ISCHR(mode
))
521 else if (S_ISFIFO(mode
))
523 else if (S_ISSOCK(mode
))
528 for (i
= 1; i
< 10; i
++) {
529 buf
[i
] = (mode
& (1 << (9 - i
))) ? chars
[i
- 1] : '-';
537 get_fsize(off_t size
)
539 char *result
; /* need to be freed */
545 result_len
= 6; /* 9999X/0 */
546 result
= ecalloc(result_len
, sizeof(char));
548 while (size
>= 1000) {
573 if (snprintf(result
, result_len
, OFF_T
"%c", size
, unit
) < 0)
574 strncat(result
, "???", result_len
);
580 get_fullpath(char *first
, char *second
)
584 full_path
= ecalloc(MAX_P
, sizeof(char));
586 if (strncmp(first
, "/", MAX_P
) == 0)
587 (void)snprintf(full_path
, MAX_P
, "/%s", second
);
589 (void)snprintf(full_path
, MAX_P
, "%s/%s", first
, second
);
595 get_fusr(uid_t status
)
600 result
= ecalloc(MAX_USRN
, sizeof(char));
601 pw
= getpwuid(status
);
603 (void)snprintf(result
, MAX_USRN
, "%u", status
);
605 strncpy(result
, pw
->pw_name
, MAX_USRN
);
607 result
[MAX_USRN
- 1] = '\0';
612 get_dirsize(char *fullpath
, off_t
*fullsize
)
617 struct dirent
*entry
;
620 dir
= opendir(fullpath
);
625 while ((entry
= readdir(dir
)) != 0) {
626 if ((strncmp(entry
->d_name
, ".", 2) == 0 ||
627 strncmp(entry
->d_name
, "..", 3) == 0))
630 ent_full
= get_fullpath(fullpath
, entry
->d_name
);
631 if (lstat(ent_full
, &status
) == 0) {
632 mode
= status
.st_mode
;
634 get_dirsize(ent_full
, fullsize
);
637 *fullsize
+= status
.st_size
;
648 get_hicol(Cpair
*col
, mode_t mode
)
653 else if (S_ISLNK(mode
))
655 else if (chech_execf(mode
) > 0)
660 delent(const Arg
*arg
)
668 inp_conf
= ecalloc(conf_len
, sizeof(char));
669 if ((get_usrinput(inp_conf
, conf_len
, "delete file (yes) ?") < 0) ||
670 (strncmp(inp_conf
, conf
, conf_len
) != 0)) {
672 return; /* canceled by user or wrong inp_conf */
677 tmp
[0] = CURSOR(cpane
).name
;
678 if (spawn(rm_cmd
, rm_cmd_len
, tmp
, 1, NULL
) < 0) {
679 print_error(strerror(errno
));
685 calcdir(const Arg
*arg
)
689 if (!S_ISDIR(CURSOR(cpane
).mode
))
695 fullsize
= ecalloc(1, sizeof(off_t
));
696 get_dirsize(CURSOR(cpane
).name
, fullsize
);
697 csize
= get_fsize(*fullsize
);
699 CURSOR(cpane
).size
= *fullsize
;
700 print_info(cpane
, csize
);
707 char *user_input
, *path
;
709 user_input
= ecalloc(MAX_USRI
, sizeof(char));
710 if (get_usrinput(user_input
, MAX_USRI
, "new dir") < 0) {
715 path
= ecalloc(MAX_P
, sizeof(char));
716 if (snprintf(path
, MAX_P
, "%s/%s", cpane
->dirn
, user_input
) < 0) {
722 PERROR(mkdir(path
, ndir_perm
) < 0);
731 char *user_input
, *path
;
734 user_input
= ecalloc(MAX_USRI
, sizeof(char));
735 if (get_usrinput(user_input
, MAX_USRI
, "new file") < 0) {
740 path
= ecalloc(MAX_P
, sizeof(char));
741 if (snprintf(path
, MAX_P
, "%s/%s", cpane
->dirn
, user_input
) < 0) {
747 rf
= open(path
, O_CREAT
| O_EXCL
, nf_perm
);
750 print_error(strerror(errno
));
751 else if (close(rf
) < 0)
752 print_error(strerror(errno
));
758 mv_ver(const Arg
*arg
)
763 if (cpane
->hdir
- cpane
->firstrow
- arg
->i
< 1) { /* move to the top */
768 if (cpane
->hdir
- arg
->i
> cpane
->dirc
|| /* move to the bottom */
769 cpane
->hdir
- cpane
->firstrow
- arg
->i
> scrheight
- 1) {
775 if (cpane
->firstrow
> 1 && arg
->i
> 0 &&
776 cpane
->hdir
< (cpane
->firstrow
+ arg
->i
)) { /* scroll up */
777 cpane
->firstrow
= cpane
->firstrow
- arg
->i
;
778 rm_hi(cpane
, cpane
->hdir
- 1);
779 cpane
->hdir
= cpane
->hdir
- arg
->i
;
781 add_hi(cpane
, cpane
->hdir
- 1);
785 if (cpane
->hdir
- cpane
->firstrow
>= scrheight
- 1 &&
786 arg
->i
< 0) { /* scroll down */
787 cpane
->firstrow
= cpane
->firstrow
- arg
->i
;
788 rm_hi(cpane
, cpane
->hdir
- 1);
789 cpane
->hdir
= cpane
->hdir
- arg
->i
;
791 add_hi(cpane
, cpane
->hdir
- 1);
795 rm_hi(cpane
, cpane
->hdir
- 1);
796 cpane
->hdir
= cpane
->hdir
- arg
->i
;
797 add_hi(cpane
, cpane
->hdir
- 1);
798 print_info(cpane
, NULL
);
804 if (cpane
->dirn
[0] == '/' && cpane
->dirn
[1] == '\0') { /* cwd = / */
808 get_dirp(cpane
->dirn
);
809 if (check_dir(cpane
->dirn
) < 0) {
810 print_error(strerror(errno
));
815 cpane
->firstrow
= cpane
->parent_firstrow
;
816 cpane
->hdir
= cpane
->parent_row
;
817 PERROR(listdir(cpane
) < 0);
818 cpane
->parent_firstrow
= 0;
819 cpane
->parent_row
= 1;
823 mvbtm(const Arg
*arg
)
827 if (cpane
->dirc
> scrheight
) {
828 rm_hi(cpane
, cpane
->hdir
- 1);
829 cpane
->hdir
= cpane
->dirc
;
830 cpane
->firstrow
= cpane
->dirc
- scrheight
+ 1;
832 add_hi(cpane
, cpane
->hdir
- 1);
834 rm_hi(cpane
, cpane
->hdir
- 1);
835 cpane
->hdir
= cpane
->dirc
;
836 add_hi(cpane
, cpane
->hdir
- 1);
838 print_info(cpane
, NULL
);
842 mvfwd(const Arg
*arg
)
848 switch (check_dir(CURSOR(cpane
).name
)) {
850 strncpy(cpane
->dirn
, CURSOR(cpane
).name
, MAX_P
);
851 cpane
->parent_row
= cpane
->hdir
;
852 cpane
->parent_firstrow
= cpane
->firstrow
;
856 PERROR(listdir(cpane
) < 0);
858 case 1: /* not a directory open file */
861 s
= opnf(CURSOR(cpane
).name
);
866 print_error("process failed non-zero exit");
868 case -1: /* failed to open directory */
869 print_error(strerror(errno
));
874 mvmid(const Arg
*arg
)
878 rm_hi(cpane
, cpane
->hdir
- 1);
879 if (cpane
->dirc
< scrheight
/ 2)
880 cpane
->hdir
= (cpane
->dirc
+ 1) / 2;
882 cpane
->hdir
= (scrheight
/ 2) + cpane
->firstrow
;
883 add_hi(cpane
, cpane
->hdir
- 1);
884 print_info(cpane
, NULL
);
888 mvtop(const Arg
*arg
)
892 if (cpane
->dirc
> scrheight
) {
893 rm_hi(cpane
, cpane
->hdir
- 1);
897 add_hi(cpane
, cpane
->hdir
- 1);
899 rm_hi(cpane
, cpane
->hdir
- 1);
901 add_hi(cpane
, cpane
->hdir
- 1);
902 print_info(cpane
, NULL
);
907 bkmrk(const Arg
*arg
)
909 if (check_dir((char *)arg
->v
) != 0) {
910 print_error(strerror(errno
));
915 strncpy(cpane
->dirn
, (char *)arg
->v
, MAX_P
);
917 cpane
->parent_row
= 1;
919 PERROR(listdir(cpane
) < 0);
923 get_usrinput(char *out
, size_t sout
, const char *fmt
, ...)
925 int height
= tb_height();
928 size_t counter
= (size_t)1;
940 name_size
= vsnprintf(buf
, sizeof(buf
), fmt
, vl
);
942 print_tb(buf
, 1, height
- 1, col
.fg
, col
.bg
);
943 startat
= name_size
+ 1;
944 tb_set_cursor((int)(startat
+ 1), height
- 1);
947 while (tb_poll_event(&fev
) != 0) {
950 if (fev
.key
== TB_KEY_ESC
) {
951 tb_set_cursor(-1, -1);
956 if (fev
.key
== TB_KEY_BACKSPACE
||
957 fev
.key
== TB_KEY_BACKSPACE2
) {
958 if (BETWEEN(counter
, 2, sout
)) {
962 print_xstatus(empty
, startat
+ counter
);
964 startat
+ counter
, theight
- 1);
967 } else if (fev
.key
== TB_KEY_ENTER
) {
968 tb_set_cursor(-1, -1);
969 out
[counter
- 1] = '\0';
973 if (counter
< sout
) {
974 print_xstatus((char)fev
.ch
,
975 (startat
+ counter
));
976 out
[x
] = (char)fev
.ch
;
977 tb_set_cursor((startat
+ counter
+ 1),
1000 for (c
= 0; c
< LEN(rules
); c
++)
1001 for (d
= 0; d
< rules
[c
].exlen
; d
++)
1002 if (strncmp(rules
[c
].ext
[d
], ex
, MAX_EXT
) == 0)
1008 spawn(const void *com_argv
, size_t com_argc
, const void *f_argv
, size_t f_argc
,
1015 argc
= com_argc
+ f_argc
+ 2;
1018 memcpy(argv
, com_argv
, com_argc
* sizeof(char *)); /* command */
1019 memcpy(&argv
[com_argc
], f_argv
, f_argc
* sizeof(char *)); /* files */
1021 argv
[argc
- 2] = fn
;
1022 argv
[argc
- 1] = NULL
;
1029 execvp(argv
[0], argv
);
1032 while ((r
= waitpid(pid
, &ws
, 0)) == -1 && errno
== EINTR
)
1036 if ((WIFEXITED(ws
) != 0) && (WEXITSTATUS(ws
) != 0))
1052 if (c
< 0) /* extension not found open in editor */
1053 return spawn(editor
, 1, NULL
, 0, fn
);
1055 return spawn((char **)rules
[c
].v
, rules
[c
].vlen
, NULL
, 0, fn
);
1061 #if defined(_SYS_INOTIFY_H)
1062 inotify_fd
= inotify_init();
1065 #elif defined(_SYS_EVENT_H_)
1066 gtimeout
.tv_sec
= 1;
1075 addwatch(Pane
*pane
)
1077 #if defined(_SYS_INOTIFY_H)
1078 return pane
->inotify_wd
= inotify_add_watch(inotify_fd
, pane
->dirn
,
1079 IN_MODIFY
| IN_MOVED_FROM
| IN_MOVED_TO
| IN_CREATE
|
1080 IN_DELETE
| IN_DELETE_SELF
| IN_MOVE_SELF
);
1081 #elif defined(_SYS_EVENT_H_)
1082 pane
->event_fd
= open(pane
->dirn
, O_RDONLY
);
1083 if (pane
->event_fd
< 0)
1084 return pane
->event_fd
;
1085 EV_SET(&evlist
[pane
->pane_id
], pane
->event_fd
, EVFILT_VNODE
,
1087 NOTE_DELETE
| NOTE_EXTEND
| NOTE_LINK
| NOTE_RENAME
|
1088 NOTE_REVOKE
| NOTE_WRITE
,
1097 #if defined(_SYS_INOTIFY_H)
1100 struct inotify_event
*event
;
1101 const size_t events
= 32;
1102 const size_t evbuflen
=
1103 events
* (sizeof(struct inotify_event
) + MAX_N
+ 1);
1106 if (cpane
->inotify_wd
< 0)
1108 r
= read(inotify_fd
, buf
, evbuflen
);
1112 for (p
= buf
; p
< buf
+ r
;) {
1113 event
= (struct inotify_event
*)p
;
1120 p
+= sizeof(struct inotify_event
) + event
->len
;
1122 #elif defined(_SYS_EVENT_H_)
1123 return kevent(kq
, evlist
, 2, chlist
, 2, >imeout
);
1131 #if defined(_SYS_INOTIFY_H)
1132 if (pane
->inotify_wd
>= 0)
1133 inotify_rm_watch(inotify_fd
, pane
->inotify_wd
);
1134 #elif defined(_SYS_EVENT_H_)
1135 close(pane
->event_fd
);
1142 pthread_cancel(fsev_thread
);
1143 #if defined(__linux__)
1144 pthread_join(fsev_thread
, NULL
);
1146 rmwatch(&panes
[Left
]);
1147 rmwatch(&panes
[Right
]);
1148 #if defined(_SYS_INOTIFY_H)
1150 #elif defined(_SYS_EVENT_H_)
1156 toggle_df(const Arg
*arg
)
1158 show_dotfiles
= !show_dotfiles
;
1159 PERROR(listdir(&panes
[Left
]));
1160 PERROR(listdir(&panes
[Right
]));
1165 start_filter(const Arg
*arg
)
1167 if (cpane
->dirc
< 1)
1170 user_input
= ecalloc(MAX_USRI
, sizeof(char));
1171 if (get_usrinput(user_input
, MAX_USRI
, "filter") < 0) {
1175 cpane
->filter
= user_input
;
1176 if (listdir(cpane
) < 0)
1177 print_error("no match");
1178 cpane
->filter
= NULL
;
1183 start_vmode(const Arg
*arg
)
1185 if (cpane
->dirc
< 1)
1187 struct tb_event fev
;
1188 if (sel_indexes
!= NULL
) {
1193 sel_indexes
= ecalloc(cpane
->dirc
, sizeof(size_t));
1194 sel_indexes
[0] = cpane
->hdir
;
1196 print_prompt("-- VISUAL --");
1198 while (tb_poll_event(&fev
) != 0) {
1201 grabkeys(&fev
, vkeys
, vkeyslen
);
1202 if (cont_vmode
== -1)
1211 exit_vmode(const Arg
*arg
)
1213 refresh_pane(cpane
);
1214 add_hi(cpane
, cpane
->hdir
- 1);
1219 selup(const Arg
*arg
)
1222 print_prompt("-- VISUAL --");
1223 int index
= abs(cpane
->hdir
- sel_indexes
[0]);
1225 if (cpane
->hdir
< sel_indexes
[0]) {
1226 sel_indexes
[index
] = cpane
->hdir
;
1227 add_hi(cpane
, sel_indexes
[index
]);
1228 } else if (index
< cpane
->dirc
) {
1229 sel_indexes
[index
+ 1] = 0;
1231 if (cpane
->dirc
>= scrheight
||
1232 cpane
->hdir
<= 1) { /* rehighlight all if scrolling */
1238 seldwn(const Arg
*arg
)
1241 print_prompt("-- VISUAL --");
1242 int index
= abs(cpane
->hdir
- sel_indexes
[0]);
1244 if (cpane
->hdir
> sel_indexes
[0]) {
1245 sel_indexes
[index
] = cpane
->hdir
;
1246 add_hi(cpane
, sel_indexes
[index
] - 2);
1248 sel_indexes
[index
+ 1] = 0;
1250 if (cpane
->dirc
>= scrheight
||
1251 cpane
->hdir
>= cpane
->dirc
) { /* rehighlight all if scrolling */
1257 selall(const Arg
*arg
)
1260 for (i
= 0; i
< cpane
->dirc
; i
++) {
1261 sel_indexes
[i
] = i
+ 1;
1270 for (i
= 0; i
< cpane
->dirc
; i
++) {
1271 if (sel_indexes
[i
] < (scrheight
+ cpane
->firstrow
) &&
1273 cpane
->firstrow
) { /* checks if in the frame of the directories */
1274 add_hi(cpane
, sel_indexes
[i
] - 1);
1285 for (j
= 0; j
< cpane
->dirc
; j
++) { /* calculate used selection size */
1286 if (sel_indexes
[j
] != 0)
1298 if (sel_files
!= NULL
) {
1299 for (i
= 0; i
< sel_len
; i
++) {
1301 sel_files
[i
] = NULL
;
1315 sel_files
= ecalloc(sel_len
, sizeof(char *));
1317 for (i
= 0; i
< sel_len
; i
++) {
1318 sel_files
[i
] = ecalloc(MAX_P
, sizeof(char));
1319 strncpy(sel_files
[i
], cpane
->direntr
[sel_indexes
[i
] - 1].name
,
1325 selynk(const Arg
*arg
)
1328 refresh_pane(cpane
);
1329 add_hi(cpane
, cpane
->hdir
- 1);
1330 print_status(cprompt
, "%zu files are yanked", sel_len
);
1335 seldel(const Arg
*arg
)
1339 inp_conf
= ecalloc(delconf_len
, sizeof(char));
1340 if ((get_usrinput(inp_conf
, delconf_len
, "delete file (yes) ?") < 0) ||
1341 (strncmp(inp_conf
, delconf
, delconf_len
) != 0)) {
1343 return; /* canceled by user or wrong inp_conf */
1349 if (spawn(rm_cmd
, rm_cmd_len
, sel_files
, sel_len
, NULL
) < 0)
1350 print_error(strerror(errno
));
1352 print_status(cprompt
, "%zu files are deleted", sel_len
);
1359 paste(const Arg
*arg
)
1361 if (sel_files
== NULL
) {
1362 print_error("nothing to paste");
1366 if (spawn(cp_cmd
, cp_cmd_len
, sel_files
, sel_len
, cpane
->dirn
) < 0)
1367 print_error(strerror(errno
));
1369 print_status(cprompt
, "%zu files are copied", sel_len
);
1375 selmv(const Arg
*arg
)
1377 if (sel_files
== NULL
) {
1378 print_error("nothing to move");
1382 if (spawn(mv_cmd
, mv_cmd_len
, sel_files
, sel_len
, cpane
->dirn
) < 0)
1383 print_error(strerror(errno
));
1385 print_status(cprompt
, "%zu files are moved", sel_len
);
1391 rname(const Arg
*arg
)
1393 if (cpane
->dirc
< 1)
1395 char new_name
[MAX_P
];
1398 input_name
= ecalloc(MAX_N
, sizeof(char));
1400 if (get_usrinput(input_name
, MAX_N
, "rename: %s",
1401 basename(CURSOR(cpane
).name
)) < 0) {
1406 if (snprintf(new_name
, MAX_P
, "%s/%s", cpane
->dirn
, input_name
) < 0) {
1408 print_error(strerror(errno
));
1412 char *rename_cmd
[] = { "mv", CURSOR(cpane
).name
, new_name
};
1413 PERROR(spawn(rename_cmd
, 3, NULL
, 0, NULL
) < 0);
1419 yank(const Arg
*arg
)
1421 if (cpane
->dirc
< 1)
1426 sel_files
= ecalloc(sel_len
, sizeof(char *));
1427 sel_files
[0] = ecalloc(MAX_P
, sizeof(char));
1428 strncpy(sel_files
[0], CURSOR(cpane
).name
, MAX_P
);
1429 print_status(cprompt
, "1 file is yanked", sel_len
);
1433 switch_pane(const Arg
*arg
)
1435 if (cpane
->dirc
> 0)
1436 rm_hi(cpane
, cpane
->hdir
- 1);
1437 cpane
= &panes
[pane_idx
^= 1];
1438 if (cpane
->dirc
> 0) {
1439 add_hi(cpane
, cpane
->hdir
- 1);
1440 print_info(cpane
, NULL
);
1447 quit(const Arg
*arg
)
1449 if (cont_vmode
== -1) { /* check if selection was allocated */
1451 if (sel_files
!= NULL
)
1454 free(panes
[Left
].direntr
);
1455 free(panes
[Right
].direntr
);
1462 grabkeys(struct tb_event
*event
, Key
*key
, size_t max_keys
)
1466 for (i
= 0; i
< max_keys
; i
++) {
1467 if (event
->ch
!= 0) {
1468 if (event
->ch
== key
[i
].evkey
.ch
) {
1469 key
[i
].func(&key
[i
].arg
);
1472 } else if (event
->key
!= 0) {
1473 if (event
->key
== key
[i
].evkey
.key
) {
1474 key
[i
].func(&key
[i
].arg
);
1484 struct timespec tim
;
1486 tim
.tv_nsec
= 5000000L; /* 0.005 sec */
1489 if (read_events() > READEVSZ
) {
1490 kill(main_pid
, SIGUSR1
);
1491 nanosleep(&tim
, NULL
);
1501 while (tb_poll_event(&ev
) != 0) {
1504 grabkeys(&ev
, nkeys
, nkeyslen
);
1507 case TB_EVENT_RESIZE
:
1518 refresh_pane(Pane
*pane
)
1520 size_t y
, dyn_max
, start_from
;
1522 width
= (twidth
/ 2) - 4;
1526 start_from
= pane
->firstrow
;
1527 dyn_max
= MIN(pane
->dirc
, (scrheight
- 1) + pane
->firstrow
);
1529 /* print each entry in directory */
1530 while (start_from
< dyn_max
) {
1531 get_hicol(&col
, pane
->direntr
[start_from
].mode
);
1532 print_row(pane
, start_from
, col
);
1538 print_info(pane
, NULL
);
1542 /* print current directory title */
1543 pane
->dircol
.fg
|= TB_BOLD
;
1544 printf_tb(pane
->dirx
, 0, pane
->dircol
, " %.*s ", width
, pane
->dirn
);
1548 set_direntr(Pane
*pane
, struct dirent
*entry
, DIR *dir
, char *filter
)
1555 tmpfull = get_fullpath(pane->dirn, entry->d_name); \
1556 strncpy(pane->direntr[i].name, tmpfull, MAX_N); \
1557 if (lstat(tmpfull, &status) == 0) { \
1558 pane->direntr[i].size = status.st_size; \
1559 pane->direntr[i].mode = status.st_mode; \
1560 pane->direntr[i].group = status.st_gid; \
1561 pane->direntr[i].user = status.st_uid; \
1562 pane->direntr[i].dt = status.st_mtime; \
1569 erealloc(pane
->direntr
, (10 + pane
->dirc
) * sizeof(Entry
));
1570 while ((entry
= readdir(dir
)) != 0) {
1571 if (show_dotfiles
== 1) {
1572 if (entry
->d_name
[0] == '.' &&
1573 (entry
->d_name
[1] == '\0' ||
1574 entry
->d_name
[1] == '.'))
1577 if (entry
->d_name
[0] == '.')
1581 if (filter
== NULL
) {
1583 } else if (filter
!= NULL
) {
1584 if (strcasestr(entry
->d_name
, filter
) != NULL
) {
1597 struct dirent
*entry
;
1599 int filtercount
= 0;
1600 size_t oldc
= pane
->dirc
;
1602 width
= (twidth
/ 2) - 4;
1605 dir
= opendir(pane
->dirn
);
1609 /* get content and filter sum */
1610 while ((entry
= readdir(dir
)) != 0) {
1611 if (pane
->filter
!= NULL
) {
1612 if (strcasestr(entry
->d_name
, pane
->filter
) != NULL
)
1614 } else { /* no filter */
1619 if (pane
->filter
== NULL
) {
1624 if (pane
->filter
!= NULL
) {
1625 if (filtercount
> 0) {
1626 pane
->dirc
= filtercount
;
1629 } else if (filtercount
== 0) {
1630 if (closedir(dir
) < 0)
1637 /* print current directory title */
1638 pane
->dircol
.fg
|= TB_BOLD
;
1639 printf_tb(pane
->dirx
, 0, pane
->dircol
, " %.*s ", width
, pane
->dirn
);
1641 if (pane
->filter
== NULL
) /* dont't watch when filtering */
1642 if (addwatch(pane
) < 0)
1643 print_error("can't add watch");
1645 /* empty directory */
1646 if (pane
->dirc
== 0) {
1648 if (closedir(dir
) < 0)
1653 rewinddir(dir
); /* reset position */
1655 pane
, entry
, dir
, pane
->filter
); /* create array of entries */
1656 qsort(pane
->direntr
, pane
->dirc
, sizeof(Entry
), sort_name
);
1659 if (pane
->hdir
> pane
->dirc
)
1660 pane
->hdir
= pane
->dirc
;
1662 if (pane
== cpane
&& pane
->dirc
> 0)
1663 add_hi(pane
, pane
->hdir
- 1);
1665 if (closedir(dir
) < 0)
1675 panes
[Right
].dirx
= (twidth
/ 2) + 2;
1676 refresh_pane(&panes
[Left
]);
1677 refresh_pane(&panes
[Right
]);
1678 if (cpane
->dirc
> 0)
1679 add_hi(cpane
, cpane
->hdir
- 1);
1686 editor
[0] = getenv("EDITOR");
1689 if (editor
[0] == NULL
)
1699 home
= getenv("HOME");
1702 if ((getcwd(cwd
, sizeof(cwd
)) == NULL
))
1703 strncpy(cwd
, home
, MAX_P
);
1705 pane_idx
= Left
; /* cursor pane */
1706 cpane
= &panes
[pane_idx
];
1708 panes
[Left
].ex
= (twidth
/ 2) - 1;
1709 panes
[Left
].pane_id
= 0;
1710 panes
[Left
].dirx
= 2;
1711 panes
[Left
].dircol
= cpanell
;
1712 panes
[Left
].firstrow
= 0;
1713 panes
[Left
].direntr
= ecalloc(0, sizeof(Entry
));
1714 strncpy(panes
[Left
].dirn
, cwd
, MAX_P
);
1715 panes
[Left
].hdir
= 1;
1716 panes
[Left
].inotify_wd
= -1;
1717 panes
[Left
].parent_row
= 1;
1719 panes
[Right
].ex
= twidth
- 1;
1720 panes
[Right
].pane_id
= 1;
1721 panes
[Right
].dirx
= (twidth
/ 2) + 2;
1722 panes
[Right
].dircol
= cpanelr
;
1723 panes
[Right
].firstrow
= 0;
1724 panes
[Right
].direntr
= ecalloc(0, sizeof(Entry
));
1725 strncpy(panes
[Right
].dirn
, home
, MAX_P
);
1726 panes
[Right
].hdir
= 1;
1727 panes
[Right
].inotify_wd
= -1;
1728 panes
[Right
].parent_row
= 1;
1735 theight
= tb_height();
1736 twidth
= tb_width();
1737 scrheight
= theight
- 2;
1739 /* 2 horizontal lines */
1740 for (i
= 1; i
< twidth
- 1; ++i
) {
1741 tb_change_cell(i
, 0, u_hl
, cframe
.fg
, cframe
.bg
);
1742 tb_change_cell(i
, theight
- 2, u_hl
, cframe
.fg
, cframe
.bg
);
1745 /* 4 vertical lines */
1746 for (i
= 1; i
< theight
- 1; ++i
) {
1747 tb_change_cell(0, i
, u_vl
, cframe
.fg
, cframe
.bg
);
1749 (twidth
- 1) / 2, i
- 1, u_vl
, cframe
.fg
, cframe
.bg
);
1750 tb_change_cell(((twidth
- 1) / 2) + 1, i
- 1, u_vl
, cframe
.fg
,
1752 tb_change_cell(twidth
- 1, i
, u_vl
, cframe
.fg
, cframe
.bg
);
1756 tb_change_cell(0, 0, u_cnw
, cframe
.fg
, cframe
.bg
);
1757 tb_change_cell(twidth
- 1, 0, u_cne
, cframe
.fg
, cframe
.bg
);
1758 tb_change_cell(0, theight
- 2, u_csw
, cframe
.fg
, cframe
.bg
);
1759 tb_change_cell(twidth
- 1, theight
- 2, u_cse
, cframe
.fg
, cframe
.bg
);
1761 /* 2 middel top and bottom */
1762 tb_change_cell((twidth
- 1) / 2, 0, u_mn
, cframe
.fg
, cframe
.bg
);
1764 (twidth
- 1) / 2, theight
- 2, u_ms
, cframe
.fg
, cframe
.bg
);
1772 PERROR(listdir(&panes
[Left
]));
1773 PERROR(listdir(&panes
[Right
]));
1774 if (cpane
->dirc
> 0)
1775 add_hi(cpane
, cpane
->hdir
- 1);
1782 struct sigaction sa
;
1784 main_pid
= getpid();
1785 sa
.sa_handler
= th_handler
;
1786 sigemptyset(&sa
.sa_mask
);
1787 sa
.sa_flags
= SA_RESTART
;
1788 return sigaction(SIGUSR1
, &sa
, NULL
);
1794 FAIL_IF(tb_init() != 0, "tb_init()");
1795 if (tb_select_output_mode(TB_OUTPUT_256
) != TB_OUTPUT_256
)
1796 if (tb_select_output_mode(TB_OUTPUT_NORMAL
) != TB_OUTPUT_NORMAL
)
1797 die("output error");
1801 PERROR(start_signal() < 0);
1802 PERROR(start_signal() < 0);
1803 PERROR(fsev_init() < 0);
1804 PERROR(listdir(&panes
[Left
]) < 0);
1805 PERROR(listdir(&panes
[Right
]) < 0);
1808 pthread_create(&fsev_thread
, NULL
, read_th
, NULL
);
1813 main(int argc
, char *argv
[])
1815 #if defined(__OpenBSD__)
1816 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
1819 #endif /* __OpenBSD__ */
1822 else if (argc
== 2 && strncmp("-v", argv
[1], 2) == 0)
1823 die("sfm-" VERSION
);
1825 die("usage: sfm [-v]");