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