cex

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

cex.c (40011B)


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