[fix] parent_row after chdir to a bookmark
[sfm.git] / sfm.c
blobc6281debc5012061e6fd4bc48a0e1d5b21824287
1 /* See LICENSE file for copyright and license details. */
3 #include <sys/types.h>
4 #include <sys/resource.h>
5 #include <sys/stat.h>
6 #include <sys/time.h>
7 #include <sys/wait.h>
8 #if defined(__linux__)
9 #include <sys/inotify.h>
10 #elif defined(__FreeBSD__) || defined(__NetBSD__) ||\
11 defined(__OpenBSD__) || defined(__APPLE__)
12 #include <sys/event.h>
13 #endif
15 #include <dirent.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <grp.h>
19 #include <pwd.h>
20 #include <stdarg.h>
21 #include <stdint.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
28 #include "termbox.h"
29 #include "util.h"
31 /* macros */
32 #define MAX_P 4095
33 #define MAX_N 255
34 #define MAX_USRI 32
35 #define MAX_EXT 4
37 /* enums */
38 enum { AskDel, DAskDel }; /* delete directory */
39 enum { AddHi, NoHi }; /* add highlight in listdir */
40 enum { AllP, PtP }; /* set pane items */
42 /* typedef */
43 typedef struct {
44 char name[MAX_N];
45 gid_t group;
46 mode_t mode;
47 off_t size;
48 time_t td;
49 uid_t user;
50 } Entry;
52 typedef struct {
53 uint16_t fg;
54 uint16_t bg;
55 } Cpair;
57 typedef struct {
58 int pane_id;
59 char dirn[MAX_P]; // dir name cwd
60 Entry *direntr; // dir entries
61 int dirx; // pane cwd x pos
62 size_t dirc; // dir entries sum
63 size_t hdir; // highlighted dir
64 size_t firstrow;
65 int parent_row; // FIX
66 Cpair dircol;
67 int inotify_wd;
68 int event_fd;
69 } Pane;
71 typedef struct {
72 uint32_t ch;
73 char path[MAX_P];
74 } Bookmark;
76 typedef struct {
77 const char **ext;
78 size_t exlen;
79 const void *v;
80 } Rule;
82 typedef union {
83 uint16_t key; /* one of the TB_KEY_* constants */
84 uint32_t ch; /* unicode character */
85 } Evkey;
87 typedef struct {
88 const Evkey evkey;
89 void (*func)(void);
90 } Key;
92 /* function declarations */
93 static void print_tb(const char *, int, int, uint16_t, uint16_t);
94 static void printf_tb(int, int, Cpair, const char *, ...);
95 static void print_status(Cpair, const char *, ...);
96 static void print_xstatus(char, int);
97 static void print_error(char *);
98 static void print_prompt(char *);
99 static void print_info(void);
100 static void print_row(Pane *, size_t, Cpair);
101 static void clear(int, int, int, uint16_t);
102 static void clear_status(void);
103 static void clear_pane(int);
104 static void add_hi(Pane *, size_t);
105 static void rm_hi(Pane *, size_t);
106 static void float_to_string(float, char *);
107 static int check_dir(char *);
108 static mode_t chech_execf(mode_t);
109 static int sort_name(const void *const, const void *const);
110 static char *get_ext(char *);
111 static int get_fdt(char *, size_t, time_t);
112 static char *get_fgrp(gid_t, size_t);
113 static char *get_finfo(Entry *);
114 static char *get_fperm(mode_t);
115 static char *get_fsize(off_t);
116 static char *get_fullpath(char *, char *);
117 static char *get_fusr(uid_t, size_t);
118 static void get_dirsize(char *, off_t *);
119 static void get_hicol(Cpair *, mode_t);
120 static int deldir(char *, int);
121 static int delent(char *);
122 static int delf(char *);
123 static void calcdir(void);
124 static void crnd(void);
125 static void crnf(void);
126 static void delfd(void);
127 static void mvbk(void);
128 static void mvbtm(void);
129 static void mvdwn(void);
130 static void mvdwns(void);
131 static void mvfor(void);
132 static void mvmid(void);
133 static void mvtop(void);
134 static void mvup(void);
135 static void mvups(void);
136 static void scrdwn(void);
137 static void scrdwns(void);
138 static void scrup(void);
139 static void scrups(void);
140 static int get_usrinput(char *, size_t, char *);
141 static int frules(char *);
142 static int spawn(const void *, char *);
143 static int opnf(char *);
144 static int fsev_init(void);
145 static int addwatch(void);
146 static int read_events(void);
147 static void rmwatch(Pane *);
148 static void fsev_shdn(void);
149 static ssize_t findbm(uint32_t);
150 static void filter(void);
151 static void switch_pane(void);
152 static void quit(void);
153 static void grabkeys(struct tb_event *);
154 static void start_ev(void);
155 static void refresh_pane(void);
156 static int listdir(int, char *);
157 static void t_resize(void);
158 static void set_panes(int);
159 static void draw_frame(void);
160 static void start(void);
162 /* global variables */
163 static Pane pane_r, pane_l, *cpane;
164 static char fed[] = "vi";
165 static size_t scrheight;
166 #if defined _SYS_INOTIFY_H
167 static int inotify_fd;
168 #elif defined _SYS_EVENT_H_
169 static int kq;
170 struct kevent evlist[2]; /* events we want to monitor */
171 struct kevent chlist[2]; /* events that were triggered */
172 static struct timespec gtimeout;
173 #endif
175 /* configuration, allows nested code to access above variables */
176 #include "config.h"
178 /* function implementations */
179 static void
180 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
182 while (*str != '\0') {
183 uint32_t uni = 0;
184 str += tb_utf8_char_to_unicode(&uni, str);
185 tb_change_cell(x, y, uni, fg, bg);
186 x++;
190 static void
191 printf_tb(int x, int y, Cpair col, const char *fmt, ...)
193 char buf[4096];
194 va_list vl;
195 va_start(vl, fmt);
196 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
197 va_end(vl);
198 print_tb(buf, x, y, col.fg, col.bg);
201 static void
202 print_status(Cpair col, const char *fmt, ...)
204 int height;
205 height = tb_height();
207 char buf[256];
208 va_list vl;
209 va_start(vl, fmt);
210 (void)vsnprintf(buf, sizeof(buf), fmt, vl);
211 va_end(vl);
212 clear_status();
213 print_tb(buf, 1, height - 1, col.fg, col.bg);
216 static void
217 print_xstatus(char c, int x)
219 int height;
220 uint32_t uni = 0;
221 height = tb_height();
222 (void)tb_utf8_char_to_unicode(&uni, &c);
223 tb_change_cell(x, height - 1, uni, cstatus.fg, cstatus.bg);
226 static void
227 print_error(char *errmsg)
229 print_status(cerr, errmsg);
232 static void
233 print_prompt(char *prompt)
235 print_status(cprompt, prompt);
238 static void
239 print_info(void)
241 char *fileinfo;
242 fileinfo = get_finfo(&cpane->direntr[cpane->hdir - 1]);
243 print_status(cstatus, "%lu/%lu %s", cpane->hdir, cpane->dirc, fileinfo);
244 free(fileinfo);
247 static void
248 print_row(Pane *pane, size_t entpos, Cpair col)
250 int x, y;
251 char *result;
252 char buf[MAX_P];
253 char lnk_full[MAX_P];
254 int width;
256 width = (tb_width() / 2) - 4;
257 result = pane->direntr[entpos].name;
258 x = pane->dirx;
259 y = entpos - cpane->firstrow + 1;
261 if (S_ISLNK(pane->direntr[entpos].mode) &&
262 realpath(pane->direntr[entpos].name, buf) != NULL) {
263 strncpy(lnk_full, pane->direntr[entpos].name, MAX_N);
264 strcat(lnk_full, " -> ");
265 strncat(lnk_full, buf, MAX_N);
266 result = lnk_full;
269 printf_tb(x, y, col, "%*.*s", ~width, width, result);
272 static void
273 clear(int sx, int ex, int y, uint16_t bg)
275 /* clear line from to */
276 /* x = line number vertical */
277 /* y = column number horizontal */
278 int i;
279 for (i = sx; i < ex; i++) {
280 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
284 static void
285 clear_status(void)
287 int width, height;
288 width = tb_width();
289 height = tb_height();
290 clear(1, width - 1, height - 1, cstatus.bg);
293 static void
294 clear_pane(int pane)
296 int i, x, ex, y, width, height;
297 width = tb_width();
298 height = tb_height();
299 x = 0, y = 0, i = 0, ex = 0;
301 if (pane == 2) {
302 x = 2;
303 ex = (width / 2) - 1;
304 } else if (pane == (width / 2) + 2) {
305 x = (width / 2) + 1;
306 ex = width - 1;
309 while (i < height - 2) {
310 clear(x, ex, y, TB_DEFAULT);
311 i++;
312 y++;
314 /* draw top line */
315 for (y = x; y < ex; ++y) {
316 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
320 static void
321 add_hi(Pane *pane, size_t entpos)
323 Cpair col;
324 get_hicol(&col, pane->direntr[entpos].mode);
325 col.fg |= TB_REVERSE | TB_BOLD;
326 col.bg |= TB_REVERSE;
327 print_row(pane, entpos, col);
330 static void
331 rm_hi(Pane *pane, size_t entpos)
333 Cpair col;
334 get_hicol(&col, pane->direntr[entpos].mode);
335 print_row(pane, entpos, col);
338 static void
339 float_to_string(float f, char *r)
341 int length, length2, i, number, position,
342 tenth; /* length is size of decimal part, length2 is size of tenth part */
343 float number2;
345 f = (float)(int)(f * 10) / 10;
347 number2 = f;
348 number = (int)f;
349 length2 = 0;
350 tenth = 1;
352 /* Calculate length2 tenth part */
353 while ((number2 - (float)number) != 0.0 &&
354 !((number2 - (float)number) < 0.0)) {
355 tenth *= 10.0;
356 number2 = f * (float)tenth;
357 number = (int)number2;
359 length2++;
362 /* Calculate length decimal part */
363 for (length = (f > 1.0) ? 0 : 1; f > 1.0; length++)
364 f /= 10.0;
366 position = length;
367 length = length + 1 + length2;
368 number = (int)number2;
370 if (length2 > 0) {
371 for (i = length; i >= 0; i--) {
372 if (i == (length))
373 r[i] = '\0';
374 else if (i == (position))
375 r[i] = '.';
376 else {
377 r[i] = (char)(number % 10) + '0';
378 number /= 10;
381 } else {
382 length--;
383 for (i = length; i >= 0; i--) {
384 if (i == (length))
385 r[i] = '\0';
386 else {
387 r[i] = (char)(number % 10) + '0';
388 number /= 10;
394 static int
395 check_dir(char *path)
397 DIR *dir;
398 dir = opendir(path);
400 if (dir == NULL) {
401 if (errno == ENOTDIR) {
402 return 1;
403 } else {
404 return -1;
408 if (closedir(dir) < 0)
409 return -1;
411 return 0;
414 static mode_t
415 chech_execf(mode_t mode)
417 if (S_ISREG(mode))
418 return (((S_IXUSR | S_IXGRP | S_IXOTH) & mode));
419 return 0;
422 static int
423 sort_name(const void *const A, const void *const B)
425 int result;
426 mode_t data1 = (*(Entry *)A).mode;
427 mode_t data2 = (*(Entry *)B).mode;
429 if (data1 < data2) {
430 return -1;
431 } else if (data1 == data2) {
432 result = strcmp((*(Entry *)A).name, (*(Entry *)B).name);
433 return result;
434 } else {
435 return 1;
439 static char *
440 get_ext(char *str)
442 char *ext;
443 char dot;
444 size_t counter, len, i;
446 dot = '.';
447 counter = 0;
448 len = strlen(str);
450 for (i = len - 1; i > 0; i--) {
451 if (str[i] == dot) {
452 break;
453 } else {
454 counter++;
458 ext = ecalloc(counter + 1, sizeof(char));
459 strncpy(ext, &str[len - counter], counter);
460 return ext;
463 static int
464 get_fdt(char *result, size_t reslen, time_t status)
466 struct tm lt;
467 localtime_r(&status, &lt);
468 return strftime(result, reslen, dtfmt, &lt);
471 static char *
472 get_fgrp(gid_t status, size_t len)
474 char *result;
475 struct group *gr;
477 result = ecalloc(len, sizeof(char));
478 gr = getgrgid(status);
479 if (gr == NULL)
480 (void)snprintf(result, len - 1, "%d", (int)status);
481 else
482 strncpy(result, gr->gr_name, len - 1);
484 return result;
487 static char *
488 get_finfo(Entry *cursor)
490 char *sz, *rst, *ur, *gr, *td, *prm;
491 size_t szlen, prmlen, urlen, grlen, tdlen, rstlen;
493 szlen = 9;
494 prmlen = 11;
495 urlen = grlen = tdlen = 32;
496 rstlen = szlen + prmlen + urlen + grlen + tdlen;
497 rst = ecalloc(rstlen, sizeof(char));
499 if (show_perm == 1) {
500 prm = get_fperm(cursor->mode);
501 strncpy(rst, prm, prmlen);
502 strcat(rst, " ");
503 free(prm);
506 if (show_ug == 1) {
507 ur = get_fusr(cursor->user, urlen);
508 gr = get_fgrp(cursor->group, grlen);
509 strncat(rst, ur, urlen);
510 strcat(rst, ":");
511 strncat(rst, gr, grlen);
512 strcat(rst, " ");
513 free(ur);
514 free(gr);
517 if (show_dt == 1) {
518 td = ecalloc(tdlen, sizeof(char));
519 if (get_fdt(td, tdlen, cursor->td) > 0) {
520 strncat(rst, td, tdlen);
521 strcat(rst, " ");
523 free(td);
526 if (show_size == 1 && S_ISREG(cursor->mode)) {
527 sz = get_fsize(cursor->size);
528 strncat(rst, sz, szlen);
529 free(sz);
532 return rst;
535 static char *
536 get_fperm(mode_t mode)
538 char *buf;
539 size_t i;
541 const char chars[] = "rwxrwxrwx";
542 buf = ecalloc((size_t)11, sizeof(char));
544 if (S_ISDIR(mode))
545 buf[0] = 'd';
546 else if (S_ISREG(mode))
547 buf[0] = '-';
548 else if (S_ISLNK(mode))
549 buf[0] = 'l';
550 else if (S_ISBLK(mode))
551 buf[0] = 'b';
552 else if (S_ISCHR(mode))
553 buf[0] = 'c';
554 else if (S_ISFIFO(mode))
555 buf[0] = 'p';
556 else if (S_ISSOCK(mode))
557 buf[0] = 's';
558 else
559 buf[0] = '?';
561 for (i = 1; i < 10; i++) {
562 buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-';
564 buf[10] = '\0';
566 return buf;
569 static char *
570 get_fsize(off_t size)
572 /* need to be freed */
573 char *Rsize;
574 float lsize;
575 int counter;
576 counter = 0;
578 Rsize = ecalloc((size_t)10, sizeof(char));
579 lsize = (float)size;
581 while (lsize >= 1000.0) {
582 lsize /= 1024.0;
583 ++counter;
586 float_to_string(lsize, Rsize);
588 switch (counter) {
589 case 0:
590 strcat(Rsize, "B");
591 break;
592 case 1:
593 strcat(Rsize, "K");
594 break;
595 case 2:
596 strcat(Rsize, "M");
597 break;
598 case 3:
599 strcat(Rsize, "G");
600 break;
601 case 4:
602 strcat(Rsize, "T");
603 break;
606 return Rsize;
609 static char *
610 get_fullpath(char *first, char *second)
612 char *full_path;
613 size_t full_path_len;
615 full_path_len = strlen(first) + strlen(second) + 2;
616 full_path = ecalloc(full_path_len, sizeof(char));
618 if (strcmp(first, "/") == 0) {
619 (void)snprintf(full_path, full_path_len, "/%s", second);
621 } else {
622 (void)snprintf(full_path, full_path_len, "%s/%s", first,
623 second);
626 return full_path;
629 static char *
630 get_fusr(uid_t status, size_t len)
632 char *result;
633 struct passwd *pw;
635 result = ecalloc(len, sizeof(char));
636 pw = getpwuid(status);
637 if (pw == NULL)
638 (void)snprintf(result, len - 1, "%d", (int)status);
639 else
640 strncpy(result, pw->pw_name, len - 1);
642 return result;
645 static void
646 get_dirsize(char *fullpath, off_t *fullsize)
648 DIR *dir;
649 char *ent_full;
650 mode_t mode;
651 struct dirent *entry;
652 struct stat status;
654 dir = opendir(fullpath);
655 if (dir == NULL) {
656 return;
659 while ((entry = readdir(dir)) != 0) {
660 if ((strcmp(entry->d_name, ".") == 0 ||
661 strcmp(entry->d_name, "..") == 0))
662 continue;
664 ent_full = get_fullpath(fullpath, entry->d_name);
665 if (lstat(ent_full, &status) == 0) {
666 mode = status.st_mode;
667 if (S_ISDIR(mode)) {
668 get_dirsize(ent_full, fullsize);
669 free(ent_full);
670 } else {
671 *fullsize += status.st_size;
672 free(ent_full);
677 closedir(dir);
678 clear_status();
681 static void
682 get_hicol(Cpair *col, mode_t mode)
684 *col = cfile;
685 if (S_ISDIR(mode))
686 *col = cdir;
687 else if (S_ISLNK(mode))
688 *col = cother;
689 else if (chech_execf(mode) > 0)
690 *col = cexec;
693 static int
694 deldir(char *fullpath, int delchoice)
696 if (delchoice == (int)AskDel) {
697 char *confirmation;
698 confirmation = ecalloc((size_t)2, sizeof(char));
699 if ((get_usrinput(confirmation, (size_t)2,
700 "delete directory (Y) ?") < 0) ||
701 (strcmp(confirmation, "Y") != 0)) {
702 free(confirmation);
703 return 1; /* canceled by user or wrong confirmation */
705 free(confirmation);
708 if (rmdir(fullpath) == 0)
709 return 0; /* empty directory */
711 DIR *dir;
712 char *ent_full;
713 mode_t mode;
714 struct dirent *entry;
715 struct stat status;
717 dir = opendir(fullpath);
718 if (dir == NULL) {
719 return -1;
722 while ((entry = readdir(dir)) != 0) {
723 if ((strcmp(entry->d_name, ".") == 0 ||
724 strcmp(entry->d_name, "..") == 0))
725 continue;
727 ent_full = get_fullpath(fullpath, entry->d_name);
728 if (lstat(ent_full, &status) == 0) {
729 mode = status.st_mode;
730 if (S_ISDIR(mode)) {
731 if (deldir(ent_full, (int)DAskDel) < 0) {
732 free(ent_full);
733 return -1;
735 } else if (S_ISREG(mode)) {
736 if (unlink(ent_full) < 0) {
737 free(ent_full);
738 return -1;
742 free(ent_full);
745 print_status(cstatus, "gotit");
746 if (closedir(dir) < 0)
747 return -1;
749 return rmdir(fullpath); /* directory after delete all entries */
752 static int
753 delent(char *fullpath)
755 struct stat status;
756 mode_t mode;
758 if (lstat(fullpath, &status) < 0)
759 return -1;
761 mode = status.st_mode;
762 if (S_ISDIR(mode)) {
763 return deldir(fullpath, (int)AskDel);
764 } else {
765 return delf(fullpath);
769 static int
770 delf(char *fullpath)
772 char *confirmation;
773 confirmation = ecalloc((size_t)2, sizeof(char));
775 if ((get_usrinput(confirmation, (size_t)2, "delete file (Y) ?") < 0) ||
776 (strcmp(confirmation, "Y") != 0)) {
777 free(confirmation);
778 return 1; /* canceled by user or wrong confirmation */
781 free(confirmation);
782 return unlink(fullpath);
785 static void
786 calcdir(void)
788 off_t *fullsize;
789 char *csize;
790 char *result;
792 if (S_ISDIR(cpane->direntr[cpane->hdir - 1].mode)) {
793 fullsize = ecalloc(50, sizeof(off_t));
794 get_dirsize(cpane->direntr[cpane->hdir - 1].name, fullsize);
795 csize = get_fsize(*fullsize);
796 result = get_finfo(&cpane->direntr[cpane->hdir - 1]);
798 clear_status();
799 print_status(cstatus, "%lu/%lu %s%s", cpane->hdir, cpane->dirc,
800 result, csize);
801 free(csize);
802 free(fullsize);
803 free(result);
807 static void
808 crnd(void)
810 char *user_input, *path;
811 size_t pathlen;
813 user_input = ecalloc(MAX_USRI, sizeof(char));
814 if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
815 free(user_input);
816 return;
819 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
820 path = ecalloc(pathlen, sizeof(char));
821 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
822 free(user_input);
823 free(path);
824 return;
827 if (mkdir(path, ndir_perm) < 0)
828 print_error(strerror(errno));
829 else if (listdir(AddHi, NULL) < 0)
830 print_error(strerror(errno));
832 free(user_input);
833 free(path);
836 static void
837 crnf(void)
839 char *user_input, *path;
840 size_t pathlen;
841 int rf;
843 user_input = ecalloc(MAX_USRI, sizeof(char));
844 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
845 free(user_input);
846 return;
849 pathlen = strlen(cpane->dirn) + 1 + MAX_USRI + 1;
850 path = ecalloc(pathlen, sizeof(char));
851 if (snprintf(path, pathlen, "%s/%s", cpane->dirn, user_input) < 0) {
852 free(user_input);
853 free(path);
854 return;
857 rf = open(path, O_CREAT | O_EXCL, nf_perm);
859 if (rf < 0) {
860 print_error(strerror(errno));
861 } else {
862 if (close(rf) < 0)
863 print_error(strerror(errno));
864 else if (listdir(AddHi, NULL) < 0)
865 print_error(strerror(errno));
868 free(user_input);
869 free(path);
872 static void
873 delfd(void)
875 switch (delent(cpane->direntr[cpane->hdir - 1].name)) {
876 case -1:
877 print_error(strerror(errno));
878 break;
879 case 0:
880 if (BETWEEN(cpane->hdir - 1, 1, cpane->dirc)) /* last entry */
881 cpane->hdir--;
882 if (listdir(AddHi, NULL) < 0)
883 print_error(strerror(errno));
884 break;
888 static void
889 mvbk(void)
891 rmwatch(cpane);
892 chdir("..");
893 getcwd(cpane->dirn, MAX_P);
894 cpane->firstrow = 0;
895 cpane->hdir = cpane->parent_row;
896 if (listdir(AddHi, NULL) < 0)
897 print_error(strerror(errno));
898 cpane->parent_row = 1;
901 static void
902 mvbtm(void)
904 if (cpane->dirc < 1)
905 return;
906 if (cpane->dirc > scrheight) {
907 clear_pane(cpane->dirx);
908 rm_hi(cpane, cpane->hdir - 1);
909 cpane->hdir = cpane->dirc;
910 cpane->firstrow = cpane->dirc - scrheight + 1;
911 refresh_pane();
912 add_hi(cpane, cpane->hdir - 1);
913 } else {
914 rm_hi(cpane, cpane->hdir - 1);
915 cpane->hdir = cpane->dirc;
916 add_hi(cpane, cpane->hdir - 1);
918 print_info();
921 static void
922 mvdwn(void)
924 if (cpane->dirc < 1)
925 return;
926 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
927 rm_hi(cpane, cpane->hdir - 1);
928 cpane->hdir++;
929 add_hi(cpane, cpane->hdir - 1);
930 } else {
931 mvdwns(); /* scroll */
933 print_info();
936 static void
937 mvdwns(void)
939 size_t real;
940 real = cpane->hdir - 1 - cpane->firstrow;
942 if (real > scrheight - 3 - scrsp && cpane->hdir + scrsp < cpane->dirc) {
943 cpane->firstrow++;
944 clear_pane(cpane->dirx);
945 rm_hi(cpane, cpane->hdir - 1);
946 cpane->hdir++;
947 refresh_pane();
948 add_hi(cpane, cpane->hdir - 1);
949 } else if (cpane->hdir < cpane->dirc) {
950 rm_hi(cpane, cpane->hdir - 1);
951 cpane->hdir++;
952 add_hi(cpane, cpane->hdir - 1);
956 static void
957 mvfor(void)
959 rmwatch(cpane);
960 if (cpane->dirc < 1)
961 return;
962 int s;
964 switch (check_dir(cpane->direntr[cpane->hdir - 1].name)) {
965 case 0:
966 chdir(cpane->direntr[cpane->hdir - 1].name);
967 getcwd(cpane->dirn, MAX_P);
968 cpane->parent_row = (int)cpane->hdir;
969 cpane->hdir = 1;
970 cpane->firstrow = 0;
971 if (listdir(AddHi, NULL) < 0)
972 print_error(strerror(errno));
973 break;
974 case 1: /* not a directory open file */
975 tb_shutdown();
976 s = opnf(cpane->direntr[cpane->hdir - 1].name);
977 if (tb_init() != 0)
978 die("tb_init");
979 t_resize();
980 if (s < 0)
981 print_error("process failed non-zero exit");
982 break;
983 case -1: /* failed to open directory */
984 print_error(strerror(errno));
988 static void
989 mvmid(void)
991 if (cpane->dirc < 1)
992 return;
993 rm_hi(cpane, cpane->hdir - 1);
994 if (cpane->dirc < scrheight / 2)
995 cpane->hdir = (cpane->dirc + 1) / 2;
996 else
997 cpane->hdir = (scrheight / 2) + cpane->firstrow;
998 add_hi(cpane, cpane->hdir - 1);
999 print_info();
1002 static void
1003 mvtop(void)
1005 if (cpane->dirc < 1)
1006 return;
1007 if (cpane->dirc > scrheight) {
1008 clear_pane(cpane->dirx);
1009 rm_hi(cpane, cpane->hdir - 1);
1010 cpane->hdir = 1;
1011 cpane->firstrow = 0;
1012 refresh_pane();
1013 add_hi(cpane, cpane->hdir - 1);
1014 } else {
1015 rm_hi(cpane, cpane->hdir - 1);
1016 cpane->hdir = 1;
1017 add_hi(cpane, cpane->hdir - 1);
1018 print_info();
1022 static void
1023 mvup(void)
1025 if (cpane->dirc < 1)
1026 return;
1027 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1028 rm_hi(cpane, cpane->hdir - 1);
1029 cpane->hdir--;
1030 add_hi(cpane, cpane->hdir - 1);
1031 } else {
1032 mvups(); /* scroll */
1034 print_info();
1037 static void
1038 mvups(void)
1040 size_t real;
1041 real = cpane->hdir - 1 - cpane->firstrow;
1043 if (cpane->firstrow > 0 && real < 1 + scrsp) {
1044 cpane->firstrow--;
1045 clear_pane(cpane->dirx);
1046 rm_hi(cpane, cpane->hdir - 1);
1047 cpane->hdir--;
1048 refresh_pane();
1049 add_hi(cpane, cpane->hdir - 1);
1050 } else if (cpane->hdir > 1) {
1051 rm_hi(cpane, cpane->hdir - 1);
1052 cpane->hdir--;
1053 add_hi(cpane, cpane->hdir - 1);
1057 static void
1058 scrdwn(void)
1060 if (cpane->dirc < 1)
1061 return;
1062 if (cpane->dirc < scrheight && cpane->hdir < cpane->dirc) {
1063 if (cpane->hdir < cpane->dirc - scrmv) {
1064 rm_hi(cpane, cpane->hdir - 1);
1065 cpane->hdir += scrmv;
1066 add_hi(cpane, cpane->hdir - 1);
1067 } else {
1068 mvbtm();
1070 } else {
1071 scrdwns();
1073 print_info();
1076 static void
1077 scrdwns(void)
1079 size_t real;
1080 int dynmv;
1081 real = cpane->hdir - cpane->firstrow;
1082 dynmv = MIN(cpane->dirc - cpane->hdir - cpane->firstrow, scrmv);
1084 if (real + scrmv + 1 > scrheight &&
1085 cpane->hdir + scrsp + scrmv < cpane->dirc) { /* scroll */
1086 cpane->firstrow += dynmv;
1087 clear_pane(cpane->dirx);
1088 rm_hi(cpane, cpane->hdir - 1);
1089 cpane->hdir += scrmv;
1090 refresh_pane();
1091 add_hi(cpane, cpane->hdir - 1);
1092 } else {
1093 if (cpane->hdir < cpane->dirc - scrmv - 1) {
1094 rm_hi(cpane, cpane->hdir - 1);
1095 cpane->hdir += scrmv;
1096 add_hi(cpane, cpane->hdir - 1);
1097 } else {
1098 mvbtm();
1103 static void
1104 scrup(void)
1106 if (cpane->dirc < 1)
1107 return;
1108 if (cpane->dirc < scrheight && cpane->hdir > 1) {
1109 if (cpane->hdir > scrmv) {
1110 rm_hi(cpane, cpane->hdir - 1);
1111 cpane->hdir = cpane->hdir - scrmv;
1112 add_hi(cpane, cpane->hdir - 1);
1113 print_info();
1114 } else {
1115 mvtop();
1117 } else {
1118 scrups();
1122 static void
1123 scrups(void)
1125 size_t real;
1126 int dynmv;
1127 real = cpane->hdir - cpane->firstrow;
1128 dynmv = MIN(cpane->firstrow, scrmv);
1130 if (cpane->firstrow > 0 && real < scrmv + scrsp) {
1131 cpane->firstrow -= dynmv;
1132 clear_pane(cpane->dirx);
1133 rm_hi(cpane, cpane->hdir - 1);
1134 cpane->hdir -= scrmv;
1135 refresh_pane();
1136 add_hi(cpane, cpane->hdir - 1);
1137 } else {
1138 if (cpane->hdir > scrmv + 1) {
1139 rm_hi(cpane, cpane->hdir - 1);
1140 cpane->hdir -= scrmv;
1141 add_hi(cpane, cpane->hdir - 1);
1142 } else {
1143 mvtop();
1148 static int
1149 get_usrinput(char *out, size_t sout, char *prompt)
1151 int height = tb_height();
1152 size_t startat;
1153 struct tb_event fev;
1154 size_t counter = (size_t)1;
1155 char empty = ' ';
1156 int x = 0;
1158 clear_status();
1159 startat = strlen(prompt) + 1;
1160 print_prompt(prompt);
1161 tb_set_cursor((int)(startat + 1), height - 1);
1162 tb_present();
1164 while (tb_poll_event(&fev) != 0) {
1165 switch (fev.type) {
1166 case TB_EVENT_KEY:
1167 if (fev.key == (uint16_t)TB_KEY_ESC) {
1168 tb_set_cursor(-1, -1);
1169 clear_status();
1170 return -1;
1173 if (fev.key == (uint16_t)TB_KEY_BACKSPACE ||
1174 fev.key == (uint16_t)TB_KEY_BACKSPACE2) {
1175 if (BETWEEN(counter, (size_t)2, sout)) {
1176 out[x - 1] = '\0';
1177 counter--;
1178 x--;
1179 print_xstatus(empty, startat + counter);
1180 tb_set_cursor((int)startat + counter,
1181 height - 1);
1184 } else if (fev.key == (uint16_t)TB_KEY_ENTER) {
1185 tb_set_cursor(-1, -1);
1186 out[counter - 1] = '\0';
1187 return 0;
1189 } else {
1190 if (counter < sout) {
1191 print_xstatus((char)fev.ch,
1192 (int)(startat + counter));
1193 out[x] = (char)fev.ch;
1194 tb_set_cursor((int)(startat + counter +
1196 height - 1);
1197 counter++;
1198 x++;
1202 tb_present();
1203 break;
1205 default:
1206 return -1;
1210 return -1;
1213 static int
1214 frules(char *ex)
1216 size_t c, d;
1218 for (c = 0; c < LEN(rules); c++)
1219 for (d = 0; d < rules[c].exlen; d++)
1220 if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0)
1221 return c;
1222 return -1;
1225 static int
1226 spawn(const void *v, char *fn)
1228 int ws, x, argc;
1229 pid_t pid, r;
1231 x = 0;
1232 argc = 0;
1234 /* count args */
1235 while (((char **)v)[x++] != NULL)
1236 argc++;
1238 char *argv[argc + 2];
1239 for ( x = 0; x < argc; x++)
1240 argv[x] = ((char **)v)[x];
1242 argv[argc] = fn;
1243 argv[argc + 1] = NULL;
1245 pid = fork();
1246 switch (pid) {
1247 case -1:
1248 return -1;
1249 case 0:
1250 execvp(argv[0], argv);
1251 exit(EXIT_SUCCESS);
1252 default:
1253 while ((r = waitpid(pid, &ws, 0)) == -1 && errno == EINTR)
1254 continue;
1255 if (r == -1)
1256 return -1;
1257 if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
1258 return -1;
1260 return 0;
1263 static int
1264 opnf(char *fn)
1266 char *ed[2], *ex;
1267 int c;
1269 ex = get_ext(fn);
1270 c = frules(ex);
1271 free(ex);
1273 if (c < 0) { /* extension not found open in editor */
1274 ed[0] = getenv("EDITOR");
1275 ed[1] = NULL;
1276 if (ed[0] == NULL)
1277 ed[0] = fed;
1278 return spawn(ed, fn);
1279 } else {
1280 return spawn((char **)rules[c].v, fn);
1284 static int
1285 fsev_init(void)
1287 #if defined _SYS_INOTIFY_H
1288 inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
1289 if (inotify_fd < 0)
1290 return -1;
1291 #elif defined _SYS_EVENT_H_
1292 kq = kqueue();
1293 if (kq < 0)
1294 return -1;
1295 #endif
1296 return 0;
1299 static int
1300 addwatch(void)
1302 #if defined _SYS_INOTIFY_H
1303 return cpane->inotify_wd = inotify_add_watch(inotify_fd, cpane->dirn,
1304 IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
1305 IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF);
1306 #elif defined _SYS_EVENT_H_
1307 cpane->event_fd = open(cpane->dirn, O_RDONLY);
1308 if (cpane->event_fd < 0)
1309 return cpane->event_fd;
1310 EV_SET(&evlist[cpane->pane_id], cpane->event_fd,
1311 EVFILT_VNODE, EV_ADD | EV_CLEAR,
1312 NOTE_DELETE | NOTE_EXTEND | NOTE_LINK |
1313 NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE, 0, NULL);
1314 return 0;
1315 #endif
1318 static int
1319 read_events(void)
1321 #if defined _SYS_INOTIFY_H
1322 char *p;
1323 ssize_t r;
1324 struct inotify_event *event;
1325 const size_t events = 32;
1326 const size_t evbuflen =
1327 events * (sizeof(struct inotify_event) + MAX_N + 1);
1328 char buf[evbuflen];
1330 if (cpane->inotify_wd < 0)
1331 return -1;
1332 r = read(inotify_fd, buf, evbuflen);
1333 if (r <= 0)
1334 return r;
1336 for (p = buf; p < buf + r;) {
1337 event = (struct inotify_event *)p;
1338 if (!event->wd)
1339 break;
1340 if (event->mask) {
1341 return r;
1344 p += sizeof(struct inotify_event) + event->len;
1346 #elif defined _SYS_EVENT_H_
1347 return kevent(kq, evlist, 2, chlist, 2, &gtimeout);
1348 #endif
1349 return -1;
1352 static void
1353 rmwatch(Pane *pane)
1355 #if defined _SYS_INOTIFY_H
1356 if (pane->inotify_wd >= 0)
1357 inotify_rm_watch(inotify_fd, pane->inotify_wd);
1358 #elif defined _SYS_EVENT_H_
1359 close(pane->event_fd);
1360 return;
1361 #endif
1364 static void
1365 fsev_shdn(void)
1367 rmwatch(&pane_l);
1368 rmwatch(&pane_r);
1369 #if defined _SYS_INOTIFY_H
1370 close(inotify_fd);
1371 #elif defined _SYS_EVENT_H_
1372 close(kq);
1373 #endif
1376 static ssize_t
1377 findbm(uint32_t event)
1379 ssize_t i;
1381 for (i = 0; i < (ssize_t)LEN(bmarks); i++) {
1382 if (event == bmarks[i].ch) {
1383 if (check_dir(bmarks[i].path) != 0) {
1384 print_error(strerror(errno));
1385 return -1;
1387 return i;
1390 return -1;
1393 static void
1394 filter(void)
1396 if (cpane->dirc < 1)
1397 return;
1398 char *user_input;
1399 user_input = ecalloc(MAX_USRI, sizeof(char));
1400 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
1401 free(user_input);
1402 return;
1404 if (listdir(AddHi, user_input) < 0)
1405 print_error("no match");
1406 free(user_input);
1409 static void
1410 switch_pane(void)
1412 if (cpane->dirc > 0)
1413 rm_hi(cpane, cpane->hdir - 1);
1414 if (cpane == &pane_l)
1415 cpane = &pane_r;
1416 else if (cpane == &pane_r)
1417 cpane = &pane_l;
1418 chdir(cpane->dirn);
1419 if (cpane->dirc > 0) {
1420 add_hi(cpane, cpane->hdir - 1);
1421 print_info();
1422 } else {
1423 clear_status();
1427 static void
1428 quit(void)
1430 free(pane_l.direntr);
1431 free(pane_r.direntr);
1432 fsev_shdn();
1433 tb_shutdown();
1434 exit(EXIT_SUCCESS);
1437 static void
1438 grabkeys(struct tb_event *event)
1440 size_t i;
1441 ssize_t b;
1443 for (i = 0; i < LEN(keys); i++) {
1444 if (event->ch != 0) {
1445 if (event->ch == keys[i].evkey.ch) {
1446 keys[i].func();
1447 return;
1449 } else if (event->key != 0) {
1450 if (event->key == keys[i].evkey.key) {
1451 keys[i].func();
1452 return;
1457 /* bookmarks */
1458 b = findbm(event->ch);
1459 if (b < 0)
1460 return;
1461 rmwatch(cpane);
1462 strcpy(cpane->dirn, bmarks[b].path);
1463 cpane->firstrow = 0;
1464 cpane->parent_row = 1;
1465 cpane->hdir = 1;
1466 if (listdir(AddHi, NULL) < 0)
1467 print_error(strerror(errno));
1470 static void
1471 start_ev(void)
1473 struct tb_event ev;
1475 for (;;) {
1476 int t = tb_peek_event(&ev, 2000);
1477 if (t < 0) {
1478 tb_shutdown();
1479 return;
1482 if (t == 1) /* keyboard event */
1483 grabkeys(&ev);
1484 else if (t == 2) /* resize event */
1485 t_resize();
1486 else if (t == 0) /* filesystem event */
1487 if (read_events() > 0)
1488 if (listdir(AddHi, NULL) < 0)
1489 print_error(strerror(errno));
1491 tb_present();
1492 continue;
1494 tb_shutdown();
1497 static void
1498 refresh_pane(void)
1500 size_t y, dyn_max, start_from;
1501 int width;
1502 width = (tb_width() / 2) - 4;
1503 Cpair col;
1505 y = 1;
1506 start_from = cpane->firstrow;
1507 dyn_max = MIN(cpane->dirc, (scrheight - 1) + cpane->firstrow);
1509 /* print each entry in directory */
1510 while (start_from < dyn_max) {
1511 get_hicol(&col, cpane->direntr[start_from].mode);
1512 print_row(cpane, start_from, col);
1513 start_from++;
1514 y++;
1517 print_info();
1519 /* print current directory title */
1520 cpane->dircol.fg |= TB_BOLD;
1521 printf_tb(cpane->dirx, 0, cpane->dircol, " %.*s ", width, cpane->dirn);
1524 static int
1525 listdir(int hi, char *filter)
1527 DIR *dir;
1528 struct dirent *entry;
1529 struct stat status;
1530 int width;
1531 size_t i;
1532 int filtercount = 0;
1533 size_t oldc = cpane->dirc;
1535 width = (tb_width() / 2) - 4;
1536 cpane->dirc = 0;
1537 i = 0;
1539 if (chdir(cpane->dirn) < 0)
1540 return -1;
1542 dir = opendir(cpane->dirn);
1543 if (dir == NULL)
1544 return -1;
1546 /* get content and filter sum */
1547 while ((entry = readdir(dir)) != 0) {
1548 if (filter != NULL) {
1549 if (strstr(entry->d_name, filter) != NULL)
1550 filtercount++;
1551 } else { /* no filter */
1552 cpane->dirc++;
1556 if (filter == NULL) {
1557 clear_pane(cpane->dirx);
1558 cpane->dirc -= 2;
1561 if (filter != NULL) {
1562 if (filtercount > 0) {
1563 cpane->dirc -= 2;
1564 cpane->dirc = filtercount;
1565 clear_pane(cpane->dirx);
1566 cpane->hdir = 1;
1567 } else if (filtercount == 0) {
1568 if (closedir(dir) < 0)
1569 return -1;
1570 cpane->dirc = oldc;
1571 return -1;
1575 /* print current directory title */
1576 cpane->dircol.fg |= TB_BOLD;
1577 printf_tb(cpane->dirx, 0, cpane->dircol, " %.*s ", width, cpane->dirn);
1579 /* empty directory */
1580 if (cpane->dirc == 0) {
1581 clear_status();
1582 if (closedir(dir) < 0)
1583 return -1;
1584 return 0;
1587 rewinddir(dir); /* reset position */
1589 /* create array of entries */
1590 i = 0;
1591 cpane->direntr = erealloc(cpane->direntr, cpane->dirc * sizeof(Entry));
1592 while ((entry = readdir(dir)) != 0) {
1593 if ((strcmp(entry->d_name, ".") == 0 ||
1594 strcmp(entry->d_name, "..") == 0))
1595 continue;
1597 /* list found filter */
1598 if (filter != NULL) {
1599 if (strstr(entry->d_name, filter) != NULL) {
1600 strcpy(cpane->direntr[i].name, entry->d_name);
1601 if (lstat(entry->d_name, &status) == 0) {
1602 cpane->direntr[i].size = status.st_size;
1603 cpane->direntr[i].mode = status.st_mode;
1604 cpane->direntr[i].group = status.st_gid;
1605 cpane->direntr[i].user = status.st_uid;
1606 cpane->direntr[i].td = status.st_mtime;
1608 i++;
1611 } else {
1612 strcpy(cpane->direntr[i].name, entry->d_name);
1613 if (lstat(entry->d_name, &status) == 0) {
1614 cpane->direntr[i].size = status.st_size;
1615 cpane->direntr[i].mode = status.st_mode;
1616 cpane->direntr[i].group = status.st_gid;
1617 cpane->direntr[i].user = status.st_uid;
1618 cpane->direntr[i].td = status.st_mtime;
1620 i++;
1624 if (addwatch() < 0)
1625 print_error("can't add watch");
1626 cpane->dirc = i;
1627 qsort(cpane->direntr, cpane->dirc, sizeof(Entry), sort_name);
1628 refresh_pane();
1630 if (hi == AddHi)
1631 add_hi(cpane, cpane->hdir - 1);
1633 if (closedir(dir) < 0)
1634 return -1;
1635 return 0;
1638 static void
1639 t_resize(void)
1641 /* TODO need refactoring */
1642 tb_clear();
1643 draw_frame();
1644 set_panes(PtP);
1646 if (cpane == &pane_l) {
1647 chdir(pane_r.dirn);
1648 cpane = &pane_r;
1649 refresh_pane();
1650 chdir(pane_l.dirn);
1651 cpane = &pane_l;
1652 refresh_pane();
1653 add_hi(&pane_l, pane_l.hdir - 1);
1654 } else if (cpane == &pane_r) {
1655 chdir(pane_l.dirn);
1656 cpane = &pane_l;
1657 refresh_pane();
1658 chdir(pane_r.dirn);
1659 cpane = &pane_r;
1660 refresh_pane();
1661 add_hi(&pane_r, pane_r.hdir - 1);
1664 tb_present();
1667 static void
1668 set_panes(int paneitem)
1670 int width;
1671 char *home;
1672 char cwd[MAX_P];
1673 scrheight = tb_height() - 2;
1675 home = getenv("HOME");
1676 width = tb_width();
1677 if ((getcwd(cwd, sizeof(cwd)) == NULL))
1678 return;
1679 if (home == NULL)
1680 home = "/";
1682 pane_l.pane_id = 0;
1683 pane_l.dirx = 2;
1684 pane_l.dircol = cpanell;
1685 if (paneitem == AllP) {
1686 pane_l.firstrow = 0;
1687 pane_l.direntr = ecalloc(0, sizeof(Entry));
1688 strcpy(pane_l.dirn, cwd);
1689 pane_l.hdir = 1;
1690 pane_l.inotify_wd = -1;
1691 pane_l.parent_row = 1;
1694 pane_r.pane_id = 1;
1695 pane_r.dirx = (width / 2) + 2;
1696 pane_r.dircol = cpanelr;
1697 if (paneitem == AllP) {
1698 pane_r.firstrow = 0;
1699 pane_r.direntr = ecalloc(0, sizeof(Entry));
1700 strcpy(pane_r.dirn, home);
1701 pane_r.hdir = 1;
1702 pane_r.inotify_wd = -1;
1703 pane_r.parent_row = 1;
1707 static void
1708 draw_frame(void)
1710 int height, width, i;
1712 width = tb_width();
1713 height = tb_height();
1715 /* 2 horizontal lines */
1716 for (i = 1; i < width - 1; ++i) {
1717 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
1718 tb_change_cell(i, height - 2, u_hl, cframe.fg, cframe.bg);
1721 /* 3 vertical lines */
1722 for (i = 1; i < height - 1; ++i) {
1723 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
1724 tb_change_cell((width - 1) / 2, i - 1, u_vl, cframe.fg,
1725 cframe.bg);
1726 tb_change_cell(width - 1, i, u_vl, cframe.fg, cframe.bg);
1729 /* 4 corners */
1730 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
1731 tb_change_cell(width - 1, 0, u_cne, cframe.fg, cframe.bg);
1732 tb_change_cell(0, height - 2, u_csw, cframe.fg, cframe.bg);
1733 tb_change_cell(width - 1, height - 2, u_cse, cframe.fg, cframe.bg);
1735 /* 2 middel top and bottom */
1736 tb_change_cell((width - 1) / 2, 0, u_mn, cframe.fg, cframe.bg);
1737 tb_change_cell((width - 1) / 2, height - 2, u_ms, cframe.fg, cframe.bg);
1740 static void
1741 start(void)
1743 if (tb_init() != 0)
1744 die("tb_init");
1745 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
1746 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
1747 die("output error");
1749 draw_frame();
1750 set_panes(AllP);
1751 if (fsev_init() < 0)
1752 print_error(strerror(errno));
1753 cpane = &pane_r;
1754 if (listdir(NoHi, NULL) < 0)
1755 print_error(strerror(errno));
1756 cpane = &pane_l;
1757 if (listdir(AddHi, NULL) < 0)
1758 print_error(strerror(errno));
1759 tb_present();
1760 start_ev();
1764 main(int argc, char *argv[])
1766 #ifdef __OpenBSD__
1767 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
1768 NULL) == -1)
1769 die("pledge");
1770 #endif /* __OpenBSD__ */
1771 if (argc == 1)
1772 start();
1773 else if (argc == 2 && strncmp("-v", argv[1], 2) == 0)
1774 die("sfm-" VERSION);
1775 else
1776 die("usage: sfm [-v]");
1777 return 0;