cex

C/Curses file EXplorer
git clone git://git.wimdupont.com/cex.git
Log | Files | Refs | README | LICENSE

cex.c (41747B)


      1 #include <sys/stat.h>
      2 #include <sys/param.h>
      3 #include <ctype.h>
      4 #include <unistd.h>
      5 #include <stdlib.h>
      6 #include <libgen.h>
      7 #include <string.h>
      8 #include <time.h>
      9 #include <langinfo.h>
     10 #include <pwd.h>
     11 #include <grp.h>
     12 #include <dirent.h>
     13 #include <errno.h>
     14 #include <curses.h>
     15 
     16 #include "config.h"
     17 
     18 #if GIT_INFO
     19 #include <git2.h>
     20 #endif
     21 
     22 #define ctrl(x)		((x) & 0x1f)
     23 #define arrlen(arr)	(sizeof(arr)/sizeof(0[arr]))
     24 
     25 typedef enum {OTHER, LEFT, RIGHT, UP, DOWN} Direction;
     26 typedef enum {CURRENT, PARENT, CHILD} WinType;
     27 typedef enum {COPY, REMOVE, MOVE} SelAction;
     28 
     29 typedef struct _win_file {
     30 	char d_name[256];
     31 	unsigned char d_type;
     32 	bool selected;
     33 	bool link_valid;
     34 } WinFile;
     35 
     36 typedef struct _dir_win {
     37 	WinType wintype;
     38 	WINDOW *window;
     39 	WinFile *winfiles;
     40 	char path[PATH_MAX];
     41 	char *message;
     42 	int maxx;
     43 	int maxy;
     44 	int startpr;
     45 	int highlight;
     46 	int filecount;
     47 	bool holdupdate;
     48 	bool usehighlight;
     49 } DirWin;
     50 
     51 static void init_dirwins(void);
     52 static void init_screen(void);
     53 static void resize(void);
     54 static void start(void);
     55 static void wpath(const char *filename);
     56 static void reset_flags(void);
     57 static void set_startpr(DirWin *dirwin);
     58 static void set_win_files(DirWin *dirwin);
     59 static void set_win_message(DirWin *dirwin, char *message);
     60 static void print_win(DirWin *dirwin);
     61 static void update_child_win(void);
     62 static void print_top_title(void);
     63 static void print_bot_title(void);
     64 static void print_content(void);
     65 static void print_error(size_t size, const char *fmt, ...);
     66 static void show_file_mime(void);
     67 static void select_file(const char *path);
     68 static void select_files(void);
     69 static void exe_selection(SelAction action, const char *askn);
     70 static void clear_selected(void);
     71 static void clear_search(void);
     72 static void combo_key(int keypress);
     73 static void combo_go(int keypress);
     74 static void combo_inf(int keypress);
     75 static void combo_open(int keypress);
     76 static void combo_make(int keypress);
     77 static void change_dir(const char *chdname, Direction direction, bool abspath);
     78 static void change_parent_dir(Direction direction);
     79 static void open_child(bool exec);
     80 static void open_nohup_xdg(void);
     81 static void search(void);
     82 static void move_top(DirWin *dirwin);
     83 static void move_bot(DirWin *dirwin);
     84 static void move_page_up(DirWin *dirwin);
     85 static void move_page_down(DirWin *dirwin);
     86 static void move_up(DirWin *dirwin);
     87 static void move_down(DirWin *dirwin);
     88 static void first_search(void);
     89 static void next_search(void);
     90 static void prev_search(void);
     91 static void free_dirwin(DirWin *dirwin);
     92 static void run_command(size_t size, const char *fmt, ...);
     93 static void run_sudo_command(size_t size, const char *fmt, ...);
     94 static void clean(void);
     95 static void fatal(const char *fmt, ...);
     96 static bool is_dir(DirWin *dirwin, size_t index);
     97 static bool is_selected(DirWin *dirwin, size_t count);
     98 static bool remove_selected(const char *sel);
     99 static bool prompt_confirm(size_t size, const char *fmt, ...);
    100 static int compare_file(const void *a, const void *b);
    101 static int get_mime(char *buf, size_t bufsize, const char *path);
    102 static int get_mime_default(char *buf, size_t bufsize, const char *mime);
    103 static int read_command(char *buf, size_t bufsize, const char *cmd);
    104 static int make_file(void);
    105 static int make_dir(void);
    106 static int make_mod(void);
    107 static int make_chown(void);
    108 static int sudo_chown(char *owner, char *group, long namelen);
    109 static void make_access(void);
    110 static void make_open_default(void);
    111 static void make_mime_default(const char *mime, const char *app);
    112 static int rm_file(const char *fname);
    113 static int rename_file(const char *fname);
    114 static char *truncate_buf_prefix(char *buf, size_t bufsize, size_t max_len);
    115 static char *human_readable_bytes(char *buf, intmax_t bytes);
    116 static char *prompt_answer(char *buf, size_t size, const char *question);
    117 static char *cpstr(char *dest, const char *src);
    118 static char *substr(char *buf, const char *haystack, const char *needle, const bool casesens);
    119 static char *get_file_info(char *buf, size_t bufsize, const char *filepath);
    120 static char *get_fullpath(char *buf, DirWin *dirwin, size_t index);
    121 static char *get_dirname(char *buf, const char *path);
    122 static char *replace_home(char *buf, const char *path);
    123 static mode_t get_st_mode(DirWin *dirwin, size_t index);
    124 
    125 static DirWin curwin, parwin, childwin;
    126 static char *userhome, *username, *editor, **selected, searchq[SEARCHLEN+1];
    127 static int maxy, maxx, start_sel = -1;
    128 static size_t selc, searchc;
    129 static bool print_bot, hide = DEFAULT_HIDE, searchcs = DEFAULT_SEARCH_CASE_SENS;
    130 
    131 #if GIT_INFO
    132 static void init_status_options(git_status_options *out_opts);
    133 static int is_dirty(git_repository *repo, const git_status_options *opts);
    134 static char *retrieve_git_branch(char *buf, size_t bufsize, const char *repo_path);
    135 
    136 static char gitbranch[128];
    137 #endif
    138 
    139 int
    140 main(int argc, char **argv)
    141 {
    142 	struct passwd userinf;
    143 	char filename[PATH_MAX];
    144 	int opt;
    145 	bool writepath = FALSE;
    146 
    147 	while ((opt = getopt(argc, argv, "f:")) != -1) {
    148 		switch (opt) {
    149 			case 'f':
    150 				if (strlen(optarg) >= sizeof(filename))
    151 					fatal("Output path is too long.\n");
    152 				cpstr(filename, optarg);
    153 				writepath = TRUE;
    154 				break;
    155 			default:
    156 				fatal("Unknown option: %c\n", optopt);
    157 				break;
    158 		}
    159 	}
    160 
    161 #if GIT_INFO
    162 	git_libgit2_init();
    163 #endif
    164 
    165 	editor = getenv("EDITOR");
    166 	
    167 	userinf = *getpwuid(getuid());
    168 	username = strdup(userinf.pw_name);
    169 	userhome = strdup(userinf.pw_dir);
    170 
    171 	init_dirwins();
    172 	init_screen();
    173 
    174 	start();
    175 
    176 	if (writepath)
    177 		wpath(filename);
    178 
    179 	clean();
    180 }
    181 
    182 void
    183 init_dirwins(void)
    184 {
    185 	curwin.wintype = CURRENT;
    186 	parwin.wintype = PARENT;
    187 	childwin.wintype = CHILD;
    188 
    189 	reset_flags();
    190 
    191 	childwin.highlight = 0;
    192 
    193 	if (getcwd(curwin.path, PATH_MAX) == NULL)
    194 		fatal("getcwd() error");
    195 
    196 	get_dirname(parwin.path, curwin.path);
    197 
    198 	set_win_files(&curwin);
    199 	set_win_files(&parwin);
    200 
    201 	if (curwin.winfiles != NULL && curwin.winfiles[0].d_type == DT_DIR) {
    202 		cpstr(childwin.path, curwin.path);
    203 		update_child_win();
    204 	}
    205 }
    206 
    207 void
    208 init_screen(void)
    209 {
    210 	initscr();
    211 	noecho();
    212 	start_color();
    213 	use_default_colors();
    214 	keypad(stdscr, TRUE);
    215 	curs_set(0);
    216 
    217 	clear();
    218 	cbreak();
    219 
    220 	init_pair(COLOR_BLACK, COLOR_BLACK, -1);
    221 	init_pair(COLOR_RED, COLOR_RED, -1);
    222 	init_pair(COLOR_GREEN,COLOR_GREEN, -1);
    223 	init_pair(COLOR_YELLOW, COLOR_YELLOW, -1);
    224 	init_pair(COLOR_BLUE, COLOR_BLUE, -1);
    225 	init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1);
    226 	init_pair(COLOR_CYAN, COLOR_CYAN, -1);
    227 	init_pair(COLOR_WHITE, COLOR_WHITE, -1);
    228 
    229 	curwin.highlight = 0;
    230 	curwin.startpr = 0;
    231 
    232 	parwin.highlight = 0;
    233 	parwin.startpr = 0;
    234 
    235 	childwin.highlight = 0;
    236 	childwin.startpr = 0;
    237 
    238 	resize();
    239 
    240 #if GIT_INFO
    241 	retrieve_git_branch(gitbranch, sizeof(gitbranch), curwin.path);
    242 #endif
    243 	print_top_title();
    244 	print_bot_title();
    245 
    246 	print_win(&parwin);
    247 	print_win(&curwin);
    248 	print_win(&childwin);
    249 }
    250 
    251 void
    252 resize(void)
    253 {
    254 	int startx;
    255 
    256 	getmaxyx(stdscr, maxy, maxx);
    257 
    258 	if (parwin.window) delwin(parwin.window);
    259 	parwin.maxy = maxy-BORDER_SPACE_SIZE;
    260 	parwin.maxx = maxx/4-BORDER_SPACE_SIZE > 0 ? maxx/4-BORDER_SPACE_SIZE : 1;
    261 	startx = maxx > BORDER_SPACE_SIZE ? BORDER_SPACE_SIZE : 0;
    262 	parwin.window = newwin(parwin.maxy, parwin.maxx, BORDER_SPACE_SIZE, startx);
    263 
    264 	if (curwin.window) delwin(curwin.window);
    265 	curwin.maxy = maxy-BORDER_SPACE_SIZE;
    266 	curwin.maxx = maxx/4-BORDER_SPACE_SIZE > 0 ? maxx/4-BORDER_SPACE_SIZE : 1;
    267 	startx = maxx > curwin.maxx+BORDER_SPACE_SIZE*2 ? curwin.maxx+BORDER_SPACE_SIZE*2 : 0;
    268 	curwin.window = newwin(curwin.maxy, curwin.maxx, BORDER_SPACE_SIZE, startx);
    269 
    270 	if (childwin.window) delwin(childwin.window);
    271 	childwin.maxy = maxy-BORDER_SPACE_SIZE;
    272 	childwin.maxx = maxx/2-BORDER_SPACE_SIZE > 0 ? maxx/2-BORDER_SPACE_SIZE : 1;
    273 	startx = maxx > childwin.maxx+BORDER_SPACE_SIZE*2 ? childwin.maxx+BORDER_SPACE_SIZE*2 : 0;
    274 	childwin.window = newwin(childwin.maxy, childwin.maxx, BORDER_SPACE_SIZE, startx);
    275 
    276 	erase();
    277 	refresh();
    278 
    279 	update_child_win();
    280 }
    281 
    282 void
    283 start(void)
    284 {
    285 	int c, sel_anchor;
    286 	char chdname[PATH_MAX];
    287 
    288 	while ((c = getch()) != KEY_QUIT) {
    289 		move(1, 1);
    290 		clrtoeol();
    291 		reset_flags();
    292 		switch (c) {
    293 			case KEY_UP:
    294 			case KEY_VUP:
    295 				move_up(&curwin);
    296 				update_child_win();
    297 				break;
    298 			case KEY_DOWN:
    299 			case KEY_VDOWN:
    300 				move_down(&curwin);
    301 				update_child_win();
    302 				break;
    303 			case KEY_VPUP:
    304 				if (is_dir(&parwin, MAX(parwin.highlight-1, 0)))
    305 					change_parent_dir(UP);
    306 				break;
    307 			case KEY_VPDOWN:
    308 				if (is_dir(&parwin, MIN(parwin.highlight+1, parwin.filecount-1)))
    309 					change_parent_dir(DOWN);
    310 				break;
    311 			case KEY_PPAGE:
    312 			case ctrl('u'):
    313 				move_page_up(&curwin);
    314 				update_child_win();
    315 				break;
    316 			case KEY_NPAGE:
    317 			case ctrl('d'):
    318 				move_page_down(&curwin);
    319 				update_child_win();
    320 				break;
    321 			case KEY_BOT:
    322 				move_bot(&curwin);
    323 				update_child_win();
    324 				break;
    325 			case 10:
    326 			case KEY_VRIGHT:
    327 			case KEY_VRIGHT_ABS:
    328 				if (is_dir(&curwin, curwin.highlight))
    329 					change_dir(get_fullpath(chdname, &curwin, curwin.highlight),
    330 						RIGHT, c == KEY_VRIGHT_ABS);
    331 				else if (S_ISREG(get_st_mode(&curwin, curwin.highlight)))
    332 					open_child(FALSE);
    333 				break;
    334 			case KEY_LEFT:
    335 			case KEY_VLEFT:
    336 				change_dir(get_dirname(chdname, curwin.path), LEFT, FALSE);
    337 				break;
    338 			case KEY_SEARCH:
    339 				search();
    340 				update_child_win();
    341 				break;
    342 			case KEY_SEARCH_CASE:
    343 				searchcs = searchcs ? FALSE : TRUE;
    344 				set_win_files(&curwin);
    345 				set_win_files(&parwin);
    346 				update_child_win();
    347 				break;
    348 			case KEY_NEXT_SEARCH:
    349 				next_search();
    350 				update_child_win();
    351 				break;
    352 			case KEY_PREV_SEARCH:
    353 				prev_search();
    354 				update_child_win();
    355 				break;
    356 			case KEY_SEL_FILE:
    357 				select_file(get_fullpath(chdname, &curwin, curwin.highlight));
    358 				set_win_files(&curwin);
    359 				update_child_win();
    360 				break;
    361 			case KEY_SEL_FILES:
    362 				select_files();
    363 				sel_anchor = start_sel;
    364 				set_win_files(&curwin);
    365 				start_sel = sel_anchor;
    366 				update_child_win();
    367 				break;
    368 			case KEY_CLEAR_SEL:
    369 				clear_selected();
    370 				set_win_files(&curwin);
    371 				set_win_files(&parwin);
    372 				update_child_win();
    373 				break;
    374 			case KEY_CLEAR_SEARCH:
    375 				clear_search();
    376 				set_win_files(&curwin);
    377 				break;
    378 			case KEY_CP_SEL:
    379 				exe_selection(COPY, NULL);
    380 				break;
    381 			case KEY_RM_SEL:
    382 				exe_selection(REMOVE, "Remove");
    383 				break;
    384 			case KEY_MV_SEL:
    385 				exe_selection(MOVE, "Move");
    386 				break;
    387 			case KEY_RM_FILE:
    388 				rm_file(curwin.winfiles[curwin.highlight].d_name);
    389 				set_win_files(&curwin);
    390 				update_child_win();
    391 				break;
    392 			case KEY_RENAME_FILE:
    393 				rename_file(curwin.winfiles[curwin.highlight].d_name);
    394 				set_win_files(&curwin);
    395 				update_child_win();
    396 				break;
    397 			case KEY_HIDE:
    398 				hide = hide ? FALSE : TRUE;
    399 				curwin.highlight = 0;
    400 				curwin.startpr = 0;
    401 				set_win_files(&curwin);
    402 				set_win_files(&parwin);
    403 				update_child_win();
    404 				break;
    405 			case KEY_COMBO_GO:
    406 			case KEY_COMBO_INF:
    407 			case KEY_COMBO_OPEN:
    408 			case KEY_COMBO_MAKE:
    409 				combo_key(c);
    410 				break;
    411 			case KEY_RESIZE:
    412 				resize();
    413 				break;
    414 			default:
    415 				break;
    416 		}
    417 
    418 		print_top_title();
    419 		if (print_bot)
    420 			print_bot_title();
    421 		print_win(&parwin);
    422 		print_win(&curwin);
    423 		print_win(&childwin);
    424 	}
    425 }
    426 
    427 void
    428 wpath(const char *filename)
    429 {
    430 	FILE *fptr;
    431 
    432 	if ((fptr = fopen(filename, "w")) == NULL)
    433 		fatal("Error opening file \"%s\"\n", filename);
    434 	fprintf(fptr, "%s\n", curwin.path);
    435 	fclose(fptr);
    436 }
    437 
    438 void
    439 reset_flags(void)
    440 {
    441 	parwin.usehighlight = FALSE;
    442 	curwin.usehighlight = TRUE;
    443 	childwin.usehighlight = TRUE;
    444 
    445 	parwin.holdupdate = FALSE;
    446 	curwin.holdupdate = FALSE;
    447 	childwin.holdupdate = FALSE;
    448 
    449 	print_bot = TRUE;
    450 }
    451 
    452 void
    453 set_startpr(DirWin *dirwin)
    454 {
    455 	if (dirwin->winfiles == NULL) {
    456 		dirwin->startpr = 0;
    457 		return;
    458 	}
    459 
    460 	dirwin->startpr = MAX(dirwin->highlight - dirwin->maxy/2, 0);
    461 }
    462 
    463 void
    464 set_win_files(DirWin *dirwin)
    465 {
    466 	struct dirent *ent;
    467 	size_t count = 0, cap = 16;
    468 	char linkpath[PATH_MAX];
    469 	DIR *dir;
    470 
    471 	if (dirwin->wintype == CURRENT)
    472 		start_sel = -1;
    473 
    474 	free_dirwin(dirwin);
    475 
    476 	if (dirwin->wintype == PARENT && strcmp(curwin.path, "/") == 0)
    477 		return;
    478 
    479 	if ((dir = opendir(dirwin->path)) == NULL) {
    480 		switch (errno) {
    481 			case EACCES:
    482 				set_win_message(dirwin, "No permission.");
    483 				return;
    484 			default:
    485 				fatal("Could not open directory: %s", dirwin->path);
    486 		}
    487 	}
    488 
    489 	if ((dirwin->winfiles = (WinFile*) malloc(cap * sizeof(WinFile))) == NULL)
    490 		fatal("Fatal: failed to malloc.\n");
    491 
    492 	while ((ent = readdir(dir)) != NULL) {
    493 		if (strcmp(ent->d_name, ".") == 0
    494 				|| strcmp(ent->d_name, "..") == 0
    495 				|| (hide && ent->d_name[0] == '.'))
    496 			continue;
    497 		if (strnlen(ent->d_name, sizeof(dirwin->winfiles[0].d_name))
    498 				== sizeof(dirwin->winfiles[0].d_name))
    499 			continue;
    500 		if (count == cap) {
    501 			cap *= 2;
    502 			if ((dirwin->winfiles = (WinFile*) realloc(dirwin->winfiles, cap * sizeof(WinFile))) == NULL)
    503 				fatal("Fatal: failed to realloc.\n");
    504 		}
    505 		cpstr(dirwin->winfiles[count].d_name, ent->d_name);
    506 		dirwin->winfiles[count].d_type = ent->d_type;
    507 		dirwin->filecount = (int) count + 1;
    508 		dirwin->winfiles[count].selected = is_selected(dirwin, count);
    509 		if (ent->d_type == DT_LNK) {
    510 			get_fullpath(linkpath, dirwin, count);
    511 			dirwin->winfiles[count].link_valid = (linkpath[0] != '\0'
    512 				&& access(linkpath, F_OK) == 0);
    513 		} else
    514 			dirwin->winfiles[count].link_valid = TRUE;
    515 		count++;
    516 	}
    517 	closedir(dir);
    518 
    519 	dirwin->filecount = count;
    520 
    521 	if (count == 0)
    522 		set_win_message(dirwin, "empty");
    523 	else
    524 		qsort(dirwin->winfiles, count, sizeof(WinFile), compare_file);
    525 }
    526 
    527 bool
    528 is_selected(DirWin *dirwin, size_t index)
    529 {
    530 	char buf[PATH_MAX];
    531 
    532 	if (selc == 0 || selected == NULL)
    533 		return FALSE;
    534 
    535 	get_fullpath(buf, dirwin, index);
    536 
    537 	for (size_t i = 0; i < selc; i++) {
    538 		if (strcmp(buf, selected[i]) == 0)
    539 			return TRUE;
    540 	}
    541 
    542 	return FALSE;
    543 }
    544 
    545 bool
    546 remove_selected(const char *sel)
    547 {
    548 	for (size_t i = 0; i < selc; i++) {
    549 		if (strcmp(selected[i], sel) == 0) {
    550 			free(selected[i]);
    551 			selected[i] = selected[selc-1];
    552 			if (--(selc) == 0) {
    553 				free(selected);
    554 				selected = NULL;
    555 			}
    556 			return TRUE;
    557 		}
    558 	}
    559 
    560 	return FALSE;
    561 }
    562 
    563 void
    564 set_win_message(DirWin *dirwin, char *message)
    565 {
    566 	free_dirwin(dirwin);
    567 	dirwin->message = message;
    568 }
    569 
    570 void
    571 print_win(DirWin *dirwin)
    572 {
    573 	const char *curbase;
    574 
    575 	if (dirwin->holdupdate)
    576 		return;
    577 
    578 	size_t cplen, len, width = dirwin->maxx > 1 ? (size_t) dirwin->maxx-1 : 0;
    579 	char *subs, name[sizeof(dirwin->winfiles[0].d_name)+1], sbuf[SEARCHLEN+1];
    580 	int sindex, y = 0, x = 1;
    581 
    582 	werase(dirwin->window);
    583 
    584 	set_startpr(dirwin);
    585 
    586 	if (dirwin->message != NULL) {
    587 		if (dirwin->wintype == CURRENT) {
    588 			wattron(dirwin->window, A_REVERSE);
    589 			mvwaddstr(dirwin->window, y, x, dirwin->message);
    590 			wattroff(dirwin->window, A_REVERSE);
    591 		} else {
    592 			wattron(dirwin->window, COLOR_PAIR(CHILDWIN_MESSAGE_COLOR));
    593 			mvwaddstr(dirwin->window, y, x, dirwin->message);
    594 			wattroff(dirwin->window, COLOR_PAIR(CHILDWIN_MESSAGE_COLOR));
    595 		}
    596 	} else if (dirwin->winfiles != NULL) {
    597 		curbase = strrchr(curwin.path, '/');
    598 		if (curbase != NULL && curbase != curwin.path)
    599 			curbase++;
    600 		else if (curbase == NULL)
    601 			curbase = curwin.path;
    602 		while (!dirwin->usehighlight) {
    603 			for (int i = 0; i < dirwin->filecount; ++i) {
    604 				if (strcmp(curbase, dirwin->winfiles[i].d_name) == 0) {
    605 					dirwin->usehighlight = TRUE;
    606 					dirwin->highlight = i;
    607 					set_startpr(dirwin);
    608 					break;
    609 				}
    610 			}
    611 			break;
    612 		}
    613 		for (int i = dirwin->startpr; i < dirwin->filecount; ++i) {
    614 			mvwaddch(dirwin->window, y, x-1, ' ');
    615 			if (dirwin->winfiles[i].selected) {
    616 				wattron(dirwin->window, COLOR_PAIR(MARK_SELECTED_COLOR));
    617 				mvwaddch(dirwin->window, y, x-1, '|');
    618 				wattroff(dirwin->window, COLOR_PAIR(MARK_SELECTED_COLOR));
    619 			}
    620 			len = MIN(strnlen(dirwin->winfiles[i].d_name,
    621 				sizeof(dirwin->winfiles[i].d_name)), width);
    622 			memcpy(name, dirwin->winfiles[i].d_name, len);
    623 			name[len] = '\0';
    624 			if (dirwin->usehighlight && dirwin->highlight == i) {
    625 				wattron(dirwin->window, A_REVERSE);
    626 				mvwaddstr(dirwin->window, y, x, name);
    627 				wattroff(dirwin->window, A_REVERSE);
    628 			} else if (dirwin->winfiles[i].d_type == DT_DIR) {
    629 				wattron(dirwin->window, COLOR_PAIR(DIR_COLOR));
    630 				wattron(dirwin->window, A_BOLD);
    631 				mvwaddstr(dirwin->window, y, x, name);
    632 				wattroff(dirwin->window, COLOR_PAIR(DIR_COLOR));
    633 				wattroff(dirwin->window, A_BOLD);
    634 			} else if (dirwin->winfiles[i].d_type == DT_LNK) {
    635 				if (dirwin->winfiles[i].link_valid) {
    636 					wattron(dirwin->window, COLOR_PAIR(LN_COLOR));
    637 					mvwaddstr(dirwin->window, y, x, name);
    638 					wattroff(dirwin->window, COLOR_PAIR(LN_COLOR));
    639 				} else {
    640 					wattron(dirwin->window, COLOR_PAIR(INVALID_LN_COLOR));
    641 					mvwaddstr(dirwin->window, y, x, name);
    642 					wattroff(dirwin->window, COLOR_PAIR(INVALID_LN_COLOR));
    643 				}
    644 			} else
    645 				mvwaddstr(dirwin->window, y, x, name);
    646 			if (dirwin->wintype == CURRENT && searchc > 0
    647 					&& ((subs = substr(subs, name, searchq, searchcs)) != NULL)) {
    648 				sindex = (int) (subs - name);
    649 				wattron(dirwin->window, COLOR_PAIR(SEARCH_MATCH_COLOR));
    650 				if (dirwin->usehighlight && dirwin->highlight == i)
    651 					wattron(dirwin->window, A_REVERSE);
    652 				cplen = strlen(searchq);
    653 				memcpy(sbuf, name + sindex, cplen);
    654 				sbuf[cplen] = '\0';
    655 				mvwaddstr(dirwin->window, y, x+sindex, sbuf);
    656 				wattroff(dirwin->window, COLOR_PAIR(SEARCH_MATCH_COLOR));
    657 				if (dirwin->usehighlight && dirwin->highlight == i)
    658 					wattroff(dirwin->window, A_REVERSE);
    659 			}
    660 			y++;
    661 		}
    662 	}
    663 	wrefresh(dirwin->window);
    664 }
    665 
    666 void
    667 update_child_win(void)
    668 {
    669 	if (curwin.filecount <= 0) {
    670 		free_dirwin(&childwin);
    671 		return;
    672 	}
    673 
    674 	get_fullpath(childwin.path, &curwin, curwin.highlight);
    675 
    676 	switch (curwin.winfiles[curwin.highlight].d_type) {
    677 		case DT_LNK:
    678 			if (access(childwin.path, F_OK) != 0) {
    679 				set_win_message(&childwin, "Link no longer exists.");
    680 				break;
    681 			}
    682 			if (is_dir(&curwin, curwin.highlight))
    683 				goto dir;
    684 			else if (S_ISREG(get_st_mode(&curwin, curwin.highlight)))
    685 				goto reg;
    686 			else
    687 				goto def;
    688 			break;
    689 		case DT_DIR:
    690 	dir:
    691 			set_win_files(&childwin);
    692 			break;
    693 		case DT_REG:
    694 	reg:
    695 			print_content();
    696 			break;
    697 		default:
    698 	def:
    699 			werase(childwin.window);
    700 			free_dirwin(&childwin);
    701 			set_win_message(&childwin, "Not a regular file or directory.");
    702 			wrefresh(childwin.window);
    703 			break;
    704 	}
    705 }
    706 
    707 void
    708 print_top_title(void)
    709 {
    710 	char path[PATH_MAX];
    711 
    712 	move(0, 0);
    713 	clrtoeol();
    714 
    715 	if (curwin.filecount == 0)
    716 		snprintf(path, sizeof(path), "%s%s", curwin.path,
    717 			strcmp(curwin.path, "/") == 0 ? "" : "/");
    718 	else
    719 		get_fullpath(path, &curwin, curwin.highlight);
    720 	attron(COLOR_PAIR(TOP_TITLE_COLOR));
    721 #if GIT_INFO
    722 	truncate_buf_prefix(path, sizeof(path), maxx - strlen(username) - 2 - strlen(gitbranch));
    723 	printw("%s:%s", username, path);
    724 	attron(COLOR_PAIR(GIT_BRANCH_COLOR));
    725 	printw("%s", gitbranch);
    726 	attroff(COLOR_PAIR(GIT_BRANCH_COLOR));
    727 #else
    728 	truncate_buf_prefix(path, sizeof(path), maxx - strlen(username) - 2);
    729 	printw("%s:%s", username, path);
    730 #endif
    731 	attroff(COLOR_PAIR(TOP_TITLE_COLOR));
    732 }
    733 
    734 void
    735 print_bot_title(void)
    736 {
    737 	char pathbuf[PATH_MAX], fileinf[maxx + 1];
    738 
    739 	if (curwin.filecount == 0)
    740 		fileinf[0] = '\0';
    741 	else
    742 		get_file_info(fileinf, sizeof(fileinf),
    743 			get_fullpath(pathbuf, &curwin, curwin.highlight));
    744 
    745 	move(maxy-1, 0);
    746 	clrtoeol();
    747 
    748 	attron(COLOR_PAIR(BOT_TITLE_COUNT_COLOR));
    749 	printw("(%d/%d) ", curwin.filecount == 0 ? 0 : curwin.highlight+1, curwin.filecount);
    750 	attroff(COLOR_PAIR(BOT_TITLE_COUNT_COLOR));
    751 
    752 	attron(COLOR_PAIR(BOT_TITLE_INFO_COLOR));
    753 	addstr(fileinf);
    754 	attroff(COLOR_PAIR(BOT_TITLE_INFO_COLOR));
    755 }
    756 
    757 char *
    758 get_file_info(char *buf, size_t bufsize, const char *filepath)
    759 {
    760 	struct stat statbuf;
    761 	struct passwd *usr;
    762 	struct tm *tm;
    763 	struct group *grp;
    764 	char mods[11], datestr[256], modfill = '-';
    765 	char hrbytes[200];
    766 
    767 	if ((stat(filepath, &statbuf)) != 0) {
    768 		switch (errno) {
    769 			case EACCES:
    770 				buf = "No permission.";
    771 				break;
    772 			default:
    773 				buf = "Retrieving filestats failed.";
    774 				break;
    775 		}
    776 		return buf;
    777 	}
    778 
    779 	if ((usr = getpwuid(statbuf.st_uid)) == NULL) {
    780 		buf = "Retrieving username failed.";
    781 		return buf;
    782 	}
    783 	if ((grp = getgrgid(statbuf.st_gid)) == NULL) {
    784 		buf = "Retrieving groupname failed.";
    785 		return buf;
    786 	}
    787 
    788 	mods[0] = S_ISDIR(statbuf.st_mode) ? 'd' : modfill;
    789 	mods[1] = statbuf.st_mode & S_IRUSR ? 'r' : modfill;
    790 	mods[2] = statbuf.st_mode & S_IWUSR ? 'w' : modfill;
    791 	mods[3] = statbuf.st_mode & S_IXUSR ? 'x' : modfill;
    792 	mods[4] = statbuf.st_mode & S_IRGRP ? 'r' : modfill;
    793 	mods[5] = statbuf.st_mode & S_IWGRP ? 'w' : modfill;
    794 	mods[6] = statbuf.st_mode & S_IXGRP ? 'x' : modfill;
    795 	mods[7] = statbuf.st_mode & S_IROTH ? 'r' : modfill;
    796 	mods[8] = statbuf.st_mode & S_IWOTH ? 'w' : modfill;
    797 	mods[9] = statbuf.st_mode & S_IXOTH ? 'x' : modfill;
    798 	mods[10] = '\0';
    799 
    800 	tm = localtime(&statbuf.st_mtime);
    801 	strftime(datestr, sizeof(datestr), nl_langinfo(D_T_FMT), tm);
    802 
    803 	snprintf(buf, bufsize, "%10.10s %s %s %s %s",
    804 		mods,
    805 		usr->pw_name,
    806 		grp->gr_name,
    807 		human_readable_bytes(hrbytes, (intmax_t) statbuf.st_size),
    808 		datestr);
    809 
    810 	return buf;
    811 }
    812 
    813 void
    814 print_content(void)
    815 {
    816 	FILE *fp = NULL;
    817 	size_t size = childwin.maxx;
    818 	char str[size+1], buf[PATH_MAX];
    819 	int y = 0, x = 0;
    820 
    821 	werase(childwin.window);
    822 	free_dirwin(&childwin);
    823 
    824 	if ((fp = fopen(get_fullpath(buf, &curwin, curwin.highlight), "r")) == NULL) {
    825 		switch (errno) {
    826 			case EACCES:
    827 				set_win_message(&childwin, "No permission.");
    828 				return;
    829 			default:
    830 				fatal("Error opening file \"%s\"\n", buf);
    831 		}
    832 	}
    833 
    834 	childwin.holdupdate = TRUE;
    835 
    836 	while (fgets(str, size, fp) != NULL && y <= childwin.maxy) {
    837 		mvwaddstr(childwin.window, y, x, str);
    838 		y++;
    839 	}
    840 
    841 	fclose(fp);
    842 	wrefresh(childwin.window);
    843 }
    844 
    845 void
    846 print_error(size_t size, const char *fmt, ...)
    847 {
    848 	va_list ap;
    849 	char msg[size];
    850 
    851 	va_start(ap, fmt);
    852 	vsnprintf(msg, size, fmt, ap);
    853 	va_end(ap);
    854 
    855 	move(maxy-1, 0);
    856 	clrtoeol();
    857 	addstr(msg);
    858 	print_bot = FALSE;
    859 }
    860 
    861 void
    862 show_file_mime(void)
    863 {
    864 	int appsize = 256;
    865 	char mimebuf[MIME_MAX], appbuf[appsize], buf[MIME_MAX+appsize];
    866 
    867 	if (get_mime(mimebuf, MIME_MAX, curwin.winfiles[curwin.highlight].d_name) != 0)
    868 		cpstr(mimebuf, "Can't retrieve mime.");
    869 
    870 	if (get_mime_default(appbuf, appsize, mimebuf) != 0)
    871 		cpstr(appbuf, "Can't retrieve default for mime.");
    872 
    873 	snprintf(buf, MIME_MAX+appsize, "%s: %s", mimebuf, appbuf);
    874 
    875 	move(1, 1);
    876 	clrtoeol();
    877 	addstr(buf);
    878 }
    879 
    880 void
    881 combo_key(int keypress)
    882 {
    883 	int c;
    884 
    885 	move(maxy-1, maxx-1);
    886 	clrtoeol();
    887 	addch(keypress);
    888 
    889 	halfdelay(10);
    890 
    891 	while ((c = getch()) != ERR) {
    892 		switch (keypress) {
    893 			case KEY_COMBO_GO:
    894 				combo_go(c);
    895 				break;
    896 			case KEY_COMBO_INF:
    897 				combo_inf(c);
    898 				break;
    899 			case KEY_COMBO_OPEN:
    900 				combo_open(c);
    901 				break;
    902 			case KEY_COMBO_MAKE:
    903 				combo_make(c);
    904 				break;
    905 			default:
    906 				break;
    907 		}
    908 		break;
    909 	}
    910 
    911 	cbreak();
    912 	move(maxy-1, maxx-1);
    913 	clrtoeol();
    914 }
    915 
    916 void
    917 combo_go(int key)
    918 {
    919 	char pathbuf[PATH_MAX];
    920 
    921 	switch (key) {
    922 		case KEY_COMBO_GO_TOP:
    923 			move_top(&curwin);
    924 			update_child_win();
    925 			break;
    926 		case KEY_COMBO_GO_HOME:
    927 			change_dir(userhome, OTHER, FALSE);
    928 			break;
    929 		case KEY_COMBO_GO_ACCESS:
    930 			change_dir(replace_home(pathbuf, LN_ACCESS_DIR), OTHER, FALSE);
    931 			break;
    932 		default:
    933 			break;
    934 	}
    935 }
    936 
    937 void
    938 combo_inf(int key)
    939 {
    940 	switch (key) {
    941 		case KEY_COMBO_INF_OPEN:
    942 			show_file_mime();
    943 			break;
    944 		default:
    945 			break;
    946 	}
    947 }
    948 
    949 void
    950 combo_open(int key)
    951 {
    952 	char chdname[PATH_MAX];
    953 
    954 	switch (key) {
    955 		case KEY_COMBO_OPEN_NOHUP_XDG:
    956 			if (is_dir(&curwin, curwin.highlight))
    957 				change_dir(get_fullpath(chdname, &curwin, curwin.highlight), RIGHT, FALSE);
    958 			else if (S_ISREG(get_st_mode(&curwin, curwin.highlight)))
    959 				open_nohup_xdg();
    960 			break;
    961 		case KEY_COMBO_OPEN_EXEC:
    962 			if (is_dir(&curwin, curwin.highlight))
    963 				change_dir(get_fullpath(chdname, &curwin, curwin.highlight), RIGHT, FALSE);
    964 			else if (S_ISREG(get_st_mode(&curwin, curwin.highlight)))
    965 				open_child(TRUE);
    966 			break;
    967 		default:
    968 			break;
    969 	}
    970 }
    971 
    972 void
    973 combo_make(int key)
    974 {
    975 	switch (key) {
    976 		case KEY_COMBO_MAKE_FILE:
    977 			make_file();
    978 			set_win_files(&curwin);
    979 			update_child_win();
    980 			break;
    981 		case KEY_COMBO_MAKE_DIR:
    982 			make_dir();
    983 			set_win_files(&curwin);
    984 			update_child_win();
    985 			break;
    986 		case KEY_COMBO_MAKE_MOD:
    987 			make_mod();
    988 			break;
    989 		case KEY_COMBO_MAKE_CHOWN:
    990 			make_chown();
    991 			break;
    992 		case KEY_COMBO_MAKE_ACCESS:
    993 			make_access();
    994 			break;
    995 		case KEY_COMBO_MAKE_OPEN_DEFAULT:
    996 			make_open_default();
    997 			break;
    998 		default:
    999 			break;
   1000 	}
   1001 }
   1002 
   1003 void
   1004 change_dir(const char *chdname, Direction direction, bool abspath)
   1005 {
   1006 	if (chdir(chdname) != 0)
   1007 		return;
   1008 
   1009 	if (!abspath)
   1010 		cpstr(curwin.path, chdname);
   1011 	else if (getcwd(curwin.path, PATH_MAX) == NULL)
   1012 		fatal("getcwd() error");
   1013 
   1014 	switch (direction) {
   1015 		case RIGHT:
   1016 			curwin.highlight = 0;
   1017 			break;
   1018 		case LEFT:
   1019 			curwin.highlight = parwin.highlight;
   1020 			break;
   1021 		default:
   1022 			curwin.highlight = 0;
   1023 			break;
   1024 
   1025 	}
   1026 	set_win_files(&curwin);
   1027 
   1028 	get_dirname(parwin.path, curwin.path);
   1029 	set_win_files(&parwin);
   1030 
   1031 	update_child_win();
   1032 #if GIT_INFO
   1033 	retrieve_git_branch(gitbranch, sizeof(gitbranch), curwin.path);
   1034 #endif
   1035 }
   1036 
   1037 void
   1038 change_parent_dir(Direction direction)
   1039 {
   1040 	char pp[PATH_MAX];
   1041 
   1042 	switch (direction) {
   1043 		case UP:
   1044 			move_up(&parwin);
   1045 			break;
   1046 		case DOWN:
   1047 			move_down(&parwin);
   1048 			break;
   1049 		default:
   1050 			return;
   1051 	}
   1052 
   1053 	parwin.usehighlight = TRUE;
   1054 	curwin.highlight = 0;
   1055 	change_dir(get_fullpath(pp, &parwin, parwin.highlight), direction, FALSE);
   1056 }
   1057 
   1058 void
   1059 open_child(bool exec)
   1060 {
   1061 	sigset_t set;
   1062 	char mime[MIME_MAX], mimedefault[MIME_APP_MAX], pathbuf[PATH_MAX];
   1063 	bool istext;
   1064 
   1065 	if (curwin.message != NULL)
   1066 		return;
   1067 
   1068 	if (!exec) {
   1069 		if (get_mime(mime, MIME_MAX, curwin.winfiles[curwin.highlight].d_name) != 0) {
   1070 			move(1, 1);
   1071 			clrtoeol();
   1072 			printw("Can\'t open %s", curwin.winfiles[curwin.highlight].d_name);
   1073 			return;
   1074 		}
   1075 
   1076 		istext = strncmp(mime, "text/", 5) == 0;
   1077 
   1078 		if (!istext && get_mime_default(mimedefault, MIME_APP_MAX, mime) != 0) {
   1079 			move(1, 1);
   1080 			clrtoeol();
   1081 			printw("Can\'t open for mime %s", mime);
   1082 			return;
   1083 		}
   1084 	}
   1085 
   1086 	endwin();
   1087 
   1088 	if (sigemptyset (&set) == -1)
   1089 		fatal("Sigemptyset failed.");
   1090 	if (sigaddset(&set, SIGWINCH) == -1)
   1091 		fatal("Sigaddset failed.");
   1092 	if (sigprocmask(SIG_BLOCK, &set, NULL) != 0)
   1093 		fatal("Blocking sigprocmask failed.");
   1094 
   1095 	if (exec)
   1096 		run_command(PATH_MAX + 4, "\"./%s\"", curwin.winfiles[curwin.highlight].d_name);
   1097 	else
   1098 		run_command(PATH_MAX + 12, "%s \"%s\"", istext ? editor : "xdg-open",
   1099 			get_fullpath(pathbuf, &curwin, curwin.highlight));
   1100 
   1101 	if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0)
   1102 		fatal("Unblocking sigprocmask failed.");
   1103 
   1104 	erase();
   1105 	noecho();
   1106 	cbreak();
   1107 
   1108 	refresh();
   1109 	wrefresh(parwin.window);
   1110 	wrefresh(curwin.window);
   1111 	wrefresh(childwin.window);
   1112 	update_child_win();
   1113 }
   1114 
   1115 void
   1116 open_nohup_xdg(void)
   1117 {
   1118 	char *cmdfmt = "nohup xdg-open \"%s\" > /dev/null 2>&1 &";
   1119 	size_t size = PATH_MAX + strlen(cmdfmt);
   1120 
   1121 	run_command(size, cmdfmt, curwin.winfiles[curwin.highlight].d_name);
   1122 }
   1123 
   1124 void
   1125 search(void)
   1126 {
   1127 	int c, y, x;
   1128 
   1129 	clear_search();
   1130 
   1131 	move(maxy-1, 0);
   1132 	clrtoeol();
   1133 
   1134 	attron(COLOR_PAIR(PROMPT_COLOR));
   1135 	addstr("Search name? ");
   1136 	attroff(COLOR_PAIR(PROMPT_COLOR));
   1137 
   1138 	echo();
   1139 	curs_set(1);
   1140 
   1141 	getyx(stdscr, y, x);
   1142 
   1143 	while ((c = mvwgetch(stdscr, y, x)) != 10) {
   1144 		switch (c) {
   1145 			case KEY_BACKSPACE:
   1146 				if (searchc > 0) {
   1147 					searchq[--(searchc)] = '\0';
   1148 					move(y, --x);
   1149 					clrtoeol();
   1150 				}
   1151 				break;
   1152 			default:
   1153 				if (searchc < SEARCHLEN) {
   1154 					searchq[(searchc)++] = c;
   1155 					searchq[searchc] = '\0';
   1156 					move(y, ++x);
   1157 				}
   1158 				break;
   1159 		}
   1160 		first_search();
   1161 		print_win(&curwin);
   1162 	}
   1163 	noecho();
   1164 	curs_set(0);
   1165 }
   1166 
   1167 void
   1168 move_top(DirWin *dirwin)
   1169 {
   1170 	dirwin->highlight = 0;
   1171 }
   1172 
   1173 void
   1174 move_bot(DirWin *dirwin)
   1175 {
   1176 	dirwin->highlight = MAX(dirwin->filecount-1, 0);
   1177 }
   1178 
   1179 void
   1180 move_page_up(DirWin *dirwin)
   1181 {
   1182 	dirwin->highlight = MAX(dirwin->highlight - dirwin->maxy/2, 0);
   1183 }
   1184 
   1185 void
   1186 move_page_down(DirWin *dirwin)
   1187 {
   1188 	dirwin->highlight = MIN(dirwin->highlight + dirwin->maxy/2,
   1189 		MAX(dirwin->filecount-1, 0));
   1190 }
   1191 
   1192 void
   1193 move_up(DirWin *dirwin)
   1194 {
   1195 	if (dirwin->highlight > 0)
   1196 		dirwin->highlight--;
   1197 }
   1198 
   1199 void
   1200 move_down(DirWin *dirwin)
   1201 {
   1202 	if (dirwin->highlight < dirwin->filecount-1)
   1203 		dirwin->highlight++;
   1204 }
   1205 
   1206 void
   1207 first_search(void)
   1208 {
   1209 	if (searchc == 0)
   1210 		return;
   1211 
   1212 	if (substr(NULL, curwin.winfiles[curwin.highlight].d_name, searchq, searchcs) != NULL)
   1213 		return;
   1214 
   1215 	for (int i = 0; i < MAX(curwin.filecount-1, 0); i++) {
   1216 		if (substr(NULL, curwin.winfiles[i].d_name, searchq, searchcs) != NULL) {
   1217 			curwin.highlight = i;
   1218 			return;
   1219 		}
   1220 	}
   1221 }
   1222 
   1223 void
   1224 next_search(void)
   1225 {
   1226 	if (searchc == 0)
   1227 		return;
   1228 
   1229 	for (int i = curwin.highlight; i < MAX(curwin.filecount-1, 0); i++) {
   1230 		if (substr(NULL, curwin.winfiles[i+1].d_name, searchq, searchcs) != NULL) {
   1231 			curwin.highlight = i+1;
   1232 			return;
   1233 		}
   1234 	}
   1235 }
   1236 
   1237 void
   1238 prev_search(void)
   1239 {
   1240 	if (searchc == 0)
   1241 		return;
   1242 
   1243 	for (int i = curwin.highlight; i > 0; i--) {
   1244 		if (substr(NULL, curwin.winfiles[i-1].d_name, searchq, searchcs) != NULL) {
   1245 			curwin.highlight = i-1;
   1246 			return;
   1247 		}
   1248 	}
   1249 }
   1250 
   1251 void
   1252 free_dirwin(DirWin *dirwin)
   1253 {
   1254 	if (dirwin->winfiles != NULL)
   1255 		free(dirwin->winfiles);
   1256 	dirwin->filecount = 0;
   1257 	dirwin->winfiles = NULL;
   1258 	dirwin->message = NULL;
   1259 }
   1260 
   1261 void
   1262 clean(void)
   1263 {
   1264 	wclear(curwin.window);
   1265 	wclear(parwin.window);
   1266 	wclear(childwin.window);
   1267 
   1268 	free(username);
   1269 	free(userhome);
   1270 
   1271 	clear();
   1272 
   1273 	delwin(parwin.window);
   1274 	delwin(curwin.window);
   1275 	delwin(childwin.window);
   1276 	delwin(stdscr);
   1277 
   1278 	free_dirwin(&curwin);
   1279 	free_dirwin(&parwin);
   1280 	free_dirwin(&childwin);
   1281 
   1282 	clear_selected();
   1283 
   1284 	endwin();
   1285 
   1286 #if GIT_INFO
   1287 	git_libgit2_shutdown();
   1288 #endif
   1289 }
   1290 
   1291 void
   1292 fatal(const char *fmt, ...)
   1293 {
   1294 	va_list ap;
   1295 
   1296 	va_start(ap, fmt);
   1297 	vfprintf(stderr, fmt, ap);
   1298 	va_end(ap);
   1299 
   1300 	clean();
   1301 
   1302 	exit(EXIT_FAILURE);
   1303 }
   1304 
   1305 bool
   1306 is_dir(DirWin *dirwin, size_t index)
   1307 {
   1308 	if (dirwin->filecount <= 0)
   1309 		return FALSE;
   1310 
   1311 	if (dirwin->winfiles[index].d_type == DT_DIR)
   1312 		return TRUE;
   1313 
   1314 	return S_ISDIR(get_st_mode(dirwin, index));
   1315 }
   1316 
   1317 mode_t
   1318 get_st_mode(DirWin *dirwin, size_t index)
   1319 {
   1320 	struct stat statbuf;
   1321 	char linkpath[PATH_MAX], abspath[PATH_MAX];
   1322 	char *resolved;
   1323 
   1324 	get_fullpath(linkpath, dirwin, index);
   1325 	resolved = realpath(linkpath, abspath);
   1326 	if (resolved != NULL && lstat(resolved, &statbuf) >= 0)
   1327 		return statbuf.st_mode;
   1328 
   1329 	return -1;
   1330 }
   1331 
   1332 char *
   1333 truncate_buf_prefix(char *buf, size_t bufsize, size_t max_len)
   1334 {
   1335 	const char ellipsis[] = "..";
   1336 	const size_t ell_len = sizeof(ellipsis) - 1;
   1337 	size_t keep, src_index, cur_len = 0;
   1338 
   1339 	if (!buf || bufsize == 0 || max_len < ell_len + 2) {
   1340 		if (bufsize > 0) buf[bufsize - 1] = '\0';
   1341 		return buf;
   1342 	}
   1343 
   1344 	cur_len = strlen(buf);
   1345 	if (cur_len <= max_len - 1)
   1346 		return buf;
   1347 
   1348 	keep = max_len - 1 - ell_len;
   1349 
   1350 	if (keep == 0) {
   1351 		if (max_len >= ell_len + 1) {
   1352 			memcpy(buf, ellipsis, ell_len);
   1353 			buf[ell_len] = '\0';
   1354 		} else {
   1355 			buf[0] = '.';
   1356 			buf[1] = '\0';
   1357 		}
   1358 		return buf;
   1359 	}
   1360 
   1361 	src_index = cur_len - keep;
   1362 	memmove(buf + ell_len, buf + src_index, keep + 1);
   1363 	memcpy(buf, ellipsis, ell_len);
   1364 
   1365 	if (ell_len + keep >= max_len)
   1366 		buf[max_len - 1] = '\0';
   1367 
   1368 	return buf;
   1369 }
   1370 
   1371 char *
   1372 human_readable_bytes(char *buf, intmax_t bytes)
   1373 {
   1374 	double dblBytes = bytes;
   1375 	int power = SIZE_DECIMAL_FORMAT ? 1000 : 1024;
   1376 	char *suffix[] = {"B", "KB", "MB", "GB", "TB"};
   1377 	size_t length = arrlen(suffix);
   1378 	size_t i = 0;
   1379 
   1380 	if (bytes > power)
   1381 		for (i = 0; (bytes / power) > 0 && i < length-1; i++, bytes /= power)
   1382 			dblBytes = bytes / (double) power;
   1383 
   1384 	sprintf(buf, "%.02lf%s", dblBytes, suffix[i]);
   1385 
   1386 	return buf;
   1387 }
   1388 
   1389 char *
   1390 prompt_answer(char *buf, size_t size, const char *question)
   1391 {
   1392 	move(maxy-1, 0);
   1393 	clrtoeol();
   1394 
   1395 	attron(COLOR_PAIR(PROMPT_COLOR));
   1396 	addstr(question);
   1397 	attroff(COLOR_PAIR(PROMPT_COLOR));
   1398 
   1399 	if (size == 0)
   1400 		return buf;
   1401 
   1402 	echo();
   1403 	curs_set(1);
   1404 	getnstr(buf, (int) size - 1);
   1405 	noecho();
   1406 	curs_set(0);
   1407 
   1408 	return buf;
   1409 }
   1410 
   1411 bool
   1412 prompt_confirm(size_t size, const char *fmt, ...)
   1413 {
   1414 	char response[2], question[size];
   1415 	va_list ap;
   1416 
   1417 	va_start(ap, fmt);
   1418 	vsnprintf(question, size, fmt, ap);
   1419 	va_end(ap);
   1420 
   1421 	prompt_answer(response, sizeof(response), question);
   1422 
   1423 	return strcasecmp(response, "y") == 0;
   1424 }
   1425 
   1426 int
   1427 compare_file(const void *a, const void *b)
   1428 {
   1429 	int typecompare;
   1430 	const WinFile *ma = a;
   1431 	const WinFile *mb = b;
   1432 
   1433 	if ((typecompare = ma->d_type - mb->d_type) == 0)
   1434 		return strcmp(ma->d_name, mb->d_name);
   1435 
   1436 	return typecompare;
   1437 }
   1438 
   1439 int
   1440 get_mime(char *buf, size_t bufsize, const char *path)
   1441 {
   1442 	char *cmdfmt = "xdg-mime query filetype \"%s\"";
   1443 	size_t size = PATH_MAX + strlen(cmdfmt);
   1444 	char cmd[size];
   1445 
   1446 	snprintf(cmd, size, cmdfmt, path);
   1447 
   1448 	return read_command(buf, bufsize, cmd);
   1449 }
   1450 
   1451 int
   1452 get_mime_default(char *buf, size_t bufsize, const char *mime)
   1453 {
   1454 	char *cmdfmt = "xdg-mime query default \"%s\"";
   1455 	size_t cmdsize = MIME_MAX + strlen(cmdfmt);
   1456 	char cmd[cmdsize];
   1457 
   1458 	snprintf(cmd, cmdsize, cmdfmt, mime);
   1459 
   1460 	return read_command(buf, bufsize, cmd);
   1461 }
   1462 
   1463 int
   1464 read_command(char *buf, size_t bufsize, const char *cmd)
   1465 {
   1466 	FILE *file;
   1467 
   1468 	if ((file = popen(cmd, "r")) == NULL) {
   1469 		buf = NULL;
   1470 		return 1;
   1471 	}
   1472 
   1473 	if (fgets(buf, bufsize, file) == NULL) {
   1474 		buf = NULL;
   1475 		pclose(file);
   1476 		return 2;
   1477 	}
   1478 
   1479 	buf[strcspn(buf, "\n")] = '\0';
   1480 
   1481 	pclose(file);
   1482 
   1483 	return 0;
   1484 }
   1485 
   1486 void
   1487 run_command(size_t size, const char *fmt, ...)
   1488 {
   1489 	va_list ap;
   1490 	char cmd[size];
   1491 
   1492 	va_start(ap, fmt);
   1493 	vsnprintf(cmd, size, fmt, ap);
   1494 	va_end(ap);
   1495 
   1496 	system(cmd);
   1497 }
   1498 
   1499 void
   1500 run_sudo_command(size_t size, const char *fmt, ...)
   1501 {
   1502 	va_list ap;
   1503 	char cmd[size], *msg;
   1504 	sigset_t set;
   1505 
   1506 	if (!USE_SUDO_COMMANDS) {
   1507 		msg = "Requires sudo command but USE_SUDO_COMMANDS is disabled.";
   1508 		print_error(strlen(msg), msg);
   1509 		return;
   1510 	}
   1511 
   1512 	endwin();
   1513 
   1514 	if (sigemptyset (&set) == -1)
   1515 		fatal("Sigemptyset failed.");
   1516 	if (sigaddset(&set, SIGWINCH) == -1)
   1517 		fatal("Sigaddset failed.");
   1518 	if (sigprocmask(SIG_BLOCK, &set, NULL) != 0)
   1519 		fatal("Blocking sigprocmask failed.");
   1520 
   1521 	va_start(ap, fmt);
   1522 	vsnprintf(cmd, size, fmt, ap);
   1523 	va_end(ap);
   1524 
   1525 	system(cmd);
   1526 
   1527 	if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0)
   1528 		fatal("Unblocking sigprocmask failed.");
   1529 
   1530 	erase();
   1531 	refresh();
   1532 	wrefresh(parwin.window);
   1533 	wrefresh(curwin.window);
   1534 	wrefresh(childwin.window);
   1535 }
   1536 
   1537 int
   1538 make_dir(void)
   1539 {
   1540 	char name[PATH_MAX];
   1541 
   1542 	prompt_answer(name, PATH_MAX, "Name of directory? ");
   1543 	return mkdir(name, 0755);
   1544 }
   1545 
   1546 int
   1547 make_file(void)
   1548 {
   1549 	FILE *fptr;
   1550 	char name[PATH_MAX];
   1551 
   1552 	prompt_answer(name, PATH_MAX, "Name of file? ");
   1553 
   1554 	if (strlen(name) > 0) {
   1555 		if ((fptr = fopen(name, "w")) == NULL)
   1556 			fatal("Error opening file \"%s\"\n", name);
   1557 		fclose(fptr);
   1558 	}
   1559 	return 0;
   1560 }
   1561 
   1562 int
   1563 rm_file(const char *fname)
   1564 {
   1565 	char *msg, *rmdirfmt = "rm -rf \"%s\"", *pfmt = "Remove %s? (y/N) ";
   1566 	int res = 0;
   1567 	size_t cmdsize = PATH_MAX + strlen(rmdirfmt), psize = strlen(pfmt) + strlen(fname);
   1568 
   1569 	if (prompt_confirm(psize, pfmt, fname)) {
   1570 		if (is_dir(&curwin, curwin.highlight))
   1571 			run_command(cmdsize, rmdirfmt, fname);
   1572 		else
   1573 			res = remove(fname);
   1574 		curwin.highlight = curwin.highlight > 0 ? curwin.highlight -1 : 0;
   1575 	}
   1576 
   1577 	if (res != 0) {
   1578 		switch (errno) {
   1579 			case EACCES:
   1580 				msg = "No permission.";
   1581 				break;
   1582 			case EEXIST:
   1583 			case ENOTEMPTY:
   1584 				msg = "Directory is not empty.";
   1585 				break;
   1586 			default:
   1587 				msg = "Could not remove file.";
   1588 				break;
   1589 		}
   1590 		print_error(strlen(msg), msg);
   1591 	}
   1592 
   1593 	return res;
   1594 }
   1595 
   1596 int
   1597 rename_file(const char *fname)
   1598 {
   1599 	char name[PATH_MAX];
   1600 
   1601 	prompt_answer(name, PATH_MAX, "New name? ");
   1602 
   1603 	if (strlen(name) > 0)
   1604 		return rename(fname, name);
   1605 
   1606 	return 0;
   1607 }
   1608 
   1609 int
   1610 make_mod(void)
   1611 {
   1612 	char name[4];
   1613 	bool isvalid;
   1614 
   1615 	prompt_answer(name, sizeof(name), "File mods (numeric)? ");
   1616 
   1617 	if (strlen(name) == 3) {
   1618 		isvalid = TRUE;
   1619 		for (int i = 0; i < 3; i++) {
   1620 			if (name[i] < '0' || name[i] > '7')
   1621 				isvalid = FALSE;
   1622 		}
   1623 		if (isvalid)
   1624 			chmod(curwin.winfiles[curwin.highlight].d_name, strtol(name, NULL, 8));
   1625 		else
   1626 			return -1;
   1627 	}
   1628 
   1629 	return 0;
   1630 }
   1631 
   1632 int
   1633 make_chown(void)
   1634 {
   1635 	struct passwd *usr;
   1636 	struct group *grp;
   1637 	long maxlogin = sysconf(_SC_LOGIN_NAME_MAX);
   1638 	size_t len = maxlogin > 0 ? (size_t) maxlogin + 1 : 256;
   1639 	char owner[len], group[len], *err;
   1640 
   1641 	prompt_answer(owner, len, "Owner name? ");
   1642 
   1643 	if ((usr = getpwnam(owner)) == NULL) {
   1644 		err = "No results found for owner %s.";
   1645 		print_error(strlen(err) + len, err, owner);
   1646 		return -1;
   1647 	}
   1648 
   1649 	prompt_answer(group, len, "Group name? ");
   1650 
   1651 	if ((grp = getgrnam(group)) == NULL) {
   1652 		err = "No results found for group %s.";
   1653 		print_error(strlen(err) + len, err, group);
   1654 		return -1;
   1655 	}
   1656 
   1657 	if ((chown(curwin.winfiles[curwin.highlight].d_name, usr->pw_uid, grp->gr_gid)) != 0) {
   1658 		switch (errno) {
   1659 			case EACCES:
   1660 			case EPERM:
   1661 				sudo_chown(owner, group, (long) len);
   1662 				break;
   1663 			default:
   1664 				err = "Error during chown: %d.";
   1665 				print_error(strlen(err), err, errno);
   1666 				return errno;
   1667 		}
   1668 	}
   1669 	return 0;
   1670 }
   1671 
   1672 int
   1673 sudo_chown(char *owner, char *group, long namelen)
   1674 {
   1675 	size_t cmdsize;
   1676 	char pathbuf[PATH_MAX], *cmdfmt = "sudo chown %s:%s %s";
   1677 
   1678 	cmdsize = namelen*2 + PATH_MAX + strlen(cmdfmt);
   1679 	run_sudo_command(cmdsize, cmdfmt, owner, group, get_fullpath(pathbuf, &curwin, curwin.highlight));
   1680 
   1681 	return 0;
   1682 }
   1683 
   1684 void
   1685 make_access(void)
   1686 {
   1687 	char pathbuf[PATH_MAX], *cmdfmt = "ln -s \"%s\" \"%s\"", *pfmt = "Make access \"%s\"? (y/N) ";
   1688 	size_t cmdsize = PATH_MAX + strlen(cmdfmt), psize = strlen(pfmt) + PATH_MAX;
   1689 
   1690 	if (prompt_confirm(psize, pfmt, curwin.winfiles[curwin.highlight].d_name))
   1691 		run_command(cmdsize, cmdfmt, get_fullpath(pathbuf, &curwin, curwin.highlight), LN_ACCESS_DIR);
   1692 }
   1693 
   1694 void
   1695 make_open_default(void)
   1696 {
   1697 	int appsize = 256, qsize = MIME_MAX+25;
   1698 	char mime[MIME_MAX], question[qsize], app[appsize];
   1699 
   1700 	if (get_mime(mime, MIME_MAX, curwin.winfiles[curwin.highlight].d_name) != 0)
   1701 		cpstr(mime, "Can't retrieve mime.");
   1702 
   1703 	snprintf(question, qsize, "Application for mime %s?", mime);
   1704 
   1705 	prompt_answer(app, appsize, question);
   1706 
   1707 	if (strlen(app) > 0)
   1708 		make_mime_default(mime, app);
   1709 }
   1710 
   1711 void
   1712 make_mime_default(const char *mime, const char *app)
   1713 {
   1714 	char *cmdfmt = "xdg-mime default \"%s\" \"%s\"";
   1715 	size_t cmdsize = MIME_MAX + 256 + strlen(cmdfmt);
   1716 
   1717 	run_command(cmdsize, cmdfmt, app, mime);
   1718 }
   1719 
   1720 void
   1721 exe_selection(SelAction action, const char *askn)
   1722 {
   1723 	char *pfmt = "%s selection (%d files) ? (y/N) ";
   1724 	size_t cmdsize = PATH_MAX*2 + 20;
   1725 
   1726 	if (selected == NULL || selc == 0)
   1727 		return;
   1728 
   1729 	if (askn != NULL && !prompt_confirm(strlen(pfmt) + strlen(askn) + 5, pfmt, askn, selc))
   1730 		return;
   1731 
   1732 	switch (action) {
   1733 		case COPY:
   1734 			for (size_t i = 0; i <selc; i++)
   1735 				run_command(cmdsize, "cp -rf \"%s\" \"%s\"", selected[i], curwin.path);
   1736 			break;
   1737 		case REMOVE:
   1738 			for (size_t i = 0; i <selc; i++)
   1739 				run_command(cmdsize, "rm -rf \"%s\"", selected[i]);
   1740 			break;
   1741 		case MOVE:
   1742 			for (size_t i = 0; i <selc; i++)
   1743 				run_command(cmdsize, "mv \"%s\" \"%s\"", selected[i], curwin.path);
   1744 			break;
   1745 		default:
   1746 			break;
   1747 	}
   1748 
   1749 	clear_selected();
   1750 	curwin.highlight = 0;
   1751 	set_win_files(&curwin);
   1752 	set_win_files(&parwin);
   1753 	update_child_win();
   1754 }
   1755 
   1756 void
   1757 select_file(const char *path)
   1758 {
   1759 	if (remove_selected(path))
   1760 		return;
   1761 
   1762 	if ((selected = (char**) realloc(selected, (selc+1) * sizeof(char *))) == NULL)
   1763 		fatal("Fatal: failed to realloc.\n");
   1764 
   1765 	selected[selc] = strdup(path);
   1766 	(selc)++;
   1767 }
   1768 
   1769 void
   1770 select_files(void)
   1771 {
   1772 	char pathbuf[PATH_MAX];
   1773 
   1774 	if (curwin.winfiles == NULL || curwin.filecount == 0){
   1775 		start_sel = -1;
   1776 		return;
   1777 	}
   1778 
   1779 	if (start_sel == -1) {
   1780 		start_sel = curwin.highlight;
   1781 		return;
   1782 	}
   1783 
   1784 	for (int i = MIN(start_sel, curwin.highlight); i <= MAX(start_sel, curwin.highlight); i++)
   1785 		select_file(get_fullpath(pathbuf, &curwin, i));
   1786 
   1787 
   1788 	start_sel = -1;
   1789 }
   1790 
   1791 void
   1792 clear_selected(void)
   1793 {
   1794 	if (selected != NULL)
   1795 		for (size_t i = 0; i < selc; i++)
   1796 			free(selected[i]);
   1797 
   1798 	free(selected);
   1799 	selected = NULL;
   1800 	selc = 0;
   1801 	start_sel = -1;
   1802 }
   1803 
   1804 void
   1805 clear_search(void)
   1806 {
   1807 	searchq[0] = '\0';
   1808 	searchc = 0;
   1809 }
   1810 
   1811 char *
   1812 cpstr(char *dest, const char *src)
   1813 {
   1814 	size_t cplen = strlen(src);
   1815 	memcpy(dest, src, cplen);
   1816 	dest[cplen] = '\0';
   1817 	return dest;
   1818 }
   1819 
   1820 char *
   1821 substr(char *buf, const char *haystack, const char *needle, const bool casesens)
   1822 {
   1823 	buf = casesens ? strstr(haystack, needle) : strcasestr(haystack, needle);
   1824 
   1825 	return buf;
   1826 }
   1827 
   1828 char *
   1829 get_fullpath(char *buf, DirWin *dirwin, size_t index)
   1830 {
   1831 	if (dirwin->filecount == 0 || dirwin->winfiles == NULL) {
   1832 		buf[0] = '\0';
   1833 		return buf;
   1834 	}
   1835 
   1836 	index = MIN(index, dirwin->filecount-1);
   1837 	index = MAX(index, 0);
   1838 
   1839 	if (strcmp(dirwin->path, "/") == 0)
   1840 		snprintf(buf, PATH_MAX, "/%s", dirwin->winfiles[index].d_name);
   1841 	else if (snprintf(buf, PATH_MAX, "%s/%s", dirwin->path,
   1842 			dirwin->winfiles[index].d_name) >= PATH_MAX) {
   1843 			buf[0] = '\0';
   1844 	}
   1845 
   1846 	return buf;
   1847 }
   1848 
   1849 char *
   1850 replace_home(char *buf, const char *path)
   1851 {
   1852 	char *envv = "$HOME";
   1853 
   1854 	if (strncmp(path, envv, strlen(envv)) == 0
   1855 			&& snprintf(buf, PATH_MAX, "%s%s", userhome,
   1856 				path + strlen(envv)) >= PATH_MAX)
   1857 		buf[0] = '\0';
   1858 	return buf;
   1859 }
   1860 
   1861 char *
   1862 get_dirname(char *buf, const char *path)
   1863 {
   1864 	cpstr(buf, path);
   1865 	dirname(buf);
   1866 	return buf;
   1867 }
   1868 
   1869 #if GIT_INFO
   1870 void
   1871 init_status_options(git_status_options *out_opts)
   1872 {
   1873 	memset(out_opts, 0, sizeof(*out_opts));
   1874 
   1875 	out_opts->version = GIT_STATUS_OPTIONS_VERSION;
   1876 	out_opts->show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
   1877 	out_opts->flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
   1878 }
   1879 
   1880 int
   1881 is_dirty(git_repository *repo, const git_status_options *opts)
   1882 {
   1883 	size_t change_count;
   1884 	int dirty = 0;
   1885 
   1886 	git_status_list *list = NULL;
   1887 	if (git_status_list_new(&list, repo, opts) < 0)
   1888 		return -1;
   1889 
   1890 	change_count = git_status_list_entrycount(list);
   1891 
   1892 	for (size_t i = 0; i < change_count; ++i) {
   1893 		const git_status_entry *entry = git_status_byindex(list, i);
   1894 		if (entry->status != GIT_STATUS_CURRENT) {
   1895 			dirty = 1;
   1896 			break;
   1897 		}
   1898 	}
   1899 
   1900 	git_status_list_free(list);
   1901 
   1902 	return dirty;
   1903 }
   1904 
   1905 char *
   1906 retrieve_git_branch(char *buf, size_t bufsize, const char *repo_path)
   1907 {
   1908 	git_repository *repo = NULL;
   1909 	git_reference *head_ref = NULL;
   1910 	const char *branch_name = NULL;
   1911 	git_status_options opts;
   1912 
   1913 	if (!buf || bufsize == 0)
   1914 		return NULL;
   1915 	buf[0] = '\0';
   1916 
   1917 	if (repo_path == NULL)
   1918 		return NULL;
   1919 
   1920 	if (git_repository_open_ext(&repo, repo_path, 0, NULL) == 0)
   1921 		if (git_repository_head(&head_ref, repo) == 0)
   1922 			if (git_reference_is_branch(head_ref))
   1923 				branch_name = git_reference_shorthand(head_ref);
   1924 
   1925 	if (branch_name != NULL) {
   1926 		init_status_options(&opts);
   1927 		snprintf(buf, bufsize, " (%s%s)", branch_name, is_dirty(repo, &opts) ? "*" : "");
   1928 	}
   1929 
   1930 	git_reference_free(head_ref);
   1931 	git_repository_free(repo);
   1932 
   1933 	return buf;
   1934 }
   1935 #endif