cex

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

cex.c (40996B)


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