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