aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dir.c57
-rw-r--r--src/dir.h30
-rw-r--r--src/display.c58
-rw-r--r--src/display.h59
-rw-r--r--src/err.c18
-rw-r--r--src/err.h8
-rw-r--r--src/fm.c463
-rw-r--r--src/fm.h69
-rw-r--r--src/keys.c67
-rw-r--r--src/keys.h9
-rw-r--r--src/log.c61
-rw-r--r--src/log.h21
-rw-r--r--src/main.c121
-rw-r--r--src/mem.c43
-rw-r--r--src/mem.h10
-rw-r--r--src/version.c16
-rw-r--r--src/version.h13
-rw-r--r--src/wm.c78
-rw-r--r--src/wm.h30
19 files changed, 1231 insertions, 0 deletions
diff --git a/src/dir.c b/src/dir.c
new file mode 100644
index 0000000..d79d804
--- /dev/null
+++ b/src/dir.c
@@ -0,0 +1,57 @@
+/*
+ * Ported/adapted from nnn (https://github.com/jarun/nnn): xdirname(),
+ * xbasename()
+ *
+ * Copyright (C) 2014-2016, Lazaros Koromilas <lostd@2f30.org>
+ * Copyright (C) 2014-2016, Dimitris Papastamos <sin@2f30.org>
+ * Copyright (C) 2016-2026, Arun Prakash Jana <engineerarun@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE
+#include "dir.h"
+#include "log.h"
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+
+char *xdirname(char *path)
+{
+ char *base = memrchr((unsigned char *)path, '/', strlen(path));
+
+ if (base == path)
+ path[1] = '\0';
+ else
+ *base = '\0';
+
+ return path;
+}
+
+char *xbasename(char *path)
+{
+ char *base = memrchr((unsigned char *)path, '/', strlen(path));
+
+ return base ? base + 1 : path;
+}
diff --git a/src/dir.h b/src/dir.h
new file mode 100644
index 0000000..7c576b4
--- /dev/null
+++ b/src/dir.h
@@ -0,0 +1,30 @@
+#ifndef SF_DIR_H
+#define SF_DIR_H
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "wm.h"
+
+/* typedef struct { */
+/* char name[256]; */
+/* bool is_dir; */
+/* } file_entry; */
+
+/* typedef struct { */
+/* char path[PATH_MAX]; */
+/* directory dir; */
+/* size_t cursor; */
+/* file_entry *entries; */
+/* size_t count; */
+/* size_t capacity; */
+/* } dir_stat; */
+
+char *xdirname(char *path);
+char *xbasename(char *path);
+/* directory *dir_read(directory *dir, const char *path); */
+
+/* void dir_free(directory *dir); */
+
+#endif /* SF_DIR_H */
diff --git a/src/display.c b/src/display.c
new file mode 100644
index 0000000..e7dd031
--- /dev/null
+++ b/src/display.c
@@ -0,0 +1,58 @@
+#include "display.h"
+
+#include <curses.h>
+
+#include "err.h"
+#include "log.h"
+#include "version.h"
+#include "wm.h"
+
+void
+color_pair_init(void)
+{
+ init_pair(COLOR_PAIR_FM_HL, COLOR_BLACK, COLOR_WHITE);
+ init_pair(COLOR_PAIR_FM_DIR, COLOR_CYAN, COLOR_BLACK);
+ init_pair(COLOR_PAIR_FM_STATUS_BAR, COLOR_BLUE, COLOR_BLACK);
+}
+
+terminal *
+ncurses_init(void)
+{
+ initscr();
+ raw();
+ curs_set(0);
+ noecho();
+ if (!has_colors()) {
+ log_write(LOG_WARN, "Terminal does not support colors");
+ }
+
+ start_color();
+ color_pair_init();
+ terminal *t = wm_init();
+ keypad(ROOT_WIN, TRUE);
+ return t;
+}
+
+void
+wprintw_colorful(WINDOW *w, int attr, const char *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ wattron(w, COLOR_PAIR(attr));
+ vw_printw(w, format, ap);
+ wattroff(w, COLOR_PAIR(attr));
+ va_end(ap);
+}
+
+void
+handle_resize(void)
+{
+ endwin();
+ refresh();
+}
+
+void
+redraw(void)
+{
+ erase();
+}
diff --git a/src/display.h b/src/display.h
new file mode 100644
index 0000000..ac405e7
--- /dev/null
+++ b/src/display.h
@@ -0,0 +1,59 @@
+#ifndef SF_DISPLAY_H
+#define SF_DISPLAY_H
+
+#include <ncurses.h>
+#include <stdlib.h>
+
+#include "dir.h"
+#include "log.h"
+#include "mem.h"
+
+#define COLOR_PAIR_FM_HL 1
+#define COLOR_PAIR_FM_DIR 2
+#define COLOR_PAIR_FM_STATUS_BAR 3
+
+#define STAT_INFO_WIDTH 15
+#define BAR_LABEL_WIDTH 7
+#define STAT_BAR_HEIGHT 1
+
+#define FM_PANE_LEFT 0
+#define FM_PANE_RIGHT 1
+
+#include "wm.h"
+
+typedef struct {
+ size_t top;
+ size_t bottom;
+ size_t height;
+ size_t widght;
+ size_t margin;
+ bool need_redraw;
+ window *left;
+ window *right;
+} view_stat;
+
+void color_pair_init(void);
+terminal *ncurses_init(void);
+
+static inline WINDOW *
+get_root_win(const terminal *t)
+{
+ if (!t) {
+ log_write(LOG_ERR, "get_root_win: terminal is NULL");
+ return NULL;
+ }
+ if (t->w_count <= 0 || !t->windows[0]) {
+ log_write(LOG_ERR, "get_root_win: windows[0] is not allocated");
+ return NULL;
+ }
+ return t->windows[0]->win;
+}
+
+#define ROOT_WIN get_root_win(t)
+
+void draw_stat_bar(terminal *t, const char *sys_version);
+void wprintw_colorful(WINDOW *w, int attr, const char *format, ...);
+void handle_resize(void);
+void redraw(void);
+
+#endif /* SF_DISPLAY_H */
diff --git a/src/err.c b/src/err.c
new file mode 100644
index 0000000..2a5859f
--- /dev/null
+++ b/src/err.c
@@ -0,0 +1,18 @@
+#include "err.h"
+
+#include <curses.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "log.h"
+
+void die(const char *err, ...)
+{
+ va_list p;
+ va_start(p, err);
+ log_write(LOG_ERR, err, p);
+ va_end(p);
+ endwin();
+ exit(128);
+}
diff --git a/src/err.h b/src/err.h
new file mode 100644
index 0000000..6bb4697
--- /dev/null
+++ b/src/err.h
@@ -0,0 +1,8 @@
+#ifndef SS_ERR_H
+#define SS_ERR_H
+
+#include <stdarg.h>
+
+void die(const char* err, ...);
+
+#endif /* SS_ERR_H */
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));
+}
diff --git a/src/fm.h b/src/fm.h
new file mode 100644
index 0000000..493d1f9
--- /dev/null
+++ b/src/fm.h
@@ -0,0 +1,69 @@
+#ifndef SF_FM_H
+#define SF_FM_H
+
+#include <limits.h>
+#include <linux/limits.h>
+#include <ncurses.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include "wm.h"
+
+#define PANE_LEFT 0
+#define PANE_RIGHT 1
+
+#define MARGIN 3
+
+typedef struct {
+ char name[256];
+ int is_dir;
+ char permission[11];
+} file_item;
+
+typedef struct {
+ file_item *items;
+ size_t count;
+ size_t cap;
+} file_list;
+
+typedef struct {
+ file_list *cur;
+ file_list *next;
+ int cursor;
+ window *windows[2];
+ /* int got_next_dir; */
+ char got_dir[PATH_MAX];
+ char cwd[PATH_MAX];
+ int top, bottom;
+} fm_info;
+
+extern bool need_redraw;
+
+#define UTIL_VI 0
+#define UTIL_XDG_OPEN 1
+
+#define ENV_SHELL 0
+#define ENV_EDITOR 1
+#define ENV_SF 2
+
+static char *enveditor;
+
+file_list *fm_getdir(file_list *list, const char *path);
+fm_info *fm_init(terminal *t);
+void fm_getcwd(fm_info *info);
+void fm_draw_entries(window *window, file_list *list, size_t cursor, int top,
+ int bottom);
+void fm_entries_scroll(fm_info *info, int n);
+void fm_draw_file_stream(window *w, FILE *fp);
+/* void fm_draw_right_window(fm_info *i); */
+
+void fm_cd_up(fm_info *i);
+void fm_cd_down(fm_info *i);
+bool fm_preview_file(window *w, const char *path);
+int open_with_editor(char **argv);
+void magic_init(void);
+void magic_cleanup(void);
+void create_status_bar_win(terminal *t);
+void draw_status_bar(window *bar_win, const char *cwd, char permission[11]);
+
+#endif /* SF_FM_H */
diff --git a/src/keys.c b/src/keys.c
new file mode 100644
index 0000000..8fa11c6
--- /dev/null
+++ b/src/keys.c
@@ -0,0 +1,67 @@
+#include "keys.h"
+
+#include <linux/limits.h>
+#include <ncurses.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "display.h"
+#include "err.h"
+#include "fm.h"
+#include "log.h"
+#include "mem.h"
+#include "wm.h"
+
+void handling_keys(int ch, fm_info *i)
+{
+ switch (ch) {
+#ifdef KEY_RESIZE
+ case KEY_RESIZE:
+ handle_resize();
+ /* TODO: redraw ui */
+ break;
+#endif
+ /* Quit */
+ case 'q':
+ endwin();
+ exit(0);
+ /* Down */
+ case 'j':
+ fm_entries_scroll(i, 1);
+ break;
+ case 'k':
+ fm_entries_scroll(i, -1);
+ break;
+ case 'h':
+ fm_cd_up(i);
+ break;
+ case 'l':
+ fm_cd_down(i);
+ break;
+ case 'o':
+ case '\n':
+ case '\r':
+ case KEY_ENTER:
+ log_write(LOG_INFO, "Open file");
+ if (!(i->cur->items[i->cursor].is_dir)) {
+ char filepath[PATH_MAX];
+ size_t len =
+ snprintf(filepath, PATH_MAX, "%s/%s", i->cwd,
+ i->cur->items[i->cursor].name);
+ if (len >= PATH_MAX) {
+ die("snprintf failed");
+ }
+
+ char *argv[] = {enveditor, filepath, NULL};
+ open_with_editor(argv);
+ break;
+ } else {
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+
+ need_redraw = true;
+}
diff --git a/src/keys.h b/src/keys.h
new file mode 100644
index 0000000..4ca2502
--- /dev/null
+++ b/src/keys.h
@@ -0,0 +1,9 @@
+#ifndef SF_KEYS_H
+#define SF_KEYS_H
+
+#include "fm.h"
+#include "wm.h"
+
+void handling_keys(int ch, fm_info *i);
+
+#endif /* SF_KEYS_H */
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..436307b
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,61 @@
+#include "log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "err.h"
+
+FILE *log_file = NULL;
+
+void
+log_init(void)
+{
+ log_file = fopen(LOG_FILEPATH, "a");
+
+ if (!log_file) {
+ die("Faild to open log file");
+ }
+}
+
+void
+log_write(log_level level, const char *fmt, ...)
+{
+ char *level_str;
+
+ if (level == LOG_INFO)
+ level_str = "INFO";
+ if (level == LOG_WARN)
+ level_str = "WARN";
+ if (level == LOG_ERR)
+ level_str = "ERR";
+
+ time_t rawtime;
+ struct tm *timeinfo;
+ char time_str[32];
+
+ time(&rawtime);
+ timeinfo = localtime(&rawtime);
+ strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", timeinfo);
+
+ fprintf(log_file, "[%s] [%s] ", time_str, level_str);
+
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(log_file, fmt, ap);
+ va_end(ap);
+
+ fprintf(log_file, "\n");
+ fflush(log_file);
+}
+
+void
+log_end(FILE *log_file)
+{
+ if (log_file) {
+ fclose(log_file);
+ log_file = NULL;
+ }
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..0b5a6ab
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,21 @@
+#ifndef SS_LOG_H
+#define SS_LOG_H
+
+#include <stdio.h>
+
+#define LOG_FILEPATH "./log"
+
+typedef enum {
+ LOG_INFO,
+ LOG_WARN,
+ LOG_ERR,
+} log_level;
+
+void log_init(void);
+
+void log_write(log_level level, const char *fmt, ...);
+
+void log_end(FILE *log_file);
+#define LOG_WRITE_INFO(fmt, ...) log_write(LOG_INFO, fmt, ##__VA_ARGS__)
+
+#endif /* SS_LOG_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..ba19c05
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,121 @@
+#include <ctype.h>
+#include <dirent.h>
+#include <limits.h>
+#include <linux/limits.h>
+#include <magic.h>
+#include <ncurses.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "dir.h"
+#include "display.h"
+#include "err.h"
+#include "fm.h"
+#include "keys.h"
+#include "log.h"
+#include "mem.h"
+#include "version.h"
+#include "wm.h"
+
+static void
+path_normalize(char *path)
+{
+ if (!path)
+ return;
+
+ char new_path[PATH_MAX];
+ if (realpath(path, new_path)) {
+ path = new_path;
+ } else {
+ return;
+ }
+
+ return;
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc > 1 &&
+ (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0))
+ version();
+
+ int ch;
+ log_init();
+
+ terminal *term = ncurses_init();
+
+ fm_info *info = fm_init(term);
+ int stat_bar_win_index = wm_fd_w_by_name(term, "status_bar");
+ if (stat_bar_win_index == -1) {
+ log_write(LOG_WARN, "Cannot find window: status_bar");
+ }
+ need_redraw = true;
+ if (argc == 2) {
+ snprintf(info->cwd, sizeof(info->cwd), "%s", argv[1]);
+ path_normalize(info->cwd);
+ } else {
+ fm_getcwd(info);
+ }
+
+ info->cur = fm_getdir(info->cur, info->cwd);
+ magic_init();
+ char next_path[PATH_MAX];
+ timeout(100);
+ while (1) {
+ if (!need_redraw)
+ continue;
+
+ fm_draw_entries(info->windows[FM_PANE_LEFT], info->cur,
+ info->cursor, info->top, info->bottom);
+
+ if (!info->cur->items[info->cursor].is_dir) {
+ size_t len = snprintf(
+ next_path, sizeof(next_path), "%s/%s", info->cwd,
+ info->cur->items[info->cursor].name);
+ if (len >= sizeof(next_path)) {
+ die("Length of items error");
+ }
+ fm_preview_file(info->windows[FM_PANE_RIGHT],
+ next_path);
+ } else {
+ size_t len = snprintf(
+ next_path, sizeof(next_path), "%s/%s", info->cwd,
+ info->cur->items[info->cursor].name);
+
+ if (len >= sizeof(next_path)) {
+ die("Length of items error");
+ }
+ file_list *new_list = info->next;
+ new_list = fm_getdir(new_list, next_path);
+ if (new_list) {
+ info->next = new_list;
+ int right_height =
+ info->windows[FM_PANE_RIGHT]->height;
+ fm_draw_entries(info->windows[FM_PANE_RIGHT],
+ info->next, -1, 0,
+ right_height);
+ }
+ }
+
+ draw_status_bar(term->windows[stat_bar_win_index], info->cwd,
+ info->cur->items[info->cursor].permission);
+
+ wrefresh(info->windows[FM_PANE_RIGHT]->win);
+ wrefresh(info->windows[FM_PANE_LEFT]->win);
+ wrefresh(term->windows[stat_bar_win_index]->win);
+
+ ch = getch();
+ handling_keys(ch, info);
+ }
+
+ magic_cleanup();
+ endwin();
+
+ return 0;
+}
diff --git a/src/mem.c b/src/mem.c
new file mode 100644
index 0000000..c3d5db3
--- /dev/null
+++ b/src/mem.c
@@ -0,0 +1,43 @@
+#include "mem.h"
+
+#include <curses.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "err.h"
+#include "log.h"
+
+void *xmalloc(size_t size)
+{
+ void *rtn = malloc(size);
+ if (!rtn) {
+ die("Out of memory");
+ }
+ return rtn;
+}
+
+void *xrealloc(void *p, size_t size)
+{
+ void *pmem = realloc(p, size);
+
+ if (!pmem)
+ free(p);
+
+ return pmem;
+}
+
+void free_pointer_array(void **array, size_t len)
+{
+ if (!array) {
+ log_write(LOG_WARN, "free_poiner_array: array is NULL");
+ return;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ if (array[i]) {
+ free(array[i]);
+ }
+ }
+
+ free(array);
+}
diff --git a/src/mem.h b/src/mem.h
new file mode 100644
index 0000000..bcc112f
--- /dev/null
+++ b/src/mem.h
@@ -0,0 +1,10 @@
+#ifndef SS_MEM_H
+#define SS_MEM_H
+
+#include <stddef.h>
+
+void *xmalloc(size_t size);
+void *xrealloc(void *p, size_t size);
+void free_pointer_array(void **array, size_t len);
+
+#endif /* SS_MEM_H */
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..01feb76
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,16 @@
+#include "./version.h"
+#include <stdio.h>
+#include <stdlib.h>
+void version(void)
+{
+ printf(PROGRAM_NAME
+ " " PROGRAM_VERSION " (built " BUILD_DATE
+ " with gcc " GCC_VERSION ")\n"
+ "Copyright (C) 2026 " PROGRAM_AUTHOR ".\n"
+ "License GPLv3+: GNU GPL version 3 or later "
+ "<https://gnu.org/licenses/gpl.html>.\n"
+ "This is free software: you are free to change and redistribute "
+ "it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+ exit(EXIT_SUCCESS);
+}
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..23d5a09
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,13 @@
+#ifndef SF_VERSION_H
+#define SF_VERSION_H
+
+#define PROGRAM_NAME "sf"
+#define PROGRAM_LONG_NAME "Status & Files"
+#define PROGRAM_VERSION "0.0.1"
+#define PROGRAM_AUTHOR "Verdant <im@verdant.ee>"
+#define BUILD_DATE __DATE__
+#define GCC_VERSION __VERSION__
+
+void version(void);
+
+#endif /* SF_VERSION_H */
diff --git a/src/wm.c b/src/wm.c
new file mode 100644
index 0000000..f25c6ba
--- /dev/null
+++ b/src/wm.c
@@ -0,0 +1,78 @@
+#include "wm.h"
+
+#include <ncurses.h>
+#include <string.h>
+
+#include "err.h"
+#include "log.h"
+#include "mem.h"
+
+window *wm_create_new_w(terminal *t, const char *name, int height, int width,
+ int start_y, int start_x)
+{
+ if (t->w_count >= WINDOWS_MAX) {
+ die("Windows count has arrived max");
+ }
+
+ WINDOW *win;
+ win = derwin(t->windows[0]->win, height, width, start_y, start_x);
+
+ int current_idx = t->w_count;
+
+ strncpy(t->windows[current_idx]->name, name, 31);
+ t->windows[current_idx]->win = win;
+ t->windows[current_idx]->start_y = start_y;
+ t->windows[current_idx]->start_x = start_x;
+ t->windows[current_idx]->width = width;
+ t->windows[current_idx]->height = height;
+
+ t->w_count++;
+
+ return t->windows[current_idx];
+}
+
+terminal *wm_init(void)
+{
+ terminal *t = xmalloc(sizeof(terminal));
+ t->windows = xmalloc(sizeof(window *) * WINDOWS_MAX);
+ for (int i = 0; i < WINDOWS_MAX; i++) {
+ t->windows[i] = xmalloc(sizeof(window));
+ }
+ t->w_count = 0;
+ getmaxyx(stdscr, t->maxy, t->maxx);
+ t->windows[0]->win = newwin(t->maxy, t->maxx, 0, 0);
+ t->windows[t->w_count]->start_y = 0;
+ t->windows[t->w_count]->start_x = 0;
+ t->windows[t->w_count]->height = t->maxy;
+ t->windows[t->w_count]->width = t->maxx;
+ t->w_count++;
+
+ return t;
+}
+
+int wm_fd_w_by_name(terminal *t, const char *name)
+{
+ log_write(LOG_INFO, "w_count = %d", t->w_count);
+ for (int i = 0; i < t->w_count; i++) {
+ if (strcmp(t->windows[i]->name, name) != 0)
+ continue;
+ return i;
+ }
+
+ return -1;
+}
+
+void wm_refresh_all(terminal *t)
+{
+ if (t->windows[0] && t->windows[0]->win) {
+ touchwin(t->windows[0]->win);
+ }
+
+ for (int i = 1; i < t->w_count; i++) {
+ if (t->windows[i] && t->windows[i]->win) {
+ wnoutrefresh(t->windows[i]->win);
+ }
+ }
+
+ doupdate();
+}
diff --git a/src/wm.h b/src/wm.h
new file mode 100644
index 0000000..713f2cb
--- /dev/null
+++ b/src/wm.h
@@ -0,0 +1,30 @@
+#ifndef SF_WM_H
+#define SF_WM_H
+
+#include <ncurses.h>
+
+#define WINDOWS_MAX 15
+
+typedef struct {
+ WINDOW *win;
+ char name[32];
+ int height;
+ int width;
+ int start_x;
+ int start_y;
+} window;
+
+typedef struct {
+ window **windows; /* windows[0] is root_win */
+ int w_count; /* index of windows */
+ int maxy;
+ int maxx;
+} terminal;
+
+terminal *wm_init(void);
+window *wm_create_new_w(terminal *t, const char *name, int height, int width,
+ int start_y, int start_x);
+int wm_fd_w_by_name(terminal *t, const char *name);
+void wm_refresh_all(terminal *t);
+
+#endif /* SF_WM_H */