sfm

simple file manager
git clone https://git.afify.dev/sfm.git
Log | Files | Refs | README | LICENSE

sfm.c (40033B)


      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__) || \
     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__)
    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
    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
    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
    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
    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
    273 print_error(char *errmsg)
    274 {
    275 	print_status(cerr, errmsg);
    276 }
    277 
    278 static void
    279 print_prompt(char *prompt)
    280 {
    281 	print_status(cprompt, prompt);
    282 }
    283 
    284 static void
    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
    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
    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
    356 clear_status(void)
    357 {
    358 	clear(1, twidth - 1, theight - 1, cstatus.bg);
    359 }
    360 
    361 static void
    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
    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
    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
    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
    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
    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 *
    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
    480 get_fdt(char *result, time_t status)
    481 {
    482 	struct tm lt;
    483 	localtime_r(&status, &lt);
    484 	return strftime(result, MAX_DTF, dtfmt, &lt);
    485 }
    486 
    487 static char *
    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 *
    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 *
    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 *
    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 *
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
   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
   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
   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
   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
   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
   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
   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, &gtimeout);
   1150 #endif
   1151 	return -1;
   1152 }
   1153 
   1154 static void
   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
   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
   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
   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
   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
   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
   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
   1267 exit_change(const Arg *arg)
   1268 {
   1269 	cont_change = -1;
   1270 	print_info(cpane, NULL);
   1271 }
   1272 
   1273 static void
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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 *
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   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
   1977 refresh(const Arg *arg)
   1978 {
   1979 	kill(main_pid, SIGWINCH);
   1980 }
   1981 
   1982 static 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
   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 }