cex

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

cex.c (28467B)


      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 "cex.h"
     17 
     18 static DirWin curwin, parwin, childwin;
     19 static char *userhome, *username, *editor, **selected, searchq[SEARCHLEN];
     20 static int maxy, maxx;
     21 static size_t selc, searchc;
     22 static bool print_bot, hide = DEFAULT_HIDE;
     23 
     24 int
     25 main(int argc, char **argv)
     26 {
     27 	struct passwd userinf;
     28 	char filename[PATH_MAX];
     29 	int opt;
     30 	bool writepath = FALSE;
     31 
     32 	while ((opt = getopt(argc, argv, "f:")) != -1) {
     33 		switch (opt) {
     34 			case 'f':
     35 				cpstr(filename, optarg);
     36 				writepath = TRUE;
     37 				break;
     38 			default:
     39 				fatal("Unknown option: %c\n", optopt);
     40 				break;
     41 		}
     42 	}
     43 
     44 	editor = getenv("EDITOR");
     45 	
     46 	userinf = *getpwuid(getuid());
     47 	username = strdup(userinf.pw_name);
     48 	userhome = strdup(userinf.pw_dir);
     49 
     50 	init_dirwins();
     51 	init_screen();
     52 
     53 	start();
     54 
     55 	if (writepath)
     56 		wpath(filename);
     57 
     58 	clean();
     59 }
     60 
     61 static void
     62 init_dirwins()
     63 {
     64 	curwin.wintype = CURRENT;
     65 	parwin.wintype = PARENT;
     66 	childwin.wintype = CHILD;
     67 
     68 	reset_flags();
     69 
     70 	childwin.highlight = 0;
     71 
     72 	if (getcwd(curwin.path, PATH_MAX) == NULL)
     73 		fatal("getcwd() error");
     74 
     75 	get_dirname(parwin.path, curwin.path);
     76 
     77 	set_win_files(&curwin);
     78 	set_win_files(&parwin);
     79 
     80 	if (curwin.winfiles != NULL && curwin.winfiles[0].d_type == DT_DIR) {
     81 		cpstr(childwin.path, curwin.path);
     82 		update_child_win();
     83 	}
     84 }
     85 
     86 static void
     87 init_screen()
     88 {
     89 	initscr();
     90 	noecho();
     91 	start_color();
     92 	use_default_colors();
     93 	keypad(stdscr, TRUE);
     94 	curs_set(0);
     95 
     96 	clear();
     97 	cbreak();
     98 
     99 	init_pair(COLOR_BLACK, COLOR_BLACK, -1);
    100 	init_pair(COLOR_RED, COLOR_RED, -1);
    101 	init_pair(COLOR_GREEN,COLOR_GREEN, -1);
    102 	init_pair(COLOR_YELLOW, COLOR_YELLOW, -1);
    103 	init_pair(COLOR_BLUE, COLOR_BLUE, -1);
    104 	init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1);
    105 	init_pair(COLOR_CYAN, COLOR_CYAN, -1);
    106 	init_pair(COLOR_WHITE, COLOR_WHITE, -1);
    107 
    108 	curwin.highlight = 0;
    109 	curwin.startpr = 0;
    110 
    111 	parwin.highlight = 0;
    112 	parwin.startpr = 0;
    113 
    114 	childwin.highlight = 0;
    115 	childwin.startpr = 0;
    116 
    117 	resize();
    118 
    119 	print_top_title();
    120 	print_bot_title();
    121 
    122 	print_win(&parwin);
    123 	print_win(&curwin);
    124 	print_win(&childwin);
    125 }
    126 
    127 static void
    128 resize()
    129 {
    130 	int startx;
    131 
    132 	getmaxyx(stdscr, maxy, maxx);
    133 
    134 	parwin.maxy = maxy-BORDER_SPACE_SIZE;
    135 	parwin.maxx = maxx/4-BORDER_SPACE_SIZE > 0 ? maxx/4-BORDER_SPACE_SIZE : 1;
    136 	startx = maxx > BORDER_SPACE_SIZE ? BORDER_SPACE_SIZE : 0;
    137 	parwin.window = newwin(parwin.maxy, parwin.maxx, BORDER_SPACE_SIZE, startx);
    138 
    139 	curwin.maxy = maxy-BORDER_SPACE_SIZE;
    140 	curwin.maxx = maxx/4-BORDER_SPACE_SIZE > 0 ? maxx/4-BORDER_SPACE_SIZE : 1;
    141 	startx = maxx > curwin.maxx+BORDER_SPACE_SIZE*2 ? curwin.maxx+BORDER_SPACE_SIZE*2 : 0;
    142 	curwin.window = newwin(curwin.maxy, curwin.maxx, BORDER_SPACE_SIZE, startx);
    143 
    144 	childwin.maxy = maxy-BORDER_SPACE_SIZE;
    145 	childwin.maxx = maxx/2-BORDER_SPACE_SIZE > 0 ? maxx/2-BORDER_SPACE_SIZE : 1;
    146 	startx = maxx > childwin.maxx+BORDER_SPACE_SIZE*2 ? childwin.maxx+BORDER_SPACE_SIZE*2 : 0;
    147 	childwin.window = newwin(childwin.maxy, childwin.maxx, BORDER_SPACE_SIZE, startx);
    148 
    149 	clear();
    150 	refresh();
    151 
    152 	update_child_win();
    153 }
    154 
    155 static void
    156 start()
    157 {
    158 	int c;
    159 	char chdname[PATH_MAX];
    160 
    161 	while ((c = getch()) != KEY_QUIT) {
    162 		move(1, 1);
    163 		clrtoeol();
    164 		reset_flags();
    165 		switch (c) {
    166 			case KEY_UP:
    167 			case KEY_VUP:
    168 				move_up(&curwin);
    169 				update_child_win();
    170 				break;
    171 			case KEY_DOWN:
    172 			case KEY_VDOWN:
    173 				move_down(&curwin);
    174 				update_child_win();
    175 				break;
    176 			case KEY_VPUP:
    177 				if (is_dir(&parwin, MAX(parwin.highlight-1, 0)))
    178 					change_parent_dir(UP);
    179 				break;
    180 			case KEY_VPDOWN:
    181 				if (is_dir(&parwin, MIN(parwin.highlight+1, parwin.filecount-1)))
    182 					change_parent_dir(DOWN);
    183 				break;
    184 			case KEY_PPAGE:
    185 			case ctrl('u'):
    186 				move_page_up(&curwin);
    187 				update_child_win();
    188 				break;
    189 			case KEY_NPAGE:
    190 			case ctrl('d'):
    191 				move_page_down(&curwin);
    192 				update_child_win();
    193 				break;
    194 			case KEY_BOT:
    195 				move_bot(&curwin);
    196 				update_child_win();
    197 				break;
    198 			case 10:
    199 			case KEY_VRIGHT:
    200 			case KEY_VRIGHT_ABS:
    201 				if (is_dir(&curwin, curwin.highlight))
    202 					change_dir(get_fullpath(chdname, &curwin, curwin.highlight),
    203 						RIGHT, c == KEY_VRIGHT_ABS);
    204 				else
    205 					open_child(FALSE);
    206 				break;
    207 			case KEY_LEFT:
    208 			case KEY_VLEFT:
    209 				change_dir(get_dirname(chdname, curwin.path), LEFT, FALSE);
    210 				break;
    211 			case KEY_SEARCH:
    212 				search();
    213 				update_child_win();
    214 				break;
    215 			case KEY_NEXT_SEARCH:
    216 				next_search();
    217 				update_child_win();
    218 				break;
    219 			case KEY_PREV_SEARCH:
    220 				prev_search();
    221 				update_child_win();
    222 				break;
    223 			case KEY_SEL_FILE:
    224 				select_file(get_fullpath(chdname, &curwin, curwin.highlight));
    225 				set_win_files(&curwin);
    226 				update_child_win();
    227 				break;
    228 			case KEY_CLEAR_SEL:
    229 				clear_selected();
    230 				set_win_files(&curwin);
    231 				set_win_files(&parwin);
    232 				update_child_win();
    233 				break;
    234 			case KEY_CLEAR_SEARCH:
    235 				clear_search();
    236 				set_win_files(&curwin);
    237 				break;
    238 			case KEY_CP_SEL:
    239 				exe_selection(COPY, NULL);
    240 				break;
    241 			case KEY_RM_SEL:
    242 				exe_selection(REMOVE, "Remove");
    243 				break;
    244 			case KEY_MV_SEL:
    245 				exe_selection(MOVE, "Move");
    246 				break;
    247 			case KEY_RM_FILE:
    248 				rm_file(curwin.winfiles[curwin.highlight].d_name);
    249 				set_win_files(&curwin);
    250 				update_child_win();
    251 				break;
    252 			case KEY_RENAME_FILE:
    253 				rename_file(curwin.winfiles[curwin.highlight].d_name);
    254 				set_win_files(&curwin);
    255 				update_child_win();
    256 				break;
    257 			case KEY_HIDE:
    258 				hide = hide ? FALSE : TRUE;
    259 				curwin.highlight = 0;
    260 				curwin.startpr = 0;
    261 				set_win_files(&curwin);
    262 				set_win_files(&parwin);
    263 				update_child_win();
    264 				break;
    265 			case KEY_COMBO_GO:
    266 			case KEY_COMBO_INF:
    267 			case KEY_COMBO_OPEN:
    268 			case KEY_COMBO_MAKE:
    269 				combo_key(c);
    270 				break;
    271 			case KEY_RESIZE:
    272 				resize();
    273 				break;
    274 			default:
    275 				break;
    276 		}
    277 
    278 		print_top_title();
    279 		if (print_bot)
    280 			print_bot_title();
    281 		print_win(&parwin);
    282 		print_win(&curwin);
    283 		print_win(&childwin);
    284 	}
    285 }
    286 
    287 static void
    288 wpath(const char *filename)
    289 {
    290 	FILE *fptr;
    291 
    292 	if ((fptr = fopen(filename, "w")) == NULL)
    293 		fatal("Error opening file \"%s\"\n", filename);
    294 	fprintf(fptr, "%s\n", curwin.path);
    295 	fclose(fptr);
    296 }
    297 
    298 static void
    299 reset_flags()
    300 {
    301 	parwin.usehighlight = FALSE;
    302 	curwin.usehighlight = TRUE;
    303 	childwin.usehighlight = TRUE;
    304 
    305 	parwin.holdupdate = FALSE;
    306 	curwin.holdupdate = FALSE;
    307 	childwin.holdupdate = FALSE;
    308 
    309 	print_bot = TRUE;
    310 }
    311 
    312 static void
    313 set_startpr(DirWin *dirwin)
    314 {
    315 	if (dirwin->winfiles == NULL) {
    316 		dirwin->startpr = 0;
    317 		return;
    318 	}
    319 
    320 	dirwin->startpr = MAX(dirwin->highlight - dirwin->maxy/2, 0);
    321 }
    322 
    323 static void
    324 set_win_files(DirWin *dirwin)
    325 {
    326 	struct dirent *ent;
    327 	size_t count = 0;
    328 	DIR *dir;
    329 
    330 	free_dirwin(dirwin);
    331 
    332 	if (dirwin->wintype == PARENT && strcmp(curwin.path, "/") == 0)
    333 		return;
    334 
    335 	if ((dir = opendir(dirwin->path)) == NULL) {
    336 		switch (errno) {
    337 			case EACCES:
    338 				set_win_message(dirwin, "No permission.");
    339 				return;
    340 			default:
    341 				fatal("Could not open directory: %s", dirwin->path);
    342 		}
    343 	}
    344 
    345 	if ((dirwin->winfiles = (WinFile*) malloc(sizeof(WinFile))) == NULL)
    346 		fatal("Fatal: failed to malloc.\n");
    347 
    348 	while ((ent = readdir(dir)) != NULL) {
    349 		if (strcmp(ent->d_name, ".") == 0
    350 				|| strcmp(ent->d_name, "..") == 0
    351 				|| (hide && ent->d_name[0] == '.'))
    352 			continue;
    353 		if ((dirwin->winfiles = (WinFile*) realloc(dirwin->winfiles, (count+1)*(sizeof(WinFile)))) == NULL)
    354 			fatal("Fatal: failed to realloc.\n");
    355 		cpstr(dirwin->winfiles[count].d_name, ent->d_name);
    356 		dirwin->winfiles[count].d_type = ent->d_type;
    357 		dirwin->winfiles[count].selected = is_selected(dirwin, count);
    358 		(count)++;
    359 	}
    360 	closedir(dir);
    361 
    362 	dirwin->filecount = count;
    363 
    364 	if (count == 0)
    365 		set_win_message(dirwin, "empty");
    366 	else
    367 		qsort(dirwin->winfiles, count, sizeof(WinFile), compare_file);
    368 }
    369 
    370 static bool
    371 is_selected(DirWin *dirwin, size_t index)
    372 {
    373 	char buf[PATH_MAX];
    374 
    375 	if (selc == 0 || selected == NULL)
    376 		return FALSE;
    377 
    378 	get_fullpath(buf, dirwin, index);
    379 
    380 	for (size_t i = 0; i < selc; i++) {
    381 		if (strcmp(buf, selected[i]) == 0)
    382 			return TRUE;
    383 	}
    384 
    385 	return FALSE;
    386 }
    387 
    388 static bool
    389 remove_selected(const char *sel)
    390 {
    391 	bool res = FALSE;
    392 
    393 	if (selc == 0)
    394 		return res;
    395 
    396 	for (size_t i = 0; i < selc; i++) {
    397 		if (strcmp(selected[i], sel) == 0) {
    398 			free(selected[i]);
    399 			selected[i] = selected[selc-1];
    400 			res = TRUE;
    401 			break;
    402 		}
    403 	}
    404 
    405 	if (!res)
    406 		return res;
    407 
    408 	if (--(selc) == 0) {
    409 		free(selected);
    410 		selected = NULL;
    411 	} else if ((selected = (char**) realloc(selected, selc * sizeof(char *))) == NULL)
    412 		fatal("Fatal: failed to realloc.\n");
    413 
    414 	return res;
    415 }
    416 
    417 static void
    418 set_win_message(DirWin *dirwin, char *message)
    419 {
    420 	free_dirwin(dirwin);
    421 	dirwin->message = message;
    422 }
    423 
    424 static void
    425 print_win(DirWin *dirwin)
    426 {
    427 	if (dirwin->holdupdate)
    428 		return;
    429 
    430 	size_t cplen, size = dirwin->maxx;
    431 	char *subs, name[size+1], sbuf[size+1], pathbuf[PATH_MAX];
    432 	int sindex, y = 0, x = 1;
    433 
    434 	wclear(dirwin->window);
    435 
    436 	set_startpr(dirwin);
    437 
    438 	if (dirwin->message != NULL) {
    439 		if (dirwin->wintype == CURRENT) {
    440 			wattron(dirwin->window, A_REVERSE);
    441 			mvwaddstr(dirwin->window, y, x, dirwin->message);
    442 			wattroff(dirwin->window, A_REVERSE);
    443 		} else {
    444 			wattron(dirwin->window, COLOR_PAIR(CHILDWIN_MESSAGE_COLOR));
    445 			mvwaddstr(dirwin->window, y, x, dirwin->message);
    446 			wattroff(dirwin->window, COLOR_PAIR(CHILDWIN_MESSAGE_COLOR));
    447 		}
    448 	} else if (dirwin->winfiles != NULL) {
    449 		while (!dirwin->usehighlight) {
    450 			for (size_t i = 0; i < dirwin->filecount; ++i) {
    451 				memcpy(name, dirwin->winfiles[i].d_name, size);
    452 				name[size] = '\0';
    453 				if (strcmp(basename(curwin.path), dirwin->winfiles[i].d_name) == 0) {
    454 					dirwin->usehighlight = TRUE;
    455 					dirwin->highlight = i;
    456 					set_startpr(dirwin);
    457 					break;
    458 				}
    459 			}
    460 			break;
    461 		}
    462 		for (size_t i = dirwin->startpr; i < dirwin->filecount; ++i) {
    463 			if (dirwin->winfiles[i].selected) {
    464 				wattron(dirwin->window, COLOR_PAIR(MARK_SELECTED_COLOR));
    465 				mvwaddch(dirwin->window, y, x-1, '|');
    466 				wattroff(dirwin->window, COLOR_PAIR(MARK_SELECTED_COLOR));
    467 			}
    468 			memcpy(name, dirwin->winfiles[i].d_name, size);
    469 			name[size-1] = '\0';
    470 			if (dirwin->usehighlight && dirwin->highlight == i) {
    471 				wattron(dirwin->window, A_REVERSE);
    472 				mvwaddstr(dirwin->window, y, x, name);
    473 				wattroff(dirwin->window, A_REVERSE);
    474 			} else if (dirwin->winfiles[i].d_type == DT_DIR) {
    475 				wattron(dirwin->window, COLOR_PAIR(DIR_COLOR));
    476 				wattron(dirwin->window, A_BOLD);
    477 				mvwaddstr(dirwin->window, y, x, name);
    478 				wattroff(dirwin->window, COLOR_PAIR(DIR_COLOR));
    479 				wattroff(dirwin->window, A_BOLD);
    480 			} else if (dirwin->winfiles[i].d_type == DT_LNK) {
    481 				if (access(get_fullpath(pathbuf, dirwin, i), F_OK) == 0) {
    482 					wattron(dirwin->window, COLOR_PAIR(LN_COLOR));
    483 					mvwaddstr(dirwin->window, y, x, name);
    484 					wattroff(dirwin->window, COLOR_PAIR(LN_COLOR));
    485 				} else {
    486 					wattron(dirwin->window, COLOR_PAIR(INVALID_LN_COLOR));
    487 					mvwaddstr(dirwin->window, y, x, name);
    488 					wattroff(dirwin->window, COLOR_PAIR(INVALID_LN_COLOR));
    489 				}
    490 			} else
    491 				mvwaddstr(dirwin->window, y, x, name);
    492 			if (dirwin->wintype == CURRENT && searchc > 0
    493 					&& ((subs = strstr(name, searchq)) != NULL)) {
    494 				sindex = (int) (subs - name);
    495 				wattron(dirwin->window, COLOR_PAIR(SEARCH_MATCH_COLOR));
    496 				if (dirwin->usehighlight && dirwin->highlight == i)
    497 					wattron(dirwin->window, A_REVERSE);
    498 				cplen = strlen(searchq);
    499 				memcpy(sbuf, name + sindex, cplen);
    500 				sbuf[cplen] = '\0';
    501 				mvwaddstr(dirwin->window, y, x+sindex, sbuf);
    502 				wattroff(dirwin->window, COLOR_PAIR(SEARCH_MATCH_COLOR));
    503 				if (dirwin->usehighlight && dirwin->highlight == i)
    504 					wattroff(dirwin->window, A_REVERSE);
    505 			}
    506 			y++;
    507 		}
    508 	}
    509 	wrefresh(dirwin->window);
    510 }
    511 
    512 static void
    513 update_child_win()
    514 {
    515 	char pathbuf[PATH_MAX];
    516 
    517 	if (curwin.filecount <= 0) {
    518 		free_dirwin(&childwin);
    519 		return;
    520 	}
    521 
    522 	get_fullpath(childwin.path, &curwin, curwin.highlight);
    523 
    524 	switch (curwin.winfiles[curwin.highlight].d_type) {
    525 		case DT_LNK:
    526 			if (access(get_fullpath(pathbuf, &curwin, curwin.highlight), F_OK) != 0) {
    527 				set_win_message(&childwin, "Link no longer exists.");
    528 				break;
    529 			}
    530 			if (is_dir(&curwin, curwin.highlight))
    531 				goto dir;
    532 			else
    533 				goto reg;
    534 		case DT_DIR:
    535 	dir:
    536 			set_win_files(&childwin);
    537 			break;
    538 		case DT_REG:
    539 	reg:
    540 			print_content();
    541 			break;
    542 		default:
    543 			free_dirwin(&childwin);
    544 			break;
    545 	}
    546 }
    547 
    548 static void
    549 print_top_title()
    550 {
    551 	move(0, 0);
    552 	clrtoeol();
    553 	attron(COLOR_PAIR(TOP_TITLE_COLOR));
    554 	printw("%s:%s/%s", username, curwin.path, curwin.winfiles[curwin.highlight].d_name);
    555 	attroff(COLOR_PAIR(TOP_TITLE_COLOR));
    556 }
    557 
    558 static void
    559 print_bot_title()
    560 {
    561 	char pathbuf[PATH_MAX], fileinf[maxx];
    562 
    563 	get_fullpath(pathbuf, &curwin, curwin.highlight);
    564 	memcpy(fileinf, get_file_info(fileinf, pathbuf), maxx);
    565 	fileinf[maxx] = '\0';
    566 
    567 	move(maxy-1, 0);
    568 	clrtoeol();
    569 
    570 	attron(COLOR_PAIR(BOT_TITLE_COUNT_COLOR));
    571 	printw("(%d/%d) ", curwin.filecount == 0 ? 0 : curwin.highlight+1, curwin.filecount);
    572 	attroff(COLOR_PAIR(BOT_TITLE_COUNT_COLOR));
    573 
    574 	attron(COLOR_PAIR(BOT_TITLE_INFO_COLOR));
    575 	addstr(fileinf);
    576 	attroff(COLOR_PAIR(BOT_TITLE_INFO_COLOR));
    577 }
    578 
    579 static char *
    580 get_file_info(char *buf, const char *filepath)
    581 {
    582 	struct stat statbuf;
    583 	struct passwd *usr;
    584 	struct tm *tm;
    585 	struct group *grp;
    586 	char mods[11], datestr[256], modfill = '-';
    587 
    588 	if ((stat(filepath, &statbuf)) != 0) {
    589 		switch (errno) {
    590 			case EACCES:
    591 				buf = "No permission.";
    592 				break;
    593 			default:
    594 				buf = "Retrieving filestats failed.";
    595 				break;
    596 		}
    597 		return buf;
    598 	}
    599 
    600 	if ((usr = getpwuid(statbuf.st_uid)) == NULL) {
    601 		buf = "Retrieving username failed.";
    602 		return buf;
    603 	}
    604 	if ((grp = getgrgid(statbuf.st_gid)) == NULL) {
    605 		buf = "Retrieving groupname failed.";
    606 		return buf;
    607 	}
    608 
    609 	mods[0] = S_ISDIR(statbuf.st_mode) ? 'd' : modfill;
    610 	mods[1] = statbuf.st_mode & S_IRUSR ? 'r' : modfill;
    611 	mods[2] = statbuf.st_mode & S_IWUSR ? 'w' : modfill;
    612 	mods[3] = statbuf.st_mode & S_IXUSR ? 'x' : modfill;
    613 	mods[4] = statbuf.st_mode & S_IRGRP ? 'r' : modfill;
    614 	mods[5] = statbuf.st_mode & S_IWGRP ? 'w' : modfill;
    615 	mods[6] = statbuf.st_mode & S_IXGRP ? 'x' : modfill;
    616 	mods[7] = statbuf.st_mode & S_IROTH ? 'r' : modfill;
    617 	mods[8] = statbuf.st_mode & S_IWOTH ? 'w' : modfill;
    618 	mods[9] = statbuf.st_mode & S_IXOTH ? 'x' : modfill;
    619 	mods[10] = '\0';
    620 
    621 	tm = localtime(&statbuf.st_mtime);
    622 	strftime(datestr, sizeof(datestr), nl_langinfo(D_T_FMT), tm);
    623 
    624 	sprintf(buf, "%10.10s %s %s %9jd %s",
    625 		mods,
    626 		usr->pw_name,
    627 		grp->gr_name,
    628 		(intmax_t)statbuf.st_size,
    629 		datestr);
    630 
    631 	return buf;
    632 }
    633 
    634 static void
    635 print_content()
    636 {
    637 	FILE *fp = NULL;
    638 	size_t size = childwin.maxx;
    639 	char str[size+1], buf[PATH_MAX];
    640 	int y = 0, x = 0;
    641 
    642 	werase(childwin.window);
    643 	free_dirwin(&childwin);
    644 
    645 	if ((fp = fopen(get_fullpath(buf, &curwin, curwin.highlight), "r")) == NULL) {
    646 		switch (errno) {
    647 			case EACCES:
    648 				set_win_message(&childwin, "No permission.");
    649 				return;
    650 			default:
    651 				fatal("Error opening file \"%s\"\n", buf);
    652 		}
    653 	}
    654 
    655 	childwin.holdupdate = TRUE;
    656 
    657 	while (fgets(str, size, fp) != NULL && y <= childwin.maxy) {
    658 		mvwaddstr(childwin.window, y, x, str);
    659 		y++;
    660 	}
    661 
    662 	fclose(fp);
    663 	wrefresh(childwin.window);
    664 }
    665 
    666 static void
    667 show_file_mime()
    668 {
    669 	char buf[MIME_MAX];
    670 
    671 	if (get_mime(buf, MIME_MAX, curwin.winfiles[curwin.highlight].d_name) != 0)
    672 		cpstr(buf, "Can't retrieve mime.");
    673 
    674 	move(1, 1);
    675 	clrtoeol();
    676 	addstr(buf);
    677 }
    678 
    679 static void
    680 combo_key(int keypress)
    681 {
    682 	int c;
    683 
    684 	move(maxy-1, maxx-1);
    685 	clrtoeol();
    686 	addch(keypress);
    687 
    688 	halfdelay(10);
    689 
    690 	while ((c = getch()) != ERR) {
    691 		switch (keypress) {
    692 			case KEY_COMBO_GO:
    693 				combo_go(c);
    694 				break;
    695 			case KEY_COMBO_INF:
    696 				combo_inf(c);
    697 				break;
    698 			case KEY_COMBO_OPEN:
    699 				combo_open(c);
    700 				break;
    701 			case KEY_COMBO_MAKE:
    702 				combo_make(c);
    703 				break;
    704 			default:
    705 				break;
    706 		}
    707 		break;
    708 	}
    709 
    710 	cbreak();
    711 	move(maxy-1, maxx-1);
    712 	clrtoeol();
    713 }
    714 
    715 static void
    716 combo_go(int key)
    717 {
    718 	char pathbuf[PATH_MAX];
    719 
    720 	switch (key) {
    721 		case KEY_COMBO_GO_TOP:
    722 			move_top(&curwin);
    723 			update_child_win();
    724 			break;
    725 		case KEY_COMBO_GO_HOME:
    726 			change_dir(userhome, OTHER, FALSE);
    727 			break;
    728 		case KEY_COMBO_GO_ACCESS:
    729 			change_dir(replace_home(pathbuf, LN_ACCESS_DIR), OTHER, FALSE);
    730 			break;
    731 		default:
    732 			break;
    733 	}
    734 }
    735 
    736 static void
    737 combo_inf(int key)
    738 {
    739 	switch (key) {
    740 		case KEY_COMBO_INF_OPEN:
    741 			show_file_mime();
    742 			break;
    743 		default:
    744 			break;
    745 	}
    746 }
    747 
    748 static void
    749 combo_open(int key)
    750 {
    751 	char chdname[PATH_MAX];
    752 
    753 	switch (key) {
    754 		case KEY_COMBO_OPEN_NOHUP_XDG:
    755 			if (is_dir(&curwin, curwin.highlight))
    756 				change_dir(get_fullpath(chdname, &curwin, curwin.highlight), RIGHT, FALSE);
    757 			else
    758 				open_nohup_xdg();
    759 			break;
    760 		case KEY_COMBO_OPEN_EXEC:
    761 			if (is_dir(&curwin, curwin.highlight))
    762 				change_dir(get_fullpath(chdname, &curwin, curwin.highlight), RIGHT, FALSE);
    763 			else
    764 				open_child(TRUE);
    765 			break;
    766 		default:
    767 			break;
    768 	}
    769 }
    770 
    771 static void
    772 combo_make(int key)
    773 {
    774 	switch (key) {
    775 		case KEY_COMBO_MAKE_FILE:
    776 			make_file(curwin.path);
    777 			set_win_files(&curwin);
    778 			update_child_win();
    779 			break;
    780 		case KEY_COMBO_MAKE_DIR:
    781 			make_dir(curwin.path);
    782 			set_win_files(&curwin);
    783 			update_child_win();
    784 			break;
    785 		case KEY_COMBO_MAKE_MOD:
    786 			make_mod();
    787 			break;
    788 		case KEY_COMBO_MAKE_ACCESS:
    789 			make_access();
    790 			break;
    791 		default:
    792 			break;
    793 	}
    794 }
    795 
    796 static void
    797 change_dir(const char *chdname, Direction direction, bool abspath)
    798 {
    799 	if (chdir(chdname) != 0)
    800 		return;
    801 
    802 	if (!abspath)
    803 		cpstr(curwin.path, chdname);
    804 	else if (getcwd(curwin.path, PATH_MAX) == NULL)
    805 		fatal("getcwd() error");
    806 
    807 	switch (direction) {
    808 		case RIGHT:
    809 			curwin.highlight = 0;
    810 			break;
    811 		case LEFT:
    812 			curwin.highlight = parwin.highlight;
    813 			break;
    814 		default:
    815 			curwin.highlight = 0;
    816 			break;
    817 
    818 	}
    819 
    820 	set_win_files(&curwin);
    821 
    822 	get_dirname(parwin.path, curwin.path);
    823 	set_win_files(&parwin);
    824 
    825 	update_child_win();
    826 }
    827 
    828 static void
    829 change_parent_dir(Direction direction)
    830 {
    831 	char pp[PATH_MAX];
    832 
    833 	switch (direction) {
    834 		case UP:
    835 			move_up(&parwin);
    836 			break;
    837 		case DOWN:
    838 			move_down(&parwin);
    839 			break;
    840 		default:
    841 			return;
    842 	}
    843 
    844 	parwin.usehighlight = TRUE;
    845 	curwin.highlight = 0;
    846 	change_dir(get_fullpath(pp, &parwin, parwin.highlight), direction, FALSE);
    847 }
    848 
    849 static void
    850 open_child(bool exec)
    851 {
    852 	sigset_t set;
    853 	char mime[MIME_MAX], mimedefault[MIME_APP_MAX], pathbuf[PATH_MAX];
    854 	bool istext;
    855 
    856 	if (curwin.message != NULL)
    857 		return;
    858 
    859 	if (!exec) {
    860 		if (get_mime(mime, MIME_MAX, curwin.winfiles[curwin.highlight].d_name) != 0) {
    861 			move(1, 1);
    862 			clrtoeol();
    863 			printw("Can\'t open %s", curwin.winfiles[curwin.highlight].d_name);
    864 			return;
    865 		}
    866 
    867 		istext = strncmp(mime, "text/", 5) == 0;
    868 
    869 		if (!istext && get_mime_default(mimedefault, MIME_APP_MAX, mime) != 0) {
    870 			move(1, 1);
    871 			clrtoeol();
    872 			printw("Can\'t open for mime %s", mime);
    873 			return;
    874 		}
    875 	}
    876 
    877 	endwin();
    878 
    879 	if (sigemptyset (&set) == -1)
    880 		fatal("Sigemptyset failed.");
    881 	if (sigaddset(&set, SIGWINCH) == -1)
    882 		fatal("Sigaddset failed.");
    883 	if (sigprocmask(SIG_BLOCK, &set, NULL) != 0)
    884 		fatal("Blocking sigprocmask failed.");
    885 
    886 	if (exec)
    887 		run_command(PATH_MAX + 4, "\"./%s\"", curwin.winfiles[curwin.highlight].d_name);
    888 	else
    889 		run_command(PATH_MAX + 12, "%s \"%s\"", istext ? editor : "xdg-open",
    890 			get_fullpath(pathbuf, &curwin, curwin.highlight));
    891 
    892 	if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0)
    893 		fatal("Unblocking sigprocmask failed.");
    894 
    895 	clear();
    896 	noecho();
    897 	cbreak();
    898 
    899 	refresh();
    900 	wrefresh(parwin.window);
    901 	wrefresh(curwin.window);
    902 	wrefresh(childwin.window);
    903 	update_child_win();
    904 }
    905 
    906 static void
    907 open_nohup_xdg()
    908 {
    909 	char *cmdfmt = "nohup xdg-open \"%s\" > /dev/null 2>&1 &";
    910 	size_t size = PATH_MAX + strlen(cmdfmt);
    911 
    912 	run_command(size, cmdfmt, curwin.winfiles[curwin.highlight].d_name);
    913 }
    914 
    915 static void
    916 search()
    917 {
    918 	int c, y, x;
    919 
    920 	clear_search();
    921 
    922 	move(maxy-1, 0);
    923 	clrtoeol();
    924 
    925 	attron(COLOR_PAIR(PROMPT_COLOR));
    926 	addstr("Search name? ");
    927 	attroff(COLOR_PAIR(PROMPT_COLOR));
    928 
    929 	echo();
    930 	curs_set(1);
    931 
    932 	getyx(stdscr, y, x);
    933 
    934 	while ((c = mvwgetch(stdscr, y, x)) != 10) {
    935 		switch (c) {
    936 			case KEY_BACKSPACE:
    937 				if (searchc > 0) {
    938 					searchq[--(searchc)] = '\0';
    939 					move(y, --x);
    940 					clrtoeol();
    941 				}
    942 				break;
    943 			default:
    944 				if (searchc < SEARCHLEN) {
    945 					searchq[(searchc)++] = c;
    946 					move(y, ++x);
    947 				}
    948 				break;
    949 		}
    950 		print_win(&curwin);
    951 	}
    952 	noecho();
    953 	curs_set(0);
    954 }
    955 
    956 static void
    957 move_top(DirWin *dirwin)
    958 {
    959 	dirwin->highlight = 0;
    960 }
    961 
    962 static void
    963 move_bot(DirWin *dirwin)
    964 {
    965 	dirwin->highlight = MAX(dirwin->filecount-1, 0);
    966 }
    967 
    968 static void
    969 move_page_up(DirWin *dirwin)
    970 {
    971 	dirwin->highlight = MAX(dirwin->highlight - dirwin->maxy/2, 0);
    972 }
    973 
    974 static void
    975 move_page_down(DirWin *dirwin)
    976 {
    977 	dirwin->highlight = MIN(dirwin->highlight + dirwin->maxy/2,
    978 		MAX(dirwin->filecount-1, 0));
    979 }
    980 
    981 static void
    982 move_up(DirWin *dirwin)
    983 {
    984 	if (dirwin->highlight > 0)
    985 		dirwin->highlight--;
    986 }
    987 
    988 static void
    989 move_down(DirWin *dirwin)
    990 {
    991 	if (dirwin->highlight < dirwin->filecount-1)
    992 		dirwin->highlight++;
    993 }
    994 
    995 static void
    996 next_search()
    997 {
    998 	if (searchc == 0)
    999 		return;
   1000 
   1001 	for (size_t i = curwin.highlight; i < MAX(curwin.filecount-1, 0); i++) {
   1002 		if ((strstr(curwin.winfiles[i+1].d_name, searchq) != NULL)) {
   1003 			curwin.highlight = i+1;
   1004 			return;
   1005 		}
   1006 	}
   1007 }
   1008 
   1009 static void
   1010 prev_search()
   1011 {
   1012 	if (searchc == 0)
   1013 		return;
   1014 
   1015 	for (size_t i = curwin.highlight; i > 0; i--) {
   1016 		if ((strstr(curwin.winfiles[i-1].d_name, searchq) != NULL)) {
   1017 			curwin.highlight = i-1;
   1018 			return;
   1019 		}
   1020 	}
   1021 }
   1022 
   1023 static void
   1024 free_dirwin(DirWin *dirwin)
   1025 {
   1026 	free(dirwin->winfiles);
   1027 	dirwin->filecount = 0;
   1028 	dirwin->winfiles = NULL;
   1029 	dirwin->message = NULL;
   1030 }
   1031 
   1032 static void
   1033 clean()
   1034 {
   1035 	wclear(curwin.window);
   1036 	wclear(parwin.window);
   1037 	wclear(childwin.window);
   1038 
   1039 	free(username);
   1040 	free(userhome);
   1041 
   1042 	clear();
   1043 
   1044 	delwin(parwin.window);
   1045 	delwin(curwin.window);
   1046 	delwin(childwin.window);
   1047 	delwin(stdscr);
   1048 
   1049 	free_dirwin(&curwin);
   1050 	free_dirwin(&parwin);
   1051 	free_dirwin(&childwin);
   1052 
   1053 	clear_selected();
   1054 
   1055 	endwin();
   1056 }
   1057 
   1058 static void
   1059 fatal(const char *fmt, ...)
   1060 {
   1061 	va_list ap;
   1062 
   1063 	va_start(ap, fmt);
   1064 	vfprintf(stderr, fmt, ap);
   1065 	va_end(ap);
   1066 
   1067 	clean();
   1068 
   1069 	exit(EXIT_FAILURE);
   1070 }
   1071 
   1072 static bool
   1073 is_dir(DirWin *dirwin, size_t index)
   1074 {
   1075 	struct stat statbuf;
   1076 	char linkpath[PATH_MAX], abspath[PATH_MAX];
   1077 
   1078 	if (dirwin->filecount <= 0)
   1079 		return FALSE;
   1080 
   1081 	if (dirwin->winfiles[index].d_type == DT_DIR)
   1082 		return TRUE;
   1083 
   1084 	get_fullpath(linkpath, dirwin, index);
   1085 
   1086 	if (dirwin->winfiles[index].d_type == DT_LNK)
   1087 		return (lstat(realpath(linkpath, abspath), &statbuf) >= 0) && S_ISDIR(statbuf.st_mode);
   1088 
   1089 	return FALSE;
   1090 }
   1091 
   1092 static char *
   1093 prompt_answer(char *buf, size_t size, const char *question)
   1094 {
   1095 	move(maxy-1, 0);
   1096 	clrtoeol();
   1097 
   1098 	attron(COLOR_PAIR(PROMPT_COLOR));
   1099 	addstr(question);
   1100 	attroff(COLOR_PAIR(PROMPT_COLOR));
   1101 
   1102 	echo();
   1103 	curs_set(1);
   1104 	getnstr(buf, size);
   1105 	noecho();
   1106 	curs_set(0);
   1107 
   1108 	return buf;
   1109 }
   1110 
   1111 static bool
   1112 prompt_confirm(size_t size, const char *fmt, ...)
   1113 {
   1114 	char response[1], question[size];
   1115 	va_list ap;
   1116 
   1117 	va_start(ap, fmt);
   1118 	vsnprintf(question, size, fmt, ap);
   1119 	va_end(ap);
   1120 
   1121 	prompt_answer(response, 1, question);
   1122 
   1123 	return strcasecmp(response, "y") == 0;
   1124 }
   1125 
   1126 static int
   1127 compare_file(const void *a, const void *b)
   1128 {
   1129 	int typecompare;
   1130 	const WinFile *ma = a;
   1131 	const WinFile *mb = b;
   1132 
   1133 	if ((typecompare = ma->d_type - mb->d_type) == 0)
   1134 		return strcmp(ma->d_name, mb->d_name);
   1135 
   1136 	return typecompare;
   1137 }
   1138 
   1139 static int
   1140 get_mime(char *buf, size_t bufsize, const char *path)
   1141 {
   1142 	char *cmdfmt = "xdg-mime query filetype \"%s\"";
   1143 	size_t size = PATH_MAX + strlen(cmdfmt);
   1144 	char cmd[size];
   1145 
   1146 	snprintf(cmd, size, cmdfmt, path);
   1147 
   1148 	return read_command(buf, bufsize, cmd);
   1149 }
   1150 
   1151 static int
   1152 get_mime_default(char *buf, size_t bufsize, const char *mime)
   1153 {
   1154 	char *cmdfmt = "xdg-mime query default \"%s\"";
   1155 	size_t cmdsize = MIME_MAX + strlen(cmdfmt);
   1156 	char cmd[cmdsize];
   1157 
   1158 	snprintf(cmd, cmdsize, cmdfmt, mime);
   1159 
   1160 	return read_command(buf, bufsize, cmd);
   1161 }
   1162 
   1163 static int
   1164 read_command(char *buf, size_t bufsize, const char *cmd)
   1165 {
   1166 	FILE *file;
   1167 
   1168 	if ((file = popen(cmd, "r")) == NULL) {
   1169 		buf = NULL;
   1170 		return 1;
   1171 	}
   1172 
   1173 	if (fgets(buf, bufsize, file) == NULL) {
   1174 		buf = NULL;
   1175 		pclose(file);
   1176 		return 2;
   1177 	}
   1178 
   1179 	buf[strlen(buf)-1] = '\0';
   1180 
   1181 	pclose(file);
   1182 
   1183 	return 0;
   1184 }
   1185 
   1186 static void
   1187 run_command(size_t size, const char *fmt, ...)
   1188 {
   1189 	va_list ap;
   1190 	char cmd[size];
   1191 
   1192 	va_start(ap, fmt);
   1193 	vsnprintf(cmd, size, fmt, ap);
   1194 	va_end(ap);
   1195 
   1196 	system(cmd);
   1197 }
   1198 
   1199 static int
   1200 make_dir(const char *path)
   1201 {
   1202 	char name[PATH_MAX];
   1203 
   1204 	prompt_answer(name, PATH_MAX, "Name of directory? ");
   1205 	return mkdir(name, 0755);
   1206 }
   1207 
   1208 static int
   1209 make_file(const char *path)
   1210 {
   1211 	FILE *fptr;
   1212 	char name[PATH_MAX];
   1213 
   1214 	prompt_answer(name, PATH_MAX, "Name of file? ");
   1215 
   1216 	if (strlen(name) > 0) {
   1217 		if ((fptr = fopen(name, "w")) == NULL)
   1218 			fatal("Error opening file \"%s\"\n", name);
   1219 		fclose(fptr);
   1220 	}
   1221 }
   1222 
   1223 static int
   1224 rm_file(const char *fname)
   1225 {
   1226 	char *msg, *rmdirfmt = "rm -rf \"%s\"", *pfmt = "Remove %s? (y/N) ";
   1227 	int res = 0;
   1228 	size_t cmdsize = PATH_MAX + strlen(rmdirfmt), psize = strlen(pfmt) + strlen(fname);
   1229 
   1230 	if (prompt_confirm(psize, pfmt, fname)) {
   1231 		if (is_dir(&curwin, curwin.highlight))
   1232 			run_command(cmdsize, rmdirfmt, fname);
   1233 		else
   1234 			res = remove(fname);
   1235 		curwin.highlight = MAX(curwin.highlight-1, 0);
   1236 	}
   1237 
   1238 	if (res != 0) {
   1239 		switch (errno) {
   1240 			case EACCES:
   1241 				msg = "No permission.";
   1242 				break;
   1243 			case EEXIST:
   1244 			case ENOTEMPTY:
   1245 				msg = "Directory is not empty.";
   1246 				break;
   1247 			default:
   1248 				msg = "Could not remove file.";
   1249 				break;
   1250 		}
   1251 		move(maxy-1, 0);
   1252 		clrtoeol();
   1253 		addstr(msg);
   1254 		print_bot = FALSE;
   1255 	}
   1256 
   1257 	return res;
   1258 }
   1259 
   1260 static int
   1261 rename_file(const char *fname)
   1262 {
   1263 	char name[PATH_MAX];
   1264 
   1265 	prompt_answer(name, PATH_MAX, "New name? ");
   1266 
   1267 	if (strlen(name) > 0)
   1268 		return rename(fname, name);
   1269 
   1270 	return 0;
   1271 }
   1272 
   1273 static int
   1274 make_mod()
   1275 {
   1276 	char name[3];
   1277 	bool isvalid;
   1278 
   1279 	prompt_answer(name, 3, "File mods (numeric)? ");
   1280 
   1281 	if (strlen(name) == 3) {
   1282 		isvalid = TRUE;
   1283 		for (size_t i = 0; i < 3; i++) {
   1284 			if (!isdigit(name[i]))
   1285 				isvalid = FALSE;
   1286 		}
   1287 		if (isvalid)
   1288 			chmod(curwin.winfiles[curwin.highlight].d_name, strtol(name, NULL, 8));
   1289 		else
   1290 			return -1;
   1291 	}
   1292 
   1293 	return 0;
   1294 }
   1295 
   1296 static int
   1297 make_access()
   1298 {
   1299 	char pathbuf[PATH_MAX], *cmdfmt = "ln -s \"%s\" \"%s\"", *pfmt = "Make access \"%s\"? (y/N) ";
   1300 	size_t cmdsize = PATH_MAX + strlen(cmdfmt), psize = strlen(pfmt) + PATH_MAX;
   1301 
   1302 	if (prompt_confirm(psize, pfmt, curwin.winfiles[curwin.highlight].d_name))
   1303 		run_command(cmdsize, cmdfmt, get_fullpath(pathbuf, &curwin, curwin.highlight), LN_ACCESS_DIR);
   1304 }
   1305 
   1306 static void
   1307 exe_selection(SelAction action, const char *askn)
   1308 {
   1309 	char *pfmt = "%s selection (%d files) ? (y/N) ";
   1310 	size_t cmdsize = PATH_MAX*2 + 20;
   1311 
   1312 	if (selected == NULL || selc == 0)
   1313 		return;
   1314 
   1315 	if (askn != NULL && !prompt_confirm(strlen(pfmt) + strlen(askn) + 5, pfmt, askn, selc))
   1316 		return;
   1317 
   1318 	switch (action) {
   1319 		case COPY:
   1320 			for (size_t i = 0; i <selc; i++)
   1321 				run_command(cmdsize, "cp -rf \"%s\" \"%s\"", selected[i], curwin.path);
   1322 			break;
   1323 		case REMOVE:
   1324 			for (size_t i = 0; i <selc; i++)
   1325 				run_command(cmdsize, "rm -rf \"%s\"", selected[i]);
   1326 			break;
   1327 		case MOVE:
   1328 			for (size_t i = 0; i <selc; i++)
   1329 				run_command(cmdsize, "mv \"%s\" \"%s\"", selected[i], curwin.path);
   1330 			break;
   1331 		default:
   1332 			break;
   1333 	}
   1334 
   1335 	clear_selected();
   1336 	curwin.highlight = 0;
   1337 	set_win_files(&curwin);
   1338 	set_win_files(&parwin);
   1339 	update_child_win();
   1340 }
   1341 
   1342 static void
   1343 select_file(const char *path)
   1344 {
   1345 	if (selc == 0 && ((selected = (char**) malloc(sizeof(char *)))) == NULL)
   1346 		fatal("Fatal: failed to malloc selected.\n");
   1347 
   1348 	if (remove_selected(path))
   1349 		return;
   1350 
   1351 	if ((selected = (char**) realloc(selected, (selc+1) * sizeof(char *))) == NULL)
   1352 		fatal("Fatal: failed to realloc.\n");
   1353 
   1354 	selected[selc] = strdup(path);
   1355 	(selc)++;
   1356 }
   1357 
   1358 static void
   1359 clear_selected()
   1360 {
   1361 	for (size_t i = 0; i < selc; i++)
   1362 		free(selected[i]);
   1363 
   1364 	free(selected);
   1365 	selected = NULL;
   1366 	selc = 0;
   1367 }
   1368 
   1369 static void
   1370 clear_search()
   1371 {
   1372 	for (size_t i = 0; i < SEARCHLEN; i++)
   1373 		searchq[i] = '\0';
   1374 	searchc = 0;
   1375 }
   1376 
   1377 static char *
   1378 cpstr(char *dest, const char *src)
   1379 {
   1380 	size_t cplen = strlen(src);
   1381 	memcpy(dest, src, cplen);
   1382 	dest[cplen] = '\0';
   1383 	return dest;
   1384 }
   1385 
   1386 static char *
   1387 get_fullpath(char *buf, DirWin *dirwin, size_t index)
   1388 {
   1389 	index = MIN(index, dirwin->filecount-1);
   1390 	index = MAX(index, 0);
   1391 
   1392 	if (strcmp(dirwin->path, "/") == 0)
   1393 		snprintf(buf, PATH_MAX, "/%s", dirwin->winfiles[index].d_name);
   1394 	else
   1395 		snprintf(buf, PATH_MAX, "%s/%s", dirwin->path, dirwin->winfiles[index].d_name);
   1396 
   1397 	return buf;
   1398 }
   1399 
   1400 static char *
   1401 replace_home(char *buf, const char *path)
   1402 {
   1403 	char *envv = "$HOME";
   1404 
   1405 	if ((strstr(path, envv) != NULL)) {
   1406 		cpstr(buf, userhome);
   1407 		strcat(buf, path + strlen(envv));
   1408 	}
   1409 	return buf;
   1410 }
   1411 
   1412 static char *
   1413 get_dirname(char *buf, const char *path)
   1414 {
   1415 	cpstr(buf, path);
   1416 	dirname(buf);
   1417 	return buf;
   1418 }