aboutsummaryrefslogtreecommitdiffstats
path: root/src/fm.c
diff options
context:
space:
mode:
authorverdant <im@verdant.ee>2026-07-28 18:30:16 +0800
committerverdant <im@verdant.ee>2026-07-28 18:30:16 +0800
commit9bffcafa9ae0c38103a8c64b3482bb099e792869 (patch)
tree3cc18cabd99f9e300ca2539a9a48fb9c2d30e337 /src/fm.c
parentcbdc8462b6e982d3137b74d61bed6f8977405b8a (diff)
downloadsf-9bffcafa9ae0c38103a8c64b3482bb099e792869.tar.gz
sf-9bffcafa9ae0c38103a8c64b3482bb099e792869.zip
Move source files to src/
Diffstat (limited to 'src/fm.c')
-rw-r--r--src/fm.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/fm.c b/src/fm.c
new file mode 100644
index 0000000..92526c3
--- /dev/null
+++ b/src/fm.c
@@ -0,0 +1,463 @@
+#define _GNU_SOURCE
+#include "fm.h"
+#include "display.h"
+#include "err.h"
+#include "log.h"
+#include "wm.h"
+#include <curses.h>
+#include <dirent.h>
+#include <errno.h>
+#include <libgen.h>
+#include <linux/limits.h>
+#include <magic.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static magic_t g_magic_ctx = NULL;
+static char *const utils[] = {"vi", "xdg-open"};
+static char *const envs[] = {"SHELL", "EDITOR", "SF"};
+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);
+int open_with_editor(char **argv);
+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");
+}
+
+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;
+
+ wclear(w->win);
+
+ char *file_type = detect_file_type(path);
+
+ if (strstr(file_type, "text") == NULL) {
+ mvwprintw(w->win, 0, 0, "%s", file_type);
+ free(file_type);
+ return false;
+ }
+
+ FILE *fp = fopen(path, "r");
+ if (!fp) {
+ mvwprintw(w->win, 0, 0, "<Cannot open file>");
+ free(file_type);
+ return false;
+ }
+
+ fm_draw_file_stream(w, fp);
+
+ fclose(fp);
+ free(file_type);
+ 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;
+}
+
+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;
+ }
+
+ 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));
+}