/* See LICENSE file for copyright and license details. */ #if defined(__linux__) #define _GNU_SOURCE #elif defined(__APPLE__) #define _DARWIN_C_SOURCE #elif defined(__FreeBSD__) #define __BSD_VISIBLE 1 #endif #include #include #include #include #include #if defined(__linux__) #include #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ defined(__APPLE__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "termbox.h" #include "util.h" /* macros */ #define MAX_P 4096 #define MAX_N 255 #define MAX_USRI 32 #define MAX_EXT 4 #define MAX_STATUS 255 #define MAX_LINE 4096 #define MAX_USRN 32 #define MAX_GRPN 32 #define MAX_DTF 32 #define CURSOR(x) (x)->direntr[(x)->hdir - 1] /* typedef */ typedef struct { char name[MAX_N]; gid_t group; mode_t mode; off_t size; time_t dt; uid_t user; } Entry; typedef struct { uint16_t fg; uint16_t bg; } Cpair; typedef struct { int pane_id; char dirn[MAX_P]; // dir name cwd char *filter; Entry *direntr; // dir entries int dirc; // dir entries sum int hdir; // highlighted dir int x_srt; int x_end; int firstrow; int parent_firstrow; int parent_row; // FIX Cpair dircol; int inotify_wd; int event_fd; } Pane; typedef struct { const char **ext; size_t exlen; const void *v; size_t vlen; } Rule; typedef union { uint16_t key; /* one of the TB_KEY_* constants */ uint32_t ch; /* unicode character */ } Evkey; typedef union { int i; const void *v; } Arg; typedef struct { const Evkey evkey; void (*func)(const Arg *); const Arg arg; } Key; /* function declarations */ static void print_tb(const char *, int, int, uint16_t, uint16_t); static void printf_tb(int, int, Cpair, const char *, ...); static void print_status(Cpair, const char *, ...); static void print_xstatus(char, int); static void print_error(char *); static void print_prompt(char *); static void print_info(Pane *, char *); static void print_row(Pane *, size_t, Cpair); static void clear(int, int, int, uint16_t); static void clear_status(void); static void clear_pane(Pane *); static void add_hi(Pane *, size_t); static void rm_hi(Pane *, size_t); static int check_dir(char *); static int sort_name(const void *const, const void *const); static void get_dirp(char *); static char *get_ext(char *); static int get_fdt(char *, time_t); static char *get_fgrp(gid_t); static char *get_fperm(mode_t); static char *get_fsize(off_t); static char *get_fullpath(char *, char *); static char *get_fusr(uid_t); static void get_dirsize(char *, off_t *); static void get_hicol(Cpair *, mode_t); static void delent(const Arg *arg); static void calcdir(const Arg *arg); static void crnd(const Arg *arg); static void crnf(const Arg *arg); static void mv_ver(const Arg *arg); static void mvbk(const Arg *arg); static void mvbtm(const Arg *arg); static void mvfwd(const Arg *arg); static void mvtop(const Arg *arg); static void bkmrk(const Arg *arg); static int get_usrinput(char *, size_t, const char *, ...); static int frules(char *); static int spawn(const void *, size_t, const void *, size_t, char *, int); static int opnf(char *); static int fsev_init(void); static int addwatch(Pane *); static int read_events(void); static void rmwatch(Pane *); static void fsev_shdn(void); static void toggle_df(const Arg *arg); static void start_filter(const Arg *arg); static void start_vmode(const Arg *arg); static void exit_vmode(const Arg *arg); static void start_change(const Arg *arg); static void exit_change(const Arg *arg); static void selup(const Arg *arg); static void seldwn(const Arg *arg); static void selall(const Arg *arg); static void selref(void); static void selynk(const Arg *arg); static void selcalc(void); static void paste(const Arg *arg); static void selmv(const Arg *arg); static void seldel(const Arg *arg); static void init_files(void); static void free_files(void); static void yank(const Arg *arg); static void rname(const Arg *arg); static void chngo(const Arg *arg); static void chngm(const Arg *arg); static void chngf(const Arg *arg); static void dupl(const Arg *arg); static void switch_pane(const Arg *arg); static void quit(const Arg *arg); static void grabkeys(struct tb_event *, Key *, size_t); static void *read_th(void *arg); static void start_ev(void); static void refresh_pane(Pane *); static void set_direntr(Pane *, struct dirent *, DIR *, char *); static int listdir(Pane *); static void t_resize(void); static void get_shell(void); static void opnsh(const Arg *arg); static void set_panes(void); static void draw_frame(void); static void refresh(const Arg *arg); static void start(void); /* global variables */ static pthread_t fsev_thread; static Pane panes[2]; static Pane *cpane; static int pane_idx; static char *editor[2]; static char fed[] = "vi"; static char *shell[2]; static char sh[] = "/bin/sh"; static int theight, twidth, hwidth, scrheight; static int *sel_indexes; static size_t sel_len = 0; static char **sel_files; static int cont_vmode = 0; static int cont_change = 0; static pid_t fork_pid = 0, main_pid; #if defined(_SYS_INOTIFY_H) #define READEVSZ 16 static int inotify_fd; #elif defined(_SYS_EVENT_H_) #define READEVSZ 0 static int kq; struct kevent evlist[2]; /* events we want to monitor */ struct kevent chlist[2]; /* events that were triggered */ static struct timespec gtimeout; #endif #if defined(__linux__) || defined(__FreeBSD__) #define OFF_T "%ld" #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) #define OFF_T "%lld" #endif enum { Left, Right }; /* panes */ enum { Wait, DontWait }; /* spawn forks */ /* configuration, allows nested code to access above variables */ #include "config.h" /* function implementations */ static void print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg) { while (*str != '\0') { uint32_t uni = 0; str += tb_utf8_char_to_unicode(&uni, str); tb_change_cell(x, y, uni, fg, bg); x++; } } static void printf_tb(int x, int y, Cpair col, const char *fmt, ...) { char buf[MAX_LINE]; va_list vl; va_start(vl, fmt); (void)vsnprintf(buf, MAX_LINE, fmt, vl); va_end(vl); print_tb(buf, x, y, col.fg, col.bg); } static void print_status(Cpair col, const char *fmt, ...) { char buf[MAX_STATUS]; va_list vl; va_start(vl, fmt); (void)vsnprintf(buf, MAX_STATUS, fmt, vl); va_end(vl); clear_status(); print_tb(buf, 1, theight - 1, col.fg, col.bg); } static void print_xstatus(char c, int x) { uint32_t uni = 0; (void)tb_utf8_char_to_unicode(&uni, &c); tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg); } static void print_error(char *errmsg) { print_status(cerr, errmsg); } static void print_prompt(char *prompt) { print_status(cprompt, prompt); } static void print_info(Pane *pane, char *dirsize) { char *sz, *ur, *gr, *dt, *prm; dt = ecalloc(MAX_DTF, sizeof(char)); prm = get_fperm(CURSOR(pane).mode); ur = get_fusr(CURSOR(pane).user); gr = get_fgrp(CURSOR(pane).group); if (get_fdt(dt, CURSOR(pane).dt) < 0) *dt = '\0'; if (S_ISREG(CURSOR(pane).mode)) { sz = get_fsize(CURSOR(pane).size); } else { if (dirsize == NULL) { sz = ecalloc(1, sizeof(char)); *sz = '\0'; } else { sz = dirsize; } } print_status(cstatus, "%02d/%02d %s %s:%s %s %s", pane->hdir, pane->dirc, prm, ur, gr, dt, sz); free(prm); free(ur); free(gr); free(dt); free(sz); } static void print_row(Pane *pane, size_t entpos, Cpair col) { int x, y; char *full_str, *rez_pth; char lnk_full[MAX_N]; full_str = basename(pane->direntr[entpos].name); x = pane->x_srt; y = entpos - pane->firstrow + 1; if (S_ISLNK(pane->direntr[entpos].mode) != 0) { rez_pth = ecalloc(MAX_P, sizeof(char)); if (realpath(pane->direntr[entpos].name, rez_pth) != NULL) { snprintf( lnk_full, MAX_N, "%s -> %s", full_str, rez_pth); full_str = lnk_full; } free(rez_pth); } printf_tb(x, y, col, "%*.*s", ~hwidth, hwidth, full_str); } static void clear(int sx, int ex, int y, uint16_t bg) { /* clear line from to */ /* x = line number vertical */ /* y = column number horizontal */ int i; for (i = sx; i < ex; i++) { tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg); } } static void clear_status(void) { clear(1, twidth - 1, theight - 1, cstatus.bg); } static void clear_pane(Pane *pane) { int i, y; y = 0, i = 0; while (i < scrheight) { clear(pane->x_srt, pane->x_end, y, TB_DEFAULT); i++; y++; } /* draw top line */ for (y = pane->x_srt; y < pane->x_end; ++y) { tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg); } } static void add_hi(Pane *pane, size_t entpos) { Cpair col; get_hicol(&col, pane->direntr[entpos].mode); col.fg |= TB_REVERSE | TB_BOLD; col.bg |= TB_REVERSE; print_row(pane, entpos, col); } static void rm_hi(Pane *pane, size_t entpos) { Cpair col; get_hicol(&col, pane->direntr[entpos].mode); print_row(pane, entpos, col); } static int check_dir(char *path) { DIR *dir; dir = opendir(path); if (dir == NULL) { if (errno == ENOTDIR) { return 1; } else { return -1; } } if (closedir(dir) < 0) return -1; return 0; } static int sort_name(const void *const A, const void *const B) { int result; mode_t data1 = (*(Entry *)A).mode; mode_t data2 = (*(Entry *)B).mode; if (data1 < data2) { return -1; } else if (data1 == data2) { result = strncmp((*(Entry *)A).name, (*(Entry *)B).name, MAX_N); return result; } else { return 1; } } static void get_dirp(char *cdir) { int counter, len, i; counter = 0; len = strnlen(cdir, MAX_P); if (len == 1) return; for (i = len - 1; i > 1; i--) { if (cdir[i] == '/') break; else counter++; } cdir[len - counter - 1] = '\0'; } static char * get_ext(char *str) { char *ext; char dot; size_t counter, len, i; dot = '.'; counter = 0; len = strnlen(str, MAX_N); for (i = len - 1; i > 0; i--) { if (str[i] == dot) { break; } else { counter++; } } ext = ecalloc(MAX_EXT + 1, sizeof(char)); strncpy(ext, &str[len - counter], MAX_EXT); ext[MAX_EXT] = '\0'; return ext; } static int get_fdt(char *result, time_t status) { struct tm lt; localtime_r(&status, <); return strftime(result, MAX_DTF, dtfmt, <); } static char * get_fgrp(gid_t status) { char *result; struct group *gr; result = ecalloc(MAX_GRPN, sizeof(char)); gr = getgrgid(status); if (gr == NULL) (void)snprintf(result, MAX_GRPN, "%u", status); else strncpy(result, gr->gr_name, MAX_GRPN); result[MAX_GRPN - 1] = '\0'; return result; } static char * get_fperm(mode_t mode) { char *buf; size_t i; const char chars[] = "rwxrwxrwx"; buf = ecalloc(11, sizeof(char)); if (S_ISDIR(mode)) buf[0] = 'd'; else if (S_ISREG(mode)) buf[0] = '-'; else if (S_ISLNK(mode)) buf[0] = 'l'; else if (S_ISBLK(mode)) buf[0] = 'b'; else if (S_ISCHR(mode)) buf[0] = 'c'; else if (S_ISFIFO(mode)) buf[0] = 'p'; else if (S_ISSOCK(mode)) buf[0] = 's'; else buf[0] = '?'; for (i = 1; i < 10; i++) { buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-'; } buf[10] = '\0'; return buf; } static char * get_fsize(off_t size) { char *result; /* need to be freed */ char unit; int result_len; int counter; counter = 0; result_len = 6; /* 9999X/0 */ result = ecalloc(result_len, sizeof(char)); while (size >= 1000) { size /= 1024; ++counter; } switch (counter) { case 0: unit = 'B'; break; case 1: unit = 'K'; break; case 2: unit = 'M'; break; case 3: unit = 'G'; break; case 4: unit = 'T'; break; default: unit = '?'; } if (snprintf(result, result_len, OFF_T "%c", size, unit) < 0) strncat(result, "???", result_len); return result; } static char * get_fullpath(char *first, char *second) { char *full_path; full_path = ecalloc(MAX_P, sizeof(char)); if (strncmp(first, "/", MAX_P) == 0) (void)snprintf(full_path, MAX_P, "/%s", second); else (void)snprintf(full_path, MAX_P, "%s/%s", first, second); return full_path; } static char * get_fusr(uid_t status) { char *result; struct passwd *pw; result = ecalloc(MAX_USRN, sizeof(char)); pw = getpwuid(status); if (pw == NULL) (void)snprintf(result, MAX_USRN, "%u", status); else strncpy(result, pw->pw_name, MAX_USRN); result[MAX_USRN - 1] = '\0'; return result; } static void get_dirsize(char *fullpath, off_t *fullsize) { DIR *dir; char *ent_full; mode_t mode; struct dirent *entry; struct stat status; dir = opendir(fullpath); if (dir == NULL) { return; } while ((entry = readdir(dir)) != 0) { if ((strncmp(entry->d_name, ".", 2) == 0 || strncmp(entry->d_name, "..", 3) == 0)) continue; ent_full = get_fullpath(fullpath, entry->d_name); if (lstat(ent_full, &status) == 0) { mode = status.st_mode; if (S_ISDIR(mode)) { get_dirsize(ent_full, fullsize); free(ent_full); } else { *fullsize += status.st_size; free(ent_full); } } } closedir(dir); clear_status(); } static void get_hicol(Cpair *col, mode_t mode) { switch (mode & S_IFMT) { case S_IFREG: *col = cfile; if ((S_IXUSR | S_IXGRP | S_IXOTH) & mode) *col = cexec; break; case S_IFDIR: *col = cdir; break; case S_IFLNK: *col = clnk; break; case S_IFBLK: *col = cblk; break; case S_IFCHR: *col = cchr; break; case S_IFIFO: *col = cifo; break; case S_IFSOCK: *col = csock; break; default: *col = cother; break; } } static void delent(const Arg *arg) { if (cpane->dirc < 1) return; char *inp_conf; inp_conf = ecalloc(delconf_len, sizeof(char)); if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?", delconf) < 0) || (strncmp(inp_conf, delconf, delconf_len) != 0)) { free(inp_conf); return; /* canceled by user or wrong inp_conf */ } free(inp_conf); char *tmp[1]; tmp[0] = CURSOR(cpane).name; if (spawn(rm_cmd, rm_cmd_len, tmp, 1, NULL, DontWait) < 0) { print_error(strerror(errno)); return; } } static void calcdir(const Arg *arg) { if (cpane->dirc < 1) return; if (!S_ISDIR(CURSOR(cpane).mode)) return; off_t *fullsize; char *csize; fullsize = ecalloc(1, sizeof(off_t)); get_dirsize(CURSOR(cpane).name, fullsize); csize = get_fsize(*fullsize); CURSOR(cpane).size = *fullsize; print_info(cpane, csize); free(fullsize); } static void crnd(const Arg *arg) { char *user_input, *path; user_input = ecalloc(MAX_USRI, sizeof(char)); if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) { free(user_input); return; } path = ecalloc(MAX_P, sizeof(char)); if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) { free(user_input); free(path); return; } PERROR(mkdir(path, ndir_perm) < 0); free(user_input); free(path); } static void crnf(const Arg *arg) { char *user_input, *path; int rf; user_input = ecalloc(MAX_USRI, sizeof(char)); if (get_usrinput(user_input, MAX_USRI, "new file") < 0) { free(user_input); return; } path = ecalloc(MAX_P, sizeof(char)); if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) { free(user_input); free(path); return; } rf = open(path, O_CREAT | O_EXCL, nf_perm); if (rf < 0) print_error(strerror(errno)); else if (close(rf) < 0) print_error(strerror(errno)); free(user_input); free(path); } static void mv_ver(const Arg *arg) { if (cpane->dirc < 1) return; if (cpane->hdir - arg->i < 1) /* first line */ return; if (cpane->hdir - arg->i > cpane->dirc) /* last line */ return; if (cpane->firstrow > 0 && arg->i > 0 && cpane->hdir <= (cpane->firstrow + arg->i)) { /* scroll up */ cpane->firstrow = cpane->firstrow - arg->i; rm_hi(cpane, cpane->hdir - 1); cpane->hdir = cpane->hdir - arg->i; refresh_pane(cpane); add_hi(cpane, cpane->hdir - 1); return; } if (cpane->hdir - cpane->firstrow >= scrheight + arg->i && arg->i < 0) { /* scroll down */ cpane->firstrow = cpane->firstrow - arg->i; rm_hi(cpane, cpane->hdir - 1); cpane->hdir = cpane->hdir - arg->i; refresh_pane(cpane); add_hi(cpane, cpane->hdir - 1); return; } rm_hi(cpane, cpane->hdir - 1); cpane->hdir = cpane->hdir - arg->i; add_hi(cpane, cpane->hdir - 1); print_info(cpane, NULL); } static void mvbk(const Arg *arg) { if (cpane->dirn[0] == '/' && cpane->dirn[1] == '\0') { /* cwd = / */ return; } get_dirp(cpane->dirn); if (check_dir(cpane->dirn) < 0) { print_error(strerror(errno)); return; } cpane->firstrow = cpane->parent_firstrow; cpane->hdir = cpane->parent_row; PERROR(listdir(cpane) < 0); cpane->parent_firstrow = 0; cpane->parent_row = 1; } static void mvbtm(const Arg *arg) { if (cpane->dirc < 1) return; if (cpane->dirc > scrheight) { rm_hi(cpane, cpane->hdir - 1); cpane->hdir = cpane->dirc; cpane->firstrow = cpane->dirc - scrheight + 1; refresh_pane(cpane); add_hi(cpane, cpane->hdir - 1); } else { rm_hi(cpane, cpane->hdir - 1); cpane->hdir = cpane->dirc; add_hi(cpane, cpane->hdir - 1); } print_info(cpane, NULL); } static void mvfwd(const Arg *arg) { if (cpane->dirc < 1) return; int s; switch (check_dir(CURSOR(cpane).name)) { case 0: strncpy(cpane->dirn, CURSOR(cpane).name, MAX_P); cpane->parent_row = cpane->hdir; cpane->parent_firstrow = cpane->firstrow; cpane->hdir = 1; cpane->firstrow = 0; PERROR(listdir(cpane) < 0); break; case 1: /* not a directory open file */ tb_shutdown(); s = opnf(CURSOR(cpane).name); if (tb_init() != 0) die("tb_init"); t_resize(); if (s < 0) print_error("process failed non-zero exit"); break; case -1: /* failed to open directory */ print_error(strerror(errno)); } } static void mvtop(const Arg *arg) { if (cpane->dirc < 1) return; if (cpane->dirc > scrheight) { rm_hi(cpane, cpane->hdir - 1); cpane->hdir = 1; cpane->firstrow = 0; refresh_pane(cpane); add_hi(cpane, cpane->hdir - 1); } else { rm_hi(cpane, cpane->hdir - 1); cpane->hdir = 1; add_hi(cpane, cpane->hdir - 1); print_info(cpane, NULL); } } static void bkmrk(const Arg *arg) { if (check_dir((char *)arg->v) != 0) { print_error(strerror(errno)); return; } strncpy(cpane->dirn, (char *)arg->v, MAX_P); cpane->firstrow = 0; cpane->parent_row = 1; cpane->hdir = 1; PERROR(listdir(cpane) < 0); } static int get_usrinput(char *result, size_t max_chars, const char *fmt, ...) { char msg[MAX_N]; size_t i, cpos, startat; struct tb_event fev; va_list vl; i = 0; cpos = 1; va_start(vl, fmt); startat = vsnprintf(msg, MAX_N, fmt, vl) + 1; va_end(vl); clear_status(); print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg); tb_set_cursor(startat + 1, theight - 1); tb_present(); while (tb_poll_event(&fev) != 0) { switch (fev.type) { case TB_EVENT_KEY: if (fev.key == TB_KEY_ESC) { tb_set_cursor(-1, -1); clear_status(); return -1; } if (fev.key == TB_KEY_BACKSPACE || fev.key == TB_KEY_BACKSPACE2) { if (BETWEEN(cpos, 2, max_chars)) { result[i - 1] = '\0'; cpos--; i--; print_xstatus(' ', startat + cpos); tb_set_cursor( startat + cpos, theight - 1); } } else if (fev.key == TB_KEY_ENTER) { tb_set_cursor(-1, -1); result[cpos - 1] = '\0'; return 0; } else if (fev.key) { /* disable other TB_KEY_* */ break; } else { if (cpos < max_chars) { print_xstatus( (char)fev.ch, (startat + cpos)); result[i] = (char)fev.ch; tb_set_cursor((startat + cpos + 1), theight - 1); cpos++; i++; } } tb_present(); break; case TB_EVENT_RESIZE: t_resize(); clear_status(); print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg); print_tb(result, startat + 1, theight - 1, cstatus.fg, cstatus.bg); tb_present(); break; default: return -1; } } return -1; } static int frules(char *ex) { size_t c, d; for (c = 0; c < LEN(rules); c++) for (d = 0; d < rules[c].exlen; d++) if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0) return c; return -1; } static int spawn(const void *com_argv, size_t com_argc, const void *f_argv, size_t f_argc, char *fn, int waiting) { int ws; size_t argc; pid_t r; argc = com_argc + f_argc + 2; char *argv[argc]; memcpy(argv, com_argv, com_argc * sizeof(char *)); /* command */ memcpy(&argv[com_argc], f_argv, f_argc * sizeof(char *)); /* files */ argv[argc - 2] = fn; argv[argc - 1] = NULL; fork_pid = fork(); switch (fork_pid) { case -1: return -1; case 0: execvp(argv[0], argv); exit(EXIT_SUCCESS); default: if (waiting == Wait) { while ((r = waitpid(fork_pid, &ws, 0)) == -1 && errno == EINTR) continue; if (r == -1) return -1; if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0)) return -1; } } fork_pid = 0; /* enable th_handler() */ return 0; } static int opnf(char *fn) { char *ex; int c; ex = get_ext(fn); c = frules(ex); free(ex); if (c < 0) /* extension not found open in editor */ return spawn(editor, 1, NULL, 0, fn, Wait); else return spawn( (char **)rules[c].v, rules[c].vlen, NULL, 0, fn, Wait); } static void opnsh(const Arg *arg) { int s; tb_shutdown(); chdir(cpane->dirn); s = spawn(shell, 1, NULL, 0, NULL, Wait); if (tb_init() != 0) die("tb_init"); t_resize(); if (s < 0) print_error("process failed non-zero exit"); } static int fsev_init(void) { #if defined(_SYS_INOTIFY_H) inotify_fd = inotify_init(); if (inotify_fd < 0) return -1; #elif defined(_SYS_EVENT_H_) gtimeout.tv_sec = 1; kq = kqueue(); if (kq < 0) return -1; #endif return 0; } static int addwatch(Pane *pane) { #if defined(_SYS_INOTIFY_H) return pane->inotify_wd = inotify_add_watch(inotify_fd, pane->dirn, IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | IN_ATTRIB | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF); #elif defined(_SYS_EVENT_H_) pane->event_fd = open(pane->dirn, O_RDONLY); if (pane->event_fd < 0) return pane->event_fd; EV_SET(&evlist[pane->pane_id], pane->event_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_LINK | NOTE_RENAME | NOTE_ATTRIB | NOTE_REVOKE | NOTE_WRITE, 0, NULL); return 0; #endif } static int read_events(void) { #if defined(_SYS_INOTIFY_H) char *p; ssize_t r; struct inotify_event *event; const size_t events = 32; const size_t evbuflen = events * (sizeof(struct inotify_event) + MAX_N + 1); char buf[evbuflen]; if (cpane->inotify_wd < 0) return -1; r = read(inotify_fd, buf, evbuflen); if (r <= 0) return r; for (p = buf; p < buf + r;) { event = (struct inotify_event *)p; if (!event->wd) break; if (event->mask) { return r; } p += sizeof(struct inotify_event) + event->len; } #elif defined(_SYS_EVENT_H_) return kevent(kq, evlist, 2, chlist, 2, >imeout); #endif return -1; } static void rmwatch(Pane *pane) { #if defined(_SYS_INOTIFY_H) if (pane->inotify_wd >= 0) inotify_rm_watch(inotify_fd, pane->inotify_wd); #elif defined(_SYS_EVENT_H_) close(pane->event_fd); #endif } static void fsev_shdn(void) { pthread_cancel(fsev_thread); #if defined(__linux__) pthread_join(fsev_thread, NULL); #endif rmwatch(&panes[Left]); rmwatch(&panes[Right]); #if defined(_SYS_INOTIFY_H) close(inotify_fd); #elif defined(_SYS_EVENT_H_) close(kq); #endif } static void toggle_df(const Arg *arg) { show_dotfiles = !show_dotfiles; PERROR(listdir(&panes[Left])); PERROR(listdir(&panes[Right])); tb_present(); } static void start_filter(const Arg *arg) { if (cpane->dirc < 1) return; char *user_input; user_input = ecalloc(MAX_USRI, sizeof(char)); if (get_usrinput(user_input, MAX_USRI, "filter") < 0) { free(user_input); return; } cpane->filter = user_input; if (listdir(cpane) < 0) print_error("no match"); cpane->filter = NULL; free(user_input); } static void start_vmode(const Arg *arg) { if (cpane->dirc < 1) return; struct tb_event fev; if (sel_indexes != NULL) { free(sel_indexes); sel_indexes = NULL; } sel_indexes = ecalloc(cpane->dirc, sizeof(size_t)); sel_indexes[0] = cpane->hdir; cont_vmode = 0; print_prompt("-- VISUAL --"); tb_present(); while (tb_poll_event(&fev) != 0) { switch (fev.type) { case TB_EVENT_KEY: grabkeys(&fev, vkeys, vkeyslen); if (cont_vmode == -1) return; tb_present(); break; } } } static void exit_vmode(const Arg *arg) { refresh_pane(cpane); add_hi(cpane, cpane->hdir - 1); cont_vmode = -1; } static void start_change(const Arg *arg) { if (cpane->dirc < 1) return; struct tb_event fev; cont_change = 0; print_prompt("c [womf]"); tb_present(); while (tb_poll_event(&fev) != 0) { switch (fev.type) { case TB_EVENT_KEY: grabkeys(&fev, ckeys, ckeyslen); if (cont_change == -1) return; tb_present(); break; } } } static void exit_change(const Arg *arg) { cont_change = -1; print_info(cpane, NULL); } static void selup(const Arg *arg) { mv_ver(arg); print_prompt("-- VISUAL --"); int index = abs(cpane->hdir - sel_indexes[0]); if (cpane->hdir < sel_indexes[0]) { sel_indexes[index] = cpane->hdir; add_hi(cpane, sel_indexes[index]); } else if (index < cpane->dirc) { sel_indexes[index + 1] = 0; } if (cpane->dirc >= scrheight || cpane->hdir <= 1) { /* rehighlight all if scrolling */ selref(); } } static void seldwn(const Arg *arg) { mv_ver(arg); print_prompt("-- VISUAL --"); int index = abs(cpane->hdir - sel_indexes[0]); if (cpane->hdir > sel_indexes[0]) { sel_indexes[index] = cpane->hdir; add_hi(cpane, sel_indexes[index] - 2); } else { sel_indexes[index + 1] = 0; } if (cpane->dirc >= scrheight || cpane->hdir >= cpane->dirc) { /* rehighlight all if scrolling */ selref(); } } static void selall(const Arg *arg) { int i; for (i = 0; i < cpane->dirc; i++) { sel_indexes[i] = i + 1; } selref(); } static void selref(void) { int i; for (i = 0; i < cpane->dirc; i++) { if (sel_indexes[i] < (scrheight + cpane->firstrow) && sel_indexes[i] > cpane->firstrow) { /* checks if in the frame of the directories */ add_hi(cpane, sel_indexes[i] - 1); } } } static void selcalc(void) { int j; sel_len = 0; for (j = 0; j < cpane->dirc; j++) { /* calculate used selection size */ if (sel_indexes[j] != 0) sel_len++; else break; } } static void free_files(void) { size_t i; if (sel_files != NULL) { for (i = 0; i < sel_len; i++) { free(sel_files[i]); sel_files[i] = NULL; } free(sel_files); sel_files = NULL; } } static void init_files(void) { size_t i; free_files(); selcalc(); sel_files = ecalloc(sel_len, sizeof(char *)); for (i = 0; i < sel_len; i++) { sel_files[i] = ecalloc(MAX_P, sizeof(char)); strncpy(sel_files[i], cpane->direntr[sel_indexes[i] - 1].name, MAX_P); } } static void selynk(const Arg *arg) { init_files(); refresh_pane(cpane); add_hi(cpane, cpane->hdir - 1); print_status(cprompt, "%zu files are yanked", sel_len); cont_vmode = -1; } static void seldel(const Arg *arg) { char *inp_conf; inp_conf = ecalloc(delconf_len, sizeof(char)); if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?", delconf) < 0) || (strncmp(inp_conf, delconf, delconf_len) != 0)) { free(inp_conf); return; /* canceled by user or wrong inp_conf */ } free(inp_conf); init_files(); if (spawn(rm_cmd, rm_cmd_len, sel_files, sel_len, NULL, DontWait) < 0) print_error(strerror(errno)); else print_status(cprompt, "%zu files are deleted", sel_len); free_files(); cont_vmode = -1; } static void paste(const Arg *arg) { if (sel_files == NULL) { print_error("nothing to paste"); return; } if (spawn(cp_cmd, cp_cmd_len, sel_files, sel_len, cpane->dirn, DontWait) < 0) print_error(strerror(errno)); else print_status(cprompt, "%zu files are copied", sel_len); free_files(); } static void selmv(const Arg *arg) { if (sel_files == NULL) { print_error("nothing to move"); return; } if (spawn(mv_cmd, mv_cmd_len, sel_files, sel_len, cpane->dirn, DontWait) < 0) print_error(strerror(errno)); else print_status(cprompt, "%zu files are moved", sel_len); free_files(); } static void rname(const Arg *arg) { if (cpane->dirc < 1) return; char new_name[MAX_P]; char *input_name; input_name = ecalloc(MAX_N, sizeof(char)); if (get_usrinput(input_name, MAX_N, "rename: %s", basename(CURSOR(cpane).name)) < 0) { exit_change(0); free(input_name); return; } if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) { free(input_name); print_error(strerror(errno)); return; } char *rename_cmd[] = { "mv", CURSOR(cpane).name, new_name }; PERROR(spawn(rename_cmd, 3, NULL, 0, NULL, DontWait) < 0); free(input_name); exit_change(0); } static void chngo(const Arg *arg) { if (cpane->dirc < 1) return; char *input_og; char *tmp[1]; input_og = ecalloc(MAX_N, sizeof(char)); if (get_usrinput(input_og, MAX_N, "OWNER:GROUP %s", basename(CURSOR(cpane).name)) < 0) { exit_change(0); free(input_og); return; } tmp[0] = input_og; if (spawn(chown_cmd, chown_cmd_len, tmp, 1, CURSOR(cpane).name, DontWait) < 0) { print_error(strerror(errno)); return; } free(input_og); exit_change(0); } static void chngm(const Arg *arg) { if (cpane->dirc < 1) return; char *input_og; char *tmp[1]; input_og = ecalloc(MAX_N, sizeof(char)); if (get_usrinput(input_og, MAX_N, "chmod %s", basename(CURSOR(cpane).name)) < 0) { exit_change(0); free(input_og); return; } tmp[0] = input_og; if (spawn(chmod_cmd, chmod_cmd_len, tmp, 1, CURSOR(cpane).name, DontWait) < 0) { print_error(strerror(errno)); return; } free(input_og); exit_change(0); } static void chngf(const Arg *arg) { if (cpane->dirc < 1) return; char *input_og; char *tmp[1]; input_og = ecalloc(MAX_N, sizeof(char)); if (get_usrinput(input_og, MAX_N, CHFLAG " %s", basename(CURSOR(cpane).name)) < 0) { exit_change(0); free(input_og); return; } tmp[0] = input_og; if (spawn(chflags_cmd, chflags_cmd_len, tmp, 1, CURSOR(cpane).name, DontWait) < 0) { print_error(strerror(errno)); return; } free(input_og); exit_change(0); } static void dupl(const Arg *arg) { if (cpane->dirc < 1) return; char new_name[MAX_P]; char *input_name; input_name = ecalloc(MAX_N, sizeof(char)); if (get_usrinput(input_name, MAX_N, "new name: %s", basename(CURSOR(cpane).name)) < 0) { free(input_name); return; } if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) { free(input_name); print_error(strerror(errno)); return; } char *tmp[1]; tmp[0] = CURSOR(cpane).name; if (spawn(cp_cmd, cp_cmd_len, tmp, 1, new_name, DontWait) < 0) { print_error(strerror(errno)); return; } free(input_name); } static void yank(const Arg *arg) { if (cpane->dirc < 1) return; free_files(); sel_len = 1; sel_files = ecalloc(sel_len, sizeof(char *)); sel_files[0] = ecalloc(MAX_P, sizeof(char)); strncpy(sel_files[0], CURSOR(cpane).name, MAX_P); print_status(cprompt, "1 file is yanked", sel_len); } static void switch_pane(const Arg *arg) { if (cpane->dirc > 0) rm_hi(cpane, cpane->hdir - 1); cpane = &panes[pane_idx ^= 1]; if (cpane->dirc > 0) { add_hi(cpane, cpane->hdir - 1); print_info(cpane, NULL); } else { clear_status(); } } static void quit(const Arg *arg) { if (cont_vmode == -1) { /* check if selection was allocated */ free(sel_indexes); if (sel_files != NULL) free_files(); } free(panes[Left].direntr); free(panes[Right].direntr); fsev_shdn(); tb_shutdown(); exit(EXIT_SUCCESS); } static void grabkeys(struct tb_event *event, Key *key, size_t max_keys) { size_t i; for (i = 0; i < max_keys; i++) { if (event->ch != 0) { if (event->ch == key[i].evkey.ch) { key[i].func(&key[i].arg); return; } } else if (event->key != 0) { if (event->key == key[i].evkey.key) { key[i].func(&key[i].arg); return; } } } } void * read_th(void *arg) { struct timespec tim; tim.tv_sec = 0; tim.tv_nsec = 5000000L; /* 0.005 sec */ while (1) if (read_events() > READEVSZ) { kill(main_pid, SIGUSR1); nanosleep(&tim, NULL); } return arg; } static void start_ev(void) { struct tb_event ev; while (tb_poll_event(&ev) != 0) { switch (ev.type) { case TB_EVENT_KEY: grabkeys(&ev, nkeys, nkeyslen); tb_present(); break; case TB_EVENT_RESIZE: t_resize(); break; default: break; } } tb_shutdown(); } static void refresh_pane(Pane *pane) { size_t y, dyn_max, start_from; hwidth = (twidth / 2) - 4; Cpair col; y = 1; start_from = pane->firstrow; dyn_max = MIN(pane->dirc, (scrheight - 1) + pane->firstrow); /* print each entry in directory */ while (start_from < dyn_max) { get_hicol(&col, pane->direntr[start_from].mode); print_row(pane, start_from, col); start_from++; y++; } if (pane->dirc > 0) print_info(pane, NULL); else clear_status(); /* print current directory title */ pane->dircol.fg |= TB_BOLD; printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn); } static void set_direntr(Pane *pane, struct dirent *entry, DIR *dir, char *filter) { int i; char *tmpfull; struct stat status; #define ADD_ENTRY \ tmpfull = get_fullpath(pane->dirn, entry->d_name); \ strncpy(pane->direntr[i].name, tmpfull, MAX_N); \ if (lstat(tmpfull, &status) == 0) { \ pane->direntr[i].size = status.st_size; \ pane->direntr[i].mode = status.st_mode; \ pane->direntr[i].group = status.st_gid; \ pane->direntr[i].user = status.st_uid; \ pane->direntr[i].dt = status.st_mtime; \ } \ i++; \ free(tmpfull); i = 0; pane->direntr = erealloc(pane->direntr, (10 + pane->dirc) * sizeof(Entry)); while ((entry = readdir(dir)) != 0) { if (show_dotfiles == 1) { if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || entry->d_name[1] == '.')) continue; } else { if (entry->d_name[0] == '.') continue; } if (filter == NULL) { ADD_ENTRY } else if (filter != NULL) { if (strcasestr(entry->d_name, filter) != NULL) { ADD_ENTRY } } } pane->dirc = i; } static int listdir(Pane *pane) { DIR *dir; struct dirent *entry; int filtercount = 0; size_t oldc = pane->dirc; pane->dirc = 0; dir = opendir(pane->dirn); if (dir == NULL) return -1; /* get content and filter sum */ while ((entry = readdir(dir)) != 0) { if (pane->filter != NULL) { if (strcasestr(entry->d_name, pane->filter) != NULL) filtercount++; } else { /* no filter */ pane->dirc++; } } if (pane->filter == NULL) { clear_pane(pane); pane->dirc -= 2; } if (pane->filter != NULL) { if (filtercount > 0) { pane->dirc = filtercount; clear_pane(pane); pane->hdir = 1; } else if (filtercount == 0) { if (closedir(dir) < 0) return -1; pane->dirc = oldc; return -1; } } /* print current directory title */ pane->dircol.fg |= TB_BOLD; printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn); if (pane->filter == NULL) /* dont't watch when filtering */ if (addwatch(pane) < 0) print_error("can't add watch"); /* empty directory */ if (pane->dirc == 0) { clear_status(); if (closedir(dir) < 0) return -1; return 0; } rewinddir(dir); /* reset position */ set_direntr( pane, entry, dir, pane->filter); /* create array of entries */ qsort(pane->direntr, pane->dirc, sizeof(Entry), sort_name); refresh_pane(pane); if (pane->hdir > pane->dirc) pane->hdir = pane->dirc; if (pane == cpane && pane->dirc > 0) add_hi(pane, pane->hdir - 1); if (closedir(dir) < 0) return -1; return 0; } static void t_resize(void) { tb_clear(); draw_frame(); panes[Left].x_end = (twidth / 2) - 1; panes[Right].x_end = twidth - 1; panes[Right].x_srt = (twidth / 2) + 2; refresh_pane(&panes[Left]); refresh_pane(&panes[Right]); if (cpane->dirc > 0) add_hi(cpane, cpane->hdir - 1); tb_present(); } static void get_editor(void) { editor[0] = getenv("EDITOR"); editor[1] = NULL; if (editor[0] == NULL) editor[0] = fed; } static void get_shell(void) { shell[0] = getenv("SHELL"); shell[1] = NULL; if (shell[0] == NULL) shell[0] = sh; } static void set_panes(void) { char *home; char cwd[MAX_P]; home = getenv("HOME"); if (home == NULL) home = "/"; if ((getcwd(cwd, sizeof(cwd)) == NULL)) strncpy(cwd, home, MAX_P); pane_idx = Left; /* cursor pane */ cpane = &panes[pane_idx]; panes[Left].pane_id = 0; panes[Left].x_srt = 2; panes[Left].x_end = (twidth / 2) - 1; panes[Left].dircol = cpanell; panes[Left].firstrow = 0; panes[Left].direntr = ecalloc(0, sizeof(Entry)); strncpy(panes[Left].dirn, cwd, MAX_P); panes[Left].hdir = 1; panes[Left].inotify_wd = -1; panes[Left].parent_row = 1; panes[Right].pane_id = 1; panes[Right].x_srt = (twidth / 2) + 2; panes[Right].x_end = twidth - 1; panes[Right].dircol = cpanelr; panes[Right].firstrow = 0; panes[Right].direntr = ecalloc(0, sizeof(Entry)); strncpy(panes[Right].dirn, home, MAX_P); panes[Right].hdir = 1; panes[Right].inotify_wd = -1; panes[Right].parent_row = 1; } static void draw_frame(void) { int i; theight = tb_height(); twidth = tb_width(); hwidth = (twidth / 2) - 4; scrheight = theight - 2; /* 2 horizontal lines */ for (i = 1; i < twidth - 1; ++i) { tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg); tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg); } /* 4 vertical lines */ for (i = 1; i < theight - 1; ++i) { tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg); tb_change_cell( (twidth - 1) / 2, i - 1, u_vl, cframe.fg, cframe.bg); tb_change_cell(((twidth - 1) / 2) + 1, i - 1, u_vl, cframe.fg, cframe.bg); tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg); } /* 4 corners */ tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg); tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg); tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg); tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg); /* 2 middel top and bottom */ tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg); tb_change_cell( (twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg); } void th_handler(int num) { if (fork_pid > 0) /* while forking don't listdir() */ return; (void)num; PERROR(listdir(&panes[Left])); PERROR(listdir(&panes[Right])); tb_present(); } static int start_signal(void) { struct sigaction sa; main_pid = getpid(); sa.sa_handler = th_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; return sigaction(SIGUSR1, &sa, NULL); } static void refresh(const Arg *arg) { kill(main_pid, SIGWINCH); } static void start(void) { switch (tb_init()) { case TB_EFAILED_TO_OPEN_TTY: die("TB_EFAILED_TO_OPEN_TTY"); break; case TB_EUNSUPPORTED_TERMINAL: die("TB_EUNSUPPORTED_TERMINAL"); break; case TB_EPIPE_TRAP_ERROR: die("TB_EUNSUPPORTED_TERMINAL"); break; case 0: break; default: die("UNKNOWN FAILURE"); } if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256) if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL) die("output error"); draw_frame(); set_panes(); get_editor(); get_shell(); PERROR(start_signal() < 0); PERROR(fsev_init() < 0); PERROR(listdir(&panes[Left]) < 0); PERROR(listdir(&panes[Right]) < 0); tb_present(); pthread_create(&fsev_thread, NULL, read_th, NULL); start_ev(); } int main(int argc, char *argv[]) { #if defined(__OpenBSD__) if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath", NULL) == -1) die("pledge"); #endif /* __OpenBSD__ */ if (argc == 1) start(); else if (argc == 2 && strncmp("-v", argv[1], 2) == 0) die("sfm-" VERSION); else die("usage: sfm [-v]"); return 0; }