From 0f734f0e317996d246fed2b0bdd1550c49d46e5b Mon Sep 17 00:00:00 2001 From: stkhan Date: Sat, 7 May 2022 12:42:36 +0000 Subject: Fixed things, added sfm --- .config | 1 - Makefile | 6 + config.mk | 1 + dwm-6.3/config.h | 4 +- dwm-6.3/scripts/dwm-search | 4 +- scripts/set_theme.sh | 5 +- sfm-0.4/LICENSE | 19 + sfm-0.4/Makefile | 51 ++ sfm-0.4/README.md | 78 ++ sfm-0.4/config.def.h | 163 ++++ sfm-0.4/config.h | 163 ++++ sfm-0.4/config.mk | 14 + sfm-0.4/sfm.1 | 147 ++++ sfm-0.4/sfm.c | 2033 ++++++++++++++++++++++++++++++++++++++++++++ sfm-0.4/sfm.png | Bin 0 -> 745 bytes sfm-0.4/termbox.c | 1509 ++++++++++++++++++++++++++++++++ sfm-0.4/termbox.h | 317 +++++++ sfm-0.4/utf8.c | 78 ++ sfm-0.4/util.c | 44 + sfm-0.4/util.h | 17 + 20 files changed, 4645 insertions(+), 9 deletions(-) delete mode 100644 .config create mode 100644 sfm-0.4/LICENSE create mode 100644 sfm-0.4/Makefile create mode 100644 sfm-0.4/README.md create mode 100644 sfm-0.4/config.def.h create mode 100644 sfm-0.4/config.h create mode 100644 sfm-0.4/config.mk create mode 100644 sfm-0.4/sfm.1 create mode 100644 sfm-0.4/sfm.c create mode 100644 sfm-0.4/sfm.png create mode 100644 sfm-0.4/termbox.c create mode 100644 sfm-0.4/termbox.h create mode 100644 sfm-0.4/utf8.c create mode 100644 sfm-0.4/util.c create mode 100644 sfm-0.4/util.h diff --git a/.config b/.config deleted file mode 100644 index de7c102..0000000 --- a/.config +++ /dev/null @@ -1 +0,0 @@ -THEME=nature1 diff --git a/Makefile b/Makefile index 2190d15..2268240 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ help: @echo "sarc compilation" @echo "" @echo " c, clean: cleans dir" + @echo " cleanp: really cleans dir" @echo " config: sets a theme for sarc" @echo " install: installs build" @echo @@ -18,6 +19,7 @@ sarc: clean make -C $(DWM) make -C $(ST) make -C $(DMENU) + make -C $(SFM) make -C surf make -C dwmblocks make -C nsxiv @@ -32,6 +34,7 @@ install: make install -C $(DWM) make install -C $(ST) make install -C $(DMENU) + make install -C $(SFM) make install -C surf make install -C dwmblocks make install -C nsxiv @@ -45,6 +48,7 @@ clean: make clean -C dwm-6.3 make clean -C st-0.8.4 make clean -C dmenu-5.0 + make clean -C $(SFM) make clean -C surf make clean -C nsxiv make clean -C tabbed @@ -53,4 +57,6 @@ clean: make clean -C sent make clean -C wmname rm -rf scripts/gip colors.h +cleanp: clean + rm -rf .config diff --git a/config.mk b/config.mk index 0a5831d..16285d0 100644 --- a/config.mk +++ b/config.mk @@ -2,6 +2,7 @@ PREFIX=/usr/local MANPREFIX=$(PREFIX)/share/man X11INC=/usr/X11R6/include X11LIB=/usr/X11R6/lib +SFM=sfm-0.4 DWM=dwm-6.3 ST=st-0.8.4 DMENU=dmenu-5.0 diff --git a/dwm-6.3/config.h b/dwm-6.3/config.h index 5a8f71c..16fbdb4 100644 --- a/dwm-6.3/config.h +++ b/dwm-6.3/config.h @@ -1,4 +1,3 @@ -#include "../colors.h" #include "cmd.h" /* See LICENSE file for copyright and license details. */ @@ -17,6 +16,7 @@ static const char *fonts[] = { "Source Code Pro:size=9" , "NotoColorEmo static const char dmenufont[] = "Source Code Pro:size=9"; static const unsigned int baralpha = 0xd0; static const unsigned int borderalpha = OPAQUE; +#include "../colors.h" static const char *colors[][3] = { /* fg bg border */ [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, @@ -120,7 +120,7 @@ static Key keys[] = { { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, { MODKEY, XK_r, spawn, SHCMD("mpvopen") }, - { MODKEY, XK_F3, spawn, SHCMD("st ranger") }, + { MODKEY, XK_F3, spawn, SHCMD("st sfm") }, { MODKEY, XK_F5, spawn, SHCMD("st ncmpcpp") }, { MODKEY|ShiftMask, XK_f, spawn, SHCMD("firejail --noprofile --hosts-file=~/.config/surf/ads.txt tabbed surf -e") }, { MODKEY, XK_w, spawn, SHCMD("get_weather") }, diff --git a/dwm-6.3/scripts/dwm-search b/dwm-6.3/scripts/dwm-search index a747424..ff49fc7 100755 --- a/dwm-6.3/scripts/dwm-search +++ b/dwm-6.3/scripts/dwm-search @@ -1,7 +1,7 @@ #!/bin/bash QUERY=$(tac ~/.config/surf/search.txt | sort -u | dmenu -l 20 -p "Search DuckDuckGo: ") -SEARCH=$(echo $QUERY | sed 's/ /+/g') +SEARCH=$(echo $QUERY | sed 's/ /+/g; s/-e//') URL="duckduckgo.com/?q=$SEARCH" CMD="firejail --noprofile --hosts-file=~/.config/surf/ads.txt tabbed -dn tabbed-surf -r 2 surf -e '' $URL" @@ -9,6 +9,6 @@ if [[ -z "$SEARCH" ]]; then exit else # saves search to history - echo $QUERY >> ~/.config/surf/search.txt + echo $QUERY | sed 's/-e//' >> ~/.config/surf/search.txt $CMD fi diff --git a/scripts/set_theme.sh b/scripts/set_theme.sh index 87589c3..075924e 100755 --- a/scripts/set_theme.sh +++ b/scripts/set_theme.sh @@ -3,7 +3,4 @@ echo "Choose a theme:" && ls ./theme/ read theme echo "THEME=$theme" >> ./.config -#cp ./theme/$theme/$theme.png $HOME/.config/wallpaper.png -#cp ./theme/$theme/colors.h ./ - -echo "Done" +echo "Set theme to $theme" 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 +© 2020 Mohamed Afify +© 2021 Nikolay Korotkiy +© 2021 David Kalliecharan +© 2021 Tdukv + +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 @@ +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 +``` +sfm screenshot + +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 +#include +#include +#include +#include +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__APPLE__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, <); + return strftime(result, MAX_DTF, dtfmt, <); +} + +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, >imeout); +#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 Binary files /dev/null and b/sfm-0.4/sfm.png 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +/* 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 +#include +#include +#include +#include + +#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 */ -- cgit v1.2.3