sfm.c (40033B)
1 /* See LICENSE file for copyright and license details. */ 2 3 #if defined(__linux__) 4 #define _GNU_SOURCE 5 #elif defined(__APPLE__) 6 #define _DARWIN_C_SOURCE 7 #elif defined(__FreeBSD__) 8 #define __BSD_VISIBLE 1 9 #endif 10 #include <sys/types.h> 11 #include <sys/resource.h> 12 #include <sys/stat.h> 13 #include <sys/time.h> 14 #include <sys/wait.h> 15 #if defined(__linux__) 16 #include <sys/inotify.h> 17 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ 18 defined(__APPLE__) 19 #include <sys/event.h> 20 #endif 21 22 #include <dirent.h> 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <grp.h> 26 #include <libgen.h> 27 #include <pthread.h> 28 #include <pwd.h> 29 #include <signal.h> 30 #include <stdarg.h> 31 #include <stdint.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <unistd.h> 37 38 #include "termbox.h" 39 #include "util.h" 40 41 /* macros */ 42 #define MAX_P 4096 43 #define MAX_N 255 44 #define MAX_USRI 32 45 #define MAX_EXT 4 46 #define MAX_STATUS 255 47 #define MAX_LINE 4096 48 #define MAX_USRN 32 49 #define MAX_GRPN 32 50 #define MAX_DTF 32 51 #define CURSOR(x) (x)->direntr[(x)->hdir - 1] 52 53 /* typedef */ 54 typedef struct { 55 char name[MAX_N]; 56 gid_t group; 57 mode_t mode; 58 off_t size; 59 time_t dt; 60 uid_t user; 61 } Entry; 62 63 typedef struct { 64 uint16_t fg; 65 uint16_t bg; 66 } Cpair; 67 68 typedef struct { 69 int pane_id; 70 char dirn[MAX_P]; // dir name cwd 71 char *filter; 72 Entry *direntr; // dir entries 73 int dirc; // dir entries sum 74 int hdir; // highlighted dir 75 int x_srt; 76 int x_end; 77 int firstrow; 78 int parent_firstrow; 79 int parent_row; // FIX 80 Cpair dircol; 81 int inotify_wd; 82 int event_fd; 83 } Pane; 84 85 typedef struct { 86 const char **ext; 87 size_t exlen; 88 const void *v; 89 size_t vlen; 90 } Rule; 91 92 typedef union { 93 uint16_t key; /* one of the TB_KEY_* constants */ 94 uint32_t ch; /* unicode character */ 95 } Evkey; 96 97 typedef union { 98 int i; 99 const void *v; 100 } Arg; 101 102 typedef struct { 103 const Evkey evkey; 104 void (*func)(const Arg *); 105 const Arg arg; 106 } Key; 107 108 /* function declarations */ 109 static void print_tb(const char *, int, int, uint16_t, uint16_t); 110 static void printf_tb(int, int, Cpair, const char *, ...); 111 static void print_status(Cpair, const char *, ...); 112 static void print_xstatus(char, int); 113 static void print_error(char *); 114 static void print_prompt(char *); 115 static void print_info(Pane *, char *); 116 static void print_row(Pane *, size_t, Cpair); 117 static void clear(int, int, int, uint16_t); 118 static void clear_status(void); 119 static void clear_pane(Pane *); 120 static void add_hi(Pane *, size_t); 121 static void rm_hi(Pane *, size_t); 122 static int check_dir(char *); 123 static int sort_name(const void *const, const void *const); 124 static void get_dirp(char *); 125 static char *get_ext(char *); 126 static int get_fdt(char *, time_t); 127 static char *get_fgrp(gid_t); 128 static char *get_fperm(mode_t); 129 static char *get_fsize(off_t); 130 static char *get_fullpath(char *, char *); 131 static char *get_fusr(uid_t); 132 static void get_dirsize(char *, off_t *); 133 static void get_hicol(Cpair *, mode_t); 134 static void delent(const Arg *arg); 135 static void calcdir(const Arg *arg); 136 static void crnd(const Arg *arg); 137 static void crnf(const Arg *arg); 138 static void mv_ver(const Arg *arg); 139 static void mvbk(const Arg *arg); 140 static void mvbtm(const Arg *arg); 141 static void mvfwd(const Arg *arg); 142 static void mvtop(const Arg *arg); 143 static void bkmrk(const Arg *arg); 144 static int get_usrinput(char *, size_t, const char *, ...); 145 static int frules(char *); 146 static int spawn(const void *, size_t, const void *, size_t, char *, int); 147 static int opnf(char *); 148 static int fsev_init(void); 149 static int addwatch(Pane *); 150 static int read_events(void); 151 static void rmwatch(Pane *); 152 static void fsev_shdn(void); 153 static void toggle_df(const Arg *arg); 154 static void start_filter(const Arg *arg); 155 static void start_vmode(const Arg *arg); 156 static void exit_vmode(const Arg *arg); 157 static void start_change(const Arg *arg); 158 static void exit_change(const Arg *arg); 159 static void selup(const Arg *arg); 160 static void seldwn(const Arg *arg); 161 static void selall(const Arg *arg); 162 static void selref(void); 163 static void selynk(const Arg *arg); 164 static void selcalc(void); 165 static void paste(const Arg *arg); 166 static void selmv(const Arg *arg); 167 static void seldel(const Arg *arg); 168 static void init_files(void); 169 static void free_files(void); 170 static void yank(const Arg *arg); 171 static void rname(const Arg *arg); 172 static void chngo(const Arg *arg); 173 static void chngm(const Arg *arg); 174 static void chngf(const Arg *arg); 175 static void dupl(const Arg *arg); 176 static void switch_pane(const Arg *arg); 177 static void quit(const Arg *arg); 178 static void grabkeys(struct tb_event *, Key *, size_t); 179 static void *read_th(void *arg); 180 static void start_ev(void); 181 static void refresh_pane(Pane *); 182 static void set_direntr(Pane *, struct dirent *, DIR *, char *); 183 static int listdir(Pane *); 184 static void t_resize(void); 185 static void get_shell(void); 186 static void opnsh(const Arg *arg); 187 static void set_panes(void); 188 static void draw_frame(void); 189 static void refresh(const Arg *arg); 190 static void start(void); 191 192 /* global variables */ 193 static pthread_t fsev_thread; 194 static Pane panes[2]; 195 static Pane *cpane; 196 static int pane_idx; 197 static char *editor[2]; 198 static char fed[] = "vi"; 199 static char *shell[2]; 200 static char sh[] = "/bin/sh"; 201 static int theight, twidth, hwidth, scrheight; 202 static int *sel_indexes; 203 static size_t sel_len = 0; 204 static char **sel_files; 205 static int cont_vmode = 0; 206 static int cont_change = 0; 207 static pid_t fork_pid = 0, main_pid; 208 #if defined(_SYS_INOTIFY_H) 209 #define READEVSZ 16 210 static int inotify_fd; 211 #elif defined(_SYS_EVENT_H_) 212 #define READEVSZ 0 213 static int kq; 214 struct kevent evlist[2]; /* events we want to monitor */ 215 struct kevent chlist[2]; /* events that were triggered */ 216 static struct timespec gtimeout; 217 #endif 218 #if defined(__linux__) || defined(__FreeBSD__) 219 #define OFF_T "%ld" 220 #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) 221 #define OFF_T "%lld" 222 #endif 223 enum { Left, Right }; /* panes */ 224 enum { Wait, DontWait }; /* spawn forks */ 225 226 /* configuration, allows nested code to access above variables */ 227 #include "config.h" 228 229 /* function implementations */ 230 static void 231 print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg) 232 { 233 while (*str != '\0') { 234 uint32_t uni = 0; 235 str += tb_utf8_char_to_unicode(&uni, str); 236 tb_change_cell(x, y, uni, fg, bg); 237 x++; 238 } 239 } 240 241 static void 242 printf_tb(int x, int y, Cpair col, const char *fmt, ...) 243 { 244 char buf[MAX_LINE]; 245 va_list vl; 246 va_start(vl, fmt); 247 (void)vsnprintf(buf, MAX_LINE, fmt, vl); 248 va_end(vl); 249 print_tb(buf, x, y, col.fg, col.bg); 250 } 251 252 static void 253 print_status(Cpair col, const char *fmt, ...) 254 { 255 char buf[MAX_STATUS]; 256 va_list vl; 257 va_start(vl, fmt); 258 (void)vsnprintf(buf, MAX_STATUS, fmt, vl); 259 va_end(vl); 260 clear_status(); 261 print_tb(buf, 1, theight - 1, col.fg, col.bg); 262 } 263 264 static void 265 print_xstatus(char c, int x) 266 { 267 uint32_t uni = 0; 268 (void)tb_utf8_char_to_unicode(&uni, &c); 269 tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg); 270 } 271 272 static void 273 print_error(char *errmsg) 274 { 275 print_status(cerr, errmsg); 276 } 277 278 static void 279 print_prompt(char *prompt) 280 { 281 print_status(cprompt, prompt); 282 } 283 284 static void 285 print_info(Pane *pane, char *dirsize) 286 { 287 char *sz, *ur, *gr, *dt, *prm; 288 289 dt = ecalloc(MAX_DTF, sizeof(char)); 290 291 prm = get_fperm(CURSOR(pane).mode); 292 ur = get_fusr(CURSOR(pane).user); 293 gr = get_fgrp(CURSOR(pane).group); 294 295 if (get_fdt(dt, CURSOR(pane).dt) < 0) 296 *dt = '\0'; 297 298 if (S_ISREG(CURSOR(pane).mode)) { 299 sz = get_fsize(CURSOR(pane).size); 300 } else { 301 if (dirsize == NULL) { 302 sz = ecalloc(1, sizeof(char)); 303 *sz = '\0'; 304 } else { 305 sz = dirsize; 306 } 307 } 308 309 print_status(cstatus, "%02d/%02d %s %s:%s %s %s", pane->hdir, 310 pane->dirc, prm, ur, gr, dt, sz); 311 312 free(prm); 313 free(ur); 314 free(gr); 315 free(dt); 316 free(sz); 317 } 318 319 static void 320 print_row(Pane *pane, size_t entpos, Cpair col) 321 { 322 int x, y; 323 char *full_str, *rez_pth; 324 char lnk_full[MAX_N]; 325 326 full_str = basename(pane->direntr[entpos].name); 327 x = pane->x_srt; 328 y = entpos - pane->firstrow + 1; 329 330 if (S_ISLNK(pane->direntr[entpos].mode) != 0) { 331 rez_pth = ecalloc(MAX_P, sizeof(char)); 332 if (realpath(pane->direntr[entpos].name, rez_pth) != NULL) { 333 snprintf( 334 lnk_full, MAX_N, "%s -> %s", full_str, rez_pth); 335 full_str = lnk_full; 336 } 337 free(rez_pth); 338 } 339 340 printf_tb(x, y, col, "%*.*s", ~hwidth, hwidth, full_str); 341 } 342 343 static void 344 clear(int sx, int ex, int y, uint16_t bg) 345 { 346 /* clear line from to */ 347 /* x = line number vertical */ 348 /* y = column number horizontal */ 349 int i; 350 for (i = sx; i < ex; i++) { 351 tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg); 352 } 353 } 354 355 static void 356 clear_status(void) 357 { 358 clear(1, twidth - 1, theight - 1, cstatus.bg); 359 } 360 361 static void 362 clear_pane(Pane *pane) 363 { 364 int i, y; 365 y = 0, i = 0; 366 367 while (i < scrheight) { 368 clear(pane->x_srt, pane->x_end, y, TB_DEFAULT); 369 i++; 370 y++; 371 } 372 373 /* draw top line */ 374 for (y = pane->x_srt; y < pane->x_end; ++y) { 375 tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg); 376 } 377 } 378 379 static void 380 add_hi(Pane *pane, size_t entpos) 381 { 382 Cpair col; 383 get_hicol(&col, pane->direntr[entpos].mode); 384 col.fg |= TB_REVERSE | TB_BOLD; 385 col.bg |= TB_REVERSE; 386 print_row(pane, entpos, col); 387 } 388 389 static void 390 rm_hi(Pane *pane, size_t entpos) 391 { 392 Cpair col; 393 get_hicol(&col, pane->direntr[entpos].mode); 394 print_row(pane, entpos, col); 395 } 396 397 static int 398 check_dir(char *path) 399 { 400 DIR *dir; 401 dir = opendir(path); 402 403 if (dir == NULL) { 404 if (errno == ENOTDIR) { 405 return 1; 406 } else { 407 return -1; 408 } 409 } 410 411 if (closedir(dir) < 0) 412 return -1; 413 414 return 0; 415 } 416 417 static int 418 sort_name(const void *const A, const void *const B) 419 { 420 int result; 421 mode_t data1 = (*(Entry *)A).mode; 422 mode_t data2 = (*(Entry *)B).mode; 423 424 if (data1 < data2) { 425 return -1; 426 } else if (data1 == data2) { 427 result = strncmp((*(Entry *)A).name, (*(Entry *)B).name, MAX_N); 428 return result; 429 } else { 430 return 1; 431 } 432 } 433 434 static void 435 get_dirp(char *cdir) 436 { 437 int counter, len, i; 438 439 counter = 0; 440 len = strnlen(cdir, MAX_P); 441 if (len == 1) 442 return; 443 444 for (i = len - 1; i > 1; i--) { 445 if (cdir[i] == '/') 446 break; 447 else 448 counter++; 449 } 450 451 cdir[len - counter - 1] = '\0'; 452 } 453 454 static char * 455 get_ext(char *str) 456 { 457 char *ext; 458 char dot; 459 size_t counter, len, i; 460 461 dot = '.'; 462 counter = 0; 463 len = strnlen(str, MAX_N); 464 465 for (i = len - 1; i > 0; i--) { 466 if (str[i] == dot) { 467 break; 468 } else { 469 counter++; 470 } 471 } 472 473 ext = ecalloc(MAX_EXT + 1, sizeof(char)); 474 strncpy(ext, &str[len - counter], MAX_EXT); 475 ext[MAX_EXT] = '\0'; 476 return ext; 477 } 478 479 static int 480 get_fdt(char *result, time_t status) 481 { 482 struct tm lt; 483 localtime_r(&status, <); 484 return strftime(result, MAX_DTF, dtfmt, <); 485 } 486 487 static char * 488 get_fgrp(gid_t status) 489 { 490 char *result; 491 struct group *gr; 492 493 result = ecalloc(MAX_GRPN, sizeof(char)); 494 gr = getgrgid(status); 495 if (gr == NULL) 496 (void)snprintf(result, MAX_GRPN, "%u", status); 497 else 498 strncpy(result, gr->gr_name, MAX_GRPN); 499 500 result[MAX_GRPN - 1] = '\0'; 501 return result; 502 } 503 504 static char * 505 get_fperm(mode_t mode) 506 { 507 char *buf; 508 size_t i; 509 510 const char chars[] = "rwxrwxrwx"; 511 buf = ecalloc(11, sizeof(char)); 512 513 if (S_ISDIR(mode)) 514 buf[0] = 'd'; 515 else if (S_ISREG(mode)) 516 buf[0] = '-'; 517 else if (S_ISLNK(mode)) 518 buf[0] = 'l'; 519 else if (S_ISBLK(mode)) 520 buf[0] = 'b'; 521 else if (S_ISCHR(mode)) 522 buf[0] = 'c'; 523 else if (S_ISFIFO(mode)) 524 buf[0] = 'p'; 525 else if (S_ISSOCK(mode)) 526 buf[0] = 's'; 527 else 528 buf[0] = '?'; 529 530 for (i = 1; i < 10; i++) { 531 buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-'; 532 } 533 buf[10] = '\0'; 534 535 return buf; 536 } 537 538 static char * 539 get_fsize(off_t size) 540 { 541 char *result; /* need to be freed */ 542 char unit; 543 int result_len; 544 int counter; 545 546 counter = 0; 547 result_len = 6; /* 9999X/0 */ 548 result = ecalloc(result_len, sizeof(char)); 549 550 while (size >= 1000) { 551 size /= 1024; 552 ++counter; 553 } 554 555 switch (counter) { 556 case 0: 557 unit = 'B'; 558 break; 559 case 1: 560 unit = 'K'; 561 break; 562 case 2: 563 unit = 'M'; 564 break; 565 case 3: 566 unit = 'G'; 567 break; 568 case 4: 569 unit = 'T'; 570 break; 571 default: 572 unit = '?'; 573 } 574 575 if (snprintf(result, result_len, OFF_T "%c", size, unit) < 0) 576 strncat(result, "???", result_len); 577 578 return result; 579 } 580 581 static char * 582 get_fullpath(char *first, char *second) 583 { 584 char *full_path; 585 586 full_path = ecalloc(MAX_P, sizeof(char)); 587 588 if (strncmp(first, "/", MAX_P) == 0) 589 (void)snprintf(full_path, MAX_P, "/%s", second); 590 else 591 (void)snprintf(full_path, MAX_P, "%s/%s", first, second); 592 593 return full_path; 594 } 595 596 static char * 597 get_fusr(uid_t status) 598 { 599 char *result; 600 struct passwd *pw; 601 602 result = ecalloc(MAX_USRN, sizeof(char)); 603 pw = getpwuid(status); 604 if (pw == NULL) 605 (void)snprintf(result, MAX_USRN, "%u", status); 606 else 607 strncpy(result, pw->pw_name, MAX_USRN); 608 609 result[MAX_USRN - 1] = '\0'; 610 return result; 611 } 612 613 static void 614 get_dirsize(char *fullpath, off_t *fullsize) 615 { 616 DIR *dir; 617 char *ent_full; 618 mode_t mode; 619 struct dirent *entry; 620 struct stat status; 621 622 dir = opendir(fullpath); 623 if (dir == NULL) { 624 return; 625 } 626 627 while ((entry = readdir(dir)) != 0) { 628 if ((strncmp(entry->d_name, ".", 2) == 0 || 629 strncmp(entry->d_name, "..", 3) == 0)) 630 continue; 631 632 ent_full = get_fullpath(fullpath, entry->d_name); 633 if (lstat(ent_full, &status) == 0) { 634 mode = status.st_mode; 635 if (S_ISDIR(mode)) { 636 get_dirsize(ent_full, fullsize); 637 free(ent_full); 638 } else { 639 *fullsize += status.st_size; 640 free(ent_full); 641 } 642 } 643 } 644 645 closedir(dir); 646 clear_status(); 647 } 648 649 static void 650 get_hicol(Cpair *col, mode_t mode) 651 { 652 switch (mode & S_IFMT) { 653 case S_IFREG: 654 *col = cfile; 655 if ((S_IXUSR | S_IXGRP | S_IXOTH) & mode) 656 *col = cexec; 657 break; 658 case S_IFDIR: 659 *col = cdir; 660 break; 661 case S_IFLNK: 662 *col = clnk; 663 break; 664 case S_IFBLK: 665 *col = cblk; 666 break; 667 case S_IFCHR: 668 *col = cchr; 669 break; 670 case S_IFIFO: 671 *col = cifo; 672 break; 673 case S_IFSOCK: 674 *col = csock; 675 break; 676 default: 677 *col = cother; 678 break; 679 } 680 } 681 682 static void 683 delent(const Arg *arg) 684 { 685 if (cpane->dirc < 1) 686 return; 687 char *inp_conf; 688 689 inp_conf = ecalloc(delconf_len, sizeof(char)); 690 if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?", 691 delconf) < 0) || 692 (strncmp(inp_conf, delconf, delconf_len) != 0)) { 693 free(inp_conf); 694 return; /* canceled by user or wrong inp_conf */ 695 } 696 free(inp_conf); 697 698 char *tmp[1]; 699 tmp[0] = CURSOR(cpane).name; 700 if (spawn(rm_cmd, rm_cmd_len, tmp, 1, NULL, DontWait) < 0) { 701 print_error(strerror(errno)); 702 return; 703 } 704 } 705 706 static void 707 calcdir(const Arg *arg) 708 { 709 if (cpane->dirc < 1) 710 return; 711 if (!S_ISDIR(CURSOR(cpane).mode)) 712 return; 713 714 off_t *fullsize; 715 char *csize; 716 717 fullsize = ecalloc(1, sizeof(off_t)); 718 get_dirsize(CURSOR(cpane).name, fullsize); 719 csize = get_fsize(*fullsize); 720 721 CURSOR(cpane).size = *fullsize; 722 print_info(cpane, csize); 723 free(fullsize); 724 } 725 726 static void 727 crnd(const Arg *arg) 728 { 729 char *user_input, *path; 730 731 user_input = ecalloc(MAX_USRI, sizeof(char)); 732 if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) { 733 free(user_input); 734 return; 735 } 736 737 path = ecalloc(MAX_P, sizeof(char)); 738 if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) { 739 free(user_input); 740 free(path); 741 return; 742 } 743 744 PERROR(mkdir(path, ndir_perm) < 0); 745 746 free(user_input); 747 free(path); 748 } 749 750 static void 751 crnf(const Arg *arg) 752 { 753 char *user_input, *path; 754 int rf; 755 756 user_input = ecalloc(MAX_USRI, sizeof(char)); 757 if (get_usrinput(user_input, MAX_USRI, "new file") < 0) { 758 free(user_input); 759 return; 760 } 761 762 path = ecalloc(MAX_P, sizeof(char)); 763 if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) { 764 free(user_input); 765 free(path); 766 return; 767 } 768 769 rf = open(path, O_CREAT | O_EXCL, nf_perm); 770 771 if (rf < 0) 772 print_error(strerror(errno)); 773 else if (close(rf) < 0) 774 print_error(strerror(errno)); 775 776 free(user_input); 777 free(path); 778 } 779 static void 780 mv_ver(const Arg *arg) 781 { 782 783 if (cpane->dirc < 1) 784 return; 785 if (cpane->hdir - arg->i < 1) /* first line */ 786 return; 787 788 if (cpane->hdir - arg->i > cpane->dirc) /* last line */ 789 return; 790 791 if (cpane->firstrow > 0 && arg->i > 0 && 792 cpane->hdir <= (cpane->firstrow + arg->i)) { /* scroll up */ 793 cpane->firstrow = cpane->firstrow - arg->i; 794 rm_hi(cpane, cpane->hdir - 1); 795 cpane->hdir = cpane->hdir - arg->i; 796 refresh_pane(cpane); 797 add_hi(cpane, cpane->hdir - 1); 798 return; 799 } 800 801 if (cpane->hdir - cpane->firstrow >= scrheight + arg->i && 802 arg->i < 0) { /* scroll down */ 803 cpane->firstrow = cpane->firstrow - arg->i; 804 rm_hi(cpane, cpane->hdir - 1); 805 cpane->hdir = cpane->hdir - arg->i; 806 refresh_pane(cpane); 807 add_hi(cpane, cpane->hdir - 1); 808 return; 809 } 810 811 rm_hi(cpane, cpane->hdir - 1); 812 cpane->hdir = cpane->hdir - arg->i; 813 add_hi(cpane, cpane->hdir - 1); 814 print_info(cpane, NULL); 815 } 816 817 static void 818 mvbk(const Arg *arg) 819 { 820 if (cpane->dirn[0] == '/' && cpane->dirn[1] == '\0') { /* cwd = / */ 821 return; 822 } 823 824 get_dirp(cpane->dirn); 825 if (check_dir(cpane->dirn) < 0) { 826 print_error(strerror(errno)); 827 return; 828 } 829 830 cpane->firstrow = cpane->parent_firstrow; 831 cpane->hdir = cpane->parent_row; 832 PERROR(listdir(cpane) < 0); 833 cpane->parent_firstrow = 0; 834 cpane->parent_row = 1; 835 } 836 837 static void 838 mvbtm(const Arg *arg) 839 { 840 if (cpane->dirc < 1) 841 return; 842 if (cpane->dirc > scrheight) { 843 rm_hi(cpane, cpane->hdir - 1); 844 cpane->hdir = cpane->dirc; 845 cpane->firstrow = cpane->dirc - scrheight + 1; 846 refresh_pane(cpane); 847 add_hi(cpane, cpane->hdir - 1); 848 } else { 849 rm_hi(cpane, cpane->hdir - 1); 850 cpane->hdir = cpane->dirc; 851 add_hi(cpane, cpane->hdir - 1); 852 } 853 print_info(cpane, NULL); 854 } 855 856 static void 857 mvfwd(const Arg *arg) 858 { 859 if (cpane->dirc < 1) 860 return; 861 int s; 862 863 switch (check_dir(CURSOR(cpane).name)) { 864 case 0: 865 strncpy(cpane->dirn, CURSOR(cpane).name, MAX_P); 866 cpane->parent_row = cpane->hdir; 867 cpane->parent_firstrow = cpane->firstrow; 868 cpane->hdir = 1; 869 cpane->firstrow = 0; 870 PERROR(listdir(cpane) < 0); 871 break; 872 case 1: /* not a directory open file */ 873 tb_shutdown(); 874 s = opnf(CURSOR(cpane).name); 875 if (tb_init() != 0) 876 die("tb_init"); 877 t_resize(); 878 if (s < 0) 879 print_error("process failed non-zero exit"); 880 break; 881 case -1: /* failed to open directory */ 882 print_error(strerror(errno)); 883 } 884 } 885 886 static void 887 mvtop(const Arg *arg) 888 { 889 if (cpane->dirc < 1) 890 return; 891 if (cpane->dirc > scrheight) { 892 rm_hi(cpane, cpane->hdir - 1); 893 cpane->hdir = 1; 894 cpane->firstrow = 0; 895 refresh_pane(cpane); 896 add_hi(cpane, cpane->hdir - 1); 897 } else { 898 rm_hi(cpane, cpane->hdir - 1); 899 cpane->hdir = 1; 900 add_hi(cpane, cpane->hdir - 1); 901 print_info(cpane, NULL); 902 } 903 } 904 905 static void 906 bkmrk(const Arg *arg) 907 { 908 if (check_dir((char *)arg->v) != 0) { 909 print_error(strerror(errno)); 910 return; 911 } 912 913 strncpy(cpane->dirn, (char *)arg->v, MAX_P); 914 cpane->firstrow = 0; 915 cpane->parent_row = 1; 916 cpane->hdir = 1; 917 PERROR(listdir(cpane) < 0); 918 } 919 920 static int 921 get_usrinput(char *result, size_t max_chars, const char *fmt, ...) 922 { 923 char msg[MAX_N]; 924 size_t i, cpos, startat; 925 struct tb_event fev; 926 va_list vl; 927 928 i = 0; 929 cpos = 1; 930 931 va_start(vl, fmt); 932 startat = vsnprintf(msg, MAX_N, fmt, vl) + 1; 933 va_end(vl); 934 935 clear_status(); 936 print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg); 937 tb_set_cursor(startat + 1, theight - 1); 938 tb_present(); 939 940 while (tb_poll_event(&fev) != 0) { 941 switch (fev.type) { 942 case TB_EVENT_KEY: 943 if (fev.key == TB_KEY_ESC) { 944 tb_set_cursor(-1, -1); 945 clear_status(); 946 return -1; 947 } 948 949 if (fev.key == TB_KEY_BACKSPACE || 950 fev.key == TB_KEY_BACKSPACE2) { 951 if (BETWEEN(cpos, 2, max_chars)) { 952 result[i - 1] = '\0'; 953 cpos--; 954 i--; 955 print_xstatus(' ', startat + cpos); 956 tb_set_cursor( 957 startat + cpos, theight - 1); 958 } 959 960 } else if (fev.key == TB_KEY_ENTER) { 961 tb_set_cursor(-1, -1); 962 result[cpos - 1] = '\0'; 963 return 0; 964 965 } else if (fev.key) { /* disable other TB_KEY_* */ 966 break; 967 968 } else { 969 if (cpos < max_chars) { 970 print_xstatus( 971 (char)fev.ch, (startat + cpos)); 972 result[i] = (char)fev.ch; 973 tb_set_cursor((startat + cpos + 1), 974 theight - 1); 975 cpos++; 976 i++; 977 } 978 } 979 980 tb_present(); 981 break; 982 983 case TB_EVENT_RESIZE: 984 t_resize(); 985 clear_status(); 986 print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg); 987 print_tb(result, startat + 1, theight - 1, cstatus.fg, 988 cstatus.bg); 989 tb_present(); 990 break; 991 992 default: 993 return -1; 994 } 995 } 996 997 return -1; 998 } 999 1000 static int 1001 frules(char *ex) 1002 { 1003 size_t c, d; 1004 1005 for (c = 0; c < LEN(rules); c++) 1006 for (d = 0; d < rules[c].exlen; d++) 1007 if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0) 1008 return c; 1009 return -1; 1010 } 1011 1012 static int 1013 spawn(const void *com_argv, size_t com_argc, const void *f_argv, size_t f_argc, 1014 char *fn, int waiting) 1015 { 1016 int ws; 1017 size_t argc; 1018 pid_t r; 1019 1020 argc = com_argc + f_argc + 2; 1021 char *argv[argc]; 1022 1023 memcpy(argv, com_argv, com_argc * sizeof(char *)); /* command */ 1024 memcpy(&argv[com_argc], f_argv, f_argc * sizeof(char *)); /* files */ 1025 1026 argv[argc - 2] = fn; 1027 argv[argc - 1] = NULL; 1028 1029 fork_pid = fork(); 1030 switch (fork_pid) { 1031 case -1: 1032 return -1; 1033 case 0: 1034 execvp(argv[0], argv); 1035 exit(EXIT_SUCCESS); 1036 default: 1037 if (waiting == Wait) { 1038 while ((r = waitpid(fork_pid, &ws, 0)) == -1 && 1039 errno == EINTR) 1040 continue; 1041 if (r == -1) 1042 return -1; 1043 if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0)) 1044 return -1; 1045 } 1046 } 1047 fork_pid = 0; /* enable th_handler() */ 1048 return 0; 1049 } 1050 1051 static int 1052 opnf(char *fn) 1053 { 1054 char *ex; 1055 int c; 1056 1057 ex = get_ext(fn); 1058 c = frules(ex); 1059 free(ex); 1060 1061 if (c < 0) /* extension not found open in editor */ 1062 return spawn(editor, 1, NULL, 0, fn, Wait); 1063 else 1064 return spawn( 1065 (char **)rules[c].v, rules[c].vlen, NULL, 0, fn, Wait); 1066 } 1067 1068 static void 1069 opnsh(const Arg *arg) 1070 { 1071 int s; 1072 1073 tb_shutdown(); 1074 chdir(cpane->dirn); 1075 s = spawn(shell, 1, NULL, 0, NULL, Wait); 1076 if (tb_init() != 0) 1077 die("tb_init"); 1078 t_resize(); 1079 if (s < 0) 1080 print_error("process failed non-zero exit"); 1081 } 1082 1083 static int 1084 fsev_init(void) 1085 { 1086 #if defined(_SYS_INOTIFY_H) 1087 inotify_fd = inotify_init(); 1088 if (inotify_fd < 0) 1089 return -1; 1090 #elif defined(_SYS_EVENT_H_) 1091 gtimeout.tv_sec = 1; 1092 kq = kqueue(); 1093 if (kq < 0) 1094 return -1; 1095 #endif 1096 return 0; 1097 } 1098 1099 static int 1100 addwatch(Pane *pane) 1101 { 1102 #if defined(_SYS_INOTIFY_H) 1103 return pane->inotify_wd = inotify_add_watch(inotify_fd, pane->dirn, 1104 IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | 1105 IN_ATTRIB | IN_DELETE | IN_DELETE_SELF | 1106 IN_MOVE_SELF); 1107 #elif defined(_SYS_EVENT_H_) 1108 pane->event_fd = open(pane->dirn, O_RDONLY); 1109 if (pane->event_fd < 0) 1110 return pane->event_fd; 1111 EV_SET(&evlist[pane->pane_id], pane->event_fd, EVFILT_VNODE, 1112 EV_ADD | EV_CLEAR, 1113 NOTE_DELETE | NOTE_EXTEND | NOTE_LINK | NOTE_RENAME | 1114 NOTE_ATTRIB | NOTE_REVOKE | NOTE_WRITE, 1115 0, NULL); 1116 return 0; 1117 #endif 1118 } 1119 1120 static int 1121 read_events(void) 1122 { 1123 #if defined(_SYS_INOTIFY_H) 1124 char *p; 1125 ssize_t r; 1126 struct inotify_event *event; 1127 const size_t events = 32; 1128 const size_t evbuflen = 1129 events * (sizeof(struct inotify_event) + MAX_N + 1); 1130 char buf[evbuflen]; 1131 1132 if (cpane->inotify_wd < 0) 1133 return -1; 1134 r = read(inotify_fd, buf, evbuflen); 1135 if (r <= 0) 1136 return r; 1137 1138 for (p = buf; p < buf + r;) { 1139 event = (struct inotify_event *)p; 1140 if (!event->wd) 1141 break; 1142 if (event->mask) { 1143 return r; 1144 } 1145 1146 p += sizeof(struct inotify_event) + event->len; 1147 } 1148 #elif defined(_SYS_EVENT_H_) 1149 return kevent(kq, evlist, 2, chlist, 2, >imeout); 1150 #endif 1151 return -1; 1152 } 1153 1154 static void 1155 rmwatch(Pane *pane) 1156 { 1157 #if defined(_SYS_INOTIFY_H) 1158 if (pane->inotify_wd >= 0) 1159 inotify_rm_watch(inotify_fd, pane->inotify_wd); 1160 #elif defined(_SYS_EVENT_H_) 1161 close(pane->event_fd); 1162 #endif 1163 } 1164 1165 static void 1166 fsev_shdn(void) 1167 { 1168 pthread_cancel(fsev_thread); 1169 #if defined(__linux__) 1170 pthread_join(fsev_thread, NULL); 1171 #endif 1172 rmwatch(&panes[Left]); 1173 rmwatch(&panes[Right]); 1174 #if defined(_SYS_INOTIFY_H) 1175 close(inotify_fd); 1176 #elif defined(_SYS_EVENT_H_) 1177 close(kq); 1178 #endif 1179 } 1180 1181 static void 1182 toggle_df(const Arg *arg) 1183 { 1184 show_dotfiles = !show_dotfiles; 1185 PERROR(listdir(&panes[Left])); 1186 PERROR(listdir(&panes[Right])); 1187 tb_present(); 1188 } 1189 1190 static void 1191 start_filter(const Arg *arg) 1192 { 1193 if (cpane->dirc < 1) 1194 return; 1195 char *user_input; 1196 user_input = ecalloc(MAX_USRI, sizeof(char)); 1197 if (get_usrinput(user_input, MAX_USRI, "filter") < 0) { 1198 free(user_input); 1199 return; 1200 } 1201 cpane->filter = user_input; 1202 if (listdir(cpane) < 0) 1203 print_error("no match"); 1204 cpane->filter = NULL; 1205 free(user_input); 1206 } 1207 1208 static void 1209 start_vmode(const Arg *arg) 1210 { 1211 if (cpane->dirc < 1) 1212 return; 1213 struct tb_event fev; 1214 if (sel_indexes != NULL) { 1215 free(sel_indexes); 1216 sel_indexes = NULL; 1217 } 1218 1219 sel_indexes = ecalloc(cpane->dirc, sizeof(size_t)); 1220 sel_indexes[0] = cpane->hdir; 1221 cont_vmode = 0; 1222 print_prompt("-- VISUAL --"); 1223 tb_present(); 1224 while (tb_poll_event(&fev) != 0) { 1225 switch (fev.type) { 1226 case TB_EVENT_KEY: 1227 grabkeys(&fev, vkeys, vkeyslen); 1228 if (cont_vmode == -1) 1229 return; 1230 tb_present(); 1231 break; 1232 } 1233 } 1234 } 1235 1236 static void 1237 exit_vmode(const Arg *arg) 1238 { 1239 refresh_pane(cpane); 1240 add_hi(cpane, cpane->hdir - 1); 1241 cont_vmode = -1; 1242 } 1243 1244 static void 1245 start_change(const Arg *arg) 1246 { 1247 if (cpane->dirc < 1) 1248 return; 1249 struct tb_event fev; 1250 1251 cont_change = 0; 1252 print_prompt("c [womf]"); 1253 tb_present(); 1254 while (tb_poll_event(&fev) != 0) { 1255 switch (fev.type) { 1256 case TB_EVENT_KEY: 1257 grabkeys(&fev, ckeys, ckeyslen); 1258 if (cont_change == -1) 1259 return; 1260 tb_present(); 1261 break; 1262 } 1263 } 1264 } 1265 1266 static void 1267 exit_change(const Arg *arg) 1268 { 1269 cont_change = -1; 1270 print_info(cpane, NULL); 1271 } 1272 1273 static void 1274 selup(const Arg *arg) 1275 { 1276 mv_ver(arg); 1277 print_prompt("-- VISUAL --"); 1278 int index = abs(cpane->hdir - sel_indexes[0]); 1279 1280 if (cpane->hdir < sel_indexes[0]) { 1281 sel_indexes[index] = cpane->hdir; 1282 add_hi(cpane, sel_indexes[index]); 1283 } else if (index < cpane->dirc) { 1284 sel_indexes[index + 1] = 0; 1285 } 1286 if (cpane->dirc >= scrheight || 1287 cpane->hdir <= 1) { /* rehighlight all if scrolling */ 1288 selref(); 1289 } 1290 } 1291 1292 static void 1293 seldwn(const Arg *arg) 1294 { 1295 mv_ver(arg); 1296 print_prompt("-- VISUAL --"); 1297 int index = abs(cpane->hdir - sel_indexes[0]); 1298 1299 if (cpane->hdir > sel_indexes[0]) { 1300 sel_indexes[index] = cpane->hdir; 1301 add_hi(cpane, sel_indexes[index] - 2); 1302 } else { 1303 sel_indexes[index + 1] = 0; 1304 } 1305 if (cpane->dirc >= scrheight || 1306 cpane->hdir >= cpane->dirc) { /* rehighlight all if scrolling */ 1307 selref(); 1308 } 1309 } 1310 1311 static void 1312 selall(const Arg *arg) 1313 { 1314 int i; 1315 for (i = 0; i < cpane->dirc; i++) { 1316 sel_indexes[i] = i + 1; 1317 } 1318 selref(); 1319 } 1320 1321 static void 1322 selref(void) 1323 { 1324 int i; 1325 for (i = 0; i < cpane->dirc; i++) { 1326 if (sel_indexes[i] < (scrheight + cpane->firstrow) && 1327 sel_indexes[i] > 1328 cpane->firstrow) { /* checks if in the frame of the directories */ 1329 add_hi(cpane, sel_indexes[i] - 1); 1330 } 1331 } 1332 } 1333 1334 static void 1335 selcalc(void) 1336 { 1337 int j; 1338 sel_len = 0; 1339 1340 for (j = 0; j < cpane->dirc; j++) { /* calculate used selection size */ 1341 if (sel_indexes[j] != 0) 1342 sel_len++; 1343 else 1344 break; 1345 } 1346 } 1347 1348 static void 1349 free_files(void) 1350 { 1351 size_t i; 1352 1353 if (sel_files != NULL) { 1354 for (i = 0; i < sel_len; i++) { 1355 free(sel_files[i]); 1356 sel_files[i] = NULL; 1357 } 1358 free(sel_files); 1359 sel_files = NULL; 1360 } 1361 } 1362 1363 static void 1364 init_files(void) 1365 { 1366 size_t i; 1367 free_files(); 1368 1369 selcalc(); 1370 sel_files = ecalloc(sel_len, sizeof(char *)); 1371 1372 for (i = 0; i < sel_len; i++) { 1373 sel_files[i] = ecalloc(MAX_P, sizeof(char)); 1374 strncpy(sel_files[i], cpane->direntr[sel_indexes[i] - 1].name, 1375 MAX_P); 1376 } 1377 } 1378 1379 static void 1380 selynk(const Arg *arg) 1381 { 1382 init_files(); 1383 refresh_pane(cpane); 1384 add_hi(cpane, cpane->hdir - 1); 1385 print_status(cprompt, "%zu files are yanked", sel_len); 1386 cont_vmode = -1; 1387 } 1388 1389 static void 1390 seldel(const Arg *arg) 1391 { 1392 char *inp_conf; 1393 1394 inp_conf = ecalloc(delconf_len, sizeof(char)); 1395 if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?", 1396 delconf) < 0) || 1397 (strncmp(inp_conf, delconf, delconf_len) != 0)) { 1398 free(inp_conf); 1399 return; /* canceled by user or wrong inp_conf */ 1400 } 1401 free(inp_conf); 1402 1403 init_files(); 1404 1405 if (spawn(rm_cmd, rm_cmd_len, sel_files, sel_len, NULL, DontWait) < 0) 1406 print_error(strerror(errno)); 1407 else 1408 print_status(cprompt, "%zu files are deleted", sel_len); 1409 1410 free_files(); 1411 cont_vmode = -1; 1412 } 1413 1414 static void 1415 paste(const Arg *arg) 1416 { 1417 if (sel_files == NULL) { 1418 print_error("nothing to paste"); 1419 return; 1420 } 1421 1422 if (spawn(cp_cmd, cp_cmd_len, sel_files, sel_len, cpane->dirn, 1423 DontWait) < 0) 1424 print_error(strerror(errno)); 1425 else 1426 print_status(cprompt, "%zu files are copied", sel_len); 1427 1428 free_files(); 1429 } 1430 1431 static void 1432 selmv(const Arg *arg) 1433 { 1434 if (sel_files == NULL) { 1435 print_error("nothing to move"); 1436 return; 1437 } 1438 1439 if (spawn(mv_cmd, mv_cmd_len, sel_files, sel_len, cpane->dirn, 1440 DontWait) < 0) 1441 print_error(strerror(errno)); 1442 else 1443 print_status(cprompt, "%zu files are moved", sel_len); 1444 1445 free_files(); 1446 } 1447 1448 static void 1449 rname(const Arg *arg) 1450 { 1451 if (cpane->dirc < 1) 1452 return; 1453 char new_name[MAX_P]; 1454 char *input_name; 1455 1456 input_name = ecalloc(MAX_N, sizeof(char)); 1457 1458 if (get_usrinput(input_name, MAX_N, "rename: %s", 1459 basename(CURSOR(cpane).name)) < 0) { 1460 exit_change(0); 1461 free(input_name); 1462 return; 1463 } 1464 1465 if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) { 1466 free(input_name); 1467 print_error(strerror(errno)); 1468 return; 1469 } 1470 1471 char *rename_cmd[] = { "mv", CURSOR(cpane).name, new_name }; 1472 PERROR(spawn(rename_cmd, 3, NULL, 0, NULL, DontWait) < 0); 1473 1474 free(input_name); 1475 exit_change(0); 1476 } 1477 1478 static void 1479 chngo(const Arg *arg) 1480 { 1481 if (cpane->dirc < 1) 1482 return; 1483 char *input_og; 1484 char *tmp[1]; 1485 1486 input_og = ecalloc(MAX_N, sizeof(char)); 1487 1488 if (get_usrinput(input_og, MAX_N, "OWNER:GROUP %s", 1489 basename(CURSOR(cpane).name)) < 0) { 1490 exit_change(0); 1491 free(input_og); 1492 return; 1493 } 1494 1495 tmp[0] = input_og; 1496 if (spawn(chown_cmd, chown_cmd_len, tmp, 1, CURSOR(cpane).name, 1497 DontWait) < 0) { 1498 print_error(strerror(errno)); 1499 return; 1500 } 1501 1502 free(input_og); 1503 exit_change(0); 1504 } 1505 1506 static void 1507 chngm(const Arg *arg) 1508 { 1509 if (cpane->dirc < 1) 1510 return; 1511 char *input_og; 1512 char *tmp[1]; 1513 1514 input_og = ecalloc(MAX_N, sizeof(char)); 1515 1516 if (get_usrinput(input_og, MAX_N, "chmod %s", 1517 basename(CURSOR(cpane).name)) < 0) { 1518 exit_change(0); 1519 free(input_og); 1520 return; 1521 } 1522 1523 tmp[0] = input_og; 1524 if (spawn(chmod_cmd, chmod_cmd_len, tmp, 1, CURSOR(cpane).name, 1525 DontWait) < 0) { 1526 print_error(strerror(errno)); 1527 return; 1528 } 1529 1530 free(input_og); 1531 exit_change(0); 1532 } 1533 1534 static void 1535 chngf(const Arg *arg) 1536 { 1537 if (cpane->dirc < 1) 1538 return; 1539 char *input_og; 1540 char *tmp[1]; 1541 1542 input_og = ecalloc(MAX_N, sizeof(char)); 1543 1544 if (get_usrinput(input_og, MAX_N, CHFLAG " %s", 1545 basename(CURSOR(cpane).name)) < 0) { 1546 exit_change(0); 1547 free(input_og); 1548 return; 1549 } 1550 1551 tmp[0] = input_og; 1552 if (spawn(chflags_cmd, chflags_cmd_len, tmp, 1, CURSOR(cpane).name, 1553 DontWait) < 0) { 1554 print_error(strerror(errno)); 1555 return; 1556 } 1557 1558 free(input_og); 1559 exit_change(0); 1560 } 1561 1562 static void 1563 dupl(const Arg *arg) 1564 { 1565 if (cpane->dirc < 1) 1566 return; 1567 char new_name[MAX_P]; 1568 char *input_name; 1569 1570 input_name = ecalloc(MAX_N, sizeof(char)); 1571 1572 if (get_usrinput(input_name, MAX_N, "new name: %s", 1573 basename(CURSOR(cpane).name)) < 0) { 1574 free(input_name); 1575 return; 1576 } 1577 1578 if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) { 1579 free(input_name); 1580 print_error(strerror(errno)); 1581 return; 1582 } 1583 1584 char *tmp[1]; 1585 tmp[0] = CURSOR(cpane).name; 1586 if (spawn(cp_cmd, cp_cmd_len, tmp, 1, new_name, DontWait) < 0) { 1587 print_error(strerror(errno)); 1588 return; 1589 } 1590 1591 free(input_name); 1592 } 1593 1594 static void 1595 yank(const Arg *arg) 1596 { 1597 if (cpane->dirc < 1) 1598 return; 1599 1600 free_files(); 1601 sel_len = 1; 1602 sel_files = ecalloc(sel_len, sizeof(char *)); 1603 sel_files[0] = ecalloc(MAX_P, sizeof(char)); 1604 strncpy(sel_files[0], CURSOR(cpane).name, MAX_P); 1605 print_status(cprompt, "1 file is yanked", sel_len); 1606 } 1607 1608 static void 1609 switch_pane(const Arg *arg) 1610 { 1611 if (cpane->dirc > 0) 1612 rm_hi(cpane, cpane->hdir - 1); 1613 cpane = &panes[pane_idx ^= 1]; 1614 if (cpane->dirc > 0) { 1615 add_hi(cpane, cpane->hdir - 1); 1616 print_info(cpane, NULL); 1617 } else { 1618 clear_status(); 1619 } 1620 } 1621 1622 static void 1623 quit(const Arg *arg) 1624 { 1625 if (cont_vmode == -1) { /* check if selection was allocated */ 1626 free(sel_indexes); 1627 if (sel_files != NULL) 1628 free_files(); 1629 } 1630 free(panes[Left].direntr); 1631 free(panes[Right].direntr); 1632 fsev_shdn(); 1633 tb_shutdown(); 1634 exit(EXIT_SUCCESS); 1635 } 1636 1637 static void 1638 grabkeys(struct tb_event *event, Key *key, size_t max_keys) 1639 { 1640 size_t i; 1641 1642 for (i = 0; i < max_keys; i++) { 1643 if (event->ch != 0) { 1644 if (event->ch == key[i].evkey.ch) { 1645 key[i].func(&key[i].arg); 1646 return; 1647 } 1648 } else if (event->key != 0) { 1649 if (event->key == key[i].evkey.key) { 1650 key[i].func(&key[i].arg); 1651 return; 1652 } 1653 } 1654 } 1655 } 1656 1657 void * 1658 read_th(void *arg) 1659 { 1660 struct timespec tim; 1661 tim.tv_sec = 0; 1662 tim.tv_nsec = 5000000L; /* 0.005 sec */ 1663 1664 while (1) 1665 if (read_events() > READEVSZ) { 1666 kill(main_pid, SIGUSR1); 1667 nanosleep(&tim, NULL); 1668 } 1669 return arg; 1670 } 1671 1672 static void 1673 start_ev(void) 1674 { 1675 struct tb_event ev; 1676 1677 while (tb_poll_event(&ev) != 0) { 1678 switch (ev.type) { 1679 case TB_EVENT_KEY: 1680 grabkeys(&ev, nkeys, nkeyslen); 1681 tb_present(); 1682 break; 1683 case TB_EVENT_RESIZE: 1684 t_resize(); 1685 break; 1686 default: 1687 break; 1688 } 1689 } 1690 tb_shutdown(); 1691 } 1692 1693 static void 1694 refresh_pane(Pane *pane) 1695 { 1696 size_t y, dyn_max, start_from; 1697 hwidth = (twidth / 2) - 4; 1698 Cpair col; 1699 1700 y = 1; 1701 start_from = pane->firstrow; 1702 dyn_max = MIN(pane->dirc, (scrheight - 1) + pane->firstrow); 1703 1704 /* print each entry in directory */ 1705 while (start_from < dyn_max) { 1706 get_hicol(&col, pane->direntr[start_from].mode); 1707 print_row(pane, start_from, col); 1708 start_from++; 1709 y++; 1710 } 1711 1712 if (pane->dirc > 0) 1713 print_info(pane, NULL); 1714 else 1715 clear_status(); 1716 1717 /* print current directory title */ 1718 pane->dircol.fg |= TB_BOLD; 1719 printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn); 1720 } 1721 1722 static void 1723 set_direntr(Pane *pane, struct dirent *entry, DIR *dir, char *filter) 1724 { 1725 int i; 1726 char *tmpfull; 1727 struct stat status; 1728 1729 #define ADD_ENTRY \ 1730 tmpfull = get_fullpath(pane->dirn, entry->d_name); \ 1731 strncpy(pane->direntr[i].name, tmpfull, MAX_N); \ 1732 if (lstat(tmpfull, &status) == 0) { \ 1733 pane->direntr[i].size = status.st_size; \ 1734 pane->direntr[i].mode = status.st_mode; \ 1735 pane->direntr[i].group = status.st_gid; \ 1736 pane->direntr[i].user = status.st_uid; \ 1737 pane->direntr[i].dt = status.st_mtime; \ 1738 } \ 1739 i++; \ 1740 free(tmpfull); 1741 1742 i = 0; 1743 pane->direntr = 1744 erealloc(pane->direntr, (10 + pane->dirc) * sizeof(Entry)); 1745 while ((entry = readdir(dir)) != 0) { 1746 if (show_dotfiles == 1) { 1747 if (entry->d_name[0] == '.' && 1748 (entry->d_name[1] == '\0' || 1749 entry->d_name[1] == '.')) 1750 continue; 1751 } else { 1752 if (entry->d_name[0] == '.') 1753 continue; 1754 } 1755 1756 if (filter == NULL) { 1757 ADD_ENTRY 1758 } else if (filter != NULL) { 1759 if (strcasestr(entry->d_name, filter) != NULL) { 1760 ADD_ENTRY 1761 } 1762 } 1763 } 1764 1765 pane->dirc = i; 1766 } 1767 1768 static int 1769 listdir(Pane *pane) 1770 { 1771 DIR *dir; 1772 struct dirent *entry; 1773 int filtercount = 0; 1774 size_t oldc = pane->dirc; 1775 1776 pane->dirc = 0; 1777 1778 dir = opendir(pane->dirn); 1779 if (dir == NULL) 1780 return -1; 1781 1782 /* get content and filter sum */ 1783 while ((entry = readdir(dir)) != 0) { 1784 if (pane->filter != NULL) { 1785 if (strcasestr(entry->d_name, pane->filter) != NULL) 1786 filtercount++; 1787 } else { /* no filter */ 1788 pane->dirc++; 1789 } 1790 } 1791 1792 if (pane->filter == NULL) { 1793 clear_pane(pane); 1794 pane->dirc -= 2; 1795 } 1796 1797 if (pane->filter != NULL) { 1798 if (filtercount > 0) { 1799 pane->dirc = filtercount; 1800 clear_pane(pane); 1801 pane->hdir = 1; 1802 } else if (filtercount == 0) { 1803 if (closedir(dir) < 0) 1804 return -1; 1805 pane->dirc = oldc; 1806 return -1; 1807 } 1808 } 1809 1810 /* print current directory title */ 1811 pane->dircol.fg |= TB_BOLD; 1812 printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn); 1813 1814 if (pane->filter == NULL) /* dont't watch when filtering */ 1815 if (addwatch(pane) < 0) 1816 print_error("can't add watch"); 1817 1818 /* empty directory */ 1819 if (pane->dirc == 0) { 1820 clear_status(); 1821 if (closedir(dir) < 0) 1822 return -1; 1823 return 0; 1824 } 1825 1826 rewinddir(dir); /* reset position */ 1827 set_direntr( 1828 pane, entry, dir, pane->filter); /* create array of entries */ 1829 qsort(pane->direntr, pane->dirc, sizeof(Entry), sort_name); 1830 refresh_pane(pane); 1831 1832 if (pane->hdir > pane->dirc) 1833 pane->hdir = pane->dirc; 1834 1835 if (pane == cpane && pane->dirc > 0) 1836 add_hi(pane, pane->hdir - 1); 1837 1838 if (closedir(dir) < 0) 1839 return -1; 1840 return 0; 1841 } 1842 1843 static void 1844 t_resize(void) 1845 { 1846 tb_clear(); 1847 draw_frame(); 1848 panes[Left].x_end = (twidth / 2) - 1; 1849 panes[Right].x_end = twidth - 1; 1850 panes[Right].x_srt = (twidth / 2) + 2; 1851 refresh_pane(&panes[Left]); 1852 refresh_pane(&panes[Right]); 1853 if (cpane->dirc > 0) 1854 add_hi(cpane, cpane->hdir - 1); 1855 tb_present(); 1856 } 1857 1858 static void 1859 get_editor(void) 1860 { 1861 editor[0] = getenv("EDITOR"); 1862 editor[1] = NULL; 1863 1864 if (editor[0] == NULL) 1865 editor[0] = fed; 1866 } 1867 1868 static void 1869 get_shell(void) 1870 { 1871 shell[0] = getenv("SHELL"); 1872 shell[1] = NULL; 1873 1874 if (shell[0] == NULL) 1875 shell[0] = sh; 1876 } 1877 1878 static void 1879 set_panes(void) 1880 { 1881 char *home; 1882 char cwd[MAX_P]; 1883 1884 home = getenv("HOME"); 1885 if (home == NULL) 1886 home = "/"; 1887 if ((getcwd(cwd, sizeof(cwd)) == NULL)) 1888 strncpy(cwd, home, MAX_P); 1889 1890 pane_idx = Left; /* cursor pane */ 1891 cpane = &panes[pane_idx]; 1892 1893 panes[Left].pane_id = 0; 1894 panes[Left].x_srt = 2; 1895 panes[Left].x_end = (twidth / 2) - 1; 1896 panes[Left].dircol = cpanell; 1897 panes[Left].firstrow = 0; 1898 panes[Left].direntr = ecalloc(0, sizeof(Entry)); 1899 strncpy(panes[Left].dirn, cwd, MAX_P); 1900 panes[Left].hdir = 1; 1901 panes[Left].inotify_wd = -1; 1902 panes[Left].parent_row = 1; 1903 1904 panes[Right].pane_id = 1; 1905 panes[Right].x_srt = (twidth / 2) + 2; 1906 panes[Right].x_end = twidth - 1; 1907 panes[Right].dircol = cpanelr; 1908 panes[Right].firstrow = 0; 1909 panes[Right].direntr = ecalloc(0, sizeof(Entry)); 1910 strncpy(panes[Right].dirn, home, MAX_P); 1911 panes[Right].hdir = 1; 1912 panes[Right].inotify_wd = -1; 1913 panes[Right].parent_row = 1; 1914 } 1915 1916 static void 1917 draw_frame(void) 1918 { 1919 int i; 1920 theight = tb_height(); 1921 twidth = tb_width(); 1922 hwidth = (twidth / 2) - 4; 1923 scrheight = theight - 2; 1924 1925 /* 2 horizontal lines */ 1926 for (i = 1; i < twidth - 1; ++i) { 1927 tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg); 1928 tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg); 1929 } 1930 1931 /* 4 vertical lines */ 1932 for (i = 1; i < theight - 1; ++i) { 1933 tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg); 1934 tb_change_cell( 1935 (twidth - 1) / 2, i - 1, u_vl, cframe.fg, cframe.bg); 1936 tb_change_cell(((twidth - 1) / 2) + 1, i - 1, u_vl, cframe.fg, 1937 cframe.bg); 1938 tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg); 1939 } 1940 1941 /* 4 corners */ 1942 tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg); 1943 tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg); 1944 tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg); 1945 tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg); 1946 1947 /* 2 middel top and bottom */ 1948 tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg); 1949 tb_change_cell( 1950 (twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg); 1951 } 1952 1953 void 1954 th_handler(int num) 1955 { 1956 if (fork_pid > 0) /* while forking don't listdir() */ 1957 return; 1958 (void)num; 1959 PERROR(listdir(&panes[Left])); 1960 PERROR(listdir(&panes[Right])); 1961 tb_present(); 1962 } 1963 1964 static int 1965 start_signal(void) 1966 { 1967 struct sigaction sa; 1968 1969 main_pid = getpid(); 1970 sa.sa_handler = th_handler; 1971 sigemptyset(&sa.sa_mask); 1972 sa.sa_flags = SA_RESTART; 1973 return sigaction(SIGUSR1, &sa, NULL); 1974 } 1975 1976 static void 1977 refresh(const Arg *arg) 1978 { 1979 kill(main_pid, SIGWINCH); 1980 } 1981 1982 static void 1983 start(void) 1984 { 1985 switch (tb_init()) { 1986 case TB_EFAILED_TO_OPEN_TTY: 1987 die("TB_EFAILED_TO_OPEN_TTY"); 1988 break; 1989 case TB_EUNSUPPORTED_TERMINAL: 1990 die("TB_EUNSUPPORTED_TERMINAL"); 1991 break; 1992 case TB_EPIPE_TRAP_ERROR: 1993 die("TB_EUNSUPPORTED_TERMINAL"); 1994 break; 1995 case 0: 1996 break; 1997 default: 1998 die("UNKNOWN FAILURE"); 1999 } 2000 2001 if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256) 2002 if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL) 2003 die("output error"); 2004 draw_frame(); 2005 set_panes(); 2006 get_editor(); 2007 get_shell(); 2008 PERROR(start_signal() < 0); 2009 PERROR(fsev_init() < 0); 2010 PERROR(listdir(&panes[Left]) < 0); 2011 PERROR(listdir(&panes[Right]) < 0); 2012 tb_present(); 2013 2014 pthread_create(&fsev_thread, NULL, read_th, NULL); 2015 start_ev(); 2016 } 2017 2018 int 2019 main(int argc, char *argv[]) 2020 { 2021 #if defined(__OpenBSD__) 2022 if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath", 2023 NULL) == -1) 2024 die("pledge"); 2025 #endif /* __OpenBSD__ */ 2026 if (argc == 1) 2027 start(); 2028 else if (argc == 2 && strncmp("-v", argv[1], 2) == 0) 2029 die("sfm-" VERSION); 2030 else 2031 die("usage: sfm [-v]"); 2032 return 0; 2033 }