summaryrefslogtreecommitdiff
path: root/sfm-0.4
diff options
context:
space:
mode:
authorstkhan <personal@slickd.xyz>2022-05-07 12:42:36 +0000
committerstkhan <personal@slickd.xyz>2022-05-07 12:42:36 +0000
commit0f734f0e317996d246fed2b0bdd1550c49d46e5b (patch)
treedb0f535dcb02dc1959508a1e94570db74ea3bbb2 /sfm-0.4
parent42966e3e531b8e46e4ab31480d45aa7a141f19ce (diff)
Fixed things, added sfm
Diffstat (limited to 'sfm-0.4')
-rw-r--r--sfm-0.4/LICENSE19
-rw-r--r--sfm-0.4/Makefile51
-rw-r--r--sfm-0.4/README.md78
-rw-r--r--sfm-0.4/config.def.h163
-rw-r--r--sfm-0.4/config.h163
-rw-r--r--sfm-0.4/config.mk14
-rw-r--r--sfm-0.4/sfm.1147
-rw-r--r--sfm-0.4/sfm.c2033
-rw-r--r--sfm-0.4/sfm.pngbin0 -> 745 bytes
-rw-r--r--sfm-0.4/termbox.c1509
-rw-r--r--sfm-0.4/termbox.h317
-rw-r--r--sfm-0.4/utf8.c78
-rw-r--r--sfm-0.4/util.c44
-rw-r--r--sfm-0.4/util.h17
14 files changed, 4633 insertions, 0 deletions
diff --git a/sfm-0.4/LICENSE b/sfm-0.4/LICENSE
new file mode 100644
index 0000000..342ffaa
--- /dev/null
+++ b/sfm-0.4/LICENSE
@@ -0,0 +1,19 @@
+ISC License
+
+© 2020-2021 Hassan Afify <hassan at afify dot dev>
+© 2020 Mohamed Afify <mohamed at afify dot dev>
+© 2021 Nikolay Korotkiy <sikmir@gmail.com>
+© 2021 David Kalliecharan <david@david.science>
+© 2021 Tdukv <tdukv@protonmail.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/sfm-0.4/Makefile b/sfm-0.4/Makefile
new file mode 100644
index 0000000..dae243f
--- /dev/null
+++ b/sfm-0.4/Makefile
@@ -0,0 +1,51 @@
+# sfm - simple file manager
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = sfm.c util.c termbox.c utf8.c
+OBJ = ${SRC:.c=.o}
+
+all: options sfm
+
+options:
+ @echo sfm build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.h config.mk
+
+config.h:
+ cp config.def.h $@
+
+sfm: ${OBJ}
+ ${CC} ${LDFLAGS} -o $@ ${OBJ}
+
+clean:
+ rm -f sfm ${OBJ} sfm-${VERSION}.tar.gz
+
+dist: clean
+ mkdir -p sfm-${VERSION}
+ cp -R LICENSE Makefile README.md config.def.h config.mk\
+ sfm.1 sfm.png termbox.h util.h ${SRC} sfm-${VERSION}
+ tar -cf sfm-${VERSION}.tar sfm-${VERSION}
+ gzip sfm-${VERSION}.tar
+ rm -rf sfm-${VERSION}
+
+install: sfm
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f sfm ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/sfm
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ sed "s/VERSION/${VERSION}/g" < sfm.1 > ${DESTDIR}${MANPREFIX}/man1/sfm.1
+ chmod 644 ${DESTDIR}${MANPREFIX}/man1/sfm.1
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/sfm\
+ ${DESTDIR}${MANPREFIX}/man1/sfm.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/sfm-0.4/README.md b/sfm-0.4/README.md
new file mode 100644
index 0000000..81eb044
--- /dev/null
+++ b/sfm-0.4/README.md
@@ -0,0 +1,78 @@
+<img src="https://afify.dev/img/sfm.png" alt="sfm logo"/>
+
+**simple file manager**
+
+[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/afify/sfm.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/afify/sfm/context:cpp)
+[![Build status](https://ci.appveyor.com/api/projects/status/goq88ahjyvtjrui2?svg=true)](https://ci.appveyor.com/project/afify/sfm)
+[![code-inspector](https://www.code-inspector.com/project/19656/score/svg)](https://frontend.code-inspector.com/public/project/19656/sfm/dashboard)
+[![code-inspector](https://www.code-inspector.com/project/19656/status/svg)](https://frontend.code-inspector.com/public/project/19656/sfm/dashboard)
+
+Description
+------------
+sfm is a simple file manager for unix-like systems.
+* pthreads(7) to read events, no timers.
+* BSD kqueue(2) - kernel event notification mechanism.
+* Linux inotify(7) - monitoring filesystem events.
+* dual pane.
+* bookmarks.
+* open files by extension.
+* bottom statusbar.
+* vim-like key bindings.
+* filter.
+* no dependencies.
+* c99 static linking.
+* based on [termbox](https://github.com/nsf/termbox).
+* Inspired by [vifm](https://vifm.info/) and [noice](https://git.2f30.org/noice/).
+* Follows the suckless [philosophy](https://suckless.org/philosophy/).
+
+Patches
+-------
+[sfm-patches](https://github.com/afify/sfm-patches)
+
+Performance
+------------
+```sh
+$ perf stat -r 10 sfm
+```
+
+Options
+-------
+```sh
+$ sfm [-v]
+$ man sfm
+```
+<img src="https://afify.dev/img/sfm_sc.png" alt="sfm screenshot" width="800"/>
+
+Installation
+------------
+**current**
+```sh
+git clone git://git.afify.dev/sfm
+cd sfm/
+make
+make install
+```
+**latest release**
+```sh
+[ "$(uname)" = "Linux" ] && shacmd="sha256sum" grepf="--color=never"|| shacmd="sha256"
+latest=$(curl -s https://git.afify.dev/sfm/tags.xml | grep $grepf -m 1 -o "\[v.*\]" | tr -d '[]')
+tgz="https://git.afify.dev/sfm/releases/sfm-${latest}.tar.gz"
+sha="${tgz}.sha256"
+wget "${tgz}"
+wget "${sha}"
+${shacmd} -c "sfm-${latest}.tar.gz.sha256" && \
+tar -xzf "sfm-${latest}.tar.gz" && cd "sfm-${latest}" && \
+make
+make install
+```
+
+Run
+---
+```sh
+$ sfm
+```
+
+Configuration
+-------------
+The configuration of sfm is done by creating a custom config.h
+and (re)compiling the source code. This keeps it fast, secure and simple.
diff --git a/sfm-0.4/config.def.h b/sfm-0.4/config.def.h
new file mode 100644
index 0000000..067411e
--- /dev/null
+++ b/sfm-0.4/config.def.h
@@ -0,0 +1,163 @@
+/* See LICENSE file for copyright and license details.*/
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/* colors fg, bg */
+static const Cpair cdir = { 31, 0 };
+static const Cpair cfile = { 243, 0 };
+static const Cpair clnk = { 96, 0 };
+static const Cpair cblk = { 95, 0 };
+static const Cpair cchr = { 94, 0 };
+static const Cpair cifo = { 93, 0 };
+static const Cpair csock = { 92, 0 };
+static const Cpair cexec = { 91, 0 };
+static const Cpair cother = { 90, 0 };
+
+static const Cpair cframe = { 233, 233 };
+static const Cpair cpanell = { 166, 233 };
+static const Cpair cpanelr = { 5, 233 };
+static const Cpair cerr = { 124, 0 };
+static const Cpair cprompt = { 33, 0 };
+static const Cpair csearch = { 255, 0 };
+static const Cpair cstatus = { 243, 0 };
+
+/* commands */
+#if defined(__linux__)
+#define CHFLAG "chattr"
+#else
+#define CHFLAG "chflags"
+#endif
+static const char *rm_cmd[] = { "rm", "-rf" }; /* delete */
+static const char *cp_cmd[] = { "cp", "-r" }; /* copy */
+static const char *chown_cmd[] = { "chown", "-R" }; /* change file owner and group */
+static const char *chmod_cmd[] = { "chmod" }; /* change file mode bits */
+static const char *chflags_cmd[] = { CHFLAG }; /* change file flags */
+static const char *mv_cmd[] = { "mv" }; /* move */
+static const char delconf[] = "yes";
+
+static const size_t rm_cmd_len = LEN(rm_cmd);
+static const size_t cp_cmd_len = LEN(cp_cmd);
+static const size_t chown_cmd_len = LEN(chown_cmd);
+static const size_t chmod_cmd_len = LEN(chmod_cmd);
+static const size_t chflags_cmd_len = LEN(chflags_cmd);
+static const size_t mv_cmd_len = LEN(mv_cmd);
+static const size_t delconf_len = LEN(delconf);
+
+/* bookmarks */
+static const char root[] = "/";
+
+/* software */
+static const char *mpv[] = { "mpv", "--fullscreen" };
+static const char *sxiv[] = { "sxiv" };
+static const char *mupdf[] = { "mupdf", "-I" };
+static const char *libreoffice[] = { "libreoffice" };
+static const char *gimp[] = { "gimp" };
+static const char *r2[] = { "r2", "-c", "vv" };
+
+/* extensions*/
+static const char *images[] = { "bmp", "jpg", "jpeg", "png", "gif", "xpm" };
+static const char *pdf[] = { "epub", "pdf" };
+static const char *arts[] = { "xcf" };
+static const char *obj[] = { "o", "a", "so" };
+static const char *videos[] = { "avi", "flv", "wav", "webm", "wma", "wmv",
+ "m2v", "m4a", "m4v", "mkv", "mov", "mp3",
+ "mp4", "mpeg", "mpg" };
+static const char *documents[] = { "odt", "doc", "docx", "xls", "xlsx", "odp",
+ "ods", "pptx", "odg" };
+
+static Rule rules[] = {
+ {videos, LEN(videos), mpv, LEN(mpv) },
+ {images, LEN(images), sxiv, LEN(sxiv) },
+ {pdf, LEN(pdf), mupdf, LEN(mupdf) },
+ {documents, LEN(documents), libreoffice, LEN(libreoffice) },
+ {arts, LEN(arts), gimp, LEN(gimp) },
+ {obj, LEN(obj), r2, LEN(r2) },
+};
+
+/* normal keys */
+static Key nkeys[] = {
+ /* keyval function arg */
+ { {.ch = 'j'}, mv_ver, {.i = -1} },
+ { {.key = TB_KEY_ARROW_DOWN}, mv_ver, {.i = -1} },
+ { {.ch = 'k'}, mv_ver, {.i = +1} },
+ { {.key = TB_KEY_ARROW_UP}, mv_ver, {.i = +1} },
+ { {.key = TB_KEY_CTRL_U}, mv_ver, {.i = +3} },
+ { {.key = TB_KEY_CTRL_D}, mv_ver, {.i = -3} },
+ { {.ch = 'l'}, mvfwd, {.i = 0} },
+ { {.key = TB_KEY_ARROW_RIGHT}, mvfwd, {.i = 0} },
+ { {.ch = 'h'}, mvbk, {.i = 0} },
+ { {.key = TB_KEY_ARROW_LEFT}, mvbk, {.i = 0} },
+ { {.ch = 'g'}, mvtop, {.i = 0} },
+ { {.ch = 'G'}, mvbtm, {.i = 0} },
+ { {.ch = 'n'}, crnf, {0} },
+ { {.ch = 'N'}, crnd, {0} },
+ { {.ch = 'd'}, delent, {0} },
+ { {.ch = 'D'}, dupl, {0} },
+ { {.ch = 'x'}, calcdir, {0} },
+ { {.ch = '/'}, start_filter, {0} },
+ { {.ch = 'q'}, quit, {0} },
+ { {.ch = 'v'}, start_vmode, {0} },
+ { {.ch = 'y'}, yank, {0} },
+ { {.ch = 'p'}, paste, {0} },
+ { {.ch = 'P'}, selmv, {0} },
+ { {.ch = 'c'}, start_change, {0} },
+ { {.ch = 'b'}, opnsh, {0} },
+ { {.key = TB_KEY_SPACE}, switch_pane, {0} },
+ { {.key = TB_KEY_CTRL_R}, refresh, {0} },
+ { {.ch = '\\'}, bkmrk, {.v = root} },
+ { {.ch = '.'}, toggle_df, {0} },
+};
+
+/* change keys */
+static Key ckeys[] = {
+ /* keyval function arg */
+ { {.ch = 'w'}, rname, {0} },
+ { {.ch = 'o'}, chngo, {0} },
+ { {.ch = 'm'}, chngm, {0} },
+ { {.ch = 'f'}, chngf, {0} },
+ { {.ch = 'q'}, exit_change, {0} },
+ { {.ch = 'c'}, exit_change, {0} },
+ { {.key = TB_KEY_ESC}, exit_change, {0} },
+};
+
+/* visual keys */
+static Key vkeys[] = {
+ /* keyval function arg */
+ { {.ch = 'j'}, seldwn, {.i = -1} },
+ { {.key = TB_KEY_ARROW_DOWN}, seldwn, {.i = -1} },
+ { {.ch = 'k'}, selup, {.i = +1} },
+ { {.key = TB_KEY_ARROW_UP}, selup, {.i = +1} },
+ { {.ch = 'a'}, selall, {0} },
+ { {.ch = 'y'}, selynk, {0} },
+ { {.ch = 'd'}, seldel, {.v = delconf} },
+ { {.ch = 'q'}, exit_vmode, {0} },
+ { {.ch = 'v'}, exit_vmode, {0} },
+ { {.key = TB_KEY_ESC}, exit_vmode, {0} },
+};
+
+static const size_t nkeyslen = LEN(nkeys);
+static const size_t vkeyslen = LEN(vkeys);
+static const size_t ckeyslen = LEN(ckeys);
+
+/* permissions */
+static const mode_t ndir_perm = S_IRWXU;
+static const mode_t nf_perm = S_IRUSR | S_IWUSR;
+
+/* dotfiles */
+static int show_dotfiles = 1;
+
+/* statusbar */
+static const char dtfmt[] = "%F %R"; /* date time format */
+
+/* unicode chars */
+#define u_hl 0x2500 /* ─ */
+#define u_vl 0x2502 /* │ */
+#define u_cnw 0x250C /* ┌ */
+#define u_cne 0x2510 /* ┐ */
+#define u_csw 0x2514 /* └ */
+#define u_cse 0x2518 /* ┘ */
+#define u_mn 0x252C /* ┬ */
+#define u_ms 0x2534 /* ┴ */
+
+#endif /* CONFIG_H */
diff --git a/sfm-0.4/config.h b/sfm-0.4/config.h
new file mode 100644
index 0000000..939b0c9
--- /dev/null
+++ b/sfm-0.4/config.h
@@ -0,0 +1,163 @@
+/* See LICENSE file for copyright and license details.*/
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/* colors fg, bg */
+static const Cpair cdir = { 31, 0 };
+static const Cpair cfile = { 243, 0 };
+static const Cpair clnk = { 96, 0 };
+static const Cpair cblk = { 95, 0 };
+static const Cpair cchr = { 94, 0 };
+static const Cpair cifo = { 93, 0 };
+static const Cpair csock = { 92, 0 };
+static const Cpair cexec = { 91, 0 };
+static const Cpair cother = { 90, 0 };
+
+static const Cpair cframe = { 233, 233 };
+static const Cpair cpanell = { 166, 233 };
+static const Cpair cpanelr = { 5, 233 };
+static const Cpair cerr = { 124, 0 };
+static const Cpair cprompt = { 33, 0 };
+static const Cpair csearch = { 255, 0 };
+static const Cpair cstatus = { 243, 0 };
+
+/* commands */
+#if defined(__linux__)
+#define CHFLAG "chattr"
+#else
+#define CHFLAG "chflags"
+#endif
+static const char *rm_cmd[] = { "rm", "-rf" }; /* delete */
+static const char *cp_cmd[] = { "cp", "-r" }; /* copy */
+static const char *chown_cmd[] = { "chown", "-R" }; /* change file owner and group */
+static const char *chmod_cmd[] = { "chmod" }; /* change file mode bits */
+static const char *chflags_cmd[] = { CHFLAG }; /* change file flags */
+static const char *mv_cmd[] = { "mv" }; /* move */
+static const char delconf[] = "yes";
+
+static const size_t rm_cmd_len = LEN(rm_cmd);
+static const size_t cp_cmd_len = LEN(cp_cmd);
+static const size_t chown_cmd_len = LEN(chown_cmd);
+static const size_t chmod_cmd_len = LEN(chmod_cmd);
+static const size_t chflags_cmd_len = LEN(chflags_cmd);
+static const size_t mv_cmd_len = LEN(mv_cmd);
+static const size_t delconf_len = LEN(delconf);
+
+/* bookmarks */
+static const char root[] = "/";
+
+/* software */
+static const char *mpv[] = { "mpv", "--fullscreen" };
+static const char *sxiv[] = { "nsxiv" };
+static const char *mupdf[] = { "mupdf", "-I" };
+static const char *libreoffice[] = { "libreoffice" };
+static const char *gimp[] = { "gimp" };
+static const char *r2[] = { "r2", "-c", "vv" };
+
+/* extensions*/
+static const char *images[] = { "bmp", "jpg", "jpeg", "png", "gif", "xpm" };
+static const char *pdf[] = { "epub", "pdf" };
+static const char *arts[] = { "xcf" };
+static const char *obj[] = { "o", "a", "so" };
+static const char *videos[] = { "avi", "flv", "wav", "webm", "wma", "wmv",
+ "m2v", "m4a", "m4v", "mkv", "mov", "mp3",
+ "mp4", "mpeg", "mpg" };
+static const char *documents[] = { "odt", "doc", "docx", "xls", "xlsx", "odp",
+ "ods", "pptx", "odg" };
+
+static Rule rules[] = {
+ {videos, LEN(videos), mpv, LEN(mpv) },
+ {images, LEN(images), sxiv, LEN(sxiv) },
+ {pdf, LEN(pdf), mupdf, LEN(mupdf) },
+ {documents, LEN(documents), libreoffice, LEN(libreoffice) },
+ {arts, LEN(arts), gimp, LEN(gimp) },
+ {obj, LEN(obj), r2, LEN(r2) },
+};
+
+/* normal keys */
+static Key nkeys[] = {
+ /* keyval function arg */
+ { {.ch = 'j'}, mv_ver, {.i = -1} },
+ { {.key = TB_KEY_ARROW_DOWN}, mv_ver, {.i = -1} },
+ { {.ch = 'k'}, mv_ver, {.i = +1} },
+ { {.key = TB_KEY_ARROW_UP}, mv_ver, {.i = +1} },
+ { {.key = TB_KEY_CTRL_U}, mv_ver, {.i = +3} },
+ { {.key = TB_KEY_CTRL_D}, mv_ver, {.i = -3} },
+ { {.ch = 'l'}, mvfwd, {.i = 0} },
+ { {.key = TB_KEY_ARROW_RIGHT}, mvfwd, {.i = 0} },
+ { {.ch = 'h'}, mvbk, {.i = 0} },
+ { {.key = TB_KEY_ARROW_LEFT}, mvbk, {.i = 0} },
+ { {.ch = 'g'}, mvtop, {.i = 0} },
+ { {.ch = 'G'}, mvbtm, {.i = 0} },
+ { {.ch = 'n'}, crnf, {0} },
+ { {.ch = 'N'}, crnd, {0} },
+ { {.ch = 'd'}, delent, {0} },
+ { {.ch = 'D'}, dupl, {0} },
+ { {.ch = 'x'}, calcdir, {0} },
+ { {.ch = '/'}, start_filter, {0} },
+ { {.ch = 'q'}, quit, {0} },
+ { {.ch = 'v'}, start_vmode, {0} },
+ { {.ch = 'y'}, yank, {0} },
+ { {.ch = 'p'}, paste, {0} },
+ { {.ch = 'P'}, selmv, {0} },
+ { {.ch = 'c'}, start_change, {0} },
+ { {.ch = 'b'}, opnsh, {0} },
+ { {.key = TB_KEY_SPACE}, switch_pane, {0} },
+ { {.key = TB_KEY_CTRL_R}, refresh, {0} },
+ { {.ch = '\\'}, bkmrk, {.v = root} },
+ { {.ch = '.'}, toggle_df, {0} },
+};
+
+/* change keys */
+static Key ckeys[] = {
+ /* keyval function arg */
+ { {.ch = 'w'}, rname, {0} },
+ { {.ch = 'o'}, chngo, {0} },
+ { {.ch = 'm'}, chngm, {0} },
+ { {.ch = 'f'}, chngf, {0} },
+ { {.ch = 'q'}, exit_change, {0} },
+ { {.ch = 'c'}, exit_change, {0} },
+ { {.key = TB_KEY_ESC}, exit_change, {0} },
+};
+
+/* visual keys */
+static Key vkeys[] = {
+ /* keyval function arg */
+ { {.ch = 'j'}, seldwn, {.i = -1} },
+ { {.key = TB_KEY_ARROW_DOWN}, seldwn, {.i = -1} },
+ { {.ch = 'k'}, selup, {.i = +1} },
+ { {.key = TB_KEY_ARROW_UP}, selup, {.i = +1} },
+ { {.ch = 'a'}, selall, {0} },
+ { {.ch = 'y'}, selynk, {0} },
+ { {.ch = 'd'}, seldel, {.v = delconf} },
+ { {.ch = 'q'}, exit_vmode, {0} },
+ { {.ch = 'v'}, exit_vmode, {0} },
+ { {.key = TB_KEY_ESC}, exit_vmode, {0} },
+};
+
+static const size_t nkeyslen = LEN(nkeys);
+static const size_t vkeyslen = LEN(vkeys);
+static const size_t ckeyslen = LEN(ckeys);
+
+/* permissions */
+static const mode_t ndir_perm = S_IRWXU;
+static const mode_t nf_perm = S_IRUSR | S_IWUSR;
+
+/* dotfiles */
+static int show_dotfiles = 1;
+
+/* statusbar */
+static const char dtfmt[] = "%F %R"; /* date time format */
+
+/* unicode chars */
+#define u_hl 0x2500 /* ─ */
+#define u_vl 0x2502 /* │ */
+#define u_cnw 0x250C /* ┌ */
+#define u_cne 0x2510 /* ┐ */
+#define u_csw 0x2514 /* └ */
+#define u_cse 0x2518 /* ┘ */
+#define u_mn 0x252C /* ┬ */
+#define u_ms 0x2534 /* ┴ */
+
+#endif /* CONFIG_H */
diff --git a/sfm-0.4/config.mk b/sfm-0.4/config.mk
new file mode 100644
index 0000000..5732f77
--- /dev/null
+++ b/sfm-0.4/config.mk
@@ -0,0 +1,14 @@
+# sfm version
+VERSION = 0.4
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -DVERSION=\"${VERSION}\"
+CFLAGS = -std=c99 -pedantic -Wextra -Wall -Wno-unused-parameter -Os ${CPPFLAGS}
+LDFLAGS = -pthread -s
+
+# compiler and linker
+CC = cc
diff --git a/sfm-0.4/sfm.1 b/sfm-0.4/sfm.1
new file mode 100644
index 0000000..788acdf
--- /dev/null
+++ b/sfm-0.4/sfm.1
@@ -0,0 +1,147 @@
+.TH sfm 1 sfm\-VERSION
+.SH NAME
+sfm \- simple file manager
+.SH SYNOPSIS
+.B sfm
+.RB [ \-v ]
+.SH DESCRIPTION
+sfm is a simple file manager for unix-like systems based on termbox.
+dual panes, bottom statusbar, bookmarks, open files by extention, vim-like key bindings as default configuration. cwd is left pane dir.
+.P
+.SH OPTIONS
+.TP
+.B \-v
+print version.
+.SH USAGE
+.SS Normal Mode
+.TP
+.B q
+quit
+.TP
+.B h
+back
+.TP
+.B j
+down
+.TP
+.B k
+up
+.TP
+.B l
+open dir | file
+.TP
+.B g
+top
+.TP
+.B G
+bottom
+.TP
+.B ctrl+u
+scroll up
+.TP
+.B ctrl+d
+scroll down
+.TP
+.B n
+create new file if not exists
+.TP
+.B N
+create new directory if not exists
+.TP
+.B d
+delete file | directory recursively
+.TP
+.B D
+duplicate file | directory recursively
+.TP
+.B y
+yank
+.TP
+.B p
+paste
+.TP
+.B P
+move
+.TP
+.B b
+spawn a shell in the current directory
+.TP
+.B c
+start change
+.TP
+.B cw
+rename
+.TP
+.B co
+change owner and group (chown)
+.TP
+.B cm
+change mode (chmod)
+.TP
+.B cf
+change flags (chflags | chattr)
+.TP
+.B cc
+exit change
+.TP
+.B cq
+exit change
+.TP
+.B .
+toggle dotfiles
+.TP
+.B v
+start visual mode
+.TP
+.B /
+start filter
+.TP
+.B ENTER
+find filter
+.TP
+.B ESC
+exit filter
+.TP
+.B SPACE
+switch pane
+.TP
+.B ctrl+r
+refresh panes
+.SS Visual Mode
+.TP
+.B j
+select down
+.TP
+.B k
+select up
+.TP
+.B d
+delete selection
+.TP
+.B v
+exit visual mode
+.TP
+.B q
+exit visual mode
+.TP
+.B ESC
+exit visual mode | change
+.SH CUSTOMIZATION
+sfm is customized by creating a custom
+.IR config.h
+and (re)compiling the source
+code. This keeps it fast, secure and simple.
+.SH ENVIRONMENT
+.TP
+.B EDITOR
+open unconfigured file extention. vi(1) if not set.
+.TP
+.B HOME
+right pane default directory. / if not set.
+.TP
+.B SHELL
+shell spawned with the 'b' key. /bin/sh if not set.
+.SH AUTHORS
+See the LICENSE file for the authors.
+.SH LICENSE
+See the LICENSE file for the terms of redistribution.
diff --git a/sfm-0.4/sfm.c b/sfm-0.4/sfm.c
new file mode 100644
index 0000000..3a69586
--- /dev/null
+++ b/sfm-0.4/sfm.c
@@ -0,0 +1,2033 @@
+/* See LICENSE file for copyright and license details. */
+
+#if defined(__linux__)
+#define _GNU_SOURCE
+#elif defined(__APPLE__)
+#define _DARWIN_C_SOURCE
+#elif defined(__FreeBSD__)
+#define __BSD_VISIBLE 1
+#endif
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#if defined(__linux__)
+#include <sys/inotify.h>
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
+ defined(__APPLE__)
+#include <sys/event.h>
+#endif
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <libgen.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "termbox.h"
+#include "util.h"
+
+/* macros */
+#define MAX_P 4096
+#define MAX_N 255
+#define MAX_USRI 32
+#define MAX_EXT 4
+#define MAX_STATUS 255
+#define MAX_LINE 4096
+#define MAX_USRN 32
+#define MAX_GRPN 32
+#define MAX_DTF 32
+#define CURSOR(x) (x)->direntr[(x)->hdir - 1]
+
+/* typedef */
+typedef struct {
+ char name[MAX_N];
+ gid_t group;
+ mode_t mode;
+ off_t size;
+ time_t dt;
+ uid_t user;
+} Entry;
+
+typedef struct {
+ uint16_t fg;
+ uint16_t bg;
+} Cpair;
+
+typedef struct {
+ int pane_id;
+ char dirn[MAX_P]; // dir name cwd
+ char *filter;
+ Entry *direntr; // dir entries
+ int dirc; // dir entries sum
+ int hdir; // highlighted dir
+ int x_srt;
+ int x_end;
+ int firstrow;
+ int parent_firstrow;
+ int parent_row; // FIX
+ Cpair dircol;
+ int inotify_wd;
+ int event_fd;
+} Pane;
+
+typedef struct {
+ const char **ext;
+ size_t exlen;
+ const void *v;
+ size_t vlen;
+} Rule;
+
+typedef union {
+ uint16_t key; /* one of the TB_KEY_* constants */
+ uint32_t ch; /* unicode character */
+} Evkey;
+
+typedef union {
+ int i;
+ const void *v;
+} Arg;
+
+typedef struct {
+ const Evkey evkey;
+ void (*func)(const Arg *);
+ const Arg arg;
+} Key;
+
+/* function declarations */
+static void print_tb(const char *, int, int, uint16_t, uint16_t);
+static void printf_tb(int, int, Cpair, const char *, ...);
+static void print_status(Cpair, const char *, ...);
+static void print_xstatus(char, int);
+static void print_error(char *);
+static void print_prompt(char *);
+static void print_info(Pane *, char *);
+static void print_row(Pane *, size_t, Cpair);
+static void clear(int, int, int, uint16_t);
+static void clear_status(void);
+static void clear_pane(Pane *);
+static void add_hi(Pane *, size_t);
+static void rm_hi(Pane *, size_t);
+static int check_dir(char *);
+static int sort_name(const void *const, const void *const);
+static void get_dirp(char *);
+static char *get_ext(char *);
+static int get_fdt(char *, time_t);
+static char *get_fgrp(gid_t);
+static char *get_fperm(mode_t);
+static char *get_fsize(off_t);
+static char *get_fullpath(char *, char *);
+static char *get_fusr(uid_t);
+static void get_dirsize(char *, off_t *);
+static void get_hicol(Cpair *, mode_t);
+static void delent(const Arg *arg);
+static void calcdir(const Arg *arg);
+static void crnd(const Arg *arg);
+static void crnf(const Arg *arg);
+static void mv_ver(const Arg *arg);
+static void mvbk(const Arg *arg);
+static void mvbtm(const Arg *arg);
+static void mvfwd(const Arg *arg);
+static void mvtop(const Arg *arg);
+static void bkmrk(const Arg *arg);
+static int get_usrinput(char *, size_t, const char *, ...);
+static int frules(char *);
+static int spawn(const void *, size_t, const void *, size_t, char *, int);
+static int opnf(char *);
+static int fsev_init(void);
+static int addwatch(Pane *);
+static int read_events(void);
+static void rmwatch(Pane *);
+static void fsev_shdn(void);
+static void toggle_df(const Arg *arg);
+static void start_filter(const Arg *arg);
+static void start_vmode(const Arg *arg);
+static void exit_vmode(const Arg *arg);
+static void start_change(const Arg *arg);
+static void exit_change(const Arg *arg);
+static void selup(const Arg *arg);
+static void seldwn(const Arg *arg);
+static void selall(const Arg *arg);
+static void selref(void);
+static void selynk(const Arg *arg);
+static void selcalc(void);
+static void paste(const Arg *arg);
+static void selmv(const Arg *arg);
+static void seldel(const Arg *arg);
+static void init_files(void);
+static void free_files(void);
+static void yank(const Arg *arg);
+static void rname(const Arg *arg);
+static void chngo(const Arg *arg);
+static void chngm(const Arg *arg);
+static void chngf(const Arg *arg);
+static void dupl(const Arg *arg);
+static void switch_pane(const Arg *arg);
+static void quit(const Arg *arg);
+static void grabkeys(struct tb_event *, Key *, size_t);
+static void *read_th(void *arg);
+static void start_ev(void);
+static void refresh_pane(Pane *);
+static void set_direntr(Pane *, struct dirent *, DIR *, char *);
+static int listdir(Pane *);
+static void t_resize(void);
+static void get_shell(void);
+static void opnsh(const Arg *arg);
+static void set_panes(void);
+static void draw_frame(void);
+static void refresh(const Arg *arg);
+static void start(void);
+
+/* global variables */
+static pthread_t fsev_thread;
+static Pane panes[2];
+static Pane *cpane;
+static int pane_idx;
+static char *editor[2];
+static char fed[] = "vi";
+static char *shell[2];
+static char sh[] = "/bin/sh";
+static int theight, twidth, hwidth, scrheight;
+static int *sel_indexes;
+static size_t sel_len = 0;
+static char **sel_files;
+static int cont_vmode = 0;
+static int cont_change = 0;
+static pid_t fork_pid = 0, main_pid;
+#if defined(_SYS_INOTIFY_H)
+#define READEVSZ 16
+static int inotify_fd;
+#elif defined(_SYS_EVENT_H_)
+#define READEVSZ 0
+static int kq;
+struct kevent evlist[2]; /* events we want to monitor */
+struct kevent chlist[2]; /* events that were triggered */
+static struct timespec gtimeout;
+#endif
+#if defined(__linux__) || defined(__FreeBSD__)
+#define OFF_T "%ld"
+#elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
+#define OFF_T "%lld"
+#endif
+enum { Left, Right }; /* panes */
+enum { Wait, DontWait }; /* spawn forks */
+
+/* configuration, allows nested code to access above variables */
+#include "config.h"
+
+/* function implementations */
+static void
+print_tb(const char *str, int x, int y, uint16_t fg, uint16_t bg)
+{
+ while (*str != '\0') {
+ uint32_t uni = 0;
+ str += tb_utf8_char_to_unicode(&uni, str);
+ tb_change_cell(x, y, uni, fg, bg);
+ x++;
+ }
+}
+
+static void
+printf_tb(int x, int y, Cpair col, const char *fmt, ...)
+{
+ char buf[MAX_LINE];
+ va_list vl;
+ va_start(vl, fmt);
+ (void)vsnprintf(buf, MAX_LINE, fmt, vl);
+ va_end(vl);
+ print_tb(buf, x, y, col.fg, col.bg);
+}
+
+static void
+print_status(Cpair col, const char *fmt, ...)
+{
+ char buf[MAX_STATUS];
+ va_list vl;
+ va_start(vl, fmt);
+ (void)vsnprintf(buf, MAX_STATUS, fmt, vl);
+ va_end(vl);
+ clear_status();
+ print_tb(buf, 1, theight - 1, col.fg, col.bg);
+}
+
+static void
+print_xstatus(char c, int x)
+{
+ uint32_t uni = 0;
+ (void)tb_utf8_char_to_unicode(&uni, &c);
+ tb_change_cell(x, theight - 1, uni, cstatus.fg, cstatus.bg);
+}
+
+static void
+print_error(char *errmsg)
+{
+ print_status(cerr, errmsg);
+}
+
+static void
+print_prompt(char *prompt)
+{
+ print_status(cprompt, prompt);
+}
+
+static void
+print_info(Pane *pane, char *dirsize)
+{
+ char *sz, *ur, *gr, *dt, *prm;
+
+ dt = ecalloc(MAX_DTF, sizeof(char));
+
+ prm = get_fperm(CURSOR(pane).mode);
+ ur = get_fusr(CURSOR(pane).user);
+ gr = get_fgrp(CURSOR(pane).group);
+
+ if (get_fdt(dt, CURSOR(pane).dt) < 0)
+ *dt = '\0';
+
+ if (S_ISREG(CURSOR(pane).mode)) {
+ sz = get_fsize(CURSOR(pane).size);
+ } else {
+ if (dirsize == NULL) {
+ sz = ecalloc(1, sizeof(char));
+ *sz = '\0';
+ } else {
+ sz = dirsize;
+ }
+ }
+
+ print_status(cstatus, "%02d/%02d %s %s:%s %s %s", pane->hdir,
+ pane->dirc, prm, ur, gr, dt, sz);
+
+ free(prm);
+ free(ur);
+ free(gr);
+ free(dt);
+ free(sz);
+}
+
+static void
+print_row(Pane *pane, size_t entpos, Cpair col)
+{
+ int x, y;
+ char *full_str, *rez_pth;
+ char lnk_full[MAX_N];
+
+ full_str = basename(pane->direntr[entpos].name);
+ x = pane->x_srt;
+ y = entpos - pane->firstrow + 1;
+
+ if (S_ISLNK(pane->direntr[entpos].mode) != 0) {
+ rez_pth = ecalloc(MAX_P, sizeof(char));
+ if (realpath(pane->direntr[entpos].name, rez_pth) != NULL) {
+ snprintf(
+ lnk_full, MAX_N, "%s -> %s", full_str, rez_pth);
+ full_str = lnk_full;
+ }
+ free(rez_pth);
+ }
+
+ printf_tb(x, y, col, "%*.*s", ~hwidth, hwidth, full_str);
+}
+
+static void
+clear(int sx, int ex, int y, uint16_t bg)
+{
+ /* clear line from to */
+ /* x = line number vertical */
+ /* y = column number horizontal */
+ int i;
+ for (i = sx; i < ex; i++) {
+ tb_change_cell(i, y, 0x0000, TB_DEFAULT, bg);
+ }
+}
+
+static void
+clear_status(void)
+{
+ clear(1, twidth - 1, theight - 1, cstatus.bg);
+}
+
+static void
+clear_pane(Pane *pane)
+{
+ int i, y;
+ y = 0, i = 0;
+
+ while (i < scrheight) {
+ clear(pane->x_srt, pane->x_end, y, TB_DEFAULT);
+ i++;
+ y++;
+ }
+
+ /* draw top line */
+ for (y = pane->x_srt; y < pane->x_end; ++y) {
+ tb_change_cell(y, 0, u_hl, cframe.fg, cframe.bg);
+ }
+}
+
+static void
+add_hi(Pane *pane, size_t entpos)
+{
+ Cpair col;
+ get_hicol(&col, pane->direntr[entpos].mode);
+ col.fg |= TB_REVERSE | TB_BOLD;
+ col.bg |= TB_REVERSE;
+ print_row(pane, entpos, col);
+}
+
+static void
+rm_hi(Pane *pane, size_t entpos)
+{
+ Cpair col;
+ get_hicol(&col, pane->direntr[entpos].mode);
+ print_row(pane, entpos, col);
+}
+
+static int
+check_dir(char *path)
+{
+ DIR *dir;
+ dir = opendir(path);
+
+ if (dir == NULL) {
+ if (errno == ENOTDIR) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ if (closedir(dir) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int
+sort_name(const void *const A, const void *const B)
+{
+ int result;
+ mode_t data1 = (*(Entry *)A).mode;
+ mode_t data2 = (*(Entry *)B).mode;
+
+ if (data1 < data2) {
+ return -1;
+ } else if (data1 == data2) {
+ result = strncmp((*(Entry *)A).name, (*(Entry *)B).name, MAX_N);
+ return result;
+ } else {
+ return 1;
+ }
+}
+
+static void
+get_dirp(char *cdir)
+{
+ int counter, len, i;
+
+ counter = 0;
+ len = strnlen(cdir, MAX_P);
+ if (len == 1)
+ return;
+
+ for (i = len - 1; i > 1; i--) {
+ if (cdir[i] == '/')
+ break;
+ else
+ counter++;
+ }
+
+ cdir[len - counter - 1] = '\0';
+}
+
+static char *
+get_ext(char *str)
+{
+ char *ext;
+ char dot;
+ size_t counter, len, i;
+
+ dot = '.';
+ counter = 0;
+ len = strnlen(str, MAX_N);
+
+ for (i = len - 1; i > 0; i--) {
+ if (str[i] == dot) {
+ break;
+ } else {
+ counter++;
+ }
+ }
+
+ ext = ecalloc(MAX_EXT + 1, sizeof(char));
+ strncpy(ext, &str[len - counter], MAX_EXT);
+ ext[MAX_EXT] = '\0';
+ return ext;
+}
+
+static int
+get_fdt(char *result, time_t status)
+{
+ struct tm lt;
+ localtime_r(&status, &lt);
+ return strftime(result, MAX_DTF, dtfmt, &lt);
+}
+
+static char *
+get_fgrp(gid_t status)
+{
+ char *result;
+ struct group *gr;
+
+ result = ecalloc(MAX_GRPN, sizeof(char));
+ gr = getgrgid(status);
+ if (gr == NULL)
+ (void)snprintf(result, MAX_GRPN, "%u", status);
+ else
+ strncpy(result, gr->gr_name, MAX_GRPN);
+
+ result[MAX_GRPN - 1] = '\0';
+ return result;
+}
+
+static char *
+get_fperm(mode_t mode)
+{
+ char *buf;
+ size_t i;
+
+ const char chars[] = "rwxrwxrwx";
+ buf = ecalloc(11, sizeof(char));
+
+ if (S_ISDIR(mode))
+ buf[0] = 'd';
+ else if (S_ISREG(mode))
+ buf[0] = '-';
+ else if (S_ISLNK(mode))
+ buf[0] = 'l';
+ else if (S_ISBLK(mode))
+ buf[0] = 'b';
+ else if (S_ISCHR(mode))
+ buf[0] = 'c';
+ else if (S_ISFIFO(mode))
+ buf[0] = 'p';
+ else if (S_ISSOCK(mode))
+ buf[0] = 's';
+ else
+ buf[0] = '?';
+
+ for (i = 1; i < 10; i++) {
+ buf[i] = (mode & (1 << (9 - i))) ? chars[i - 1] : '-';
+ }
+ buf[10] = '\0';
+
+ return buf;
+}
+
+static char *
+get_fsize(off_t size)
+{
+ char *result; /* need to be freed */
+ char unit;
+ int result_len;
+ int counter;
+
+ counter = 0;
+ result_len = 6; /* 9999X/0 */
+ result = ecalloc(result_len, sizeof(char));
+
+ while (size >= 1000) {
+ size /= 1024;
+ ++counter;
+ }
+
+ switch (counter) {
+ case 0:
+ unit = 'B';
+ break;
+ case 1:
+ unit = 'K';
+ break;
+ case 2:
+ unit = 'M';
+ break;
+ case 3:
+ unit = 'G';
+ break;
+ case 4:
+ unit = 'T';
+ break;
+ default:
+ unit = '?';
+ }
+
+ if (snprintf(result, result_len, OFF_T "%c", size, unit) < 0)
+ strncat(result, "???", result_len);
+
+ return result;
+}
+
+static char *
+get_fullpath(char *first, char *second)
+{
+ char *full_path;
+
+ full_path = ecalloc(MAX_P, sizeof(char));
+
+ if (strncmp(first, "/", MAX_P) == 0)
+ (void)snprintf(full_path, MAX_P, "/%s", second);
+ else
+ (void)snprintf(full_path, MAX_P, "%s/%s", first, second);
+
+ return full_path;
+}
+
+static char *
+get_fusr(uid_t status)
+{
+ char *result;
+ struct passwd *pw;
+
+ result = ecalloc(MAX_USRN, sizeof(char));
+ pw = getpwuid(status);
+ if (pw == NULL)
+ (void)snprintf(result, MAX_USRN, "%u", status);
+ else
+ strncpy(result, pw->pw_name, MAX_USRN);
+
+ result[MAX_USRN - 1] = '\0';
+ return result;
+}
+
+static void
+get_dirsize(char *fullpath, off_t *fullsize)
+{
+ DIR *dir;
+ char *ent_full;
+ mode_t mode;
+ struct dirent *entry;
+ struct stat status;
+
+ dir = opendir(fullpath);
+ if (dir == NULL) {
+ return;
+ }
+
+ while ((entry = readdir(dir)) != 0) {
+ if ((strncmp(entry->d_name, ".", 2) == 0 ||
+ strncmp(entry->d_name, "..", 3) == 0))
+ continue;
+
+ ent_full = get_fullpath(fullpath, entry->d_name);
+ if (lstat(ent_full, &status) == 0) {
+ mode = status.st_mode;
+ if (S_ISDIR(mode)) {
+ get_dirsize(ent_full, fullsize);
+ free(ent_full);
+ } else {
+ *fullsize += status.st_size;
+ free(ent_full);
+ }
+ }
+ }
+
+ closedir(dir);
+ clear_status();
+}
+
+static void
+get_hicol(Cpair *col, mode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFREG:
+ *col = cfile;
+ if ((S_IXUSR | S_IXGRP | S_IXOTH) & mode)
+ *col = cexec;
+ break;
+ case S_IFDIR:
+ *col = cdir;
+ break;
+ case S_IFLNK:
+ *col = clnk;
+ break;
+ case S_IFBLK:
+ *col = cblk;
+ break;
+ case S_IFCHR:
+ *col = cchr;
+ break;
+ case S_IFIFO:
+ *col = cifo;
+ break;
+ case S_IFSOCK:
+ *col = csock;
+ break;
+ default:
+ *col = cother;
+ break;
+ }
+}
+
+static void
+delent(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char *inp_conf;
+
+ inp_conf = ecalloc(delconf_len, sizeof(char));
+ if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?",
+ delconf) < 0) ||
+ (strncmp(inp_conf, delconf, delconf_len) != 0)) {
+ free(inp_conf);
+ return; /* canceled by user or wrong inp_conf */
+ }
+ free(inp_conf);
+
+ char *tmp[1];
+ tmp[0] = CURSOR(cpane).name;
+ if (spawn(rm_cmd, rm_cmd_len, tmp, 1, NULL, DontWait) < 0) {
+ print_error(strerror(errno));
+ return;
+ }
+}
+
+static void
+calcdir(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ if (!S_ISDIR(CURSOR(cpane).mode))
+ return;
+
+ off_t *fullsize;
+ char *csize;
+
+ fullsize = ecalloc(1, sizeof(off_t));
+ get_dirsize(CURSOR(cpane).name, fullsize);
+ csize = get_fsize(*fullsize);
+
+ CURSOR(cpane).size = *fullsize;
+ print_info(cpane, csize);
+ free(fullsize);
+}
+
+static void
+crnd(const Arg *arg)
+{
+ char *user_input, *path;
+
+ user_input = ecalloc(MAX_USRI, sizeof(char));
+ if (get_usrinput(user_input, MAX_USRI, "new dir") < 0) {
+ free(user_input);
+ return;
+ }
+
+ path = ecalloc(MAX_P, sizeof(char));
+ if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
+ free(user_input);
+ free(path);
+ return;
+ }
+
+ PERROR(mkdir(path, ndir_perm) < 0);
+
+ free(user_input);
+ free(path);
+}
+
+static void
+crnf(const Arg *arg)
+{
+ char *user_input, *path;
+ int rf;
+
+ user_input = ecalloc(MAX_USRI, sizeof(char));
+ if (get_usrinput(user_input, MAX_USRI, "new file") < 0) {
+ free(user_input);
+ return;
+ }
+
+ path = ecalloc(MAX_P, sizeof(char));
+ if (snprintf(path, MAX_P, "%s/%s", cpane->dirn, user_input) < 0) {
+ free(user_input);
+ free(path);
+ return;
+ }
+
+ rf = open(path, O_CREAT | O_EXCL, nf_perm);
+
+ if (rf < 0)
+ print_error(strerror(errno));
+ else if (close(rf) < 0)
+ print_error(strerror(errno));
+
+ free(user_input);
+ free(path);
+}
+static void
+mv_ver(const Arg *arg)
+{
+
+ if (cpane->dirc < 1)
+ return;
+ if (cpane->hdir - arg->i < 1) /* first line */
+ return;
+
+ if (cpane->hdir - arg->i > cpane->dirc) /* last line */
+ return;
+
+ if (cpane->firstrow > 0 && arg->i > 0 &&
+ cpane->hdir <= (cpane->firstrow + arg->i)) { /* scroll up */
+ cpane->firstrow = cpane->firstrow - arg->i;
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = cpane->hdir - arg->i;
+ refresh_pane(cpane);
+ add_hi(cpane, cpane->hdir - 1);
+ return;
+ }
+
+ if (cpane->hdir - cpane->firstrow >= scrheight + arg->i &&
+ arg->i < 0) { /* scroll down */
+ cpane->firstrow = cpane->firstrow - arg->i;
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = cpane->hdir - arg->i;
+ refresh_pane(cpane);
+ add_hi(cpane, cpane->hdir - 1);
+ return;
+ }
+
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = cpane->hdir - arg->i;
+ add_hi(cpane, cpane->hdir - 1);
+ print_info(cpane, NULL);
+}
+
+static void
+mvbk(const Arg *arg)
+{
+ if (cpane->dirn[0] == '/' && cpane->dirn[1] == '\0') { /* cwd = / */
+ return;
+ }
+
+ get_dirp(cpane->dirn);
+ if (check_dir(cpane->dirn) < 0) {
+ print_error(strerror(errno));
+ return;
+ }
+
+ cpane->firstrow = cpane->parent_firstrow;
+ cpane->hdir = cpane->parent_row;
+ PERROR(listdir(cpane) < 0);
+ cpane->parent_firstrow = 0;
+ cpane->parent_row = 1;
+}
+
+static void
+mvbtm(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ if (cpane->dirc > scrheight) {
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = cpane->dirc;
+ cpane->firstrow = cpane->dirc - scrheight + 1;
+ refresh_pane(cpane);
+ add_hi(cpane, cpane->hdir - 1);
+ } else {
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = cpane->dirc;
+ add_hi(cpane, cpane->hdir - 1);
+ }
+ print_info(cpane, NULL);
+}
+
+static void
+mvfwd(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ int s;
+
+ switch (check_dir(CURSOR(cpane).name)) {
+ case 0:
+ strncpy(cpane->dirn, CURSOR(cpane).name, MAX_P);
+ cpane->parent_row = cpane->hdir;
+ cpane->parent_firstrow = cpane->firstrow;
+ cpane->hdir = 1;
+ cpane->firstrow = 0;
+ PERROR(listdir(cpane) < 0);
+ break;
+ case 1: /* not a directory open file */
+ tb_shutdown();
+ s = opnf(CURSOR(cpane).name);
+ if (tb_init() != 0)
+ die("tb_init");
+ t_resize();
+ if (s < 0)
+ print_error("process failed non-zero exit");
+ break;
+ case -1: /* failed to open directory */
+ print_error(strerror(errno));
+ }
+}
+
+static void
+mvtop(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ if (cpane->dirc > scrheight) {
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = 1;
+ cpane->firstrow = 0;
+ refresh_pane(cpane);
+ add_hi(cpane, cpane->hdir - 1);
+ } else {
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane->hdir = 1;
+ add_hi(cpane, cpane->hdir - 1);
+ print_info(cpane, NULL);
+ }
+}
+
+static void
+bkmrk(const Arg *arg)
+{
+ if (check_dir((char *)arg->v) != 0) {
+ print_error(strerror(errno));
+ return;
+ }
+
+ strncpy(cpane->dirn, (char *)arg->v, MAX_P);
+ cpane->firstrow = 0;
+ cpane->parent_row = 1;
+ cpane->hdir = 1;
+ PERROR(listdir(cpane) < 0);
+}
+
+static int
+get_usrinput(char *result, size_t max_chars, const char *fmt, ...)
+{
+ char msg[MAX_N];
+ size_t i, cpos, startat;
+ struct tb_event fev;
+ va_list vl;
+
+ i = 0;
+ cpos = 1;
+
+ va_start(vl, fmt);
+ startat = vsnprintf(msg, MAX_N, fmt, vl) + 1;
+ va_end(vl);
+
+ clear_status();
+ print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg);
+ tb_set_cursor(startat + 1, theight - 1);
+ tb_present();
+
+ while (tb_poll_event(&fev) != 0) {
+ switch (fev.type) {
+ case TB_EVENT_KEY:
+ if (fev.key == TB_KEY_ESC) {
+ tb_set_cursor(-1, -1);
+ clear_status();
+ return -1;
+ }
+
+ if (fev.key == TB_KEY_BACKSPACE ||
+ fev.key == TB_KEY_BACKSPACE2) {
+ if (BETWEEN(cpos, 2, max_chars)) {
+ result[i - 1] = '\0';
+ cpos--;
+ i--;
+ print_xstatus(' ', startat + cpos);
+ tb_set_cursor(
+ startat + cpos, theight - 1);
+ }
+
+ } else if (fev.key == TB_KEY_ENTER) {
+ tb_set_cursor(-1, -1);
+ result[cpos - 1] = '\0';
+ return 0;
+
+ } else if (fev.key) { /* disable other TB_KEY_* */
+ break;
+
+ } else {
+ if (cpos < max_chars) {
+ print_xstatus(
+ (char)fev.ch, (startat + cpos));
+ result[i] = (char)fev.ch;
+ tb_set_cursor((startat + cpos + 1),
+ theight - 1);
+ cpos++;
+ i++;
+ }
+ }
+
+ tb_present();
+ break;
+
+ case TB_EVENT_RESIZE:
+ t_resize();
+ clear_status();
+ print_tb(msg, 1, theight - 1, cprompt.fg, cprompt.bg);
+ print_tb(result, startat + 1, theight - 1, cstatus.fg,
+ cstatus.bg);
+ tb_present();
+ break;
+
+ default:
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+static int
+frules(char *ex)
+{
+ size_t c, d;
+
+ for (c = 0; c < LEN(rules); c++)
+ for (d = 0; d < rules[c].exlen; d++)
+ if (strncmp(rules[c].ext[d], ex, MAX_EXT) == 0)
+ return c;
+ return -1;
+}
+
+static int
+spawn(const void *com_argv, size_t com_argc, const void *f_argv, size_t f_argc,
+ char *fn, int waiting)
+{
+ int ws;
+ size_t argc;
+ pid_t r;
+
+ argc = com_argc + f_argc + 2;
+ char *argv[argc];
+
+ memcpy(argv, com_argv, com_argc * sizeof(char *)); /* command */
+ memcpy(&argv[com_argc], f_argv, f_argc * sizeof(char *)); /* files */
+
+ argv[argc - 2] = fn;
+ argv[argc - 1] = NULL;
+
+ fork_pid = fork();
+ switch (fork_pid) {
+ case -1:
+ return -1;
+ case 0:
+ execvp(argv[0], argv);
+ exit(EXIT_SUCCESS);
+ default:
+ if (waiting == Wait) {
+ while ((r = waitpid(fork_pid, &ws, 0)) == -1 &&
+ errno == EINTR)
+ continue;
+ if (r == -1)
+ return -1;
+ if ((WIFEXITED(ws) != 0) && (WEXITSTATUS(ws) != 0))
+ return -1;
+ }
+ }
+ fork_pid = 0; /* enable th_handler() */
+ return 0;
+}
+
+static int
+opnf(char *fn)
+{
+ char *ex;
+ int c;
+
+ ex = get_ext(fn);
+ c = frules(ex);
+ free(ex);
+
+ if (c < 0) /* extension not found open in editor */
+ return spawn(editor, 1, NULL, 0, fn, Wait);
+ else
+ return spawn(
+ (char **)rules[c].v, rules[c].vlen, NULL, 0, fn, Wait);
+}
+
+static void
+opnsh(const Arg *arg)
+{
+ int s;
+
+ tb_shutdown();
+ chdir(cpane->dirn);
+ s = spawn(shell, 1, NULL, 0, NULL, Wait);
+ if (tb_init() != 0)
+ die("tb_init");
+ t_resize();
+ if (s < 0)
+ print_error("process failed non-zero exit");
+}
+
+static int
+fsev_init(void)
+{
+#if defined(_SYS_INOTIFY_H)
+ inotify_fd = inotify_init();
+ if (inotify_fd < 0)
+ return -1;
+#elif defined(_SYS_EVENT_H_)
+ gtimeout.tv_sec = 1;
+ kq = kqueue();
+ if (kq < 0)
+ return -1;
+#endif
+ return 0;
+}
+
+static int
+addwatch(Pane *pane)
+{
+#if defined(_SYS_INOTIFY_H)
+ return pane->inotify_wd = inotify_add_watch(inotify_fd, pane->dirn,
+ IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE |
+ IN_ATTRIB | IN_DELETE | IN_DELETE_SELF |
+ IN_MOVE_SELF);
+#elif defined(_SYS_EVENT_H_)
+ pane->event_fd = open(pane->dirn, O_RDONLY);
+ if (pane->event_fd < 0)
+ return pane->event_fd;
+ EV_SET(&evlist[pane->pane_id], pane->event_fd, EVFILT_VNODE,
+ EV_ADD | EV_CLEAR,
+ NOTE_DELETE | NOTE_EXTEND | NOTE_LINK | NOTE_RENAME |
+ NOTE_ATTRIB | NOTE_REVOKE | NOTE_WRITE,
+ 0, NULL);
+ return 0;
+#endif
+}
+
+static int
+read_events(void)
+{
+#if defined(_SYS_INOTIFY_H)
+ char *p;
+ ssize_t r;
+ struct inotify_event *event;
+ const size_t events = 32;
+ const size_t evbuflen =
+ events * (sizeof(struct inotify_event) + MAX_N + 1);
+ char buf[evbuflen];
+
+ if (cpane->inotify_wd < 0)
+ return -1;
+ r = read(inotify_fd, buf, evbuflen);
+ if (r <= 0)
+ return r;
+
+ for (p = buf; p < buf + r;) {
+ event = (struct inotify_event *)p;
+ if (!event->wd)
+ break;
+ if (event->mask) {
+ return r;
+ }
+
+ p += sizeof(struct inotify_event) + event->len;
+ }
+#elif defined(_SYS_EVENT_H_)
+ return kevent(kq, evlist, 2, chlist, 2, &gtimeout);
+#endif
+ return -1;
+}
+
+static void
+rmwatch(Pane *pane)
+{
+#if defined(_SYS_INOTIFY_H)
+ if (pane->inotify_wd >= 0)
+ inotify_rm_watch(inotify_fd, pane->inotify_wd);
+#elif defined(_SYS_EVENT_H_)
+ close(pane->event_fd);
+#endif
+}
+
+static void
+fsev_shdn(void)
+{
+ pthread_cancel(fsev_thread);
+#if defined(__linux__)
+ pthread_join(fsev_thread, NULL);
+#endif
+ rmwatch(&panes[Left]);
+ rmwatch(&panes[Right]);
+#if defined(_SYS_INOTIFY_H)
+ close(inotify_fd);
+#elif defined(_SYS_EVENT_H_)
+ close(kq);
+#endif
+}
+
+static void
+toggle_df(const Arg *arg)
+{
+ show_dotfiles = !show_dotfiles;
+ PERROR(listdir(&panes[Left]));
+ PERROR(listdir(&panes[Right]));
+ tb_present();
+}
+
+static void
+start_filter(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char *user_input;
+ user_input = ecalloc(MAX_USRI, sizeof(char));
+ if (get_usrinput(user_input, MAX_USRI, "filter") < 0) {
+ free(user_input);
+ return;
+ }
+ cpane->filter = user_input;
+ if (listdir(cpane) < 0)
+ print_error("no match");
+ cpane->filter = NULL;
+ free(user_input);
+}
+
+static void
+start_vmode(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ struct tb_event fev;
+ if (sel_indexes != NULL) {
+ free(sel_indexes);
+ sel_indexes = NULL;
+ }
+
+ sel_indexes = ecalloc(cpane->dirc, sizeof(size_t));
+ sel_indexes[0] = cpane->hdir;
+ cont_vmode = 0;
+ print_prompt("-- VISUAL --");
+ tb_present();
+ while (tb_poll_event(&fev) != 0) {
+ switch (fev.type) {
+ case TB_EVENT_KEY:
+ grabkeys(&fev, vkeys, vkeyslen);
+ if (cont_vmode == -1)
+ return;
+ tb_present();
+ break;
+ }
+ }
+}
+
+static void
+exit_vmode(const Arg *arg)
+{
+ refresh_pane(cpane);
+ add_hi(cpane, cpane->hdir - 1);
+ cont_vmode = -1;
+}
+
+static void
+start_change(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ struct tb_event fev;
+
+ cont_change = 0;
+ print_prompt("c [womf]");
+ tb_present();
+ while (tb_poll_event(&fev) != 0) {
+ switch (fev.type) {
+ case TB_EVENT_KEY:
+ grabkeys(&fev, ckeys, ckeyslen);
+ if (cont_change == -1)
+ return;
+ tb_present();
+ break;
+ }
+ }
+}
+
+static void
+exit_change(const Arg *arg)
+{
+ cont_change = -1;
+ print_info(cpane, NULL);
+}
+
+static void
+selup(const Arg *arg)
+{
+ mv_ver(arg);
+ print_prompt("-- VISUAL --");
+ int index = abs(cpane->hdir - sel_indexes[0]);
+
+ if (cpane->hdir < sel_indexes[0]) {
+ sel_indexes[index] = cpane->hdir;
+ add_hi(cpane, sel_indexes[index]);
+ } else if (index < cpane->dirc) {
+ sel_indexes[index + 1] = 0;
+ }
+ if (cpane->dirc >= scrheight ||
+ cpane->hdir <= 1) { /* rehighlight all if scrolling */
+ selref();
+ }
+}
+
+static void
+seldwn(const Arg *arg)
+{
+ mv_ver(arg);
+ print_prompt("-- VISUAL --");
+ int index = abs(cpane->hdir - sel_indexes[0]);
+
+ if (cpane->hdir > sel_indexes[0]) {
+ sel_indexes[index] = cpane->hdir;
+ add_hi(cpane, sel_indexes[index] - 2);
+ } else {
+ sel_indexes[index + 1] = 0;
+ }
+ if (cpane->dirc >= scrheight ||
+ cpane->hdir >= cpane->dirc) { /* rehighlight all if scrolling */
+ selref();
+ }
+}
+
+static void
+selall(const Arg *arg)
+{
+ int i;
+ for (i = 0; i < cpane->dirc; i++) {
+ sel_indexes[i] = i + 1;
+ }
+ selref();
+}
+
+static void
+selref(void)
+{
+ int i;
+ for (i = 0; i < cpane->dirc; i++) {
+ if (sel_indexes[i] < (scrheight + cpane->firstrow) &&
+ sel_indexes[i] >
+ cpane->firstrow) { /* checks if in the frame of the directories */
+ add_hi(cpane, sel_indexes[i] - 1);
+ }
+ }
+}
+
+static void
+selcalc(void)
+{
+ int j;
+ sel_len = 0;
+
+ for (j = 0; j < cpane->dirc; j++) { /* calculate used selection size */
+ if (sel_indexes[j] != 0)
+ sel_len++;
+ else
+ break;
+ }
+}
+
+static void
+free_files(void)
+{
+ size_t i;
+
+ if (sel_files != NULL) {
+ for (i = 0; i < sel_len; i++) {
+ free(sel_files[i]);
+ sel_files[i] = NULL;
+ }
+ free(sel_files);
+ sel_files = NULL;
+ }
+}
+
+static void
+init_files(void)
+{
+ size_t i;
+ free_files();
+
+ selcalc();
+ sel_files = ecalloc(sel_len, sizeof(char *));
+
+ for (i = 0; i < sel_len; i++) {
+ sel_files[i] = ecalloc(MAX_P, sizeof(char));
+ strncpy(sel_files[i], cpane->direntr[sel_indexes[i] - 1].name,
+ MAX_P);
+ }
+}
+
+static void
+selynk(const Arg *arg)
+{
+ init_files();
+ refresh_pane(cpane);
+ add_hi(cpane, cpane->hdir - 1);
+ print_status(cprompt, "%zu files are yanked", sel_len);
+ cont_vmode = -1;
+}
+
+static void
+seldel(const Arg *arg)
+{
+ char *inp_conf;
+
+ inp_conf = ecalloc(delconf_len, sizeof(char));
+ if ((get_usrinput(inp_conf, delconf_len, "delete files(s) (%s) ?",
+ delconf) < 0) ||
+ (strncmp(inp_conf, delconf, delconf_len) != 0)) {
+ free(inp_conf);
+ return; /* canceled by user or wrong inp_conf */
+ }
+ free(inp_conf);
+
+ init_files();
+
+ if (spawn(rm_cmd, rm_cmd_len, sel_files, sel_len, NULL, DontWait) < 0)
+ print_error(strerror(errno));
+ else
+ print_status(cprompt, "%zu files are deleted", sel_len);
+
+ free_files();
+ cont_vmode = -1;
+}
+
+static void
+paste(const Arg *arg)
+{
+ if (sel_files == NULL) {
+ print_error("nothing to paste");
+ return;
+ }
+
+ if (spawn(cp_cmd, cp_cmd_len, sel_files, sel_len, cpane->dirn,
+ DontWait) < 0)
+ print_error(strerror(errno));
+ else
+ print_status(cprompt, "%zu files are copied", sel_len);
+
+ free_files();
+}
+
+static void
+selmv(const Arg *arg)
+{
+ if (sel_files == NULL) {
+ print_error("nothing to move");
+ return;
+ }
+
+ if (spawn(mv_cmd, mv_cmd_len, sel_files, sel_len, cpane->dirn,
+ DontWait) < 0)
+ print_error(strerror(errno));
+ else
+ print_status(cprompt, "%zu files are moved", sel_len);
+
+ free_files();
+}
+
+static void
+rname(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char new_name[MAX_P];
+ char *input_name;
+
+ input_name = ecalloc(MAX_N, sizeof(char));
+
+ if (get_usrinput(input_name, MAX_N, "rename: %s",
+ basename(CURSOR(cpane).name)) < 0) {
+ exit_change(0);
+ free(input_name);
+ return;
+ }
+
+ if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
+ free(input_name);
+ print_error(strerror(errno));
+ return;
+ }
+
+ char *rename_cmd[] = { "mv", CURSOR(cpane).name, new_name };
+ PERROR(spawn(rename_cmd, 3, NULL, 0, NULL, DontWait) < 0);
+
+ free(input_name);
+ exit_change(0);
+}
+
+static void
+chngo(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char *input_og;
+ char *tmp[1];
+
+ input_og = ecalloc(MAX_N, sizeof(char));
+
+ if (get_usrinput(input_og, MAX_N, "OWNER:GROUP %s",
+ basename(CURSOR(cpane).name)) < 0) {
+ exit_change(0);
+ free(input_og);
+ return;
+ }
+
+ tmp[0] = input_og;
+ if (spawn(chown_cmd, chown_cmd_len, tmp, 1, CURSOR(cpane).name,
+ DontWait) < 0) {
+ print_error(strerror(errno));
+ return;
+ }
+
+ free(input_og);
+ exit_change(0);
+}
+
+static void
+chngm(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char *input_og;
+ char *tmp[1];
+
+ input_og = ecalloc(MAX_N, sizeof(char));
+
+ if (get_usrinput(input_og, MAX_N, "chmod %s",
+ basename(CURSOR(cpane).name)) < 0) {
+ exit_change(0);
+ free(input_og);
+ return;
+ }
+
+ tmp[0] = input_og;
+ if (spawn(chmod_cmd, chmod_cmd_len, tmp, 1, CURSOR(cpane).name,
+ DontWait) < 0) {
+ print_error(strerror(errno));
+ return;
+ }
+
+ free(input_og);
+ exit_change(0);
+}
+
+static void
+chngf(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char *input_og;
+ char *tmp[1];
+
+ input_og = ecalloc(MAX_N, sizeof(char));
+
+ if (get_usrinput(input_og, MAX_N, CHFLAG " %s",
+ basename(CURSOR(cpane).name)) < 0) {
+ exit_change(0);
+ free(input_og);
+ return;
+ }
+
+ tmp[0] = input_og;
+ if (spawn(chflags_cmd, chflags_cmd_len, tmp, 1, CURSOR(cpane).name,
+ DontWait) < 0) {
+ print_error(strerror(errno));
+ return;
+ }
+
+ free(input_og);
+ exit_change(0);
+}
+
+static void
+dupl(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+ char new_name[MAX_P];
+ char *input_name;
+
+ input_name = ecalloc(MAX_N, sizeof(char));
+
+ if (get_usrinput(input_name, MAX_N, "new name: %s",
+ basename(CURSOR(cpane).name)) < 0) {
+ free(input_name);
+ return;
+ }
+
+ if (snprintf(new_name, MAX_P, "%s/%s", cpane->dirn, input_name) < 0) {
+ free(input_name);
+ print_error(strerror(errno));
+ return;
+ }
+
+ char *tmp[1];
+ tmp[0] = CURSOR(cpane).name;
+ if (spawn(cp_cmd, cp_cmd_len, tmp, 1, new_name, DontWait) < 0) {
+ print_error(strerror(errno));
+ return;
+ }
+
+ free(input_name);
+}
+
+static void
+yank(const Arg *arg)
+{
+ if (cpane->dirc < 1)
+ return;
+
+ free_files();
+ sel_len = 1;
+ sel_files = ecalloc(sel_len, sizeof(char *));
+ sel_files[0] = ecalloc(MAX_P, sizeof(char));
+ strncpy(sel_files[0], CURSOR(cpane).name, MAX_P);
+ print_status(cprompt, "1 file is yanked", sel_len);
+}
+
+static void
+switch_pane(const Arg *arg)
+{
+ if (cpane->dirc > 0)
+ rm_hi(cpane, cpane->hdir - 1);
+ cpane = &panes[pane_idx ^= 1];
+ if (cpane->dirc > 0) {
+ add_hi(cpane, cpane->hdir - 1);
+ print_info(cpane, NULL);
+ } else {
+ clear_status();
+ }
+}
+
+static void
+quit(const Arg *arg)
+{
+ if (cont_vmode == -1) { /* check if selection was allocated */
+ free(sel_indexes);
+ if (sel_files != NULL)
+ free_files();
+ }
+ free(panes[Left].direntr);
+ free(panes[Right].direntr);
+ fsev_shdn();
+ tb_shutdown();
+ exit(EXIT_SUCCESS);
+}
+
+static void
+grabkeys(struct tb_event *event, Key *key, size_t max_keys)
+{
+ size_t i;
+
+ for (i = 0; i < max_keys; i++) {
+ if (event->ch != 0) {
+ if (event->ch == key[i].evkey.ch) {
+ key[i].func(&key[i].arg);
+ return;
+ }
+ } else if (event->key != 0) {
+ if (event->key == key[i].evkey.key) {
+ key[i].func(&key[i].arg);
+ return;
+ }
+ }
+ }
+}
+
+void *
+read_th(void *arg)
+{
+ struct timespec tim;
+ tim.tv_sec = 0;
+ tim.tv_nsec = 5000000L; /* 0.005 sec */
+
+ while (1)
+ if (read_events() > READEVSZ) {
+ kill(main_pid, SIGUSR1);
+ nanosleep(&tim, NULL);
+ }
+ return arg;
+}
+
+static void
+start_ev(void)
+{
+ struct tb_event ev;
+
+ while (tb_poll_event(&ev) != 0) {
+ switch (ev.type) {
+ case TB_EVENT_KEY:
+ grabkeys(&ev, nkeys, nkeyslen);
+ tb_present();
+ break;
+ case TB_EVENT_RESIZE:
+ t_resize();
+ break;
+ default:
+ break;
+ }
+ }
+ tb_shutdown();
+}
+
+static void
+refresh_pane(Pane *pane)
+{
+ size_t y, dyn_max, start_from;
+ hwidth = (twidth / 2) - 4;
+ Cpair col;
+
+ y = 1;
+ start_from = pane->firstrow;
+ dyn_max = MIN(pane->dirc, (scrheight - 1) + pane->firstrow);
+
+ /* print each entry in directory */
+ while (start_from < dyn_max) {
+ get_hicol(&col, pane->direntr[start_from].mode);
+ print_row(pane, start_from, col);
+ start_from++;
+ y++;
+ }
+
+ if (pane->dirc > 0)
+ print_info(pane, NULL);
+ else
+ clear_status();
+
+ /* print current directory title */
+ pane->dircol.fg |= TB_BOLD;
+ printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn);
+}
+
+static void
+set_direntr(Pane *pane, struct dirent *entry, DIR *dir, char *filter)
+{
+ int i;
+ char *tmpfull;
+ struct stat status;
+
+#define ADD_ENTRY \
+ tmpfull = get_fullpath(pane->dirn, entry->d_name); \
+ strncpy(pane->direntr[i].name, tmpfull, MAX_N); \
+ if (lstat(tmpfull, &status) == 0) { \
+ pane->direntr[i].size = status.st_size; \
+ pane->direntr[i].mode = status.st_mode; \
+ pane->direntr[i].group = status.st_gid; \
+ pane->direntr[i].user = status.st_uid; \
+ pane->direntr[i].dt = status.st_mtime; \
+ } \
+ i++; \
+ free(tmpfull);
+
+ i = 0;
+ pane->direntr =
+ erealloc(pane->direntr, (10 + pane->dirc) * sizeof(Entry));
+ while ((entry = readdir(dir)) != 0) {
+ if (show_dotfiles == 1) {
+ if (entry->d_name[0] == '.' &&
+ (entry->d_name[1] == '\0' ||
+ entry->d_name[1] == '.'))
+ continue;
+ } else {
+ if (entry->d_name[0] == '.')
+ continue;
+ }
+
+ if (filter == NULL) {
+ ADD_ENTRY
+ } else if (filter != NULL) {
+ if (strcasestr(entry->d_name, filter) != NULL) {
+ ADD_ENTRY
+ }
+ }
+ }
+
+ pane->dirc = i;
+}
+
+static int
+listdir(Pane *pane)
+{
+ DIR *dir;
+ struct dirent *entry;
+ int filtercount = 0;
+ size_t oldc = pane->dirc;
+
+ pane->dirc = 0;
+
+ dir = opendir(pane->dirn);
+ if (dir == NULL)
+ return -1;
+
+ /* get content and filter sum */
+ while ((entry = readdir(dir)) != 0) {
+ if (pane->filter != NULL) {
+ if (strcasestr(entry->d_name, pane->filter) != NULL)
+ filtercount++;
+ } else { /* no filter */
+ pane->dirc++;
+ }
+ }
+
+ if (pane->filter == NULL) {
+ clear_pane(pane);
+ pane->dirc -= 2;
+ }
+
+ if (pane->filter != NULL) {
+ if (filtercount > 0) {
+ pane->dirc = filtercount;
+ clear_pane(pane);
+ pane->hdir = 1;
+ } else if (filtercount == 0) {
+ if (closedir(dir) < 0)
+ return -1;
+ pane->dirc = oldc;
+ return -1;
+ }
+ }
+
+ /* print current directory title */
+ pane->dircol.fg |= TB_BOLD;
+ printf_tb(pane->x_srt, 0, pane->dircol, " %.*s", hwidth, pane->dirn);
+
+ if (pane->filter == NULL) /* dont't watch when filtering */
+ if (addwatch(pane) < 0)
+ print_error("can't add watch");
+
+ /* empty directory */
+ if (pane->dirc == 0) {
+ clear_status();
+ if (closedir(dir) < 0)
+ return -1;
+ return 0;
+ }
+
+ rewinddir(dir); /* reset position */
+ set_direntr(
+ pane, entry, dir, pane->filter); /* create array of entries */
+ qsort(pane->direntr, pane->dirc, sizeof(Entry), sort_name);
+ refresh_pane(pane);
+
+ if (pane->hdir > pane->dirc)
+ pane->hdir = pane->dirc;
+
+ if (pane == cpane && pane->dirc > 0)
+ add_hi(pane, pane->hdir - 1);
+
+ if (closedir(dir) < 0)
+ return -1;
+ return 0;
+}
+
+static void
+t_resize(void)
+{
+ tb_clear();
+ draw_frame();
+ panes[Left].x_end = (twidth / 2) - 1;
+ panes[Right].x_end = twidth - 1;
+ panes[Right].x_srt = (twidth / 2) + 2;
+ refresh_pane(&panes[Left]);
+ refresh_pane(&panes[Right]);
+ if (cpane->dirc > 0)
+ add_hi(cpane, cpane->hdir - 1);
+ tb_present();
+}
+
+static void
+get_editor(void)
+{
+ editor[0] = getenv("EDITOR");
+ editor[1] = NULL;
+
+ if (editor[0] == NULL)
+ editor[0] = fed;
+}
+
+static void
+get_shell(void)
+{
+ shell[0] = getenv("SHELL");
+ shell[1] = NULL;
+
+ if (shell[0] == NULL)
+ shell[0] = sh;
+}
+
+static void
+set_panes(void)
+{
+ char *home;
+ char cwd[MAX_P];
+
+ home = getenv("HOME");
+ if (home == NULL)
+ home = "/";
+ if ((getcwd(cwd, sizeof(cwd)) == NULL))
+ strncpy(cwd, home, MAX_P);
+
+ pane_idx = Left; /* cursor pane */
+ cpane = &panes[pane_idx];
+
+ panes[Left].pane_id = 0;
+ panes[Left].x_srt = 2;
+ panes[Left].x_end = (twidth / 2) - 1;
+ panes[Left].dircol = cpanell;
+ panes[Left].firstrow = 0;
+ panes[Left].direntr = ecalloc(0, sizeof(Entry));
+ strncpy(panes[Left].dirn, cwd, MAX_P);
+ panes[Left].hdir = 1;
+ panes[Left].inotify_wd = -1;
+ panes[Left].parent_row = 1;
+
+ panes[Right].pane_id = 1;
+ panes[Right].x_srt = (twidth / 2) + 2;
+ panes[Right].x_end = twidth - 1;
+ panes[Right].dircol = cpanelr;
+ panes[Right].firstrow = 0;
+ panes[Right].direntr = ecalloc(0, sizeof(Entry));
+ strncpy(panes[Right].dirn, home, MAX_P);
+ panes[Right].hdir = 1;
+ panes[Right].inotify_wd = -1;
+ panes[Right].parent_row = 1;
+}
+
+static void
+draw_frame(void)
+{
+ int i;
+ theight = tb_height();
+ twidth = tb_width();
+ hwidth = (twidth / 2) - 4;
+ scrheight = theight - 2;
+
+ /* 2 horizontal lines */
+ for (i = 1; i < twidth - 1; ++i) {
+ tb_change_cell(i, 0, u_hl, cframe.fg, cframe.bg);
+ tb_change_cell(i, theight - 2, u_hl, cframe.fg, cframe.bg);
+ }
+
+ /* 4 vertical lines */
+ for (i = 1; i < theight - 1; ++i) {
+ tb_change_cell(0, i, u_vl, cframe.fg, cframe.bg);
+ tb_change_cell(
+ (twidth - 1) / 2, i - 1, u_vl, cframe.fg, cframe.bg);
+ tb_change_cell(((twidth - 1) / 2) + 1, i - 1, u_vl, cframe.fg,
+ cframe.bg);
+ tb_change_cell(twidth - 1, i, u_vl, cframe.fg, cframe.bg);
+ }
+
+ /* 4 corners */
+ tb_change_cell(0, 0, u_cnw, cframe.fg, cframe.bg);
+ tb_change_cell(twidth - 1, 0, u_cne, cframe.fg, cframe.bg);
+ tb_change_cell(0, theight - 2, u_csw, cframe.fg, cframe.bg);
+ tb_change_cell(twidth - 1, theight - 2, u_cse, cframe.fg, cframe.bg);
+
+ /* 2 middel top and bottom */
+ tb_change_cell((twidth - 1) / 2, 0, u_mn, cframe.fg, cframe.bg);
+ tb_change_cell(
+ (twidth - 1) / 2, theight - 2, u_ms, cframe.fg, cframe.bg);
+}
+
+void
+th_handler(int num)
+{
+ if (fork_pid > 0) /* while forking don't listdir() */
+ return;
+ (void)num;
+ PERROR(listdir(&panes[Left]));
+ PERROR(listdir(&panes[Right]));
+ tb_present();
+}
+
+static int
+start_signal(void)
+{
+ struct sigaction sa;
+
+ main_pid = getpid();
+ sa.sa_handler = th_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ return sigaction(SIGUSR1, &sa, NULL);
+}
+
+static void
+refresh(const Arg *arg)
+{
+ kill(main_pid, SIGWINCH);
+}
+
+static void
+start(void)
+{
+ switch (tb_init()) {
+ case TB_EFAILED_TO_OPEN_TTY:
+ die("TB_EFAILED_TO_OPEN_TTY");
+ break;
+ case TB_EUNSUPPORTED_TERMINAL:
+ die("TB_EUNSUPPORTED_TERMINAL");
+ break;
+ case TB_EPIPE_TRAP_ERROR:
+ die("TB_EUNSUPPORTED_TERMINAL");
+ break;
+ case 0:
+ break;
+ default:
+ die("UNKNOWN FAILURE");
+ }
+
+ if (tb_select_output_mode(TB_OUTPUT_256) != TB_OUTPUT_256)
+ if (tb_select_output_mode(TB_OUTPUT_NORMAL) != TB_OUTPUT_NORMAL)
+ die("output error");
+ draw_frame();
+ set_panes();
+ get_editor();
+ get_shell();
+ PERROR(start_signal() < 0);
+ PERROR(fsev_init() < 0);
+ PERROR(listdir(&panes[Left]) < 0);
+ PERROR(listdir(&panes[Right]) < 0);
+ tb_present();
+
+ pthread_create(&fsev_thread, NULL, read_th, NULL);
+ start_ev();
+}
+
+int
+main(int argc, char *argv[])
+{
+#if defined(__OpenBSD__)
+ if (pledge("cpath exec getpw proc rpath stdio tmppath tty wpath",
+ NULL) == -1)
+ die("pledge");
+#endif /* __OpenBSD__ */
+ if (argc == 1)
+ start();
+ else if (argc == 2 && strncmp("-v", argv[1], 2) == 0)
+ die("sfm-" VERSION);
+ else
+ die("usage: sfm [-v]");
+ return 0;
+}
diff --git a/sfm-0.4/sfm.png b/sfm-0.4/sfm.png
new file mode 100644
index 0000000..c7b1f8e
--- /dev/null
+++ b/sfm-0.4/sfm.png
Binary files differ
diff --git a/sfm-0.4/termbox.c b/sfm-0.4/termbox.c
new file mode 100644
index 0000000..1c7bfdd
--- /dev/null
+++ b/sfm-0.4/termbox.c
@@ -0,0 +1,1509 @@
+#if defined(__linux__)
+#define _GNU_SOURCE
+#elif defined(__APPLE__)
+#define _DARWIN_C_SOURCE
+#elif defined(__FreeBSD__)
+#define __BSD_VISIBLE 1
+#endif
+#include "termbox.h"
+
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
+#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
+#define EUNSUPPORTED_TERM -1
+#define TI_MAGIC 0432
+#define TI_ALT_MAGIC 542
+#define TI_HEADER_LENGTH 12
+#define TB_KEYS_NUM 22
+#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
+#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
+#define LAST_COORD_INIT -1
+#define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X) - 1)
+#define WRITE_INT(X) \
+ bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
+
+struct cellbuf {
+ int width;
+ int height;
+ struct tb_cell *cells;
+};
+
+struct bytebuffer {
+ char *buf;
+ int len;
+ int cap;
+};
+
+enum {
+ T_ENTER_CA,
+ T_EXIT_CA,
+ T_SHOW_CURSOR,
+ T_HIDE_CURSOR,
+ T_CLEAR_SCREEN,
+ T_SGR0,
+ T_UNDERLINE,
+ T_BOLD,
+ T_BLINK,
+ T_REVERSE,
+ T_ENTER_KEYPAD,
+ T_EXIT_KEYPAD,
+ T_ENTER_MOUSE,
+ T_EXIT_MOUSE,
+ T_FUNCS_NUM,
+};
+
+static int init_term_builtin(void);
+static char *read_file(const char *file);
+static char *terminfo_try_path(const char *path, const char *term);
+static char *load_terminfo(void);
+static const char *terminfo_copy_string(char *data, int str, int table);
+static int init_term(void);
+static void shutdown_term(void);
+static bool starts_with(const char *s1, int len, const char *s2);
+static int parse_mouse_event(struct tb_event *event, const char *buf, int len);
+static int parse_escape_seq(struct tb_event *event, const char *buf, int len);
+static int convertnum(uint32_t num, char *buf);
+static void write_cursor(int x, int y);
+static void write_sgr(uint16_t fg, uint16_t bg);
+static void cellbuf_init(struct cellbuf *buf, int width, int height);
+static void cellbuf_resize(struct cellbuf *buf, int width, int height);
+static void cellbuf_clear(struct cellbuf *buf);
+static void cellbuf_free(struct cellbuf *buf);
+static void get_term_size(int *w, int *h);
+static void update_term_size(void);
+static void send_attr(uint16_t fg, uint16_t bg);
+static void send_char(int x, int y, uint32_t c);
+static void send_clear(void);
+static void sigwinch_handler(int xxx);
+static void update_size(void);
+static int read_up_to(int n);
+static int wait_fill_event(struct tb_event *event, struct timeval *timeout);
+static void bytebuffer_reserve(struct bytebuffer *b, int cap);
+static void bytebuffer_init(struct bytebuffer *b, int cap);
+static void bytebuffer_free(struct bytebuffer *b);
+static void bytebuffer_clear(struct bytebuffer *b);
+static void bytebuffer_append(struct bytebuffer *b, const char *data, int len);
+static void bytebuffer_puts(struct bytebuffer *b, const char *str);
+static void bytebuffer_resize(struct bytebuffer *b, int len);
+static void bytebuffer_flush(struct bytebuffer *b, int fd);
+static void bytebuffer_truncate(struct bytebuffer *b, int n);
+
+static struct termios orig_tios;
+static struct cellbuf back_buffer;
+static struct cellbuf front_buffer;
+static struct bytebuffer output_buffer;
+static struct bytebuffer input_buffer;
+static int termw = -1;
+static int termh = -1;
+static int inputmode = TB_INPUT_ESC;
+static int outputmode = TB_OUTPUT_NORMAL;
+static int inout;
+static int winch_fds[2];
+static int lastx = LAST_COORD_INIT;
+static int lasty = LAST_COORD_INIT;
+static int cursor_x = -1;
+static int cursor_y = -1;
+static uint16_t background = TB_DEFAULT;
+static uint16_t foreground = TB_DEFAULT;
+/* may happen in a different thread */
+static volatile int buffer_size_change_request;
+// rxvt-256color
+static const char *rxvt_256color_keys[] = { "\033[11~", "\033[12~", "\033[13~",
+ "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~",
+ "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~",
+ "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C",
+ 0 };
+static const char *rxvt_256color_funcs[] = {
+ "\0337\033[?47h",
+ "\033[2J\033[?47l\0338",
+ "\033[?25h",
+ "\033[?25l",
+ "\033[H\033[2J",
+ "\033[m",
+ "\033[4m",
+ "\033[1m",
+ "\033[5m",
+ "\033[7m",
+ "\033=",
+ "\033>",
+ ENTER_MOUSE_SEQ,
+ EXIT_MOUSE_SEQ,
+};
+// Eterm
+static const char *eterm_keys[] = { "\033[11~", "\033[12~", "\033[13~",
+ "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~",
+ "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~",
+ "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C",
+ 0 };
+static const char *eterm_funcs[] = {
+ "\0337\033[?47h",
+ "\033[2J\033[?47l\0338",
+ "\033[?25h",
+ "\033[?25l",
+ "\033[H\033[2J",
+ "\033[m",
+ "\033[4m",
+ "\033[1m",
+ "\033[5m",
+ "\033[7m",
+ "",
+ "",
+ "",
+ "",
+};
+// screen
+static const char *screen_keys[] = { "\033OP", "\033OQ", "\033OR", "\033OS",
+ "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~",
+ "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~",
+ "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0 };
+static const char *screen_funcs[] = {
+ "\033[?1049h",
+ "\033[?1049l",
+ "\033[34h\033[?25h",
+ "\033[?25l",
+ "\033[H\033[J",
+ "\033[m",
+ "\033[4m",
+ "\033[1m",
+ "\033[5m",
+ "\033[7m",
+ "\033[?1h\033=",
+ "\033[?1l\033>",
+ ENTER_MOUSE_SEQ,
+ EXIT_MOUSE_SEQ,
+};
+// rxvt-unicode
+static const char *rxvt_unicode_keys[] = { "\033[11~", "\033[12~", "\033[13~",
+ "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~",
+ "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~",
+ "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C",
+ 0 };
+static const char *rxvt_unicode_funcs[] = {
+ "\033[?1049h",
+ "\033[r\033[?1049l",
+ "\033[?25h",
+ "\033[?25l",
+ "\033[H\033[2J",
+ "\033[m\033(B",
+ "\033[4m",
+ "\033[1m",
+ "\033[5m",
+ "\033[7m",
+ "\033=",
+ "\033>",
+ ENTER_MOUSE_SEQ,
+ EXIT_MOUSE_SEQ,
+};
+// linux
+static const char *linux_keys[] = { "\033[[A", "\033[[B", "\033[[C", "\033[[D",
+ "\033[[E", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~",
+ "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~",
+ "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0 };
+static const char *linux_funcs[] = {
+ "",
+ "",
+ "\033[?25h\033[?0c",
+ "\033[?25l\033[?1c",
+ "\033[H\033[J",
+ "\033[0;10m",
+ "\033[4m",
+ "\033[1m",
+ "\033[5m",
+ "\033[7m",
+ "",
+ "",
+ "",
+ "",
+};
+// xterm
+static const char *xterm_keys[] = { "\033OP", "\033OQ", "\033OR", "\033OS",
+ "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~",
+ "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033OH", "\033OF",
+ "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0 };
+static const char *xterm_funcs[] = {
+ "\033[?1049h",
+ "\033[?1049l",
+ "\033[?12l\033[?25h",
+ "\033[?25l",
+ "\033[H\033[2J",
+ "\033(B\033[m",
+ "\033[4m",
+ "\033[1m",
+ "\033[5m",
+ "\033[7m",
+ "\033[?1h\033=",
+ "\033[?1l\033>",
+ ENTER_MOUSE_SEQ,
+ EXIT_MOUSE_SEQ,
+};
+static struct term {
+ const char *name;
+ const char **keys;
+ const char **funcs;
+} terms[] = {
+ { "rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs },
+ { "Eterm", eterm_keys, eterm_funcs },
+ { "screen", screen_keys, screen_funcs },
+ { "rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs },
+ { "linux", linux_keys, linux_funcs },
+ { "xterm", xterm_keys, xterm_funcs },
+ { 0, 0, 0 },
+};
+static bool init_from_terminfo = false;
+static const char **keys;
+static const char **funcs;
+
+static int
+try_compatible(const char *term, const char *name, const char **tkeys,
+ const char **tfuncs)
+{
+ if (strstr(term, name)) {
+ keys = tkeys;
+ funcs = tfuncs;
+ return 0;
+ }
+
+ return EUNSUPPORTED_TERM;
+}
+
+static int
+init_term_builtin(void)
+{
+ int i;
+ const char *term = getenv("TERM");
+
+ if (term) {
+ for (i = 0; terms[i].name; i++) {
+ if (!strcmp(terms[i].name, term)) {
+ keys = terms[i].keys;
+ funcs = terms[i].funcs;
+ return 0;
+ }
+ }
+
+ /* let's do some heuristic, maybe it's a compatible terminal */
+ if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
+ return 0;
+ if (try_compatible(term, "rxvt", rxvt_unicode_keys,
+ rxvt_unicode_funcs) == 0)
+ return 0;
+ if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
+ return 0;
+ if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
+ return 0;
+ if (try_compatible(term, "screen", screen_keys, screen_funcs) ==
+ 0)
+ return 0;
+ if (try_compatible(term, "tmux", screen_keys, screen_funcs) ==
+ 0)
+ return 0;
+ /* let's assume that 'cygwin' is xterm compatible */
+ if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) ==
+ 0)
+ return 0;
+ }
+
+ return EUNSUPPORTED_TERM;
+}
+
+//----------------------------------------------------------------------
+// terminfo
+//----------------------------------------------------------------------
+
+static char *
+read_file(const char *file)
+{
+ FILE *f = fopen(file, "rb");
+ if (!f)
+ return 0;
+
+ struct stat st;
+ if (fstat(fileno(f), &st) != 0) {
+ fclose(f);
+ return 0;
+ }
+
+ char *data = malloc(st.st_size);
+ if (!data) {
+ fclose(f);
+ return 0;
+ }
+
+ if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) {
+ fclose(f);
+ free(data);
+ return 0;
+ }
+
+ fclose(f);
+ return data;
+}
+
+static char *
+terminfo_try_path(const char *path, const char *term)
+{
+ char tmp[4096];
+ snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
+ tmp[sizeof(tmp) - 1] = '\0';
+ char *data = read_file(tmp);
+ if (data) {
+ return data;
+ }
+
+ // fallback to darwin specific dirs structure
+ snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
+ tmp[sizeof(tmp) - 1] = '\0';
+ return read_file(tmp);
+}
+
+static char *
+load_terminfo(void)
+{
+ char tmp[4096];
+ const char *term = getenv("TERM");
+ if (!term) {
+ return 0;
+ }
+
+ // if TERMINFO is set, no other directory should be searched
+ const char *terminfo = getenv("TERMINFO");
+ if (terminfo) {
+ return terminfo_try_path(terminfo, term);
+ }
+
+ // next, consider ~/.terminfo
+ const char *home = getenv("HOME");
+ if (home) {
+ snprintf(tmp, sizeof(tmp), "%s/.terminfo", home);
+ tmp[sizeof(tmp) - 1] = '\0';
+ char *data = terminfo_try_path(tmp, term);
+ if (data)
+ return data;
+ }
+
+ // next, TERMINFO_DIRS
+ const char *dirs = getenv("TERMINFO_DIRS");
+ if (dirs) {
+ snprintf(tmp, sizeof(tmp), "%s", dirs);
+ tmp[sizeof(tmp) - 1] = '\0';
+ char *dir = strtok(tmp, ":");
+ while (dir) {
+ const char *cdir = dir;
+ if (strcmp(cdir, "") == 0) {
+ cdir = "/usr/share/terminfo";
+ }
+ char *data = terminfo_try_path(cdir, term);
+ if (data)
+ return data;
+ dir = strtok(0, ":");
+ }
+ }
+
+ // fallback to /usr/share/terminfo
+ return terminfo_try_path("/usr/share/terminfo", term);
+}
+
+static const char *
+terminfo_copy_string(char *data, int str, int table)
+{
+ const int16_t off = *(int16_t *)(data + str);
+ const char *src = data + table + off;
+ int len = strlen(src);
+ char *dst = malloc(len + 1);
+ strcpy(dst, src);
+ return dst;
+}
+
+static const int16_t ti_funcs[] = {
+ 28,
+ 40,
+ 16,
+ 13,
+ 5,
+ 39,
+ 36,
+ 27,
+ 26,
+ 34,
+ 89,
+ 88,
+};
+
+static const int16_t ti_keys[] = {
+ 66,
+ 68 /* apparently not a typo; 67 is F10 for whatever reason */,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 67,
+ 216,
+ 217,
+ 77,
+ 59,
+ 76,
+ 164,
+ 82,
+ 81,
+ 87,
+ 61,
+ 79,
+ 83,
+};
+
+static int
+init_term(void)
+{
+ int i;
+ char *data = load_terminfo();
+ if (!data) {
+ init_from_terminfo = false;
+ return init_term_builtin();
+ }
+
+ int16_t *header = (int16_t *)data;
+
+ const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2;
+
+ if ((header[1] + header[2]) % 2) {
+ // old quirk to align everything on word boundaries
+ header[2] += 1;
+ }
+
+ const int str_offset = TI_HEADER_LENGTH + header[1] + header[2] +
+ number_sec_len * header[3];
+ const int table_offset = str_offset + 2 * header[4];
+
+ keys = malloc(sizeof(const char *) * (TB_KEYS_NUM + 1));
+ for (i = 0; i < TB_KEYS_NUM; i++) {
+ keys[i] = terminfo_copy_string(
+ data, str_offset + 2 * ti_keys[i], table_offset);
+ }
+ keys[TB_KEYS_NUM] = 0;
+
+ funcs = malloc(sizeof(const char *) * T_FUNCS_NUM);
+ // the last two entries are reserved for mouse. because the table offset is
+ // not there, the two entries have to fill in manually
+ for (i = 0; i < T_FUNCS_NUM - 2; i++) {
+ funcs[i] = terminfo_copy_string(
+ data, str_offset + 2 * ti_funcs[i], table_offset);
+ }
+
+ funcs[T_FUNCS_NUM - 2] = ENTER_MOUSE_SEQ;
+ funcs[T_FUNCS_NUM - 1] = EXIT_MOUSE_SEQ;
+
+ init_from_terminfo = true;
+ free(data);
+ return 0;
+}
+
+static void
+shutdown_term(void)
+{
+ if (init_from_terminfo) {
+ int i;
+ for (i = 0; i < TB_KEYS_NUM; i++) {
+ free((void *)keys[i]);
+ }
+ // the last two entries are reserved for mouse. because the table offset
+ // is not there, the two entries have to fill in manually and do not
+ // need to be freed.
+ for (i = 0; i < T_FUNCS_NUM - 2; i++) {
+ free((void *)funcs[i]);
+ }
+ free(keys);
+ free(funcs);
+ }
+}
+
+// if s1 starts with s2 returns true, else false
+// len is the length of s1
+// s2 should be null-terminated
+static bool
+starts_with(const char *s1, int len, const char *s2)
+{
+ int n = 0;
+ while (*s2 && n < len) {
+ if (*s1++ != *s2++)
+ return false;
+ n++;
+ }
+ return *s2 == 0;
+}
+
+static int
+parse_mouse_event(struct tb_event *event, const char *buf, int len)
+{
+ if (len >= 6 && starts_with(buf, len, "\033[M")) {
+ // X10 mouse encoding, the simplest one
+ // \033 [ M Cb Cx Cy
+ int b = buf[3] - 32;
+ switch (b & 3) {
+ case 0:
+ if ((b & 64) != 0)
+ event->key = TB_KEY_MOUSE_WHEEL_UP;
+ else
+ event->key = TB_KEY_MOUSE_LEFT;
+ break;
+ case 1:
+ if ((b & 64) != 0)
+ event->key = TB_KEY_MOUSE_WHEEL_DOWN;
+ else
+ event->key = TB_KEY_MOUSE_MIDDLE;
+ break;
+ case 2:
+ event->key = TB_KEY_MOUSE_RIGHT;
+ break;
+ case 3:
+ event->key = TB_KEY_MOUSE_RELEASE;
+ break;
+ default:
+ return -6;
+ }
+ event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
+ if ((b & 32) != 0)
+ event->mod |= TB_MOD_MOTION;
+
+ // the coord is 1,1 for upper left
+ event->x = (uint8_t)buf[4] - 1 - 32;
+ event->y = (uint8_t)buf[5] - 1 - 32;
+
+ return 6;
+ } else if (starts_with(buf, len, "\033[<") ||
+ starts_with(buf, len, "\033[")) {
+ // xterm 1006 extended mode or urxvt 1015 extended mode
+ // xterm: \033 [ < Cb ; Cx ; Cy (M or m)
+ // urxvt: \033 [ Cb ; Cx ; Cy M
+ int i, mi = -1, starti = -1;
+ int isM, isU, s1 = -1, s2 = -1;
+ int n1 = 0, n2 = 0, n3 = 0;
+
+ for (i = 0; i < len; i++) {
+ // We search the first (s1) and the last (s2) ';'
+ if (buf[i] == ';') {
+ if (s1 == -1)
+ s1 = i;
+ s2 = i;
+ }
+
+ // We search for the first 'm' or 'M'
+ if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) {
+ mi = i;
+ break;
+ }
+ }
+ if (mi == -1)
+ return 0;
+
+ // whether it's a capital M or not
+ isM = (buf[mi] == 'M');
+
+ if (buf[2] == '<') {
+ isU = 0;
+ starti = 3;
+ } else {
+ isU = 1;
+ starti = 2;
+ }
+
+ if (s1 == -1 || s2 == -1 || s1 == s2)
+ return 0;
+
+ n1 = strtoul(&buf[starti], NULL, 10);
+ n2 = strtoul(&buf[s1 + 1], NULL, 10);
+ n3 = strtoul(&buf[s2 + 1], NULL, 10);
+
+ if (isU)
+ n1 -= 32;
+
+ switch (n1 & 3) {
+ case 0:
+ if ((n1 & 64) != 0) {
+ event->key = TB_KEY_MOUSE_WHEEL_UP;
+ } else {
+ event->key = TB_KEY_MOUSE_LEFT;
+ }
+ break;
+ case 1:
+ if ((n1 & 64) != 0) {
+ event->key = TB_KEY_MOUSE_WHEEL_DOWN;
+ } else {
+ event->key = TB_KEY_MOUSE_MIDDLE;
+ }
+ break;
+ case 2:
+ event->key = TB_KEY_MOUSE_RIGHT;
+ break;
+ case 3:
+ event->key = TB_KEY_MOUSE_RELEASE;
+ break;
+ default:
+ return mi + 1;
+ }
+
+ if (!isM) {
+ // on xterm mouse release is signaled by lowercase m
+ event->key = TB_KEY_MOUSE_RELEASE;
+ }
+
+ event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
+ if ((n1 & 32) != 0)
+ event->mod |= TB_MOD_MOTION;
+
+ event->x = (uint8_t)n2 - 1;
+ event->y = (uint8_t)n3 - 1;
+
+ return mi + 1;
+ }
+
+ return 0;
+}
+
+// convert escape sequence to event, and return consumed bytes on success
+// (failure == 0)
+static int
+parse_escape_seq(struct tb_event *event, const char *buf, int len)
+{
+ int mouse_parsed = parse_mouse_event(event, buf, len);
+
+ if (mouse_parsed != 0)
+ return mouse_parsed;
+
+ // it's pretty simple here, find 'starts_with' match and return
+ // success, else return failure
+ int i;
+ for (i = 0; keys[i]; i++) {
+ if (starts_with(buf, len, keys[i])) {
+ event->ch = 0;
+ event->key = 0xFFFF - i;
+ return strlen(keys[i]);
+ }
+ }
+ return 0;
+}
+
+static bool
+extract_event(struct tb_event *event, struct bytebuffer *inbuf, int in)
+{
+ const char *buf = inbuf->buf;
+ const int len = inbuf->len;
+ if (len == 0)
+ return false;
+
+ if (buf[0] == '\033') {
+ int n = parse_escape_seq(event, buf, len);
+ if (n != 0) {
+ bool success = true;
+ if (n < 0) {
+ success = false;
+ n = -n;
+ }
+ bytebuffer_truncate(inbuf, n);
+ return success;
+ } else {
+ // it's not escape sequence, then it's ALT or ESC,
+ // check inputmode
+ if (in & TB_INPUT_ESC) {
+ // if we're in escape mode, fill ESC event, pop
+ // buffer, return success
+ event->ch = 0;
+ event->key = TB_KEY_ESC;
+ event->mod = 0;
+ bytebuffer_truncate(inbuf, 1);
+ return true;
+ } else if (in & TB_INPUT_ALT) {
+ // if we're in alt mode, set ALT modifier to
+ // event and redo parsing
+ event->mod = TB_MOD_ALT;
+ bytebuffer_truncate(inbuf, 1);
+ return extract_event(event, inbuf, in);
+ }
+ assert(!"never got here");
+ }
+ }
+
+ // if we're here, this is not an escape sequence and not an alt sequence
+ // so, it's a FUNCTIONAL KEY or a UNICODE character
+
+ // first of all check if it's a functional key
+ if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
+ (unsigned char)buf[0] == TB_KEY_BACKSPACE2) {
+ // fill event, pop buffer, return success */
+ event->ch = 0;
+ event->key = (uint16_t)buf[0];
+ bytebuffer_truncate(inbuf, 1);
+ return true;
+ }
+
+ // feh... we got utf8 here
+
+ // check if there is all bytes
+ if (len >= tb_utf8_char_length(buf[0])) {
+ /* everything ok, fill event, pop buffer, return success */
+ tb_utf8_char_to_unicode(&event->ch, buf);
+ event->key = 0;
+ bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0]));
+ return true;
+ }
+
+ // event isn't recognized, perhaps there is not enough bytes in utf8
+ // sequence
+ return false;
+}
+
+/* -------------------------------------------------------- */
+
+int
+tb_init_fd(int inout_)
+{
+ inout = inout_;
+ if (inout == -1) {
+ return TB_EFAILED_TO_OPEN_TTY;
+ }
+
+ if (init_term() < 0) {
+ close(inout);
+ return TB_EUNSUPPORTED_TERMINAL;
+ }
+
+ if (pipe(winch_fds) < 0) {
+ close(inout);
+ return TB_EPIPE_TRAP_ERROR;
+ }
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sigwinch_handler;
+ sa.sa_flags = 0;
+ sigaction(SIGWINCH, &sa, 0);
+
+ tcgetattr(inout, &orig_tios);
+
+ struct termios tios;
+ memcpy(&tios, &orig_tios, sizeof(tios));
+
+ tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR |
+ ICRNL | IXON);
+ tios.c_oflag &= ~OPOST;
+ tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ tios.c_cflag &= ~(CSIZE | PARENB);
+ tios.c_cflag |= CS8;
+ tios.c_cc[VMIN] = 0;
+ tios.c_cc[VTIME] = 0;
+ tcsetattr(inout, TCSAFLUSH, &tios);
+
+ bytebuffer_init(&input_buffer, 128);
+ bytebuffer_init(&output_buffer, 32 * 1024);
+
+ bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]);
+ bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
+ bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
+ send_clear();
+
+ update_term_size();
+ cellbuf_init(&back_buffer, termw, termh);
+ cellbuf_init(&front_buffer, termw, termh);
+ cellbuf_clear(&back_buffer);
+ cellbuf_clear(&front_buffer);
+
+ return 0;
+}
+
+int
+tb_init_file(const char *name)
+{
+ return tb_init_fd(open(name, O_RDWR));
+}
+
+int
+tb_init(void)
+{
+ return tb_init_file("/dev/tty");
+}
+
+void
+tb_shutdown(void)
+{
+ if (termw == -1) {
+ fputs("tb_shutdown() should not be called twice.", stderr);
+ abort();
+ }
+
+ bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
+ bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
+ bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
+ bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]);
+ bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]);
+ bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
+ bytebuffer_flush(&output_buffer, inout);
+ tcsetattr(inout, TCSAFLUSH, &orig_tios);
+
+ shutdown_term();
+ close(inout);
+ close(winch_fds[0]);
+ close(winch_fds[1]);
+
+ cellbuf_free(&back_buffer);
+ cellbuf_free(&front_buffer);
+ bytebuffer_free(&output_buffer);
+ bytebuffer_free(&input_buffer);
+ termw = termh = -1;
+}
+
+void
+tb_present(void)
+{
+ int x, y, w, i;
+ struct tb_cell *back, *front;
+
+ /* invalidate cursor position */
+ lastx = LAST_COORD_INIT;
+ lasty = LAST_COORD_INIT;
+
+ if (buffer_size_change_request) {
+ update_size();
+ buffer_size_change_request = 0;
+ }
+
+ for (y = 0; y < front_buffer.height; ++y) {
+ for (x = 0; x < front_buffer.width;) {
+ back = &CELL(&back_buffer, x, y);
+ front = &CELL(&front_buffer, x, y);
+ w = wcwidth(back->ch);
+ if (w < 1)
+ w = 1;
+ if (memcmp(back, front, sizeof(struct tb_cell)) == 0) {
+ x += w;
+ continue;
+ }
+ memcpy(front, back, sizeof(struct tb_cell));
+ send_attr(back->fg, back->bg);
+ if (w > 1 && x >= front_buffer.width - (w - 1)) {
+ // Not enough room for wide ch, so send spaces
+ for (i = x; i < front_buffer.width; ++i) {
+ send_char(i, y, ' ');
+ }
+ } else {
+ send_char(x, y, back->ch);
+ for (i = 1; i < w; ++i) {
+ front = &CELL(&front_buffer, x + i, y);
+ front->ch = 0;
+ front->fg = back->fg;
+ front->bg = back->bg;
+ }
+ }
+ x += w;
+ }
+ }
+ if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+ write_cursor(cursor_x, cursor_y);
+ bytebuffer_flush(&output_buffer, inout);
+}
+
+void
+tb_set_cursor(int cx, int cy)
+{
+ if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
+ bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
+
+ if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
+ bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
+
+ cursor_x = cx;
+ cursor_y = cy;
+ if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+ write_cursor(cursor_x, cursor_y);
+}
+
+void
+tb_put_cell(int x, int y, const struct tb_cell *cell)
+{
+ if ((unsigned)x >= (unsigned)back_buffer.width)
+ return;
+ if ((unsigned)y >= (unsigned)back_buffer.height)
+ return;
+ CELL(&back_buffer, x, y) = *cell;
+}
+
+void
+tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg)
+{
+ struct tb_cell c = { ch, fg, bg };
+ tb_put_cell(x, y, &c);
+}
+
+void
+tb_blit(int x, int y, int w, int h, const struct tb_cell *cells)
+{
+ if (x + w < 0 || x >= back_buffer.width)
+ return;
+ if (y + h < 0 || y >= back_buffer.height)
+ return;
+ int xo = 0, yo = 0, ww = w, hh = h;
+ if (x < 0) {
+ xo = -x;
+ ww -= xo;
+ x = 0;
+ }
+ if (y < 0) {
+ yo = -y;
+ hh -= yo;
+ y = 0;
+ }
+ if (ww > back_buffer.width - x)
+ ww = back_buffer.width - x;
+ if (hh > back_buffer.height - y)
+ hh = back_buffer.height - y;
+
+ int sy;
+ struct tb_cell *dst = &CELL(&back_buffer, x, y);
+ const struct tb_cell *src = cells + yo * w + xo;
+ size_t size = sizeof(struct tb_cell) * ww;
+
+ for (sy = 0; sy < hh; ++sy) {
+ memcpy(dst, src, size);
+ dst += back_buffer.width;
+ src += w;
+ }
+}
+
+struct tb_cell *
+tb_cell_buffer(void)
+{
+ return back_buffer.cells;
+}
+
+int
+tb_poll_event(struct tb_event *event)
+{
+ return wait_fill_event(event, 0);
+}
+
+int
+tb_peek_event(struct tb_event *event, int timeout)
+{
+ struct timeval tv;
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
+ return wait_fill_event(event, &tv);
+}
+
+int
+tb_width(void)
+{
+ return termw;
+}
+
+int
+tb_height(void)
+{
+ return termh;
+}
+
+void
+tb_clear(void)
+{
+ if (buffer_size_change_request) {
+ update_size();
+ buffer_size_change_request = 0;
+ }
+ cellbuf_clear(&back_buffer);
+}
+
+int
+tb_select_input_mode(int mode)
+{
+ if (mode) {
+ if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)
+ mode |= TB_INPUT_ESC;
+
+ /* technically termbox can handle that, but let's be nice and show here
+ what mode is actually used */
+ if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) ==
+ (TB_INPUT_ESC | TB_INPUT_ALT))
+ mode &= ~TB_INPUT_ALT;
+
+ inputmode = mode;
+ if (mode & TB_INPUT_MOUSE) {
+ bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
+ bytebuffer_flush(&output_buffer, inout);
+ } else {
+ bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
+ bytebuffer_flush(&output_buffer, inout);
+ }
+ }
+ return inputmode;
+}
+
+int
+tb_select_output_mode(int mode)
+{
+ if (mode)
+ outputmode = mode;
+ return outputmode;
+}
+
+void
+tb_set_clear_attributes(uint16_t fg, uint16_t bg)
+{
+ foreground = fg;
+ background = bg;
+}
+
+/* -------------------------------------------------------- */
+
+static int
+convertnum(uint32_t num, char *buf)
+{
+ int i, l = 0;
+ int ch;
+ do {
+ buf[l++] = '0' + (num % 10);
+ num /= 10;
+ } while (num);
+ for (i = 0; i < l / 2; i++) {
+ ch = buf[i];
+ buf[i] = buf[l - 1 - i];
+ buf[l - 1 - i] = ch;
+ }
+ return l;
+}
+
+static void
+write_cursor(int x, int y)
+{
+ char buf[32];
+ WRITE_LITERAL("\033[");
+ WRITE_INT(y + 1);
+ WRITE_LITERAL(";");
+ WRITE_INT(x + 1);
+ WRITE_LITERAL("H");
+}
+
+static void
+write_sgr(uint16_t fg, uint16_t bg)
+{
+ char buf[32];
+
+ if (fg == TB_DEFAULT && bg == TB_DEFAULT)
+ return;
+
+ switch (outputmode) {
+ case TB_OUTPUT_256:
+ case TB_OUTPUT_216:
+ case TB_OUTPUT_GRAYSCALE:
+ WRITE_LITERAL("\033[");
+ if (fg != TB_DEFAULT) {
+ WRITE_LITERAL("38;5;");
+ WRITE_INT(fg);
+ if (bg != TB_DEFAULT) {
+ WRITE_LITERAL(";");
+ }
+ }
+ if (bg != TB_DEFAULT) {
+ WRITE_LITERAL("48;5;");
+ WRITE_INT(bg);
+ }
+ WRITE_LITERAL("m");
+ break;
+ case TB_OUTPUT_NORMAL:
+ default:
+ WRITE_LITERAL("\033[");
+ if (fg != TB_DEFAULT) {
+ WRITE_LITERAL("3");
+ WRITE_INT(fg - 1);
+ if (bg != TB_DEFAULT) {
+ WRITE_LITERAL(";");
+ }
+ }
+ if (bg != TB_DEFAULT) {
+ WRITE_LITERAL("4");
+ WRITE_INT(bg - 1);
+ }
+ WRITE_LITERAL("m");
+ break;
+ }
+}
+
+static void
+cellbuf_init(struct cellbuf *buf, int width, int height)
+{
+ buf->cells = (struct tb_cell *)malloc(
+ sizeof(struct tb_cell) * width * height);
+ assert(buf->cells);
+ buf->width = width;
+ buf->height = height;
+}
+
+static void
+cellbuf_resize(struct cellbuf *buf, int width, int height)
+{
+ if (buf->width == width && buf->height == height)
+ return;
+
+ int oldw = buf->width;
+ int oldh = buf->height;
+ struct tb_cell *oldcells = buf->cells;
+
+ cellbuf_init(buf, width, height);
+ cellbuf_clear(buf);
+
+ int minw = (width < oldw) ? width : oldw;
+ int minh = (height < oldh) ? height : oldh;
+ int i;
+
+ for (i = 0; i < minh; ++i) {
+ struct tb_cell *csrc = oldcells + (i * oldw);
+ struct tb_cell *cdst = buf->cells + (i * width);
+ memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
+ }
+
+ free(oldcells);
+}
+
+static void
+cellbuf_clear(struct cellbuf *buf)
+{
+ int i;
+ int ncells = buf->width * buf->height;
+
+ for (i = 0; i < ncells; ++i) {
+ buf->cells[i].ch = ' ';
+ buf->cells[i].fg = foreground;
+ buf->cells[i].bg = background;
+ }
+}
+
+static void
+cellbuf_free(struct cellbuf *buf)
+{
+ free(buf->cells);
+}
+
+static void
+get_term_size(int *w, int *h)
+{
+ struct winsize sz;
+ memset(&sz, 0, sizeof(sz));
+
+ ioctl(inout, TIOCGWINSZ, &sz);
+
+ *w = sz.ws_col > 0 ? sz.ws_col : 80;
+ *h = sz.ws_row > 0 ? sz.ws_row : 24;
+}
+
+static void
+update_term_size(void)
+{
+ struct winsize sz;
+ memset(&sz, 0, sizeof(sz));
+
+ ioctl(inout, TIOCGWINSZ, &sz);
+
+ termw = sz.ws_col > 0 ? sz.ws_col : 80;
+ termh = sz.ws_row > 0 ? sz.ws_row : 24;
+}
+
+static void
+send_attr(uint16_t fg, uint16_t bg)
+{
+#define LAST_ATTR_INIT 0xFFFF
+ static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
+ if (fg != lastfg || bg != lastbg) {
+ bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
+
+ uint16_t fgcol;
+ uint16_t bgcol;
+
+ switch (outputmode) {
+ case TB_OUTPUT_256:
+ fgcol = fg & 0xFF;
+ bgcol = bg & 0xFF;
+ break;
+
+ case TB_OUTPUT_216:
+ fgcol = fg & 0xFF;
+ if (fgcol > 215)
+ fgcol = 7;
+ bgcol = bg & 0xFF;
+ if (bgcol > 215)
+ bgcol = 0;
+ fgcol += 0x10;
+ bgcol += 0x10;
+ break;
+
+ case TB_OUTPUT_GRAYSCALE:
+ fgcol = fg & 0xFF;
+ if (fgcol > 23)
+ fgcol = 23;
+ bgcol = bg & 0xFF;
+ if (bgcol > 23)
+ bgcol = 0;
+ fgcol += 0xe8;
+ bgcol += 0xe8;
+ break;
+
+ case TB_OUTPUT_NORMAL:
+ default:
+ fgcol = fg & 0x0F;
+ bgcol = bg & 0x0F;
+ }
+
+ if (fg & TB_BOLD)
+ bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
+ if (bg & TB_BOLD)
+ bytebuffer_puts(&output_buffer, funcs[T_BLINK]);
+ if (fg & TB_UNDERLINE)
+ bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
+ if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
+ bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
+
+ write_sgr(fgcol, bgcol);
+
+ lastfg = fg;
+ lastbg = bg;
+ }
+}
+
+static void
+send_char(int x, int y, uint32_t c)
+{
+ char buf[7];
+ int bw = tb_utf8_unicode_to_char(buf, c);
+ if (x - 1 != lastx || y != lasty)
+ write_cursor(x, y);
+ lastx = x;
+ lasty = y;
+ if (!c)
+ buf[0] = ' '; // replace 0 with whitespace
+ bytebuffer_append(&output_buffer, buf, bw);
+}
+
+static void
+send_clear(void)
+{
+ send_attr(foreground, background);
+ bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
+ if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+ write_cursor(cursor_x, cursor_y);
+ bytebuffer_flush(&output_buffer, inout);
+
+ /* we need to invalidate cursor position too and these two vars are
+ * used only for simple cursor positioning optimization, cursor
+ * actually may be in the correct place, but we simply discard
+ * optimization once and it gives us simple solution for the case when
+ * cursor moved */
+ lastx = LAST_COORD_INIT;
+ lasty = LAST_COORD_INIT;
+}
+
+static void
+sigwinch_handler(int xxx)
+{
+ (void)xxx;
+ const int zzz = 1;
+ write(winch_fds[1], &zzz, sizeof(int));
+}
+
+static void
+update_size(void)
+{
+ update_term_size();
+ cellbuf_resize(&back_buffer, termw, termh);
+ cellbuf_resize(&front_buffer, termw, termh);
+ cellbuf_clear(&front_buffer);
+ send_clear();
+}
+
+static int
+read_up_to(int n)
+{
+ assert(n > 0);
+ const int prevlen = input_buffer.len;
+ bytebuffer_resize(&input_buffer, prevlen + n);
+
+ int read_n = 0;
+ while (read_n <= n) {
+ ssize_t r = 0;
+ if (read_n < n) {
+ r = read(inout, input_buffer.buf + prevlen + read_n,
+ n - read_n);
+ }
+#ifdef __CYGWIN__
+ // While linux man for tty says when VMIN == 0 && VTIME == 0, read
+ // should return 0 when there is nothing to read, cygwin's read returns
+ // -1. Not sure why and if it's correct to ignore it, but let's pretend
+ // it's zero.
+ if (r < 0)
+ r = 0;
+#endif
+ if (r < 0) {
+ // EAGAIN / EWOULDBLOCK shouldn't occur here
+ assert(errno != EAGAIN && errno != EWOULDBLOCK);
+ return -1;
+ } else if (r > 0) {
+ read_n += r;
+ } else {
+ bytebuffer_resize(&input_buffer, prevlen + read_n);
+ return read_n;
+ }
+ }
+ assert(!"unreachable");
+ return 0;
+}
+
+static int
+wait_fill_event(struct tb_event *event, struct timeval *timeout)
+{
+ // ;-)
+#define ENOUGH_DATA_FOR_PARSING 64
+ fd_set events;
+ memset(event, 0, sizeof(struct tb_event));
+
+ // try to extract event from input buffer, return on success
+ event->type = TB_EVENT_KEY;
+ if (extract_event(event, &input_buffer, inputmode))
+ return event->type;
+
+ // it looks like input buffer is incomplete, let's try the short path,
+ // but first make sure there is enough space
+ int n = read_up_to(ENOUGH_DATA_FOR_PARSING);
+ if (n < 0)
+ return -1;
+ if (n > 0 && extract_event(event, &input_buffer, inputmode))
+ return event->type;
+
+ // n == 0, or not enough data, let's go to select
+ while (1) {
+ FD_ZERO(&events);
+ FD_SET(inout, &events);
+ FD_SET(winch_fds[0], &events);
+ int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout;
+ int result = select(maxfd + 1, &events, 0, 0, timeout);
+ if (!result)
+ return 0;
+
+ if (FD_ISSET(inout, &events)) {
+ event->type = TB_EVENT_KEY;
+ n = read_up_to(ENOUGH_DATA_FOR_PARSING);
+ if (n < 0)
+ return -1;
+
+ if (n == 0)
+ continue;
+
+ if (extract_event(event, &input_buffer, inputmode))
+ return event->type;
+ }
+ if (FD_ISSET(winch_fds[0], &events)) {
+ event->type = TB_EVENT_RESIZE;
+ int zzz = 0;
+ read(winch_fds[0], &zzz, sizeof(int));
+ buffer_size_change_request = 1;
+ get_term_size(&event->w, &event->h);
+ return TB_EVENT_RESIZE;
+ }
+ }
+}
+
+static void
+bytebuffer_reserve(struct bytebuffer *b, int cap)
+{
+ if (b->cap >= cap) {
+ return;
+ }
+
+ // prefer doubling capacity
+ if (b->cap * 2 >= cap) {
+ cap = b->cap * 2;
+ }
+
+ char *newbuf = realloc(b->buf, cap);
+ b->buf = newbuf;
+ b->cap = cap;
+}
+
+static void
+bytebuffer_init(struct bytebuffer *b, int cap)
+{
+ b->cap = 0;
+ b->len = 0;
+ b->buf = 0;
+
+ if (cap > 0) {
+ b->cap = cap;
+ b->buf = malloc(cap); // just assume malloc works always
+ }
+}
+
+static void
+bytebuffer_free(struct bytebuffer *b)
+{
+ if (b->buf)
+ free(b->buf);
+}
+
+static void
+bytebuffer_clear(struct bytebuffer *b)
+{
+ b->len = 0;
+}
+
+static void
+bytebuffer_append(struct bytebuffer *b, const char *data, int len)
+{
+ bytebuffer_reserve(b, b->len + len);
+ memcpy(b->buf + b->len, data, len);
+ b->len += len;
+}
+
+static void
+bytebuffer_puts(struct bytebuffer *b, const char *str)
+{
+ bytebuffer_append(b, str, strlen(str));
+}
+
+static void
+bytebuffer_resize(struct bytebuffer *b, int len)
+{
+ bytebuffer_reserve(b, len);
+ b->len = len;
+}
+
+static void
+bytebuffer_flush(struct bytebuffer *b, int fd)
+{
+ write(fd, b->buf, b->len);
+ bytebuffer_clear(b);
+}
+
+static void
+bytebuffer_truncate(struct bytebuffer *b, int n)
+{
+ if (n <= 0)
+ return;
+ if (n > b->len)
+ n = b->len;
+ const int nmove = b->len - n;
+ memmove(b->buf, b->buf + n, nmove);
+ b->len -= n;
+}
diff --git a/sfm-0.4/termbox.h b/sfm-0.4/termbox.h
new file mode 100644
index 0000000..7f6e775
--- /dev/null
+++ b/sfm-0.4/termbox.h
@@ -0,0 +1,317 @@
+#ifndef TERMBOX_H
+#define TERMBOX_H
+
+#include <stdint.h>
+
+/* Key constants. See also struct tb_event's key field.
+ *
+ * These are a safe subset of terminfo keys, which exist on all popular
+ * terminals. Termbox uses only them to stay truly portable.
+ */
+#define TB_KEY_F1 (0xFFFF-0)
+#define TB_KEY_F2 (0xFFFF-1)
+#define TB_KEY_F3 (0xFFFF-2)
+#define TB_KEY_F4 (0xFFFF-3)
+#define TB_KEY_F5 (0xFFFF-4)
+#define TB_KEY_F6 (0xFFFF-5)
+#define TB_KEY_F7 (0xFFFF-6)
+#define TB_KEY_F8 (0xFFFF-7)
+#define TB_KEY_F9 (0xFFFF-8)
+#define TB_KEY_F10 (0xFFFF-9)
+#define TB_KEY_F11 (0xFFFF-10)
+#define TB_KEY_F12 (0xFFFF-11)
+#define TB_KEY_INSERT (0xFFFF-12)
+#define TB_KEY_DELETE (0xFFFF-13)
+#define TB_KEY_HOME (0xFFFF-14)
+#define TB_KEY_END (0xFFFF-15)
+#define TB_KEY_PGUP (0xFFFF-16)
+#define TB_KEY_PGDN (0xFFFF-17)
+#define TB_KEY_ARROW_UP (0xFFFF-18)
+#define TB_KEY_ARROW_DOWN (0xFFFF-19)
+#define TB_KEY_ARROW_LEFT (0xFFFF-20)
+#define TB_KEY_ARROW_RIGHT (0xFFFF-21)
+#define TB_KEY_MOUSE_LEFT (0xFFFF-22)
+#define TB_KEY_MOUSE_RIGHT (0xFFFF-23)
+#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24)
+#define TB_KEY_MOUSE_RELEASE (0xFFFF-25)
+#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26)
+#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
+
+/* These are all ASCII code points below SPACE character and a BACKSPACE key. */
+#define TB_KEY_CTRL_TILDE 0x00
+#define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */
+#define TB_KEY_CTRL_A 0x01
+#define TB_KEY_CTRL_B 0x02
+#define TB_KEY_CTRL_C 0x03
+#define TB_KEY_CTRL_D 0x04
+#define TB_KEY_CTRL_E 0x05
+#define TB_KEY_CTRL_F 0x06
+#define TB_KEY_CTRL_G 0x07
+#define TB_KEY_BACKSPACE 0x08
+#define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */
+#define TB_KEY_TAB 0x09
+#define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */
+#define TB_KEY_CTRL_J 0x0A
+#define TB_KEY_CTRL_K 0x0B
+#define TB_KEY_CTRL_L 0x0C
+#define TB_KEY_ENTER 0x0D
+#define TB_KEY_CTRL_M 0x0D /* clash with 'ENTER' */
+#define TB_KEY_CTRL_N 0x0E
+#define TB_KEY_CTRL_O 0x0F
+#define TB_KEY_CTRL_P 0x10
+#define TB_KEY_CTRL_Q 0x11
+#define TB_KEY_CTRL_R 0x12
+#define TB_KEY_CTRL_S 0x13
+#define TB_KEY_CTRL_T 0x14
+#define TB_KEY_CTRL_U 0x15
+#define TB_KEY_CTRL_V 0x16
+#define TB_KEY_CTRL_W 0x17
+#define TB_KEY_CTRL_X 0x18
+#define TB_KEY_CTRL_Y 0x19
+#define TB_KEY_CTRL_Z 0x1A
+#define TB_KEY_ESC 0x1B
+#define TB_KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */
+#define TB_KEY_CTRL_3 0x1B /* clash with 'ESC' */
+#define TB_KEY_CTRL_4 0x1C
+#define TB_KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */
+#define TB_KEY_CTRL_5 0x1D
+#define TB_KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */
+#define TB_KEY_CTRL_6 0x1E
+#define TB_KEY_CTRL_7 0x1F
+#define TB_KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */
+#define TB_KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */
+#define TB_KEY_SPACE 0x20
+#define TB_KEY_BACKSPACE2 0x7F
+#define TB_KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */
+
+/* These are non-existing ones.
+ *
+ * #define TB_KEY_CTRL_1 clash with '1'
+ * #define TB_KEY_CTRL_9 clash with '9'
+ * #define TB_KEY_CTRL_0 clash with '0'
+ */
+
+/*
+ * Alt modifier constant, see tb_event.mod field and tb_select_input_mode function.
+ * Mouse-motion modifier
+ */
+#define TB_MOD_ALT 0x01
+#define TB_MOD_MOTION 0x02
+
+/* Colors (see struct tb_cell's fg and bg fields). */
+#define TB_DEFAULT 0x00
+#define TB_BLACK 0x01
+#define TB_RED 0x02
+#define TB_GREEN 0x03
+#define TB_YELLOW 0x04
+#define TB_BLUE 0x05
+#define TB_MAGENTA 0x06
+#define TB_CYAN 0x07
+#define TB_WHITE 0x08
+
+// #define TB_GREY 0x09
+// #define TB_RED_B 0x0A
+// #define TB_GREEN_B 0x0B
+// #define TB_YELLOW_B 0x0C
+// #define TB_BLUE_B 0x0D
+// #define TB_MAGENTA_B 0x0E
+// #define TB_CYAN_B 0x0F
+// #define TB_GREY_B 0x10
+
+/* Attributes, it is possible to use multiple attributes by combining them
+ * using bitwise OR ('|'). Although, colors cannot be combined. But you can
+ * combine attributes and a single color. See also struct tb_cell's fg and bg
+ * fields.
+ */
+#define TB_BOLD 0x0100
+#define TB_UNDERLINE 0x0200
+#define TB_REVERSE 0x0400
+
+/* A cell, single conceptual entity on the terminal screen. The terminal screen
+ * is basically a 2d array of cells. It has the following fields:
+ * - 'ch' is a unicode character
+ * - 'fg' foreground color and attributes
+ * - 'bg' background color and attributes
+ */
+struct tb_cell {
+ uint32_t ch;
+ uint16_t fg;
+ uint16_t bg;
+};
+
+#define TB_EVENT_KEY 1
+#define TB_EVENT_RESIZE 2
+#define TB_EVENT_MOUSE 3
+
+/* An event, single interaction from the user. The 'mod' and 'ch' fields are
+ * valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type'
+ * is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is
+ * TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY
+ * or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only
+ * one of them can be non-zero at a time.
+ */
+struct tb_event {
+ uint8_t type;
+ uint8_t mod; /* modifiers to either 'key' or 'ch' below */
+ uint16_t key; /* one of the TB_KEY_* constants */
+ uint32_t ch; /* unicode character */
+ int32_t w;
+ int32_t h;
+ int32_t x;
+ int32_t y;
+};
+
+/* Error codes returned by tb_init(). All of them are self-explanatory, except
+ * the pipe trap error. Termbox uses unix pipes in order to deliver a message
+ * from a signal handler (SIGWINCH) to the main event reading loop. Honestly in
+ * most cases you should just check the returned code as < 0.
+ */
+#define TB_EUNSUPPORTED_TERMINAL -1
+#define TB_EFAILED_TO_OPEN_TTY -2
+#define TB_EPIPE_TRAP_ERROR -3
+
+/* Initializes the termbox library. This function should be called before any
+ * other functions. Function tb_init is same as tb_init_file("/dev/tty").
+ * After successful initialization, the library must be
+ * finalized using the tb_shutdown() function.
+ */
+int tb_init(void);
+int tb_init_file(const char* name);
+int tb_init_fd(int inout);
+void tb_shutdown(void);
+
+/* Returns the size of the internal back buffer (which is the same as
+ * terminal's window size in characters). The internal buffer can be resized
+ * after tb_clear() or tb_present() function calls. Both dimensions have an
+ * unspecified negative value when called before tb_init() or after
+ * tb_shutdown().
+ */
+int tb_width(void);
+int tb_height(void);
+
+/* Clears the internal back buffer using TB_DEFAULT color or the
+ * color/attributes set by tb_set_clear_attributes() function.
+ */
+void tb_clear(void);
+void tb_set_clear_attributes(uint16_t fg, uint16_t bg);
+
+/* Synchronizes the internal back buffer with the terminal. */
+void tb_present(void);
+
+#define TB_HIDE_CURSOR -1
+
+/* Sets the position of the cursor. Upper-left character is (0, 0). If you pass
+ * TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor
+ * is hidden by default.
+ */
+void tb_set_cursor(int cx, int cy);
+
+/* Changes cell's parameters in the internal back buffer at the specified
+ * position.
+ */
+void tb_put_cell(int x, int y, const struct tb_cell *cell);
+void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg);
+
+/* Copies the buffer from 'cells' at the specified position, assuming the
+ * buffer is a two-dimensional array of size ('w' x 'h'), represented as a
+ * one-dimensional buffer containing lines of cells starting from the top.
+ *
+ * (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own)
+ */
+void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells);
+
+/* Returns a pointer to internal cell back buffer. You can get its dimensions
+ * using tb_width() and tb_height() functions. The pointer stays valid as long
+ * as no tb_clear() and tb_present() calls are made. The buffer is
+ * one-dimensional buffer containing lines of cells starting from the top.
+ */
+struct tb_cell *tb_cell_buffer(void);
+
+#define TB_INPUT_CURRENT 0 /* 000 */
+#define TB_INPUT_ESC 1 /* 001 */
+#define TB_INPUT_ALT 2 /* 010 */
+#define TB_INPUT_MOUSE 4 /* 100 */
+
+/* Sets the termbox input mode. Termbox has two input modes:
+ * 1. Esc input mode.
+ * When ESC sequence is in the buffer and it doesn't match any known
+ * ESC sequence => ESC means TB_KEY_ESC.
+ * 2. Alt input mode.
+ * When ESC sequence is in the buffer and it doesn't match any known
+ * sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
+ *
+ * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the
+ * modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes
+ * were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some
+ * reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it
+ * will behave as if only TB_INPUT_ESC was selected.
+ *
+ * If 'mode' is TB_INPUT_CURRENT, it returns the current input mode.
+ *
+ * Default termbox input mode is TB_INPUT_ESC.
+ */
+int tb_select_input_mode(int mode);
+
+#define TB_OUTPUT_CURRENT 0
+#define TB_OUTPUT_NORMAL 1
+#define TB_OUTPUT_256 2
+#define TB_OUTPUT_216 3
+#define TB_OUTPUT_GRAYSCALE 4
+
+/* Sets the termbox output mode. Termbox has three output options:
+ * 1. TB_OUTPUT_NORMAL => [1..8]
+ * This mode provides 8 different colors:
+ * black, red, green, yellow, blue, magenta, cyan, white
+ * Shortcut: TB_BLACK, TB_RED, ...
+ * Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE
+ *
+ * Example usage:
+ * tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED);
+ *
+ * 2. TB_OUTPUT_256 => [0..256]
+ * In this mode you can leverage the 256 terminal mode:
+ * 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL
+ * 0x08 - 0x0f: TB_* | TB_BOLD
+ * 0x10 - 0xe7: 216 different colors
+ * 0xe8 - 0xff: 24 different shades of grey
+ *
+ * Example usage:
+ * tb_change_cell(x, y, '@', 184, 240);
+ * tb_change_cell(x, y, '@', 0xb8, 0xf0);
+ *
+ * 3. TB_OUTPUT_216 => [0..216]
+ * This mode supports the 3rd range of the 256 mode only.
+ * But you don't need to provide an offset.
+ *
+ * 4. TB_OUTPUT_GRAYSCALE => [0..23]
+ * This mode supports the 4th range of the 256 mode only.
+ * But you dont need to provide an offset.
+ *
+ * Execute build/src/demo/output to see its impact on your terminal.
+ *
+ * If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode.
+ *
+ * Default termbox output mode is TB_OUTPUT_NORMAL.
+ */
+int tb_select_output_mode(int mode);
+
+/* Wait for an event up to 'timeout' milliseconds and fill the 'event'
+ * structure with it, when the event is available. Returns the type of the
+ * event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case
+ * there were no event during 'timeout' period.
+ */
+int tb_peek_event(struct tb_event *event, int timeout);
+
+/* Wait for an event forever and fill the 'event' structure with it, when the
+ * event is available. Returns the type of the event (one of TB_EVENT_*
+ * constants) or -1 if there was an error.
+ */
+int tb_poll_event(struct tb_event *event);
+
+/* Utility utf8 functions. */
+#define TB_EOF -1
+int tb_utf8_char_length(char c);
+int tb_utf8_char_to_unicode(uint32_t *out, const char *c);
+int tb_utf8_unicode_to_char(char *out, uint32_t c);
+
+#endif /* TERMBOX_H */
diff --git a/sfm-0.4/utf8.c b/sfm-0.4/utf8.c
new file mode 100644
index 0000000..a8fbc35
--- /dev/null
+++ b/sfm-0.4/utf8.c
@@ -0,0 +1,78 @@
+#include "termbox.h"
+
+static const unsigned char utf8_length[256] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5,
+ 5, 6, 6, 1, 1 };
+
+static const unsigned char utf8_mask[6] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03,
+ 0x01 };
+
+int
+tb_utf8_char_length(char c)
+{
+ return utf8_length[(unsigned char)c];
+}
+
+int
+tb_utf8_char_to_unicode(uint32_t *out, const char *c)
+{
+ if (*c == 0)
+ return TB_EOF;
+
+ int i;
+ unsigned char len = tb_utf8_char_length(*c);
+ unsigned char mask = utf8_mask[len - 1];
+ uint32_t result = c[0] & mask;
+ for (i = 1; i < len; ++i) {
+ result <<= 6;
+ result |= c[i] & 0x3f;
+ }
+
+ *out = result;
+ return (int)len;
+}
+
+int
+tb_utf8_unicode_to_char(char *out, uint32_t c)
+{
+ int len = 0;
+ int first;
+ int i;
+
+ if (c < 0x80) {
+ first = 0;
+ len = 1;
+ } else if (c < 0x800) {
+ first = 0xc0;
+ len = 2;
+ } else if (c < 0x10000) {
+ first = 0xe0;
+ len = 3;
+ } else if (c < 0x200000) {
+ first = 0xf0;
+ len = 4;
+ } else if (c < 0x4000000) {
+ first = 0xf8;
+ len = 5;
+ } else {
+ first = 0xfc;
+ len = 6;
+ }
+
+ for (i = len - 1; i > 0; --i) {
+ out[i] = (c & 0x3f) | 0x80;
+ c >>= 6;
+ }
+ out[0] = c | first;
+
+ return len;
+}
diff --git a/sfm-0.4/util.c b/sfm-0.4/util.c
new file mode 100644
index 0000000..4aca4e0
--- /dev/null
+++ b/sfm-0.4/util.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+ p = calloc(nmemb, size);
+ FAIL_IF(p == NULL, "calloc");
+ return p;
+}
+
+void *
+erealloc(void *p, size_t len)
+{
+ if ((p = realloc(p, len)) == NULL)
+ die("realloc: %s\n", strerror(errno));
+ return p;
+}
+
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') {
+ (void)fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ (void)fputc('\n', stderr);
+ }
+
+ exit(EXIT_FAILURE);
+}
diff --git a/sfm-0.4/util.h b/sfm-0.4/util.h
new file mode 100644
index 0000000..2ade6d5
--- /dev/null
+++ b/sfm-0.4/util.h
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define LEN(A) (sizeof(A)/sizeof(A[0]))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+#define FAIL_IF(EXP, MSG) {if(EXP){fprintf(stderr, "[\033[31mFAILED %d\033[0m] %s\n", __LINE__, MSG);exit(EXIT_FAILURE);}}
+#define PERROR(EXP) {if(EXP){print_error(strerror(errno));}};
+
+void *ecalloc(size_t, size_t);
+void *erealloc(void*, size_t);
+void die(const char *fmt, ...);
+
+#endif /* UTIL_H */