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