#define _GNU_SOURCE #include "fm.h" #include "display.h" #include "err.h" #include "log.h" #include "wm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static magic_t g_magic_ctx = NULL; static char *const utils[] = {"vi", "xdg-open"}; static char *const envs[] = {"SHELL", "EDITOR", "SF"}; static char last_preview_path[PATH_MAX] = ""; bool need_redraw = true; /* Function forward declarations */ static fm_info *fm_w_init(terminal *term); static void fm_file_list_init(file_list *l, int cap); static void fm_file_items_realloc(file_list *l); static char *detect_file_type(const char *filepath); static void fm_getenv(void); static char *xgetenv(const char *const name, char *fallback); bool fm_preview_file(window *w, const char *path); /* Functions */ static fm_info * fm_w_init(terminal *term) { int maxx = term->maxx; int maxy = term->maxy; fm_info *info = xmalloc(sizeof(fm_info)); info->windows[PANE_LEFT] = xmalloc(sizeof(window)); info->windows[PANE_RIGHT] = xmalloc(sizeof(window)); int pane_width = maxx / 2; int pane_height = maxy - 1; int pane_starty = 0; int pane_left_startx = 0; int pane_right_startx = maxx / 2; info->windows[PANE_LEFT] = wm_create_new_w(term, "pane_left", pane_height, pane_width, pane_starty, pane_left_startx); info->windows[PANE_RIGHT] = wm_create_new_w(term, "pane_right", pane_height, pane_width, pane_starty, pane_right_startx); info->top = 0; info->bottom = pane_height; need_redraw = true; return info; } void fm_getcwd(fm_info *info) { getcwd(info->cwd, sizeof(info->cwd)); } static void fm_file_items_realloc(file_list *l) { l->cap *= 2; l->items = realloc(l->items, sizeof(file_item) * l->cap); l->items = xrealloc(l->items, sizeof(file_item) * l->cap); } void get_permission(mode_t mode, char *str) { if (S_ISDIR(mode)) str[0] = 'd'; /* Directory */ else if (S_ISLNK(mode)) str[0] = 'l'; /* Link */ else if (S_ISCHR(mode)) str[0] = 'c'; /* Charcter devices */ else if (S_ISBLK(mode)) str[0] = 'b'; /* Block devices */ else if (S_ISFIFO(mode)) str[0] = 'p'; /* FIFO */ else if (S_ISSOCK(mode)) str[0] = 's'; /* Socket */ else str[0] = '-'; /* Files */ str[1] = (mode & S_IRUSR) ? 'r' : '-'; str[2] = (mode & S_IWUSR) ? 'w' : '-'; str[3] = (mode & S_IXUSR) ? 'x' : '-'; str[4] = (mode & S_IRGRP) ? 'r' : '-'; str[5] = (mode & S_IWGRP) ? 'w' : '-'; str[6] = (mode & S_IXGRP) ? 'x' : '-'; str[7] = (mode & S_IROTH) ? 'r' : '-'; str[8] = (mode & S_IWOTH) ? 'w' : '-'; str[9] = (mode & S_IXOTH) ? 'x' : '-'; str[10] = '\0'; } file_list * fm_getdir(file_list *list, const char *path) { DIR *dir = opendir(path); if (!dir) { log_write(LOG_ERR, "Cannot open directory"); return NULL; } file_list *new_list = list; if (new_list->items && new_list->cap > 0) { LOG_WRITE_INFO("Initialize list"); memset(new_list->items, 0, new_list->cap * sizeof(file_item)); new_list->count = 0; } struct dirent *d; struct stat st; char filepath[PATH_MAX]; while ((d = readdir(dir)) != NULL) { if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue; if (new_list->count >= new_list->cap) fm_file_items_realloc(new_list); snprintf(new_list->items[new_list->count].name, sizeof(new_list->items[new_list->count].name), "%s", d->d_name); if (d->d_type == DT_DIR) { new_list->items[new_list->count].is_dir = true; } else { new_list->items[new_list->count].is_dir = false; } memset(filepath, 0, sizeof(filepath)); snprintf(filepath, sizeof(filepath), "%s/%s", path, d->d_name); if (lstat(filepath, &st) == 0) { get_permission( st.st_mode, new_list->items[new_list->count].permission); } new_list->count++; } closedir(dir); return new_list; } static void fm_file_list_init(file_list *l, int cap) { if (!l) die("File list initialization failed"); l->cap = cap; l->count = 0; l->items = xmalloc(sizeof(file_item) * cap); l->items->is_dir = 0; memset(l->items, 0, l->cap * sizeof(file_item)); fm_getenv(); } fm_info * fm_init(terminal *t) { fm_info *info = fm_w_init(t); info->cursor = 0; info->cur = xmalloc(sizeof(file_list)); info->next = xmalloc(sizeof(file_list)); fm_file_list_init(info->cur, 30); fm_file_list_init(info->next, 30); create_status_bar_win(t); return info; } static char * detect_file_type(const char *filepath) { if (!g_magic_ctx) magic_init(); const char *desc = magic_file(g_magic_ctx, filepath); return desc ? strdup(desc) : strdup("unknown"); } static bool is_previewed_path(const char *path) { return (strcmp(last_preview_path, path) == 0); } void fm_draw_file_stream(window *w, FILE *fp) { char line_buf[512]; int line_num = 0; while (line_num < w->height && fgets(line_buf, sizeof(line_buf), fp) != NULL) { mvwprintw(w->win, line_num, 0, "%.*s", w->width, line_buf); line_num++; } } bool fm_preview_file(window *w, const char *path) { if (!w || !path) return false; if (is_previewed_path(path)) return false; wclear(w->win); char *file_type = detect_file_type(path); FILE *fp = fopen(path, "r"); if (!fp) { mvwprintw(w->win, 0, 0, ""); free(file_type); return false; } else if (strstr(file_type, "text")) { fm_draw_file_stream(w, fp); } else { mvwprintw(w->win, 0, 0, "%s", file_type); } snprintf(last_preview_path, sizeof(last_preview_path), "%s", path); fclose(fp); free(file_type); return true; } bool fm_preview_dir(fm_info *info, const char *path) { if (!info) return false; if (is_previewed_path(path)) return false; window *right = info->windows[FM_PANE_RIGHT]; int right_height = info->windows[FM_PANE_RIGHT]->height; wclear(right->win); file_list *new_list = info->next; new_list = fm_getdir(new_list, path); if (new_list) { info->next = new_list; fm_draw_entries(right, new_list, -1, 0, right_height); } else { return false; } snprintf(last_preview_path, sizeof(last_preview_path), "%s", path); return true; } void fm_draw_entries(window *window, file_list *list, size_t cursor, int top, int bottom) { if (!window || !list) return; wclear(window->win); for (int i = top; i < bottom && (size_t)i < list->count; i++) { int y = i - top; int color = 0; if ((size_t)i == cursor) color = COLOR_PAIR_FM_HL; else if (list->items[i].is_dir) color = COLOR_PAIR_FM_DIR; if (color != 0) { wattron(window->win, COLOR_PAIR(color)); } mvwprintw(window->win, y, 0, "%s", list->items[i].name); if (color != 0) { wattroff(window->win, COLOR_PAIR(color)); } } } void fm_entries_scroll(fm_info *info, int n) { if (!info) return; info->cursor += n; if (info->cursor < 0) info->cursor = 0; if ((size_t)info->cursor >= info->cur->count) info->cursor = info->cur->count - 1; int win_height = info->bottom - info->top; if (info->cursor >= info->bottom) { info->bottom = info->cursor + 1; info->top = info->bottom - win_height; } if (info->cursor < info->top) { info->top = info->cursor; info->bottom = info->top + win_height; } need_redraw = true; } static void fm_reset_windows_data(fm_info *i) { if (!i) return; i->top = 0; i->bottom = i->windows[FM_PANE_LEFT]->height - 1; } void fm_cd_up(fm_info *info) { if (strcmp(info->cwd, "/") == 0) return; fm_reset_windows_data(info); char *cp_cwd = strdup(info->cwd); if (!cp_cwd) die("strdup failed"); char *parent_dir = dirname(cp_cwd); if (chdir(parent_dir) != 0) { free(cp_cwd); return; } snprintf(info->cwd, sizeof(info->cwd), "%s", parent_dir); free(cp_cwd); info->cur->cap = 30; info->cur->count = 0; file_list *new_list = fm_getdir(info->cur, info->cwd); if (new_list) { info->cur = new_list; info->cursor = 0; info->top = 0; info->bottom = info->windows[FM_PANE_LEFT]->height; need_redraw = true; } else { return; } } void fm_cd_down(fm_info *info) { if (!info->cur || info->cur->count == 0) return; if (!info->cur->items[info->cursor].is_dir) return; fm_reset_windows_data(info); size_t buf_size = strlen(info->cwd) + 1 + strlen(info->cur->items[info->cursor].name) + 1; char *next_dir = xmalloc(buf_size); if (strcmp(info->cwd, "/") == 0) { snprintf(next_dir, buf_size, "/%s", info->cur->items[info->cursor].name); } else { snprintf(next_dir, buf_size, "%s/%s", info->cwd, info->cur->items[info->cursor].name); } if (chdir(next_dir) == 0) { snprintf(info->cwd, sizeof(info->cwd), "%s", next_dir); info->cur->cap = 30; info->cur->count = 0; file_list *new_list = fm_getdir(info->cur, info->cwd); if (new_list) { info->cur = new_list; info->cursor = 0; info->top = 0; need_redraw = true; } else { free(next_dir); return; } } free(next_dir); } void magic_init(void) { if (!g_magic_ctx) { g_magic_ctx = magic_open(MAGIC_MIME_TYPE | MAGIC_SYMLINK); if (!g_magic_ctx) die("Cannot initialize magic"); if (magic_load(g_magic_ctx, NULL) != 0) die("Magic load failed: %s", magic_error(g_magic_ctx)); } } void magic_cleanup(void) { if (g_magic_ctx) magic_close(g_magic_ctx); } static char * xgetenv(const char *const name, char *fallback) { char *value = getenv(name); return value && value[0] ? value : fallback; } static void fm_getenv(void) { enveditor = xgetenv(envs[ENV_EDITOR], utils[UTIL_VI]); } int open_with_editor(char **argv) { pid_t pid; def_prog_mode(); endwin(); if (!argv[0] || *argv[0] == '\0') { argv[0] = utils[UTIL_VI]; } if ((pid = fork()) < 0) { log_write(LOG_ERR, "fork error"); return -1; } else if (pid == 0) { if (execvp(argv[0], argv) < 0) { log_write(LOG_ERR, "execvp error"); return -1; } } else { wait(NULL); need_redraw = true; } return 0; } void create_status_bar_win(terminal *t) { wm_create_new_w(t, "status_bar", 1, t->maxx, t->maxy - 1, 0); } void draw_status_bar(window *bar_win, const char *cwd, char permission[11]) { werase(bar_win->win); wattron(bar_win->win, COLOR_PAIR(COLOR_PAIR_FM_STATUS_BAR)); mvwprintw(bar_win->win, bar_win->height - 1, 0, "%s %s", cwd, permission); wattroff(bar_win->win, COLOR_PAIR(COLOR_PAIR_FM_STATUS_BAR)); }