sfm

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

sfm.c (39532B)


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