1 /* See LICENSE file for copyright and license details. */
2
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__) || defined(__DragonFly__) || \
18 defined(__APPLE__)
19 #include <sys/event.h>
20 #endif
21
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>
37
38 #include "termbox.h"
39 #include "util.h"
40
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]
52
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;
62
63 typedef struct {
64 uint16_t fg;
65 uint16_t bg;
66 } Cpair;
67
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 dirc; // dir entries sum
74 int hdir; // highlighted dir
75 int x_srt;
76 int x_end;
77 int firstrow;
78 int parent_firstrow;
79 int parent_row; // FIX
80 Cpair dircol;
81 int inotify_wd;
82 int event_fd;
83 } Pane;
84
85 typedef struct {
86 const char **ext;
87 size_t exlen;
88 const void *v;
89 size_t vlen;
90 } Rule;
91
92 typedef union {
93 uint16_t key; /* one of the TB_KEY_* constants */
94 uint32_t ch; /* unicode character */
95 } Evkey;
96
97 typedef union {
98 int i;
99 const void *v;
100 } Arg;
101
102 typedef struct {
103 const Evkey evkey;
104 void (*func)(const Arg *);
105 const Arg arg;
106 } Key;
107
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 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 mvtop(const Arg *arg);
143 static void bkmrk(const Arg *arg);
144 static int get_usrinput(char *, size_t, const char *, ...);
145 static int frules(char *);
146 static int spawn(const void *, size_t, const void *, size_t, char *, int);
147 static int opnf(char *);
148 static int fsev_init(void);
149 static int addwatch(Pane *);
150 static int read_events(void);
151 static void rmwatch(Pane *);
152 static void fsev_shdn(void);
153 static void toggle_df(const Arg *arg);
154 static void start_filter(const Arg *arg);
155 static void start_vmode(const Arg *arg);
156 static void exit_vmode(const Arg *arg);
157 static void start_change(const Arg *arg);
158 static void exit_change(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 chngo(const Arg *arg);
173 static void chngm(const Arg *arg);
174 static void chngf(const Arg *arg);
175 static void dupl(const Arg *arg);
176 static void switch_pane(const Arg *arg);
177 static void quit(const Arg *arg);
178 static void grabkeys(struct tb_event *, Key *, size_t);
179 static void *read_th(void *arg);
180 static void start_ev(void);
181 static void refresh_pane(Pane *);
182 static void set_direntr(Pane *, struct dirent *, DIR *, char *);
183 static int listdir(Pane *);
184 static void t_resize(void);
185 static void get_shell(void);
186 static void opnsh(const Arg *arg);
187 static void set_panes(void);
188 static void draw_frame(void);
189 static void refresh(const Arg *arg);
190 static void start(void);
191
192 /* global variables */
193 static pthread_t fsev_thread;
194 static Pane panes[2];
195 static Pane *cpane;
196 static int pane_idx;
197 static char *editor[2];
198 static char fed[] = "vi";
199 static char *shell[2];
200 static char sh[] = "/bin/sh";
201 static int theight, twidth, hwidth, scrheight;
202 static int *sel_indexes;
203 static size_t sel_len = 0;
204 static char **sel_files;
205 static int cont_vmode = 0;
206 static int cont_change = 0;
207 static pid_t fork_pid = 0, main_pid;
208 #if defined(_SYS_INOTIFY_H)
209 #define READEVSZ 16
210 static int inotify_fd;
211 #elif defined(_SYS_EVENT_H_)
212 #define READEVSZ 0
213 static int kq;
214 struct kevent evlist[2]; /* events we want to monitor */
215 struct kevent chlist[2]; /* events that were triggered */
216 static struct timespec gtimeout;
217 #endif
218 #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
219 #define OFF_T "%ld"
220 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
221 #define OFF_T "%lld"
222 #endif
223 enum { Left, Right }; /* panes */
224 enum { Wait, DontWait }; /* spawn forks */
225
226 /* configuration, allows nested code to access above variables */
227 #include "config.h"
228
229 /* function implementations */
230 static void
print_tb(const char * str,int x,int y,uint16_t fg,uint16_t bg)231 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
232 {
233 while (*str != '\0') {
234 uint32_t uni = 0;
235 str += tb_utf8_char_to_unicode(&uni, str);
236 tb_change_cell(x, y, uni, fg, bg);
237 x++;
238 }
239 }
240
241 static void
printf_tb(int x,int y,Cpair col,const char * fmt,...)242 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
243 {
244 char buf[MAX_LINE];
245 va_list vl;
246 va_start(vl, fmt);
247 (void)vsnprintf(buf, MAX_LINE, fmt, vl);
248 va_end(vl);
249 print_tb(buf, x, y, col.fg, col.bg);
250 }
251
252 static void
print_status(Cpair col,const char * fmt,...)253 print_status(Cpair col, const char *fmt, ...)
254 {
255 char buf[MAX_STATUS];
256 va_list vl;
257 va_start(vl, fmt);
258 (void)vsnprintf(buf, MAX_STATUS, fmt, vl);
259 va_end(vl);
260 clear_status();
261 print_tb(buf, 1, theight - 1, col.fg, col.bg);
262 }
263
264 static void
print_xstatus(char c,int x)265 print_xstatus(char c, int x)
266 {
267 uint32_t uni = 0;
268 (void)tb_utf8_char_to_unicode(&uni, &c);
269 tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg);
270 }
271
272 static void
print_error(char * errmsg)273 print_error(char *errmsg)
274 {
275 print_status(cerr, errmsg);
276 }
277
278 static void
print_prompt(char * prompt)279 print_prompt(char *prompt)
280 {
281 print_status(cprompt, prompt);
282 }
283
284 static void
print_info(Pane * pane,char * dirsize)285 print_info(Pane *pane, char *dirsize)
286 {
287 char *sz, *ur, *gr, *dt, *prm;
288
289 dt = ecalloc(MAX_DTF, sizeof(char));
290
291 prm = get_fperm(CURSOR(pane).mode);
292 ur = get_fusr(CURSOR(pane).user);
293 gr = get_fgrp(CURSOR(pane).group);
294
295 if (get_fdt(dt, CURSOR(pane).dt) < 0)
296 *dt = '\0';
297
298 if (S_ISREG(CURSOR(pane).mode)) {
299 sz = get_fsize(CURSOR(pane).size);
300 } else {
301 if (dirsize == NULL) {
302 sz = ecalloc(1, sizeof(char));
303 *sz = '\0';
304 } else {
305 sz = dirsize;
306 }
307 }
308
309 print_status(cstatus, "%02d/%02d %s %s:%s %s %s", pane->hdir,
310 pane->dirc, prm, ur, gr, dt, sz);
311
312 free(prm);
313 free(ur);
314 free(gr);
315 free(dt);
316 free(sz);
317 }
318
319 static void
print_row(Pane * pane,size_t entpos,Cpair col)320 print_row(Pane *pane, size_t entpos, Cpair col)
321 {
322 int x, y;
323 char *full_str, *rez_pth;
324 char lnk_full[MAX_N];
325
326 full_str = basename(pane->direntr[entpos].name);
327 x = pane->x_srt;
328 y = entpos - pane->firstrow + 1;
329
330 if (S_ISLNK(pane->direntr[entpos].mode) != 0) {
331 rez_pth = ecalloc(MAX_P, sizeof(char));
332 if (realpath(pane->direntr[entpos].name, rez_pth) != NULL) {
333 snprintf(
334 lnk_full, MAX_N, "%s -> %s", full_str, rez_pth);
335 full_str = lnk_full;
336 }
337 free(rez_pth);
338 }
339
340 printf_tb(x, y, col, "%*.*s", ~hwidth, hwidth, full_str);
341 }
342
343 static void
clear(int sx,int ex,int y,uint16_t bg)344 clear(int sx, int ex, int y, uint16_t bg)
345 {
346 /* clear line from to */
347 /* x = line number vertical */
348 /* y = column number horizontal */
349 int i;
350 for (i = sx; i < ex; i++) {
351 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
352 }
353 }
354
355 static void
clear_status(void)356 clear_status(void)
357 {
358 clear(1, twidth - 1, theight - 1, cstatus.bg);
359 }
360
361 static void
clear_pane(Pane * pane)362 clear_pane(Pane *pane)
363 {
364 int i, y;
365 y = 0, i = 0;
366
367 while (i < scrheight) {
368 clear(pane->x_srt, pane->x_end, y, TB_DEFAULT);
369 i++;
370 y++;
371 }
372
373 /* draw top line */
374 for (y = pane->x_srt; y < pane->x_end; ++y) {
375 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
376 }
377 }
378
379 static void
add_hi(Pane * pane,size_t entpos)380 add_hi(Pane *pane, size_t entpos)
381 {
382 Cpair col;
383 get_hicol(&col, pane->direntr[entpos].mode);
384 col.fg |= TB_REVERSE | TB_BOLD;
385 col.bg |= TB_REVERSE;
386 print_row(pane, entpos, col);
387 }
388
389 static void
rm_hi(Pane * pane,size_t entpos)390 rm_hi(Pane *pane, size_t entpos)
391 {
392 Cpair col;
393 get_hicol(&col, pane->direntr[entpos].mode);
394 print_row(pane, entpos, col);
395 }
396
397 static int
check_dir(char * path)398 check_dir(char *path)
399 {
400 DIR *dir;
401 dir = opendir(path);
402
403 if (dir == NULL) {
404 if (errno == ENOTDIR) {
405 return 1;
406 } else {
407 return -1;
408 }
409 }
410
411 if (closedir(dir) < 0)
412 return -1;
413
414 return 0;
415 }
416
417 static int
sort_name(const void * const A,const void * const B)418 sort_name(const void *const A, const void *const B)
419 {
420 int result;
421 mode_t data1 = (*(Entry *)A).mode;
422 mode_t data2 = (*(Entry *)B).mode;
423
424 if (data1 < data2) {
425 return -1;
426 } else if (data1 == data2) {
427 result = strncmp((*(Entry *)A).name, (*(Entry *)B).name, MAX_N);
428 return result;
429 } else {
430 return 1;
431 }
432 }
433
434 static void
get_dirp(char * cdir)435 get_dirp(char *cdir)
436 {
437 int counter, len, i;
438
439 counter = 0;
440 len = strnlen(cdir, MAX_P);
441 if (len == 1)
442 return;
443
444 for (i = len - 1; i > 1; i--) {
445 if (cdir[i] == '/')
446 break;
447 else
448 counter++;
449 }
450
451 cdir[len - counter - 1] = '\0';
452 }
453
454 static char *
get_ext(char * str)455 get_ext(char *str)
456 {
457 char *ext;
458 char dot;
459 size_t counter, len, i;
460
461 dot = '.';
462 counter = 0;
463 len = strnlen(str, MAX_N);
464
465 for (i = len - 1; i > 0; i--) {
466 if (str[i] == dot) {
467 break;
468 } else {
469 counter++;
470 }
471 }
472
473 ext = ecalloc(MAX_EXT + 1, sizeof(char));
474 strncpy(ext, &str[len - counter], MAX_EXT);
475 ext[MAX_EXT] = '\0';
476 return ext;
477 }
478
479 static int
get_fdt(char * result,time_t status)480 get_fdt(char *result, time_t status)
481 {
482 struct tm lt;
483 localtime_r(&status, <);
484 return strftime(result, MAX_DTF, dtfmt, <);
485 }
486
487 static char *
get_fgrp(gid_t status)488 get_fgrp(gid_t status)
489 {
490 char *result;
491 struct group *gr;
492
493 result = ecalloc(MAX_GRPN, sizeof(char));
494 gr = getgrgid(status);
495 if (gr == NULL)
496 (void)snprintf(result, MAX_GRPN, "%u", status);
497 else
498 strncpy(result, gr->gr_name, MAX_GRPN);
499
500 result[MAX_GRPN - 1] = '\0';
501 return result;
502 }
503
504 static char *
get_fperm(mode_t mode)505 get_fperm(mode_t mode)
506 {
507 char *buf;
508 size_t i;
509
510 const char chars[] = "rwxrwxrwx";
511 buf = ecalloc(11, sizeof(char));
512
513 if (S_ISDIR(mode))
514 buf[0] = 'd';
515 else if (S_ISREG(mode))
516 buf[0] = '-';
517 else if (S_ISLNK(mode))
518 buf[0] = 'l';
519 else if (S_ISBLK(mode))
520 buf[0] = 'b';
521 else if (S_ISCHR(mode))
522 buf[0] = 'c';
523 else if (S_ISFIFO(mode))
524 buf[0] = 'p';
525 else if (S_ISSOCK(mode))
526 buf[0] = 's';
527 else
528 buf[0] = '?';
529
530 for (i = 1; i < 10; i++) {
531 buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-';
532 }
533 buf[10] = '\0';
534
535 return buf;
536 }
537
538 static char *
get_fsize(off_t size)539 get_fsize(off_t size)
540 {
541 char *result; /* need to be freed */
542 char unit;
543 int result_len;
544 int counter;
545
546 counter = 0;
547 result_len = 6; /* 9999X/0 */
548 result = ecalloc(result_len, sizeof(char));
549
550 while (size >= 1000) {
551 size /= 1024;
552 ++counter;
553 }
554
555 switch (counter) {
556 case 0:
557 unit = 'B';
558 break;
559 case 1:
560 unit = 'K';
561 break;
562 case 2:
563 unit = 'M';
564 break;
565 case 3:
566 unit = 'G';
567 break;
568 case 4:
569 unit = 'T';
570 break;
571 default:
572 unit = '?';
573 }
574
575 if (snprintf(result, result_len, OFF_T "%c", size, unit) < 0)
576 strncat(result, "???", result_len);
577
578 return result;
579 }
580
581 static char *
get_fullpath(char * first,char * second)582 get_fullpath(char *first, char *second)
583 {
584 char *full_path;
585
586 full_path = ecalloc(MAX_P, sizeof(char));
587
588 if (strncmp(first, "/", MAX_P) == 0)
589 (void)snprintf(full_path, MAX_P, "/%s", second);
590 else
591 (void)snprintf(full_path, MAX_P, "%s/%s", first, second);
592
593 return full_path;
594 }
595
596 static char *
get_fusr(uid_t status)597 get_fusr(uid_t status)
598 {
599 char *result;
600 struct passwd *pw;
601
602 result = ecalloc(MAX_USRN, sizeof(char));
603 pw = getpwuid(status);
604 if (pw == NULL)
605 (void)snprintf(result, MAX_USRN, "%u", status);
606 else
607 strncpy(result, pw->pw_name, MAX_USRN);
608
609 result[MAX_USRN - 1] = '\0';
610 return result;
611 }
612
613 static void
get_dirsize(char * fullpath,off_t * fullsize)614 get_dirsize(char *fullpath, off_t *fullsize)
615 {
616 DIR *dir;
617 char *ent_full;
618 mode_t mode;
619 struct dirent *entry;
620 struct stat status;
621
622 dir = opendir(fullpath);
623 if (dir == NULL) {
624 return;
625 }
626
627 while ((entry = readdir(dir)) != 0) {
628 if ((strncmp(entry->d_name, ".", 2) == 0 ||
629 strncmp(entry->d_name, "..", 3) == 0))
630 continue;
631
632 ent_full = get_fullpath(fullpath, entry->d_name);
633 if (lstat(ent_full, &status) == 0) {
634 mode = status.st_mode;
635 if (S_ISDIR(mode)) {
636 get_dirsize(ent_full, fullsize);
637 free(ent_full);
638 } else {
639 *fullsize += status.st_size;
640 free(ent_full);
641 }
642 }
643 }
644
645 closedir(dir);
646 clear_status();
647 }
648
649 static void
get_hicol(Cpair * col,mode_t mode)650 get_hicol(Cpair *col, mode_t mode)
651 {
652 switch (mode & S_IFMT) {
653 case S_IFREG:
654 *col = cfile;
655 if ((S_IXUSR | S_IXGRP | S_IXOTH) & mode)
656 *col = cexec;
657 break;
658 case S_IFDIR:
659 *col = cdir;
660 break;
661 case S_IFLNK:
662 *col = clnk;
663 break;
664 case S_IFBLK:
665 *col = cblk;
666 break;
667 case S_IFCHR:
668 *col = cchr;
669 break;
670 case S_IFIFO:
671 *col = cifo;
672 break;
673 case S_IFSOCK:
674 *col = csock;
675 break;
676 default:
677 *col = cother;
678 break;
679 }
680 }
681
682 static void
delent(const Arg * arg)683 delent(const Arg *arg)
684 {
685 if (cpane->dirc < 1)
686 return;
687 char *inp_conf;
688
689 inp_conf = ecalloc(delconf_len, sizeof(char));
690 if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?",
691 delconf) < 0) ||
692 (strncmp(inp_conf, delconf, delconf_len) != 0)) {
693 free(inp_conf);
694 return; /* canceled by user or wrong inp_conf */
695 }
696 free(inp_conf);
697
698 char *tmp[1];
699 tmp[0] = CURSOR(cpane).name;
700 if (spawn(rm_cmd, rm_cmd_len, tmp, 1, NULL, DontWait) < 0) {
701 print_error(strerror(errno));
702 return;
703 }
704 }
705
706 static void
calcdir(const Arg * arg)707 calcdir(const Arg *arg)
708 {
709 if (cpane->dirc < 1)
710 return;
711 if (!S_ISDIR(CURSOR(cpane).mode))
712 return;
713
714 off_t *fullsize;
715 char *csize;
716
717 fullsize = ecalloc(1, sizeof(off_t));
718 get_dirsize(CURSOR(cpane).name, fullsize);
719 csize = get_fsize(*fullsize);
720
721 CURSOR(cpane).size = *fullsize;
722 print_info(cpane, csize);
723 free(fullsize);
724 }
725
726 static void
crnd(const Arg * arg)727 crnd(const Arg *arg)
728 {
729 char *user_input, *path;
730
731 user_input = ecalloc(MAX_USRI, sizeof(char));
732 if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
733 free(user_input);
734 return;
735 }
736
737 path = ecalloc(MAX_P, sizeof(char));
738 if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
739 free(user_input);
740 free(path);
741 return;
742 }
743
744 PERROR(mkdir(path, ndir_perm) < 0);
745
746 free(user_input);
747 free(path);
748 }
749
750 static void
crnf(const Arg * arg)751 crnf(const Arg *arg)
752 {
753 char *user_input, *path;
754 int rf;
755
756 user_input = ecalloc(MAX_USRI, sizeof(char));
757 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
758 free(user_input);
759 return;
760 }
761
762 path = ecalloc(MAX_P, sizeof(char));
763 if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
764 free(user_input);
765 free(path);
766 return;
767 }
768
769 rf = open(path, O_CREAT | O_EXCL, nf_perm);
770
771 if (rf < 0)
772 print_error(strerror(errno));
773 else if (close(rf) < 0)
774 print_error(strerror(errno));
775
776 free(user_input);
777 free(path);
778 }
779 static void
mv_ver(const Arg * arg)780 mv_ver(const Arg *arg)
781 {
782
783 if (cpane->dirc < 1)
784 return;
785 if (cpane->hdir - arg->i < 1) /* first line */
786 return;
787
788 if (cpane->hdir - arg->i > cpane->dirc) /* last line */
789 return;
790
791 if (cpane->firstrow > 0 && arg->i > 0 &&
792 cpane->hdir <= (cpane->firstrow + arg->i)) { /* scroll up */
793 cpane->firstrow = cpane->firstrow - arg->i;
794 rm_hi(cpane, cpane->hdir - 1);
795 cpane->hdir = cpane->hdir - arg->i;
796 refresh_pane(cpane);
797 add_hi(cpane, cpane->hdir - 1);
798 return;
799 }
800
801 if (cpane->hdir - cpane->firstrow >= scrheight + arg->i &&
802 arg->i < 0) { /* scroll down */
803 cpane->firstrow = cpane->firstrow - arg->i;
804 rm_hi(cpane, cpane->hdir - 1);
805 cpane->hdir = cpane->hdir - arg->i;
806 refresh_pane(cpane);
807 add_hi(cpane, cpane->hdir - 1);
808 return;
809 }
810
811 rm_hi(cpane, cpane->hdir - 1);
812 cpane->hdir = cpane->hdir - arg->i;
813 add_hi(cpane, cpane->hdir - 1);
814 print_info(cpane, NULL);
815 }
816
817 static void
mvbk(const Arg * arg)818 mvbk(const Arg *arg)
819 {
820 if (cpane->dirn[0] == '/' && cpane->dirn[1] == '\0') { /* cwd = / */
821 return;
822 }
823
824 get_dirp(cpane->dirn);
825 if (check_dir(cpane->dirn) < 0) {
826 print_error(strerror(errno));
827 return;
828 }
829
830 cpane->firstrow = cpane->parent_firstrow;
831 cpane->hdir = cpane->parent_row;
832 PERROR(listdir(cpane) < 0);
833 cpane->parent_firstrow = 0;
834 cpane->parent_row = 1;
835 }
836
837 static void
mvbtm(const Arg * arg)838 mvbtm(const Arg *arg)
839 {
840 if (cpane->dirc < 1)
841 return;
842 if (cpane->dirc > scrheight) {
843 rm_hi(cpane, cpane->hdir - 1);
844 cpane->hdir = cpane->dirc;
845 cpane->firstrow = cpane->dirc - scrheight + 1;
846 refresh_pane(cpane);
847 add_hi(cpane, cpane->hdir - 1);
848 } else {
849 rm_hi(cpane, cpane->hdir - 1);
850 cpane->hdir = cpane->dirc;
851 add_hi(cpane, cpane->hdir - 1);
852 }
853 print_info(cpane, NULL);
854 }
855
856 static void
mvfwd(const Arg * arg)857 mvfwd(const Arg *arg)
858 {
859 if (cpane->dirc < 1)
860 return;
861 int s;
862
863 switch (check_dir(CURSOR(cpane).name)) {
864 case 0:
865 strncpy(cpane->dirn, CURSOR(cpane).name, MAX_P);
866 cpane->parent_row = cpane->hdir;
867 cpane->parent_firstrow = cpane->firstrow;
868 cpane->hdir = 1;
869 cpane->firstrow = 0;
870 PERROR(listdir(cpane) < 0);
871 break;
872 case 1: /* not a directory open file */
873 tb_shutdown();
874 s = opnf(CURSOR(cpane).name);
875 if (tb_init() != 0)
876 die("tb_init");
877 t_resize();
878 if (s < 0)
879 print_error("process failed non-zero exit");
880 break;
881 case -1: /* failed to open directory */
882 print_error(strerror(errno));
883 }
884 }
885
886 static void
mvtop(const Arg * arg)887 mvtop(const Arg *arg)
888 {
889 if (cpane->dirc < 1)
890 return;
891 if (cpane->dirc > scrheight) {
892 rm_hi(cpane, cpane->hdir - 1);
893 cpane->hdir = 1;
894 cpane->firstrow = 0;
895 refresh_pane(cpane);
896 add_hi(cpane, cpane->hdir - 1);
897 } else {
898 rm_hi(cpane, cpane->hdir - 1);
899 cpane->hdir = 1;
900 add_hi(cpane, cpane->hdir - 1);
901 print_info(cpane, NULL);
902 }
903 }
904
905 static void
bkmrk(const Arg * arg)906 bkmrk(const Arg *arg)
907 {
908 if (check_dir((char *)arg->v) != 0) {
909 print_error(strerror(errno));
910 return;
911 }
912
913 strncpy(cpane->dirn, (char *)arg->v, MAX_P);
914 cpane->firstrow = 0;
915 cpane->parent_row = 1;
916 cpane->hdir = 1;
917 PERROR(listdir(cpane) < 0);
918 }
919
920 static int
get_usrinput(char * result,size_t max_chars,const char * fmt,...)921 get_usrinput(char *result, size_t max_chars, const char *fmt, ...)
922 {
923 char msg[MAX_N];
924 size_t i, cpos, startat;
925 struct tb_event fev;
926 va_list vl;
927
928 i = 0;
929 cpos = 1;
930
931 va_start(vl, fmt);
932 startat = vsnprintf(msg, MAX_N, fmt, vl) + 1;
933 va_end(vl);
934
935 clear_status();
936 print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg);
937 tb_set_cursor(startat + 1, theight - 1);
938 tb_present();
939
940 while (tb_poll_event(&fev) != 0) {
941 switch (fev.type) {
942 case TB_EVENT_KEY:
943 if (fev.key == TB_KEY_ESC) {
944 tb_set_cursor(-1, -1);
945 clear_status();
946 return -1;
947 }
948
949 if (fev.key == TB_KEY_BACKSPACE ||
950 fev.key == TB_KEY_BACKSPACE2) {
951 if (BETWEEN(cpos, 2, max_chars)) {
952 result[i - 1] = '\0';
953 cpos--;
954 i--;
955 print_xstatus(' ', startat + cpos);
956 tb_set_cursor(
957 startat + cpos, theight - 1);
958 }
959
960 } else if (fev.key == TB_KEY_ENTER) {
961 tb_set_cursor(-1, -1);
962 result[cpos - 1] = '\0';
963 return 0;
964
965 } else if (fev.key) { /* disable other TB_KEY_* */
966 break;
967
968 } else {
969 if (cpos < max_chars) {
970 print_xstatus(
971 (char)fev.ch, (startat + cpos));
972 result[i] = (char)fev.ch;
973 tb_set_cursor((startat + cpos + 1),
974 theight - 1);
975 cpos++;
976 i++;
977 }
978 }
979
980 tb_present();
981 break;
982
983 case TB_EVENT_RESIZE:
984 t_resize();
985 clear_status();
986 print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg);
987 print_tb(result, startat + 1, theight - 1, cstatus.fg,
988 cstatus.bg);
989 tb_present();
990 break;
991
992 default:
993 return -1;
994 }
995 }
996
997 return -1;
998 }
999
1000 static int
frules(char * ex)1001 frules(char *ex)
1002 {
1003 size_t c, d;
1004
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;
1010 }
1011
1012 static int
spawn(const void * com_argv,size_t com_argc,const void * f_argv,size_t f_argc,char * fn,int waiting)1013 spawn(const void *com_argv, size_t com_argc, const void *f_argv, size_t f_argc,
1014 char *fn, int waiting)
1015 {
1016 int ws;
1017 size_t argc;
1018 pid_t r;
1019
1020 argc = com_argc + f_argc + 2;
1021 char *argv[argc];
1022
1023 memcpy(argv, com_argv, com_argc * sizeof(char *)); /* command */
1024 memcpy(&argv[com_argc], f_argv, f_argc * sizeof(char *)); /* files */
1025
1026 argv[argc - 2] = fn;
1027 argv[argc - 1] = NULL;
1028
1029 fork_pid = fork();
1030 switch (fork_pid) {
1031 case -1:
1032 return -1;
1033 case 0:
1034 execvp(argv[0], argv);
1035 exit(EXIT_SUCCESS);
1036 default:
1037 if (waiting == Wait) {
1038 while ((r = waitpid(fork_pid, &ws, 0)) == -1 &&
1039 errno == EINTR)
1040 continue;
1041 if (r == -1)
1042 return -1;
1043 if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
1044 return -1;
1045 }
1046 }
1047 fork_pid = 0; /* enable th_handler() */
1048 return 0;
1049 }
1050
1051 static int
opnf(char * fn)1052 opnf(char *fn)
1053 {
1054 char *ex;
1055 int c;
1056
1057 ex = get_ext(fn);
1058 c = frules(ex);
1059 free(ex);
1060
1061 if (c < 0) /* extension not found open in editor */
1062 return spawn(editor, 1, NULL, 0, fn, Wait);
1063 else
1064 return spawn(
1065 (char **)rules[c].v, rules[c].vlen, NULL, 0, fn, Wait);
1066 }
1067
1068 static void
opnsh(const Arg * arg)1069 opnsh(const Arg *arg)
1070 {
1071 int s;
1072
1073 tb_shutdown();
1074 chdir(cpane->dirn);
1075 s = spawn(shell, 1, NULL, 0, NULL, Wait);
1076 if (tb_init() != 0)
1077 die("tb_init");
1078 t_resize();
1079 if (s < 0)
1080 print_error("process failed non-zero exit");
1081 }
1082
1083 static int
fsev_init(void)1084 fsev_init(void)
1085 {
1086 #if defined(_SYS_INOTIFY_H)
1087 inotify_fd = inotify_init();
1088 if (inotify_fd < 0)
1089 return -1;
1090 #elif defined(_SYS_EVENT_H_)
1091 gtimeout.tv_sec = 1;
1092 kq = kqueue();
1093 if (kq < 0)
1094 return -1;
1095 #endif
1096 return 0;
1097 }
1098
1099 static int
addwatch(Pane * pane)1100 addwatch(Pane *pane)
1101 {
1102 #if defined(_SYS_INOTIFY_H)
1103 return pane->inotify_wd = inotify_add_watch(inotify_fd, pane->dirn,
1104 IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
1105 IN_ATTRIB | IN_DELETE | IN_DELETE_SELF |
1106 IN_MOVE_SELF);
1107 #elif defined(_SYS_EVENT_H_)
1108 pane->event_fd = open(pane->dirn, O_RDONLY);
1109 if (pane->event_fd < 0)
1110 return pane->event_fd;
1111 EV_SET(&evlist[pane->pane_id], pane->event_fd, EVFILT_VNODE,
1112 EV_ADD | EV_CLEAR,
1113 NOTE_DELETE | NOTE_EXTEND | NOTE_LINK | NOTE_RENAME |
1114 NOTE_ATTRIB | NOTE_REVOKE | NOTE_WRITE,
1115 0, NULL);
1116 return 0;
1117 #endif
1118 }
1119
1120 static int
read_events(void)1121 read_events(void)
1122 {
1123 #if defined(_SYS_INOTIFY_H)
1124 char *p;
1125 ssize_t r;
1126 struct inotify_event *event;
1127 const size_t events = 32;
1128 const size_t evbuflen =
1129 events * (sizeof(struct inotify_event) + MAX_N + 1);
1130 char buf[evbuflen];
1131
1132 if (cpane->inotify_wd < 0)
1133 return -1;
1134 r = read(inotify_fd, buf, evbuflen);
1135 if (r <= 0)
1136 return r;
1137
1138 for (p = buf; p < buf + r;) {
1139 event = (struct inotify_event *)p;
1140 if (!event->wd)
1141 break;
1142 if (event->mask) {
1143 return r;
1144 }
1145
1146 p += sizeof(struct inotify_event) + event->len;
1147 }
1148 #elif defined(_SYS_EVENT_H_)
1149 return kevent(kq, evlist, 2, chlist, 2, >imeout);
1150 #endif
1151 return -1;
1152 }
1153
1154 static void
rmwatch(Pane * pane)1155 rmwatch(Pane *pane)
1156 {
1157 #if defined(_SYS_INOTIFY_H)
1158 if (pane->inotify_wd >= 0)
1159 inotify_rm_watch(inotify_fd, pane->inotify_wd);
1160 #elif defined(_SYS_EVENT_H_)
1161 close(pane->event_fd);
1162 #endif
1163 }
1164
1165 static void
fsev_shdn(void)1166 fsev_shdn(void)
1167 {
1168 pthread_cancel(fsev_thread);
1169 #if defined(__linux__)
1170 pthread_join(fsev_thread, NULL);
1171 #endif
1172 rmwatch(&panes[Left]);
1173 rmwatch(&panes[Right]);
1174 #if defined(_SYS_INOTIFY_H)
1175 close(inotify_fd);
1176 #elif defined(_SYS_EVENT_H_)
1177 close(kq);
1178 #endif
1179 }
1180
1181 static void
toggle_df(const Arg * arg)1182 toggle_df(const Arg *arg)
1183 {
1184 show_dotfiles = !show_dotfiles;
1185 PERROR(listdir(&panes[Left]));
1186 PERROR(listdir(&panes[Right]));
1187 tb_present();
1188 }
1189
1190 static void
start_filter(const Arg * arg)1191 start_filter(const Arg *arg)
1192 {
1193 if (cpane->dirc < 1)
1194 return;
1195 char *user_input;
1196 user_input = ecalloc(MAX_USRI, sizeof(char));
1197 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1198 free(user_input);
1199 return;
1200 }
1201 cpane->filter = user_input;
1202 if (listdir(cpane) < 0)
1203 print_error("no match");
1204 cpane->filter = NULL;
1205 free(user_input);
1206 }
1207
1208 static void
start_vmode(const Arg * arg)1209 start_vmode(const Arg *arg)
1210 {
1211 if (cpane->dirc < 1)
1212 return;
1213 struct tb_event fev;
1214 if (sel_indexes != NULL) {
1215 free(sel_indexes);
1216 sel_indexes = NULL;
1217 }
1218
1219 sel_indexes = ecalloc(cpane->dirc, sizeof(size_t));
1220 sel_indexes[0] = cpane->hdir;
1221 cont_vmode = 0;
1222 print_prompt("-- VISUAL --");
1223 tb_present();
1224 while (tb_poll_event(&fev) != 0) {
1225 switch (fev.type) {
1226 case TB_EVENT_KEY:
1227 grabkeys(&fev, vkeys, vkeyslen);
1228 if (cont_vmode == -1)
1229 return;
1230 tb_present();
1231 break;
1232 }
1233 }
1234 }
1235
1236 static void
exit_vmode(const Arg * arg)1237 exit_vmode(const Arg *arg)
1238 {
1239 refresh_pane(cpane);
1240 add_hi(cpane, cpane->hdir - 1);
1241 cont_vmode = -1;
1242 }
1243
1244 static void
start_change(const Arg * arg)1245 start_change(const Arg *arg)
1246 {
1247 if (cpane->dirc < 1)
1248 return;
1249 struct tb_event fev;
1250
1251 cont_change = 0;
1252 print_prompt("c [womf]");
1253 tb_present();
1254 while (tb_poll_event(&fev) != 0) {
1255 switch (fev.type) {
1256 case TB_EVENT_KEY:
1257 grabkeys(&fev, ckeys, ckeyslen);
1258 if (cont_change == -1)
1259 return;
1260 tb_present();
1261 break;
1262 }
1263 }
1264 }
1265
1266 static void
exit_change(const Arg * arg)1267 exit_change(const Arg *arg)
1268 {
1269 cont_change = -1;
1270 print_info(cpane, NULL);
1271 }
1272
1273 static void
selup(const Arg * arg)1274 selup(const Arg *arg)
1275 {
1276 mv_ver(arg);
1277 print_prompt("-- VISUAL --");
1278 int index = abs(cpane->hdir - sel_indexes[0]);
1279
1280 if (cpane->hdir < sel_indexes[0]) {
1281 sel_indexes[index] = cpane->hdir;
1282 add_hi(cpane, sel_indexes[index]);
1283 } else if (index < cpane->dirc) {
1284 sel_indexes[index + 1] = 0;
1285 }
1286 if (cpane->dirc >= scrheight ||
1287 cpane->hdir <= 1) { /* rehighlight all if scrolling */
1288 selref();
1289 }
1290 }
1291
1292 static void
seldwn(const Arg * arg)1293 seldwn(const Arg *arg)
1294 {
1295 mv_ver(arg);
1296 print_prompt("-- VISUAL --");
1297 int index = abs(cpane->hdir - sel_indexes[0]);
1298
1299 if (cpane->hdir > sel_indexes[0]) {
1300 sel_indexes[index] = cpane->hdir;
1301 add_hi(cpane, sel_indexes[index] - 2);
1302 } else {
1303 sel_indexes[index + 1] = 0;
1304 }
1305 if (cpane->dirc >= scrheight ||
1306 cpane->hdir >= cpane->dirc) { /* rehighlight all if scrolling */
1307 selref();
1308 }
1309 }
1310
1311 static void
selall(const Arg * arg)1312 selall(const Arg *arg)
1313 {
1314 int i;
1315 for (i = 0; i < cpane->dirc; i++) {
1316 sel_indexes[i] = i + 1;
1317 }
1318 selref();
1319 }
1320
1321 static void
selref(void)1322 selref(void)
1323 {
1324 int i;
1325 for (i = 0; i < cpane->dirc; i++) {
1326 if (sel_indexes[i] < (scrheight + cpane->firstrow) &&
1327 sel_indexes[i] >
1328 cpane->firstrow) { /* checks if in the frame of the directories */
1329 add_hi(cpane, sel_indexes[i] - 1);
1330 }
1331 }
1332 }
1333
1334 static void
selcalc(void)1335 selcalc(void)
1336 {
1337 int j;
1338 sel_len = 0;
1339
1340 for (j = 0; j < cpane->dirc; j++) { /* calculate used selection size */
1341 if (sel_indexes[j] != 0)
1342 sel_len++;
1343 else
1344 break;
1345 }
1346 }
1347
1348 static void
free_files(void)1349 free_files(void)
1350 {
1351 size_t i;
1352
1353 if (sel_files != NULL) {
1354 for (i = 0; i < sel_len; i++) {
1355 free(sel_files[i]);
1356 sel_files[i] = NULL;
1357 }
1358 free(sel_files);
1359 sel_files = NULL;
1360 }
1361 }
1362
1363 static void
init_files(void)1364 init_files(void)
1365 {
1366 size_t i;
1367 free_files();
1368
1369 selcalc();
1370 sel_files = ecalloc(sel_len, sizeof(char *));
1371
1372 for (i = 0; i < sel_len; i++) {
1373 sel_files[i] = ecalloc(MAX_P, sizeof(char));
1374 strncpy(sel_files[i], cpane->direntr[sel_indexes[i] - 1].name,
1375 MAX_P);
1376 }
1377 }
1378
1379 static void
selynk(const Arg * arg)1380 selynk(const Arg *arg)
1381 {
1382 init_files();
1383 refresh_pane(cpane);
1384 add_hi(cpane, cpane->hdir - 1);
1385 print_status(cprompt, "%zu files are yanked", sel_len);
1386 cont_vmode = -1;
1387 }
1388
1389 static void
seldel(const Arg * arg)1390 seldel(const Arg *arg)
1391 {
1392 char *inp_conf;
1393
1394 inp_conf = ecalloc(delconf_len, sizeof(char));
1395 if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?",
1396 delconf) < 0) ||
1397 (strncmp(inp_conf, delconf, delconf_len) != 0)) {
1398 free(inp_conf);
1399 return; /* canceled by user or wrong inp_conf */
1400 }
1401 free(inp_conf);
1402
1403 init_files();
1404
1405 if (spawn(rm_cmd, rm_cmd_len, sel_files, sel_len, NULL, DontWait) < 0)
1406 print_error(strerror(errno));
1407 else
1408 print_status(cprompt, "%zu files are deleted", sel_len);
1409
1410 free_files();
1411 cont_vmode = -1;
1412 }
1413
1414 static void
paste(const Arg * arg)1415 paste(const Arg *arg)
1416 {
1417 if (sel_files == NULL) {
1418 print_error("nothing to paste");
1419 return;
1420 }
1421
1422 if (spawn(cp_cmd, cp_cmd_len, sel_files, sel_len, cpane->dirn,
1423 DontWait) < 0)
1424 print_error(strerror(errno));
1425 else
1426 print_status(cprompt, "%zu files are copied", sel_len);
1427
1428 free_files();
1429 }
1430
1431 static void
selmv(const Arg * arg)1432 selmv(const Arg *arg)
1433 {
1434 if (sel_files == NULL) {
1435 print_error("nothing to move");
1436 return;
1437 }
1438
1439 if (spawn(mv_cmd, mv_cmd_len, sel_files, sel_len, cpane->dirn,
1440 DontWait) < 0)
1441 print_error(strerror(errno));
1442 else
1443 print_status(cprompt, "%zu files are moved", sel_len);
1444
1445 free_files();
1446 }
1447
1448 static void
rname(const Arg * arg)1449 rname(const Arg *arg)
1450 {
1451 if (cpane->dirc < 1)
1452 return;
1453 char new_name[MAX_P];
1454 char *input_name;
1455
1456 input_name = ecalloc(MAX_N, sizeof(char));
1457
1458 if (get_usrinput(input_name, MAX_N, "rename: %s",
1459 basename(CURSOR(cpane).name)) < 0) {
1460 exit_change(0);
1461 free(input_name);
1462 return;
1463 }
1464
1465 if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
1466 free(input_name);
1467 print_error(strerror(errno));
1468 return;
1469 }
1470
1471 char *rename_cmd[] = { "mv", CURSOR(cpane).name, new_name };
1472 PERROR(spawn(rename_cmd, 3, NULL, 0, NULL, DontWait) < 0);
1473
1474 free(input_name);
1475 exit_change(0);
1476 }
1477
1478 static void
chngo(const Arg * arg)1479 chngo(const Arg *arg)
1480 {
1481 if (cpane->dirc < 1)
1482 return;
1483 char *input_og;
1484 char *tmp[1];
1485
1486 input_og = ecalloc(MAX_N, sizeof(char));
1487
1488 if (get_usrinput(input_og, MAX_N, "OWNER:GROUP %s",
1489 basename(CURSOR(cpane).name)) < 0) {
1490 exit_change(0);
1491 free(input_og);
1492 return;
1493 }
1494
1495 tmp[0] = input_og;
1496 if (spawn(chown_cmd, chown_cmd_len, tmp, 1, CURSOR(cpane).name,
1497 DontWait) < 0) {
1498 print_error(strerror(errno));
1499 return;
1500 }
1501
1502 free(input_og);
1503 exit_change(0);
1504 }
1505
1506 static void
chngm(const Arg * arg)1507 chngm(const Arg *arg)
1508 {
1509 if (cpane->dirc < 1)
1510 return;
1511 char *input_og;
1512 char *tmp[1];
1513
1514 input_og = ecalloc(MAX_N, sizeof(char));
1515
1516 if (get_usrinput(input_og, MAX_N, "chmod %s",
1517 basename(CURSOR(cpane).name)) < 0) {
1518 exit_change(0);
1519 free(input_og);
1520 return;
1521 }
1522
1523 tmp[0] = input_og;
1524 if (spawn(chmod_cmd, chmod_cmd_len, tmp, 1, CURSOR(cpane).name,
1525 DontWait) < 0) {
1526 print_error(strerror(errno));
1527 return;
1528 }
1529
1530 free(input_og);
1531 exit_change(0);
1532 }
1533
1534 static void
chngf(const Arg * arg)1535 chngf(const Arg *arg)
1536 {
1537 if (cpane->dirc < 1)
1538 return;
1539 char *input_og;
1540 char *tmp[1];
1541
1542 input_og = ecalloc(MAX_N, sizeof(char));
1543
1544 if (get_usrinput(input_og, MAX_N, CHFLAG " %s",
1545 basename(CURSOR(cpane).name)) < 0) {
1546 exit_change(0);
1547 free(input_og);
1548 return;
1549 }
1550
1551 tmp[0] = input_og;
1552 if (spawn(chflags_cmd, chflags_cmd_len, tmp, 1, CURSOR(cpane).name,
1553 DontWait) < 0) {
1554 print_error(strerror(errno));
1555 return;
1556 }
1557
1558 free(input_og);
1559 exit_change(0);
1560 }
1561
1562 static void
dupl(const Arg * arg)1563 dupl(const Arg *arg)
1564 {
1565 if (cpane->dirc < 1)
1566 return;
1567 char new_name[MAX_P];
1568 char *input_name;
1569
1570 input_name = ecalloc(MAX_N, sizeof(char));
1571
1572 if (get_usrinput(input_name, MAX_N, "new name: %s",
1573 basename(CURSOR(cpane).name)) < 0) {
1574 free(input_name);
1575 return;
1576 }
1577
1578 if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
1579 free(input_name);
1580 print_error(strerror(errno));
1581 return;
1582 }
1583
1584 char *tmp[1];
1585 tmp[0] = CURSOR(cpane).name;
1586 if (spawn(cp_cmd, cp_cmd_len, tmp, 1, new_name, DontWait) < 0) {
1587 print_error(strerror(errno));
1588 return;
1589 }
1590
1591 free(input_name);
1592 }
1593
1594 static void
yank(const Arg * arg)1595 yank(const Arg *arg)
1596 {
1597 if (cpane->dirc < 1)
1598 return;
1599
1600 free_files();
1601 sel_len = 1;
1602 sel_files = ecalloc(sel_len, sizeof(char *));
1603 sel_files[0] = ecalloc(MAX_P, sizeof(char));
1604 strncpy(sel_files[0], CURSOR(cpane).name, MAX_P);
1605 print_status(cprompt, "1 file is yanked", sel_len);
1606 }
1607
1608 static void
switch_pane(const Arg * arg)1609 switch_pane(const Arg *arg)
1610 {
1611 if (cpane->dirc > 0)
1612 rm_hi(cpane, cpane->hdir - 1);
1613 cpane = &panes[pane_idx ^= 1];
1614 if (cpane->dirc > 0) {
1615 add_hi(cpane, cpane->hdir - 1);
1616 print_info(cpane, NULL);
1617 } else {
1618 clear_status();
1619 }
1620 }
1621
1622 static void
quit(const Arg * arg)1623 quit(const Arg *arg)
1624 {
1625 if (cont_vmode == -1) { /* check if selection was allocated */
1626 free(sel_indexes);
1627 if (sel_files != NULL)
1628 free_files();
1629 }
1630 free(panes[Left].direntr);
1631 free(panes[Right].direntr);
1632 fsev_shdn();
1633 tb_shutdown();
1634 exit(EXIT_SUCCESS);
1635 }
1636
1637 static void
grabkeys(struct tb_event * event,Key * key,size_t max_keys)1638 grabkeys(struct tb_event *event, Key *key, size_t max_keys)
1639 {
1640 size_t i;
1641
1642 for (i = 0; i < max_keys; i++) {
1643 if (event->ch != 0) {
1644 if (event->ch == key[i].evkey.ch) {
1645 key[i].func(&key[i].arg);
1646 return;
1647 }
1648 } else if (event->key != 0) {
1649 if (event->key == key[i].evkey.key) {
1650 key[i].func(&key[i].arg);
1651 return;
1652 }
1653 }
1654 }
1655 }
1656
1657 void *
read_th(void * arg)1658 read_th(void *arg)
1659 {
1660 struct timespec tim;
1661 tim.tv_sec = 0;
1662 tim.tv_nsec = 5000000L; /* 0.005 sec */
1663
1664 while (1)
1665 if (read_events() > READEVSZ) {
1666 kill(main_pid, SIGUSR1);
1667 nanosleep(&tim, NULL);
1668 }
1669 return arg;
1670 }
1671
1672 static void
start_ev(void)1673 start_ev(void)
1674 {
1675 struct tb_event ev;
1676
1677 while (tb_poll_event(&ev) != 0) {
1678 switch (ev.type) {
1679 case TB_EVENT_KEY:
1680 grabkeys(&ev, nkeys, nkeyslen);
1681 tb_present();
1682 break;
1683 case TB_EVENT_RESIZE:
1684 t_resize();
1685 break;
1686 default:
1687 break;
1688 }
1689 }
1690 tb_shutdown();
1691 }
1692
1693 static void
refresh_pane(Pane * pane)1694 refresh_pane(Pane *pane)
1695 {
1696 size_t y, dyn_max, start_from;
1697 hwidth = (twidth / 2) - 4;
1698 Cpair col;
1699
1700 y = 1;
1701 start_from = pane->firstrow;
1702 dyn_max = MIN(pane->dirc, (scrheight - 1) + pane->firstrow);
1703
1704 /* print each entry in directory */
1705 while (start_from < dyn_max) {
1706 get_hicol(&col, pane->direntr[start_from].mode);
1707 print_row(pane, start_from, col);
1708 start_from++;
1709 y++;
1710 }
1711
1712 if (pane->dirc > 0)
1713 print_info(pane, NULL);
1714 else
1715 clear_status();
1716
1717 /* print current directory title */
1718 pane->dircol.fg |= TB_BOLD;
1719 printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn);
1720 }
1721
1722 static void
set_direntr(Pane * pane,struct dirent * entry,DIR * dir,char * filter)1723 set_direntr(Pane *pane, struct dirent *entry, DIR *dir, char *filter)
1724 {
1725 int i;
1726 char *tmpfull;
1727 struct stat status;
1728
1729 #define ADD_ENTRY \
1730 tmpfull = get_fullpath(pane->dirn, entry->d_name); \
1731 strncpy(pane->direntr[i].name, tmpfull, MAX_N); \
1732 if (lstat(tmpfull, &status) == 0) { \
1733 pane->direntr[i].size = status.st_size; \
1734 pane->direntr[i].mode = status.st_mode; \
1735 pane->direntr[i].group = status.st_gid; \
1736 pane->direntr[i].user = status.st_uid; \
1737 pane->direntr[i].dt = status.st_mtime; \
1738 } \
1739 i++; \
1740 free(tmpfull);
1741
1742 i = 0;
1743 pane->direntr =
1744 erealloc(pane->direntr, (10 + pane->dirc) * sizeof(Entry));
1745 while ((entry = readdir(dir)) != 0) {
1746 if (show_dotfiles == 1) {
1747 if (entry->d_name[0] == '.' &&
1748 (entry->d_name[1] == '\0' ||
1749 entry->d_name[1] == '.'))
1750 continue;
1751 } else {
1752 if (entry->d_name[0] == '.')
1753 continue;
1754 }
1755
1756 if (filter == NULL) {
1757 ADD_ENTRY
1758 } else if (filter != NULL) {
1759 if (strcasestr(entry->d_name, filter) != NULL) {
1760 ADD_ENTRY
1761 }
1762 }
1763 }
1764
1765 pane->dirc = i;
1766 }
1767
1768 static int
listdir(Pane * pane)1769 listdir(Pane *pane)
1770 {
1771 DIR *dir;
1772 struct dirent *entry;
1773 int filtercount = 0;
1774 size_t oldc = pane->dirc;
1775
1776 pane->dirc = 0;
1777
1778 dir = opendir(pane->dirn);
1779 if (dir == NULL)
1780 return -1;
1781
1782 /* get content and filter sum */
1783 while ((entry = readdir(dir)) != 0) {
1784 if (pane->filter != NULL) {
1785 if (strcasestr(entry->d_name, pane->filter) != NULL)
1786 filtercount++;
1787 } else { /* no filter */
1788 pane->dirc++;
1789 }
1790 }
1791
1792 if (pane->filter == NULL) {
1793 clear_pane(pane);
1794 pane->dirc -= 2;
1795 }
1796
1797 if (pane->filter != NULL) {
1798 if (filtercount > 0) {
1799 pane->dirc = filtercount;
1800 clear_pane(pane);
1801 pane->hdir = 1;
1802 } else if (filtercount == 0) {
1803 if (closedir(dir) < 0)
1804 return -1;
1805 pane->dirc = oldc;
1806 return -1;
1807 }
1808 }
1809
1810 /* print current directory title */
1811 pane->dircol.fg |= TB_BOLD;
1812 printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn);
1813
1814 if (pane->filter == NULL) /* dont't watch when filtering */
1815 if (addwatch(pane) < 0)
1816 print_error("can't add watch");
1817
1818 /* empty directory */
1819 if (pane->dirc == 0) {
1820 clear_status();
1821 if (closedir(dir) < 0)
1822 return -1;
1823 return 0;
1824 }
1825
1826 rewinddir(dir); /* reset position */
1827 set_direntr(
1828 pane, entry, dir, pane->filter); /* create array of entries */
1829 qsort(pane->direntr, pane->dirc, sizeof(Entry), sort_name);
1830 refresh_pane(pane);
1831
1832 if (pane->hdir > pane->dirc)
1833 pane->hdir = pane->dirc;
1834
1835 if (pane == cpane && pane->dirc > 0)
1836 add_hi(pane, pane->hdir - 1);
1837
1838 if (closedir(dir) < 0)
1839 return -1;
1840 return 0;
1841 }
1842
1843 static void
t_resize(void)1844 t_resize(void)
1845 {
1846 tb_clear();
1847 draw_frame();
1848 panes[Left].x_end = (twidth / 2) - 1;
1849 panes[Right].x_end = twidth - 1;
1850 panes[Right].x_srt = (twidth / 2) + 2;
1851 refresh_pane(&panes[Left]);
1852 refresh_pane(&panes[Right]);
1853 if (cpane->dirc > 0)
1854 add_hi(cpane, cpane->hdir - 1);
1855 tb_present();
1856 }
1857
1858 static void
get_editor(void)1859 get_editor(void)
1860 {
1861 editor[0] = getenv("EDITOR");
1862 editor[1] = NULL;
1863
1864 if (editor[0] == NULL)
1865 editor[0] = fed;
1866 }
1867
1868 static void
get_shell(void)1869 get_shell(void)
1870 {
1871 shell[0] = getenv("SHELL");
1872 shell[1] = NULL;
1873
1874 if (shell[0] == NULL)
1875 shell[0] = sh;
1876 }
1877
1878 static void
set_panes(void)1879 set_panes(void)
1880 {
1881 char *home;
1882 char cwd[MAX_P];
1883
1884 home = getenv("HOME");
1885 if (home == NULL)
1886 home = "/";
1887 if ((getcwd(cwd, sizeof(cwd)) == NULL))
1888 strncpy(cwd, home, MAX_P);
1889
1890 pane_idx = Left; /* cursor pane */
1891 cpane = &panes[pane_idx];
1892
1893 panes[Left].pane_id = 0;
1894 panes[Left].x_srt = 2;
1895 panes[Left].x_end = (twidth / 2) - 1;
1896 panes[Left].dircol = cpanell;
1897 panes[Left].firstrow = 0;
1898 panes[Left].direntr = ecalloc(0, sizeof(Entry));
1899 strncpy(panes[Left].dirn, cwd, MAX_P);
1900 panes[Left].hdir = 1;
1901 panes[Left].inotify_wd = -1;
1902 panes[Left].parent_row = 1;
1903
1904 panes[Right].pane_id = 1;
1905 panes[Right].x_srt = (twidth / 2) + 2;
1906 panes[Right].x_end = twidth - 1;
1907 panes[Right].dircol = cpanelr;
1908 panes[Right].firstrow = 0;
1909 panes[Right].direntr = ecalloc(0, sizeof(Entry));
1910 strncpy(panes[Right].dirn, home, MAX_P);
1911 panes[Right].hdir = 1;
1912 panes[Right].inotify_wd = -1;
1913 panes[Right].parent_row = 1;
1914 }
1915
1916 static void
draw_frame(void)1917 draw_frame(void)
1918 {
1919 int i;
1920 theight = tb_height();
1921 twidth = tb_width();
1922 hwidth = (twidth / 2) - 4;
1923 scrheight = theight - 2;
1924
1925 /* 2 horizontal lines */
1926 for (i = 1; i < twidth - 1; ++i) {
1927 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1928 tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg);
1929 }
1930
1931 /* 4 vertical lines */
1932 for (i = 1; i < theight - 1; ++i) {
1933 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1934 tb_change_cell(
1935 (twidth - 1) / 2, i - 1, u_vl, cframe.fg, cframe.bg);
1936 tb_change_cell(((twidth - 1) / 2) + 1, i - 1, u_vl, cframe.fg,
1937 cframe.bg);
1938 tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg);
1939 }
1940
1941 /* 4 corners */
1942 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1943 tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg);
1944 tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg);
1945 tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg);
1946
1947 /* 2 middel top and bottom */
1948 tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg);
1949 tb_change_cell(
1950 (twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg);
1951 }
1952
1953 void
th_handler(int num)1954 th_handler(int num)
1955 {
1956 if (fork_pid > 0) /* while forking don't listdir() */
1957 return;
1958 (void)num;
1959 PERROR(listdir(&panes[Left]));
1960 PERROR(listdir(&panes[Right]));
1961 tb_present();
1962 }
1963
1964 static int
start_signal(void)1965 start_signal(void)
1966 {
1967 struct sigaction sa;
1968
1969 main_pid = getpid();
1970 sa.sa_handler = th_handler;
1971 sigemptyset(&sa.sa_mask);
1972 sa.sa_flags = SA_RESTART;
1973 return sigaction(SIGUSR1, &sa, NULL);
1974 }
1975
1976 static void
refresh(const Arg * arg)1977 refresh(const Arg *arg)
1978 {
1979 kill(main_pid, SIGWINCH);
1980 }
1981
1982 static void
start(void)1983 start(void)
1984 {
1985 switch (tb_init()) {
1986 case TB_EFAILED_TO_OPEN_TTY:
1987 die("TB_EFAILED_TO_OPEN_TTY");
1988 break;
1989 case TB_EUNSUPPORTED_TERMINAL:
1990 die("TB_EUNSUPPORTED_TERMINAL");
1991 break;
1992 case TB_EPIPE_TRAP_ERROR:
1993 die("TB_EUNSUPPORTED_TERMINAL");
1994 break;
1995 case 0:
1996 break;
1997 default:
1998 die("UNKNOWN FAILURE");
1999 }
2000
2001 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
2002 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
2003 die("output error");
2004 draw_frame();
2005 set_panes();
2006 get_editor();
2007 get_shell();
2008 PERROR(start_signal() < 0);
2009 PERROR(fsev_init() < 0);
2010 PERROR(listdir(&panes[Left]) < 0);
2011 PERROR(listdir(&panes[Right]) < 0);
2012 tb_present();
2013
2014 pthread_create(&fsev_thread, NULL, read_th, NULL);
2015 start_ev();
2016 }
2017
2018 int
main(int argc,char * argv[])2019 main(int argc, char *argv[])
2020 {
2021 #if defined(__OpenBSD__)
2022 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
2023 NULL) == -1)
2024 die("pledge");
2025 #endif /* __OpenBSD__ */
2026 if (argc == 1)
2027 start();
2028 else if (argc == 2 && strncmp("-v", argv[1], 2) == 0)
2029 die("sfm-" VERSION);
2030 else
2031 die("usage: sfm [-v]");
2032 return 0;
2033 }
2034