sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

commit c54128aed1ba8f4241d8d8937b2c6a2a80039512
Author: xoel <x.otero@udc.es>
Date:   Fri, 29 Sep 2023 05:01:40 +0200

Recopilacion de porgramas que uso nos meus ordenadores persoais

Diffstat:
Admenu/LICENSE | 30++++++++++++++++++++++++++++++
Admenu/Makefile | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/README | 24++++++++++++++++++++++++
Admenu/arg.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/config.def.h | 23+++++++++++++++++++++++
Admenu/config.h | 23+++++++++++++++++++++++
Admenu/config.mk | 31+++++++++++++++++++++++++++++++
Admenu/dmenu | 0
Admenu/dmenu.1 | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/dmenu.c | 789+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/dmenu.o | 0
Admenu/dmenu_path | 13+++++++++++++
Admenu/dmenu_run | 2++
Admenu/drw.c | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/drw.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/drw.o | 0
Admenu/stest | 0
Admenu/stest.1 | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/stest.c | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Admenu/stest.o | 0
Admenu/util.c | 35+++++++++++++++++++++++++++++++++++
Admenu/util.h | 8++++++++
Admenu/util.o | 0
Annn/.circleci/config.yml | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/.github/FUNDING.yml | 3+++
Annn/.github/ISSUE_TEMPLATE/bug_report.md | 42++++++++++++++++++++++++++++++++++++++++++
Annn/.github/ISSUE_TEMPLATE/feature_request.md | 29+++++++++++++++++++++++++++++
Annn/.github/workflows/ci.yml | 37+++++++++++++++++++++++++++++++++++++
Annn/.github/workflows/lock.yml | 17+++++++++++++++++
Annn/.gitignore | 3+++
Annn/CHANGELOG | 1027+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/LICENSE | 27+++++++++++++++++++++++++++
Annn/Makefile | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/README.md | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/CONTRIBUTING.md | 17+++++++++++++++++
Annn/misc/auto-completion/bash/nnn-completion.bash | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/auto-completion/fish/nnn.fish | 45+++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/auto-completion/zsh/_nnn | 46++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/desktop/nnn.desktop | 10++++++++++
Annn/misc/haiku/Makefile | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/haiku/haiku_interop.h | 14++++++++++++++
Annn/misc/haiku/nm.cpp | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/haiku/nnn-master.recipe | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/haiku/nnn.rdef | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/logo/logo-128x128.png | 0
Annn/misc/logo/logo-64x64.png | 0
Annn/misc/logo/logo.svg | 302++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/macos-legacy/mach_gettime.c | 42++++++++++++++++++++++++++++++++++++++++++
Annn/misc/macos-legacy/mach_gettime.h | 28++++++++++++++++++++++++++++
Annn/misc/musl/musl-static-ubuntu.sh | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/natool/natool | 45+++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/packagecore/packagecore.yaml | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/quitcd/quitcd.bash_zsh | 28++++++++++++++++++++++++++++
Annn/misc/quitcd/quitcd.csh | 14++++++++++++++
Annn/misc/quitcd/quitcd.fish | 37+++++++++++++++++++++++++++++++++++++
Annn/misc/test/benchmark.sh | 37+++++++++++++++++++++++++++++++++++++
Annn/misc/test/genfiles.sh | 8++++++++
Annn/misc/test/mktest.sh | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/misc/test/plot-bench.py | 20++++++++++++++++++++
Annn/nnn.1 | 583+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/patches/README.md | 16++++++++++++++++
Annn/patches/gitstatus/mainline.diff | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/patches/gitstatus/namefirst.diff | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/patches/namefirst/mainline.diff | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/patches/restorepreview/mainline.diff | 269+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/.cbcp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/.iconlookup | 428+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/.nmv | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/.nnn-plugin-helper | 38++++++++++++++++++++++++++++++++++++++
Annn/plugins/.ntfy | 22++++++++++++++++++++++
Annn/plugins/README.md | 357+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/autojump | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/boom | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/bulknew | 32++++++++++++++++++++++++++++++++
Annn/plugins/cdpath | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/chksum | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/cmusq | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/diffs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/dragdrop | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/dups | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/finder | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/fixname | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/fzcd | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/fzhist | 40++++++++++++++++++++++++++++++++++++++++
Annn/plugins/fzopen | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/fzplug | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/getplugs | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/gitroot | 15+++++++++++++++
Annn/plugins/gpgd | 28++++++++++++++++++++++++++++
Annn/plugins/gpge | 44++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/gsconnect | 19+++++++++++++++++++
Annn/plugins/gutenread | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/imgresize | 31+++++++++++++++++++++++++++++++
Annn/plugins/imgur | 595+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/imgview | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/ipinfo | 13+++++++++++++
Annn/plugins/kdeconnect | 24++++++++++++++++++++++++
Annn/plugins/launch | 42++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/mimelist | 14++++++++++++++
Annn/plugins/moclyrics | 40++++++++++++++++++++++++++++++++++++++++
Annn/plugins/mocq | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/mp3conv | 41+++++++++++++++++++++++++++++++++++++++++
Annn/plugins/mtpmount | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/nbak | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/nmount | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/nnnopen | 18++++++++++++++++++
Annn/plugins/nuke | 556+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/oldbigfile | 16++++++++++++++++
Annn/plugins/organize | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/pdfread | 30++++++++++++++++++++++++++++++
Annn/plugins/preview-tabbed | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/preview-tui | 481+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/pskill | 35+++++++++++++++++++++++++++++++++++
Annn/plugins/renamer | 45+++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/ringtone | 36++++++++++++++++++++++++++++++++++++
Annn/plugins/rsynccp | 26++++++++++++++++++++++++++
Annn/plugins/splitjoin | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/suedit | 16++++++++++++++++
Annn/plugins/togglex | 21+++++++++++++++++++++
Annn/plugins/umounttree | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/upload | 30++++++++++++++++++++++++++++++
Annn/plugins/wallpaper | 26++++++++++++++++++++++++++
Annn/plugins/x2sel | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/plugins/xdgdefault | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/.clang-tidy | 15+++++++++++++++
Annn/src/dbg.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/icons-in-terminal.h | 3736+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/icons-nerdfont.h | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/icons.h | 530+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/nnn.c | 8798+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/nnn.h | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Annn/src/qsort.h | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/LICENSE | 37+++++++++++++++++++++++++++++++++++++
Aportatil/dwm/Makefile | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/README | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/config.def.h.bak | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/config.def.h.orig | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/config.h | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/config.h.orig | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/config.h.rej | 25+++++++++++++++++++++++++
Aportatil/dwm/config.mk | 38++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/drw.c | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/drw.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/drw.o | 0
Aportatil/dwm/dwm | 0
Aportatil/dwm/dwm-attachaside-6.3.diff | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm-status2d-systray-6.3.diff | 888+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm-sticky-6.1.diff | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm.1 | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm.c | 2930+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm.c.bak | 2168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm.c.orig | 2776+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/dwm.c.rej | 12++++++++++++
Aportatil/dwm/dwm.o | 0
Aportatil/dwm/dwm.png | 0
Aportatil/dwm/dwm_status2d-6.3.diff | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/fullgaps.diff | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/move-resize.diff | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/proba.c | 8++++++++
Aportatil/dwm/transient.c | 42++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwm/util.c | 35+++++++++++++++++++++++++++++++++++
Aportatil/dwm/util.h | 8++++++++
Aportatil/dwm/util.o | 0
Aportatil/dwmblocks/.gitignore | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwmblocks/LICENSE | 7+++++++
Aportatil/dwmblocks/Makefile | 36++++++++++++++++++++++++++++++++++++
Aportatil/dwmblocks/README.md | 15+++++++++++++++
Aportatil/dwmblocks/batt.sh | 44++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwmblocks/blocks.def.h | 11+++++++++++
Aportatil/dwmblocks/cousa.txt | 1+
Aportatil/dwmblocks/dwmblocks.c | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwmblocks/dwmblocks.diff | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/dwmblocks/dwmblocks_gerard.c | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/FAQ | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/LEGACY | 17+++++++++++++++++
Aportatil/st/LICENSE | 34++++++++++++++++++++++++++++++++++
Aportatil/st/Makefile | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/README | 34++++++++++++++++++++++++++++++++++
Aportatil/st/TODO | 28++++++++++++++++++++++++++++
Aportatil/st/alpha.diff | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/arg.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/config.def.h | 480+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/config.def.h.orig | 478+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/config.h | 521+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/config.h.bak | 478+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/config.mk | 35+++++++++++++++++++++++++++++++++++
Aportatil/st/st | 0
Aportatil/st/st-scrollback-0.8.5.diff | 350+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/st.1 | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/st.c | 2761+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/st.c.bak | 2690+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/st.h | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/st.info | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/st.o | 0
Aportatil/st/win.h | 40++++++++++++++++++++++++++++++++++++++++
Aportatil/st/x.c | 2116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/x.c.orig | 2096+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aportatil/st/x.o | 0
Asxiv/.gitignore | 5+++++
Asxiv/LICENSE | 339+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/Makefile | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/README.md | 244+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/TODO | 5+++++
Asxiv/autoreload_inotify.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/autoreload_nop.c | 42++++++++++++++++++++++++++++++++++++++++++
Asxiv/commands.c | 455+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/commands.lst | 37+++++++++++++++++++++++++++++++++++++
Asxiv/config.def.h | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/exec/image-info | 20++++++++++++++++++++
Asxiv/exec/key-handler | 35+++++++++++++++++++++++++++++++++++
Asxiv/icon/128x128.png | 0
Asxiv/icon/16x16.png | 0
Asxiv/icon/32x32.png | 0
Asxiv/icon/48x48.png | 0
Asxiv/icon/64x64.png | 0
Asxiv/icon/Makefile | 12++++++++++++
Asxiv/icon/dat2h.awk | 35+++++++++++++++++++++++++++++++++++
Asxiv/icon/data.h | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/image.c | 797+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/main.c | 951+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/options.c | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/sxiv.1 | 448+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/sxiv.desktop | 8++++++++
Asxiv/sxiv.h | 448+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/thumbs.c | 594+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/utf8.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/util.c | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asxiv/window.c | 476+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/LICENSE | 23+++++++++++++++++++++++
Atabbed/Makefile | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/README | 22++++++++++++++++++++++
Atabbed/TODO | 4++++
Atabbed/alpha.diff | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/arg.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/config.def.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/config.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/config.mk | 33+++++++++++++++++++++++++++++++++
Atabbed/config.mk.orig | 33+++++++++++++++++++++++++++++++++
Atabbed/config.mk.rej | 11+++++++++++
Atabbed/tabbed | 0
Atabbed/tabbed.1 | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed.c | 1421+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed.c.orig | 1376+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed.c.rej | 13+++++++++++++
Atabbed/tabbed.o | 0
Atabbed/tabbed_xdefecto/tabbed-0.6/LICENSE | 23+++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/Makefile | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/README | 22++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/TODO | 4++++
Atabbed/tabbed_xdefecto/tabbed-0.6/arg.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/config.def.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/config.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/config.mk | 25+++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/tabbed | 0
Atabbed/tabbed_xdefecto/tabbed-0.6/tabbed.1 | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/tabbed.c | 1297+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atabbed/tabbed_xdefecto/tabbed-0.6/tabbed.o | 0
Atabbed/xembed | 0
Atabbed/xembed.1 | 35+++++++++++++++++++++++++++++++++++
Atabbed/xembed.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Atabbed/xembed.o | 0
Awallpapers/0001.jpg | 0
Awallpapers/0002.jpg | 0
Awallpapers/0003.jpg | 0
Awallpapers/0007.jpg | 0
Awallpapers/0008.jpg | 0
Awallpapers/0023.jpg | 0
Awallpapers/0029.jpg | 0
Awallpapers/0035.jpg | 0
Awallpapers/0044.jpg | 0
Awallpapers/0056.jpg | 0
Awallpapers/0058.jpg | 0
Awallpapers/0066.jpg | 0
Awallpapers/0070.jpg | 0
Awallpapers/0072.jpg | 0
Awallpapers/0073.jpg | 0
Awallpapers/0076.jpg | 0
Awallpapers/0086.jpg | 0
Awallpapers/0088.jpg | 0
Awallpapers/0097.jpg | 0
Awallpapers/0104.jpg | 0
Awallpapers/0119.jpg | 0
Awallpapers/0121.jpg | 0
Awallpapers/0127.jpg | 0
Awallpapers/0132.jpg | 0
Awallpapers/0141.jpg | 0
Awallpapers/0145.jpg | 0
Awallpapers/0152.jpg | 0
Awallpapers/0162.jpg | 0
Awallpapers/0174.jpg | 0
Awallpapers/0180.jpg | 0
Awallpapers/0188.jpg | 0
Awallpapers/0189.jpg | 0
Awallpapers/0190.jpg | 0
Awallpapers/0196.jpg | 0
Awallpapers/0197.jpg | 0
Awallpapers/0198.jpg | 0
Awallpapers/0207.jpg | 0
Awallpapers/0208.jpg | 0
Awallpapers/0212.jpg | 0
Awallpapers/0214.jpg | 0
Awallpapers/0227.jpg | 0
Awallpapers/0228.jpg | 0
Awallpapers/0236.jpg | 0
Awallpapers/0239.jpg | 0
Awallpapers/0240.jpg | 0
Awallpapers/0248.jpg | 0
Awallpapers/0251.jpg | 0
Awallpapers/0253.jpg | 0
Awallpapers/0260.jpg | 0
Awallpapers/0264.jpg | 0
Awallpapers/0270.jpg | 0
Awallpapers/0272.jpg | 0
Awallpapers/0278.jpg | 0
Awallpapers/0291.jpg | 0
Awallpapers/0294.jpg | 0
Awallpapers/0308.jpg | 0
Awallpapers/21599.jpg | 0
Awallpapers/6315.jpg | 0
Awallpapers/632957.jpg | 0
Awallpapers/68747470733a2f2f692e696d6775722e636f6d2f523435754951762e6a7067.jpg | 0
Awallpapers/736461.png | 0
Awallpapers/Noragami.png | 0
Awallpapers/background04.jpg | 0
Awallpapers/background05.jpg | 0
Awallpapers/background07.jpg | 0
Awallpapers/background09.jpg | 0
Awallpapers/background10.jpg | 0
Awallpapers/background12.jpg | 0
Awallpapers/background13.jpg | 0
Awallpapers/background14.jpg | 0
Awallpapers/background17.jpg | 0
Awallpapers/background20.jpg | 0
Awallpapers/background21.jpg | 0
Awallpapers/background22.jpg | 0
Awallpapers/background24.jpg | 0
Awallpapers/background30.jpg | 0
Awallpapers/background32.jpg | 0
Awallpapers/background33.jpg | 0
Awallpapers/background36.jpg | 0
Awallpapers/background45.jpg | 0
Awallpapers/background47.jpg | 0
Awallpapers/background48.jpg | 0
Awallpapers/background49.jpg | 0
Awallpapers/background51.jpg | 0
Awallpapers/background52.jpg | 0
Awallpapers/background55.jpg | 0
Awallpapers/background56.jpg | 0
Awallpapers/ign_waifu.png | 0
Awallpapers/nGlSFM9.png | 0
Awallpapers/rocket-7680x4320.png | 0
Awallpapers/valenberg1.png | 0
Awallpapers/valenberg2.png | 0
Awallpapers/valenberg9.png | 0
Awallpapers/wallhaven-6olw9x.jpg | 0
Awallpapers/wallhaven-g7d933.jpg | 0
Awallpapers/wallhaven-z89dgo.png | 0
Awallpapers/wallhaven-z8dg9y.png | 0
Awallpapers/wallpaper.jpg | 0
Awallpapers/wallpaperflare.com_wallpaper.jpg | 0
Awallpapers/wp1809658-4k-pc-wallpapers.jpg | 0
Awallpapers/wp1856268-shigatsu-wa-kimi-no-uso-wallpapers.jpg | 0
Awallpapers/wp2673988-dark-anime-hd-wallpapers.jpg | 0
Awallpapers/wp2695245-4k-pc-wallpapers.jpg | 0
Awallpapers/wp2821386-dark-anime-hd-wallpapers.jpg | 0
Awallpapers/xavier-cuenca-w4-3.jpg | 0
366 files changed, 62291 insertions(+), 0 deletions(-)

diff --git a/dmenu/LICENSE b/dmenu/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe <anselm@garbe.ca> +© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com> +© 2006-2007 Michał Janeczek <janeczek@gmail.com> +© 2007 Kris Maglione <jg@suckless.org> +© 2009 Gottox <gottox@s01.de> +© 2009 Markus Schnalke <meillo@marmaro.de> +© 2009 Evan Gates <evan.gates@gmail.com> +© 2010-2012 Connor Lane Smith <cls@lubutu.com> +© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2015-2019 Quentin Rameau <quinq@fifth.space> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dmenu/Makefile b/dmenu/Makefile @@ -0,0 +1,64 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dmenu.c stest.c util.c +OBJ = $(SRC:.c=.o) + +all: options dmenu stest + +options: + @echo dmenu build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +.PHONY: all options clean dist install uninstall diff --git a/dmenu/README b/dmenu/README @@ -0,0 +1,24 @@ +dmenu - dynamic menu +==================== +dmenu is an efficient dynamic menu for X. + + +Requirements +------------ +In order to build dmenu you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dmenu is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dmenu +(if necessary as root): + + make clean install + + +Running dmenu +------------- +See the man page for details. diff --git a/dmenu/arg.h b/dmenu/arg.h @@ -0,0 +1,49 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/dmenu/config.def.h b/dmenu/config.def.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/dmenu/config.h b/dmenu/config.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/dmenu/config.mk b/dmenu/config.mk @@ -0,0 +1,31 @@ +# dmenu version +VERSION = 5.1 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) +LDFLAGS = $(LIBS) + +# compiler and linker +CC = cc diff --git a/dmenu/dmenu b/dmenu/dmenu Binary files differ. diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1 @@ -0,0 +1,194 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.RB [ \-l +.IR lines ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.B \-b +dmenu appears at the bottom of the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-m " monitor" +dmenu is displayed on the monitor number supplied. Monitor numbers are starting +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. +.TP +.B Tab +Copy the selected item to the input field. +.TP +.B Return +Confirm selection. Prints the selected item to stdout and exits, returning +success. +.TP +.B Ctrl-Return +Confirm selection. Prints the selected item to stdout and continues. +.TP +.B Shift\-Return +Confirm input. Prints the input text to stdout and exits, returning success. +.TP +.B Escape +Exit without selecting an item, returning failure. +.TP +.B Ctrl-Left +Move cursor to the start of the current word +.TP +.B Ctrl-Right +Move cursor to the end of the current word +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.TP +.B C\-c +Escape +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-i +Tab +.TP +.B C\-j +Return +.TP +.B C\-J +Shift-Return +.TP +.B C\-k +Delete line right +.TP +.B C\-m +Return +.TP +.B C\-M +Shift-Return +.TP +.B C\-n +Down +.TP +.B C\-p +Up +.TP +.B C\-u +Delete line left +.TP +.B C\-w +Delete word left +.TP +.B C\-y +Paste from primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.B M\-b +Move cursor to the start of the current word +.TP +.B M\-f +Move cursor to the end of the current word +.TP +.B M\-g +Home +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu/dmenu.c b/dmenu/dmenu.c @@ -0,0 +1,789 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +/* macros */ +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *h, const char *n) +{ + size_t i; + + if (!n[0]) + return (char *)h; + + for (; *h; ++h) { + for (i = 0; n[i] && tolower((unsigned char)n[i]) == + tolower((unsigned char)h[i]); ++i) + ; + if (n[i] == '\0') + return (char *)h; + } + return NULL; +} + +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); + } + die("cannot grab focus"); +} + +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %u bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); +} + +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); +} + +static size_t +nextrune(int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +static void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } +} + +static void +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); + break; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + case XK_KP_Left: + movewordedge(-1); + goto draw; + case XK_Right: + case XK_KP_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl(*buf)) + insert(buf, len); + break; + case XK_Delete: + case XK_KP_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_End: + case XK_KP_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + cleanup(); + exit(1); + case XK_Home: + case XK_KP_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + case XK_KP_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + case XK_KP_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + case XK_KP_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + case XK_KP_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + case XK_KP_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + case XK_KP_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + drawmenu(); +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + lines = MIN(lines, i); +} + +static void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i]) != 0) + break; + + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = MIN(inputw, mw/3); + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenu/dmenu.o b/dmenu/dmenu.o Binary files differ. diff --git a/dmenu/dmenu_path b/dmenu/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu/dmenu_run b/dmenu/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/dmenu/drw.c b/dmenu/drw.c @@ -0,0 +1,436 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <X11/Xlib.h> +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/dmenu/drw.h b/dmenu/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/dmenu/drw.o b/dmenu/drw.o Binary files differ. diff --git a/dmenu/stest b/dmenu/stest Binary files differ. diff --git a/dmenu/stest.1 b/dmenu/stest.1 @@ -0,0 +1,90 @@ +.TH STEST 1 dmenu\-VERSION +.SH NAME +stest \- filter a list of files by properties +.SH SYNOPSIS +.B stest +.RB [ -abcdefghlpqrsuwx ] +.RB [ -n +.IR file ] +.RB [ -o +.IR file ] +.RI [ file ...] +.SH DESCRIPTION +.B stest +takes a list of files and filters by the files' properties, analogous to +.IR test (1). +Files which pass all tests are printed to stdout. If no files are given, stest +reads files from stdin. +.SH OPTIONS +.TP +.B \-a +Test hidden files. +.TP +.B \-b +Test that files are block specials. +.TP +.B \-c +Test that files are character specials. +.TP +.B \-d +Test that files are directories. +.TP +.B \-e +Test that files exist. +.TP +.B \-f +Test that files are regular files. +.TP +.B \-g +Test that files have their set-group-ID flag set. +.TP +.B \-h +Test that files are symbolic links. +.TP +.B \-l +Test the contents of a directory given as an argument. +.TP +.BI \-n " file" +Test that files are newer than +.IR file . +.TP +.BI \-o " file" +Test that files are older than +.IR file . +.TP +.B \-p +Test that files are named pipes. +.TP +.B \-q +No files are printed, only the exit status is returned. +.TP +.B \-r +Test that files are readable. +.TP +.B \-s +Test that files are not empty. +.TP +.B \-u +Test that files have their set-user-ID flag set. +.TP +.B \-v +Invert the sense of tests, only failing files pass. +.TP +.B \-w +Test that files are writable. +.TP +.B \-x +Test that files are executable. +.SH EXIT STATUS +.TP +.B 0 +At least one file passed all tests. +.TP +.B 1 +No files passed all tests. +.TP +.B 2 +An error occurred. +.SH SEE ALSO +.IR dmenu (1), +.IR test (1) diff --git a/dmenu/stest.c b/dmenu/stest.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <dirent.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "arg.h" +char *argv0; + +#define FLAG(x) (flag[(x)-'a']) + +static void test(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static void +test(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (line[n - 1] == '\n') + line[n - 1] = '\0'; + test(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* test directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + test(path, d->d_name); + } + closedir(dir); + } else { + test(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/dmenu/stest.o b/dmenu/stest.o Binary files differ. diff --git a/dmenu/util.c b/dmenu/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/dmenu/util.h b/dmenu/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dmenu/util.o b/dmenu/util.o Binary files differ. diff --git a/nnn/.circleci/config.yml b/nnn/.circleci/config.yml @@ -0,0 +1,131 @@ +version: 2 + +jobs: + compile: + docker: + - image: ubuntu:20.04 + working_directory: ~/nnn + environment: + CI_FORCE_TEST: 1 + steps: + - run: + command: | + apt update -qq + DEBIAN_FRONTEND="noninteractive" TZ="America/New_York" apt-get -y install tzdata + apt install -y --no-install-recommends software-properties-common wget gpg-agent shellcheck + apt install -y --no-install-recommends git make pkg-config libncurses-dev libreadline-dev + apt install -y --no-install-recommends gcc-7 gcc-8 gcc-9 gcc-10 + apt install -y --no-install-recommends clang-6.0 clang-7 clang-8 clang-9 clang-10 clang-11 clang-tidy-11 + - checkout + - run: + command: | + export CFLAGS=-Werror + make clean + echo + echo "########## gcc-7 ##########" + CC=gcc-7 make strip + ls -l nnn + make clean + echo + echo "########## gcc-8 ##########" + CC=gcc-8 make strip + ls -l nnn + make clean + echo + echo "########## gcc-9 ##########" + CC=gcc-9 make strip + ls -l nnn + make clean + echo + echo "########## gcc-10 ##########" + CC=gcc-10 make strip + ls -l nnn + make clean + echo + echo "########## clang-6 ##########" + CC=clang-6.0 make strip + ls -l nnn + make clean + echo + echo "########## clang-7 ##########" + CC=clang-7 make strip + ls -l nnn + make clean + echo + echo "########## clang-8 ##########" + CC=clang-8 make strip + ls -l nnn + make clean + echo + echo "########## clang-9 ##########" + CC=clang-9 make strip + ls -l nnn + make clean + echo + echo "########## clang-10 ##########" + CC=clang-10 make strip + ls -l nnn + make clean + echo + echo "########## clang-11 ##########" + CC=clang-11 make strip + ls -l nnn + make clean + echo + echo "########## clang-tidy-11 ##########" + clang-tidy-11 src/* -- -I/usr/include -I/usr/include/ncursesw + echo "########## shellcheck ##########" + find plugins/ -type f -not -name "*.md" -exec shellcheck -e SC1090,SC2230 {} + + + package-and-publish: + machine: true + working_directory: ~/nnn + steps: + - checkout + - run: + name: "auto-generate packages" + command: | + # Create dist directory if it doesn't exist + mkdir ./dist + # Clean up + rm -rf ./dist/* + # Pack source + git archive -o ../${CIRCLE_PROJECT_REPONAME}-${CIRCLE_TAG}.tar.gz --format tar.gz --prefix=${CIRCLE_PROJECT_REPONAME}-${CIRCLE_TAG#v}/ ${CIRCLE_TAG} + # Use latest installed python3 from pyenv + export PYENV_VERSION="$(pyenv versions | grep -Po '\b3\.\d+\.\d+' | tail -1)" + #pip install packagecore + #packagecore -c misc/packagecore/packagecore.yaml -o ./dist/ ${CIRCLE_TAG#v} + # Move source pack to dist + mv ../${CIRCLE_PROJECT_REPONAME}-${CIRCLE_TAG}.tar.gz ./dist/ + + - run: + name: "publish to GitHub" + command: | + go get github.com/tcnksm/ghr + ghr -t ${GITHUB_API_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace ${CIRCLE_TAG} ./dist/ + +workflows: + version: 2 + + test: + jobs: &all-tests + - compile + + nightly: + triggers: + - schedule: + cron: "0 0 * * 6" + filters: + branches: + only: + - master + jobs: *all-tests + + publish-github-release: + jobs: + - package-and-publish: + filters: + tags: + only: /^v.*/ + branches: + ignore: /.*/ diff --git a/nnn/.github/FUNDING.yml b/nnn/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: jarun diff --git a/nnn/.github/ISSUE_TEMPLATE/bug_report.md b/nnn/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,42 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' +--- + +`nnn` comes with excellent documentation (including a Troubleshooting section). If you have a _How to do x_ question, it may have already been answered. Wiki: https://github.com/jarun/nnn/wiki + +If it looks like a local environment issue, please try to debug yourself. Debugging local setup issues is not our top priority. + +If you are looking for a new feature or program option, ensure you have the correct version with the feature installed. Refer to the release notes to confirm. + +Before opening an issue, please try to reproduce on latest master. The bug you noticed might have already been fixed. + +Useful links: + +- compile `nnn` from source - https://github.com/jarun/nnn/wiki/Usage#from-source +- debugging `nnn` - https://github.com/jarun/nnn/wiki/Developer-guides#debugging-nnn + +If the issue can be reproduced on master, log it. + +Please provide the environment details. **If that's missing, the issue will be closed without any cited reason.** + +If we need more information and there is no communication from the bug reporter within 7 days from the date of request, we will close the issue. If you have relevant information, resume discussion any time. + +--- PLEASE DELETE THIS LINE AND EVERYTHING ABOVE --- + +#### Environment details (Put `x` in the checkbox along with the information) + +- [ ] Operating System: +- [ ] Desktop Environment: +- [ ] Terminal Emulator: +- [ ] Shell: +- [ ] Custom desktop opener (if applicable): +- [ ] Program options used: +- [ ] Configuration options set: +- [ ] Plugins are installed +- [ ] Issue exists on `nnn` master + +#### Exact steps to reproduce the issue diff --git a/nnn/.github/ISSUE_TEMPLATE/feature_request.md b/nnn/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,29 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' +--- +**Is your feature request related to a problem? Please describe.** + +A clear and concise description of what the problem is. + +**Describe the solution you'd like** + +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** + +A clear and concise description of any alternative solutions or features you've +considered. + +**Additional context** + +Add any other context or screenshots about the feature request here. + +**Consider contributing** + +Please consider contributing the feature back to `nnn`. + +Feel free to discuss in the ToDo list thread. We are more than happy to help. diff --git a/nnn/.github/workflows/ci.yml b/nnn/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: ci + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + macOS-gcc: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v2 + - name: Compile with gcc + env: + CC: gcc + run: | + export CFLAGS="$CFLAGS -Werror" + make clean + make + make clean + macOS-clang: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v2 + - name: Compile with clang + env: + CC: clang + run: | + brew update + brew install llvm + export PATH="/usr/local/opt/llvm/bin:$PATH" + export CFLAGS="$CFLAGS -Werror" + make clean + make + make clean + clang-tidy src/* -- -I/usr/include diff --git a/nnn/.github/workflows/lock.yml b/nnn/.github/workflows/lock.yml @@ -0,0 +1,17 @@ +name: 'Lock threads' + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: '30' + issue-lock-reason: '' + pr-lock-inactive-days: '30' + pr-lock-reason: '' diff --git a/nnn/.gitignore b/nnn/.gitignore @@ -0,0 +1,3 @@ +*.o +*.dSYM +nnn diff --git a/nnn/CHANGELOG b/nnn/CHANGELOG @@ -0,0 +1,1027 @@ +nnn v4.4 Tequila +2021-11-23 + +- support macOS iterm2 in plugin preview-tui (#1196) +- use selection at native command prompt with `%j` and `%J` + - docs - https://github.com/jarun/nnn/wiki/concepts#special-variables +- scroll strings longer than columns in rename/new prompts (#1213, #279) +- batch rename symlink targets in listing mode (#1214) +- option for recursive rename in plugin .nmv (#1186) +- more frequent checks for cancellation during du (#1236) +- picker mode: enable auto-proceed +- picker mode: don't pick hovered file on <kbd>Enter</kbd> if selection exists +- picker mode: fix issue in plugin `fzopen` when used to pick files +- send file to explorer FIFO on double left click instead of opening it +- new neovim plugin [nnn.nvim](https://github.com/luukvbaal/nnn.nvim) + - nvim-only, featuring explorer mode (`-F` flag) +- explorer mode for [nnn.vim](https://github.com/mcchrish/nnn.vim#explorer) +- remove option `-w`: always place HW cursor on current entry +- accept link name when linking a single target (#1201) +- option `-i` to show current file information in info bar +- force GNU sed on *BSD and Solaris +- add `nsxiv` support to nuke, preview-tabbed and imgview (#1230) +- fix preview-tui without `-a` (#1208) +- pass `pts` in env var for preview-tui to use in `tput` (#1235) +- disable editing file in picker mode (#1183) +- save session in picker mode (#1190) +- use nerd icons for gitstatus patch (#1220) + +------------------------------------------------------------------------------- + +nnn v4.3 Martini +2021-09-29 + +- cool ASCII art logo in the help screen +- add `bookmarks` directory for flexible symlinked bookmarks +- new key <kbd>B</kbd> to add a symlinked bookmark for current dir +- special variables `$dN`, `$fN` available for plugins/prompt/shell to access + dir/hovered file in each conext +- config `NNN_ORDER` to set directory-specific ordering +- show/hide hidden files as per context state in plugin based batch rename +- retain search filter history for plugin `finder` +- sync multiple instances of `nnn` after operation on selection +- signal CWD change to terminal via OSC-7 (#1147) +- save complete per-context filter when saving sessions +- disable symlink resolution for paths in `NNN_BMS` and arg `PATH` +- do not end selection mode on running plugins/prompt/shell +- plugin `bookmarks` replaced by symlinked bookmarks support +- list open locations in active contexts in help page +- make option `O_MATCHFLTR` to discard filter key if no match +- configurable `NNN_TMPFILE` to _cd on quit_ +- disable auto marking directories (use <kbd>-</kbd>) +- picker mode improvements + - open tty for input if `STDIN` is non-tty + - truncate output file before writing + - do not double select a file on <kbd>Enter</kbd> +- legacy macOS (< 10.12.0) support +- no redraw during du calculation, show processed dir name +- plugin `xdgdefault`: add dmenu support +- user patch `restorepreview`: close/restore `preview-tui` for internal edits + +------------------------------------------------------------------------------- + +nnn v4.2 Mojito +2021-07-21 + +- `NNN_PLUG` indicator symbol interpretation has **changed**: + - `!` - _run-cmd-as-plugin_ (earlier `_`) + - `&` - _run-gui-cmd-as-plugin_ (earlier `|`) + - `|` (new) - page noninteractive _run-cmd-as-plugin_ output +- persistent selection markers (#1086) +- option _extract to..._ for archives +- mount remote and mount/extract archive to a smart context +- confirm file trashing to avoid accidental press of <kbd>x</kbd> (#1101) +- insert the last command executed at prompt on <kbd>Up</kbd> or <kbd>Down</kbd> +- insert the current file name at empty prompt on <kbd>TAB</kbd> +- handle redraw issue on missed `KEY_RESIZE` (#1067) +- add force-tty capability to spawn and set pagers to it (#1064) +- clear selection mode on deselecting last selected file (#1098) +- remove selected hovered entry from selection on deletion +- disable filtering in empty directories +- ignore last pressed filter character when no matches +- fix broken screen on resize while paging (#1072) +- fix archive not hovered on creation +- remove libgit2 dependency in `gitstatus` patch (#1095) +- add `-G` flag for `gitstatus` patch +- option `-X` for explorer (persistent picker) mode +- option `-F` decommissioned in favour of config `NNN_HELP` +- `-F` redefined to multiplex `NNN_FIFO` to preview or explore +- support paging noninterative _run-cmd-as-plugin_ output +- `nuke` - add option to execute binaries (#1111) +- plugin `fzopen` - call `open` on macOS, add option to use `nuke` +- plugin `fzcd` will not modify selection +- plugin `suedit` - preserve environment +- several `preview-tui` fixes +- plugin `wall` renamed to `wallpaper` +- remove plugin `fzz` - merged into plugin `autojump` +- remove plugin `upgrade` - packaging is on OBS now +- remove plugin `treeview` - needs minor `preview-tui` tweak +- remove plugin `picker` - `nnn -p -` does the same +- remove plugin `pdfview` - needs simple change in `pdfread` +- remove plugin `uidgid` - use program option `-U` +- remove plugins `mediainf`, `hexview` - simple one-liners + +------------------------------------------------------------------------------- + +nnn v4.1.1 Sake +2021-06-03 + +- fix segfault on session save (#1041) +- remove redundant `_Atomic` usage +- move [`patches`](../tree/master/patches) directory to top-level +- fix and cleanup gitstatus patch +- plugin `imgview` improvements (#1049) +- restore source-code packing on CircleCI +- add Makefile target to compile with musl + +------------------------------------------------------------------------------- + +nnn v4.1 +2021-06-02 + +- a patch management model for approved patches +- multi-threaded disk usage calculation using pthreads and FTS +- dynamic view update when calculating disk usage +- <kbd>Bksp</kbd>/<kbd>Del</kbd> at empty filter prompt to refresh dir +- Try to create new context with <kbd>Shift-TAB</kbd>, else reverse cycle +- <kbd>Alt+Esc</kbd> to quit context from filter prompt +- fix zombies left behind after running plugins (#999) +- named persistent sessions (mcchrish/nnn.vim#43) +- consider nanosecond resolution when sorting by time (#978) +- check external selection in `'c'urrent / 's'el` prompt (#976) +- show number of files selected in local selection buffer, if any +- `nnn` & picker plugin (e.g. `fzopen`) sync (mcchrish/nnn.vim#82) +- make batch rename interactive (#971) +- cached uid/gid for performance improvement +- fixes for `nnn` with `netbsd-curses`, `musl-fts`, `musl` (#998) +- script to statically compile `nnn` with `musl gcc` on Ubuntu +- restore hovered file when plugin is chosen from plugin dir +- support QuickLook on WSL in `preview-tui` (#959) +- toggle `preview-tui` with the same _custom_ plugin key +- smoother preview toggling in `preview-tui` (#966) +- `listen_on` should be set in kitty.conf for `preview-tui` (#970) +- minimal `bat` style in `preview-tui`, honors `$BAT_STYLE` +- plugin `preview-tui-ext` supersedes `preview-tui` (#1033) +- plugin `fzcd` can now fuzzy search multiple directories +- plugin `imgview` supersedes `imgthumb` and `vidthumb` +- plugin `umounttree`: unmount remote mntpoint from within +- plugin `xdgdefault`: set the default app for hovered file type +- plugin `fzplug`: fuzzy find, preview and run other plugins +- plugin `cmusq`: queue/play music in `cmus` +- plugin `mocplay` renamed to `mocq` +- plugin `cleanfilename` renamed to `fixname` +- go to last dir on <kbd>~</kbd> (HOME) or <kbd>`</kbd> (ROOT) key repeat +- ambiguous key <kbd>^Space</kbd> to select/clear range dropped (#998) +- user wiki page for [Themes](https://github.com/jarun/nnn/wiki/Themes) +- show selection mark (`+`) in reverse bold for improved visibility +- reverse block replaces `>` to mark hovered entry in detail mode +- make option `O_CKBOARD` removed +- make option `O_NOLOC` renamed to `O_NOLC` +- ignore `O_NOLC` if `O_ICONS` or `O_NERD` is specified (#1026) +- unicode arrow indicators if `O_ICONS` or `O_NERD` is specified +- make option `NOX11`: disable notis, sel-clipboard sync, xterm title +- retain filter in _nav-to-type_ mode after file open +- fix no files picked with `NNN_TMPFILE` exported and <kbd>q</kbd> to quit +- disable xterm title setting in picker mode (#974) + +------------------------------------------------------------------------------- + +nnn v4.0 Sushi +2021-04-13 + +- show xterm title on option `-x` + +------------------------------------------------------------------------------- + +nnn v3.7 +2021-04-13 + +- allow plugins to clear selection (#884, #889, #917) +- do not clear selection on hovered file deletion +- resurrect `'c'urrent/'s'el` prompt and option `-u` (#889) +- show only file name in reverse in detail mode +- more file/mime types supported in `preview-tui-ext` +- plugin `mtpmount` - (un)mount MTP devices +- plugin `cleanfilename` - more shell-friendly file names +- plugin `rsynccp` - copy-paste with visual progress +- replace `$HOME` by `~` in address bar +- show current path in terminal title (#911) +- total links and inode number of hardlink in statusbar +- fix symlink to text file not opening in CLI editor (#890) +- fix symlink size shown as 0B in statusbar (#888) +- show symlink target in statusbar (#893) +- show correct disk free/total on macOS (#888) +- fix directory disk usage showing as 0 on macOS (#941) +- fix name col len with `-C` and icons compiled-in (#936) +- refactor printing entries in light/detail modes (#934) +- make option `O_CKBOARD` for checker board as indicator + +------------------------------------------------------------------------------- + +nnn v3.6 Nina +2021-03-16 + +- REPL command prompt (<kbd>Esc</kbd> or <kbd>Enter</kbd> to exit) +- invert selection with <kbd>A</kbd> +- option `-u` removed (always prefer selection to hovered) +- visit start dir on <kbd>@</kbd> when start path is a file +- exit filter mode and redraw on <kbd>^L</kbd> if no last filter +- plugin `fzcd` now selects the chosen file (#876) +- `ueberzug` support in plugin `preview-tui` +- new plugin `preview-tui-ext` with extra preview support +- clear selection after successful plugin invocation +- add method to sync subshell `$PWD` in WIki +- clear selection on single file deletion (#812) +- copy between instances not working (#864) +- plugin `togglex` to toggle exe mode of a selection (#813) +- fix `memccpy()` buffer overlap fault on macOS (#786) +- show `0 selected` msg on cp/mv with empty selection (#855) +- fix frozen terminal caused by opener (#858) +- migrate macOS CI to GitHub workflows, retire Travis + +------------------------------------------------------------------------------- + +nnn v3.5 Freddie +2020-11-17 + +- compile-in Alexey Tourbin's QSORT macro +- support Nerd Font patched icons [`make O_NERD=1`] +- auto-generate static binaries with icons support +- audit and adapt all plugins for macOS +- enhance plugin `dups` to delete duplicates interactively +- plugin `autojump` now supports `jump` and `zoxide` +- support `gio trash` to Trash [`export NNN_TRASH=2`] (#740) +- quit program on double <kbd>Esc</kbd> in normal mode (#775) +- <kbd>^Space</kbd> replaces <kbd>^K</kbd> for range selection/clear selection +- show selection symbol (`+`) next to filename in detail mode (#741) +- error & quit on <kbd>Q</kbd> if no selection, else pick to stdout +- repeat <kbd>^T</kbd> to cycle sort by time, size and clear +- option `-U` to show user & group info in status bar +- option `-J` to disable auto-proceed on select (#713) +- option `-D` to show dirs in context color with `NNN_FCOLORS` +- honor option `-C` for context colors +- show indicators if more entries above/below listing (#744) +- show missing utility name in flash msg (#753) +- exit `preview-tabbed` on <kbd>^C</kbd> (#727) +- invoke GNU sed (_gsed_) on macOS (#728) +- fix HW cursor moves to wrong line (#735) +- fix rollover bug with multiline scroll (#743) +- fix input stream not listed with `-s`/`-S` (#777) +- fix locker not being invoked +- make target `upx` for additional binary compression +- compress auto-generated static binaries with upx +- make variable `O_NOSSN` to compile out sessions +- make variable `O_NOUG` to compile out user & group info + +------------------------------------------------------------------------------- + +nnn v3.4 Emilia +2020-08-18 + +- icons with icon-specific colors (thanks @KlzXS) +- enhanced `NNN_COLORS` with xterm 256 colors support +- new colorscheme with `NNN_FCOLORS` (file type specific colors) +- switch `-C` to force earlier colorscheme (dirs follow context color) +- updates for Haiku (thanks @CodeforEvolution) +- fix XFS navigation issue (thanks @ucs1) +- optimize archive extension matching on file open +- show location in context color +- support `host[:dir]` format for remote mounts +- clear selection after copy +- support traversal on file/dir creation +- show selection in reverse in status bar +- show status bar indicator `H` when hidden files are listed +- show and confirm archive command output +- support _cd on quit_ in picker mode + +------------------------------------------------------------------------------- + +nnn v3.3 +2020-07-14 + +- subdir `mounts` for remote and archive mounts +- remove mount point on successful unmount of remote/archive +- show error and prompt user if `cp`/`mv`/`rm` operation fails +- support absolute/relative paths in cp/mv as +- mark current path automatically on archive/remote mount +- mark current path automatically on target file visit in _find and list_ mode +- option `-C` to place HW cursor on hovered for screen readers and braille displays +- option `-u` to use selection (if available) and skip `current/sel` prompt +- key <kbd>Alt+Esc</kbd> to clear filter prompt and redraw +- support <kbd>Esc</kbd> to cancel remove operation +- `gpge` & `gpgd`: encrypt and decrypt with GPG +- `blknew`: create new files and directories in bulk +- `preview-tui` + - unified to support `tmux`/`kitty`/`xterm`/`$TERMINAL` + - auto-determine split orientation based on terminal height and width + - provision to use [`scope.sh`](https://github.com/ranger/ranger/blob/master/ranger/data/scope.sh) and [`pistol`](https://github.com/doronbehar/pistol) +- various other improvements +- `upload`: send to Firefox Send if [`ffsend`](https://github.com/timvisee/ffsend) is found +- `hexview`: add [`hx`](https://github.com/krpors/hx) as alternative hex viewer +- `nuke` and `imgview`: add [`imv`](https://github.com/eXeC64/imv) as alternative image viewer +- add find (with `fd`) and grep (with `rg`) examples in plugins doc +- key <kbd>Esc</kbd> or left click to resend hovered file path to `NNN_FIFO` +- show `+` instead of `s` in status bar on selection +- <kbd>F5</kbd> removed (misfit for toggle hidden), <kbd>^S</kbd> removed (often masked, redundant) +- handle abnormal program termination and remove NNN_PIPE and/or NNN_FIFO +- clear selection after successful batch rename, link creation +- make option `O_CTX8` for 8 contexts (NOT backward compatible with 4 contexts) +- fix issue with child window resize (see #656) +- fix issue with `NNNLVL` on macOS (see #639) +- fix issue with restoring session with du/au enabled + +------------------------------------------------------------------------------- + +nnn v3.2 +2020-05-26 + +- an official logo +- previews + - config `NNN_FIFO` to write hovered file paths a previewer can read + - plugin `preview-tabbed`: [tabbed](https://tools.suckless.org/tabbed)/xembed based file previewer + - plugin `preview-tui`: simple TUI file previewer in tmux/xterm + - plugin `preview-kitty`: preview using kitty terminal's capabilities + - [live preview](https://github.com/jarun/nnn/wiki/Live-previews) configuration example +- find & list + - send list of files from (cmd run as) plugin to `nnn` + - plugin `finder`: find/fd/fzf/grep/ripgrep/fzf (in subtree) and list in `nnn` + - <kbd>Right</kbd> or <kbd>l</kbd> on symlink in list dir takes to target file +- persistent session option `-S` [for disk usage, run `nnn -T d` (see help)] +- hover on the file when a file path is passed as positional argument +- go to first file or match with <kbd>'</kbd> (followed by <kbd>'</kbd> or <kbd>char</kbd>) +- config `NNN_SEL` to specify custom selection file +- config `NNN_LOCKER` to specify locker program +- dim file details in detail mode +- call `chdir()` on directory change +- option `-l`: number of lines to move on mouse scroll +- graphical [keybind map](https://github.com/jarun/nnn/wiki/Usage#graphical-map) +- let `NNN_COLORS` override `NO_COLOR` +- plugins + - option `-P`: run plugin by key at start + - run plugins with <kbd>Alt+key</kbd> + - allow `NNN_PIPE` usage by commands run as plugin + - input format to `NNN_PIPE`: `<ctxcode><opcode><data>` (see plugins doc) + - set `ctxcode` to `+` for smart context usage (next inactive, else current) + - `getplugs` to fetch plugins by installed version of `nnn` + - plugin `mimelist`: list files by mime type in subtree + - plugin `bookmarks`: named bookmarks using symlinks + - plugin `nbak`: backup `nnn` config + - `nuke` adds lowdown as alternative markdown viewer + - several plugin improvements +- fix broken screen on resize (see #520) +- fix broken version sort (see #550) +- fix list and pipe modes not working together +- fix multiple issues with listing files +- fix `@` shown in detail mode for symlink to dir +- fix listing files directly under `/` +- move to `-std=c11` + +------------------------------------------------------------------------------- + +nnn v3.1 +2020-04-13 + +- unlimited bookmarks and plugin keys +- status bar text in context color +- support config `NO_COLOR` to disable colors +- config `NNN_OPTS` to specify binary options to `nnn` +- config `NNN_MCLICK` to emulate configurable key +- toggle selection on right click +- ignore hard links when calculating disk usage +- dim (hard/sym) link names (symlink to file has `@`) +- more special keys at empty filter prompt in _type-to-nav_ +- key <kbd>></kbd> to export file list +- option `-F` to show fortune in help and settings screen +- option `-T` to specify sort order (obsoletes `-v`) +- option to clear sort order +- key <kbd>T</kbd> to change time type (access/change/mod) +- `.nmv` - internal fully-functional batch renamer plugin +- make var `O_NOBATCH` to disable native batch renamer +- `nuke` & `imgview` - open all images in directory sxiv +- `nuke` - open log files in vi +- plugin `x2sel` - system clipboard to selection copier +- plugin `fzy` - cd using z database +- plugin `fzopen` - support `FZF_DEFAULT_COMMAND` +- create new context on TAB without prompt +- hover and connect by dir name (within config dir) +- move to next entry on current file delete +- on single file copy/move, select the copied/moved file +- option `-f` to use readline history file (off by default) +- use `s` in status bar to indicate selection in progress +- make var `O_NOMOUSE` to disable mouse support +- do not store `NNN_TRASH` and `-Q` in config/session +- add sample .desktop file for XDG compatible DEs +- rename _nav-as-you-type_ to _type-to-nav_ mode +- fix PCRE case-insensitive regex search +- fix no error msg when filter length limit exceeded +- fix static package generation +- fix broken abort message when started in du-mode +- fix filter lost on context switch in non _type-to-nav_ mode +- fix broken readline prompt +- fix long strings treated as action keys in filter prompt +- fix `NNNLVL` not reset when spawned shell is exited + +------------------------------------------------------------------------------- + +nnn v3.0 +2020-02-12 + +- take list of files as input and show +- option `-e` replaces `NNN_USE_EDITOR` +- option `-t` replaces `NNN_IDLE_TIMEOUT` +- PCRE support +- more readline bindings for native prompts +- run GUI app as plugin +- attempt lazy unmount when regular unmount fails +- fix unmount on macOS: use `umount` +- detect `sshfs` and `rclone` to prompt intelligently +- auto-proceed on file open (toggle key <kbd>+</kbd>) +- quit with error code on <kbd>Q</kbd> +- additional key <kbd>F5</kbd> to toggle hidden +- key <kbd>e</kbd> to edit in EDITOR (back on multiple user requests) +- option to edit list of files in selection is changed to <kbd>E</kbd> +- do not end selection on redraw +- `nuke`: [`glow`](https://github.com/charmbracelet/glow) as Markdown viewer +- `nuke`: refactor, handle some common video types by extension +- file name removed from status bar +- static Makefile target +- generate, upload static package on release +- fix crash on entering empty dir, then Down +- fix keypresses lost when showing message +- fix #227: `nnn` creates xdg-open zombies + +------------------------------------------------------------------------------- + +nnn v2.9 +2020-01-15 + +- all keybinds and options reviewed by the team and frozen (see #422) + - reduced number of keybinds +- greatly improved help screen readability +- `nuke`: sample opener (CLI-only by default) and plugin +- fast line redraws instead of full screen refresh (thanks @annagrram) +- auto archive handling by extension (see config `NNN_ARCHIVE`) +- Lead key simplified to bookmark key (<kbd>b</kbd> or <kbd>^/</kbd>) +- single key to toggle order (<kbd>t</kbd> or <kbd>^T</kbd>) +- plugins + - `.cbcp`: copy selection to system clipboard (internal, program option `-x`) + - `.ntfy`: show noti on cp, mv, rm completion (internal, program option `-x`) + - `autojump`: navigate using autojump + - `upload`: paste text files to http://ix.io, upload rest to https://file.io + - all fuzzy plugins modified to support both `fzf` and `fzy` +- more control on plugins + - prefix `-` to skip directory refresh after running (cmd as) plugin + - suffix `*` to skip confirmation after running cmd as plugin +- indicate range selection mode with `*` +- list keys at bookmark and plugin key prompts +- visit to pinned dir like bookmarks (Bookmark key followed by <kbd>,</kbd>) +- toggle executable (key <kbd>*</kbd>) +- show mime along with file details +- more special keys at empty filter prompt: + - apply the last filter (<kbd>^L</kbd>) + - toggle between string and regex (<kbd>/</kbd>) + - toggle case-sensitivity (<kbd>:</kbd>) +- retain filter on <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd> +- show filter details when filter is on +- remove option to run filter as cmd on prompt key (can be disruptive) +- program options + - option `-x`: enable notis and copy selection to system clipboard + - option `-g`: regex filters (string filter is default now) + - option `-Q`: quit program without confirmation + - option `-s`: load session + - option `-n`: start in nav-as-you-type mode + - option `-v`: version sort + - option `-V`: show program version + - option `-A`: disable dir auto-select +- ISO 8601 compliant date in status bar +- ported to Haiku OS (thanks @annagrram) +- sort only filtered entries (to avoid directory refresh) +- fix `getplugs` to install hidden files +- fix several selection issues (see #400) +- fix detail mode not restored on loading session +- fix symlink to directory not auto-selected +- fix regex error on partial regex patterns +- fix symlink not shown if `stat(2)` on target fails +- fix flags when spawning a CLI opener as default FM +- fix issue with stat flag on Sun (no support for `dirent.d_type`) +- fix current file in current context not saved correctly in session +- signed source distribution on release +- simplified debugging with line numbers in logs + +------------------------------------------------------------------------------- + +nnn v2.8.1 +2019-12-05 + +- Fix always archiving current file +- More elaborate docs on selection changes + +------------------------------------------------------------------------------- + +nnn v2.8 +2019-12-04 + +- sessions (thanks @annagrram) +- `rclone` support for remote access (mount _any_ cloud storage!!!) +- toggle selection with <kbd>Space</kbd> or <kbd>^J</kbd> +- ignore events during selection so the `+` symbol is not lost +- run custom (non-shell-interpreted) commands like plugins +- configure _cd-on-quit_ as the default behaviour +- create parent dirs for new files and dirs, duplicate a file/dir anywhere +- _copy/move as_ workflow (thanks @KlzXS) +- edit , flush selection buffer (thanks @KlzXS) +- support xargs with minimal options (as in BusyBox) (thanks @KlzXS) +- changed the key to size sort to <kbd>z</kbd> +- additional key <kbd>]</kbd> to show command prompt +- mount archives using `archivemount` +- smoother double click handling +- program option `-R` to disable rollover at edges +- keybind collision checker (for custom keybind config) (thanks @annagrram) +- show size of file in bytes in status bar in disk usage mode +- pass unresolved path as second argument (`$2`) to plugin +- mechanism for plugins to control active directory +- all binary questions are confirmed by <kbd>y</kbd> or <kbd>Y</kbd> +- plugins + - some plugins renamed + - integrated `shellcheck` in CI, POSIX-compliance fixes (thanks @koalaman) + - `getplugs` - detect modifications in exiting plugin file (thanks @KlzXS) + - `drag-file` & `drop-file`: drag & drop files using dragon + - `gutenread`: browse, download and read from Project Gutenberg + - `suedit` - edit file with superuser permissions + - `fzhist` - fuzzy select commands from history, edit and run + - `fzcd` - change to a fuzzy-searched directory + - `rename` - batch rename directory or selection using qmv or vidir + - `pskill` - fuzzy list a process or zombies by name and kill + - `exetoggle` - toggle executable status of hovered file + - `treeview` - informative tree output with file permissions and size + - `chksum` - recursively calculate checksum for files in hovered directory + - `fzopen` renamed to `fzopen` + - `imgsxiv` instructions added to browse and rename images +- create link to current file +- additional key <kbd>;</kbd> to execute plugin +- more explicit force removal message +- force non-detachable internal edits in $EDITOR (option `-E`) +- export current file as `$nnn` (instead of `$NN`) +- fix file open failure from browser when configured as default FM + +------------------------------------------------------------------------------- + +nnn v2.7 +2019-10-06 + +- plugins for image preview, image and video thumbnails +- redesigned selection workflow +- drop path prefix for files in current dir for selection based archives +- custom direct keybinds for plugins +- libreadline `.history` file moved to `nnn` config directory +- export current entry as `$NN` at command prompt +- more informative status bar in light/detail modes +- auto-proceed to next file on single file select +- path clipping for long paths +- completely revamped wiki +- new program options: + - `-a` to use file access time throughout the program + - `-c` to indicate cli-only opener + - `-f` to run filter as command on <kbd>^P</kbd> + - `-o` replaces config `NNN_RESTRICT_NAV_OPEN` + - `-t` replaces config `NNN_NO_AUTOSELECT` + - `-r` replaces config `NNN_OPS_PROG` +- plugin changes: + - `vidthumb` - show video thumbnails in terminal + - `mediainf` - show media info (decoupled as a plugin) + - `notes` - open a quick notes file/dir in `$EDITOR` (decoupled as a plugin) + - `dups` - list duplicate files in the current directory + - `oldbigfile` - list large files by access time + - `moclyrics` - show lyrics of the track currently playing in MOC + - `uidgid` list uid and gid of files in directory + - `mocplay` - now detects if a track is playing or not + - `organize` - categorize files and move to respective directories + - `pastebin` - now uses ix.io paste service + - `fzy-edit` - merged into `fzy-open` + - `viuimg` - fix directory view + - `checksum` - fixed POSIX compliance issues + - `boom` - play music in MOC +- keybind changes: + - select entry: <kbd>Space</kbd> and <kbd>^J</kbd> + - select range (or clear selection): <kbd>m</kbd> and <kbd>^K</kbd> + - select all in dir: <kbd>a</kbd> + - list selection: <kbd>M</kbd> + - <kbd>^N</kbd> replaces <kbd>^T</kbd> to toggle _nav-as-you-type_ + - <kbd>Shift TAB</kbd> to reverse context cycle + - <kbd>'</kbd> to jump to first file in dir + - <kbd>S</kbd> for du, <kbd>A</kbd> for apparent du + - additional key <kbd>:</kbd> to run plugin + - additional key <kbd>F2</kbd> to rename file + - additional key <kbd>F5</kbd> to redraw + - quit context key <kbd>Leadq</kbd> is removed +- Leader key combinations: + - <kbd>Lead'</kbd> to jump to first file in dir + - <kbd>Lead]</kbd> go to next active context + - <kbd>Lead[</kbd> go to prev active context + - <kbd>Lead.</kbd> toggle show hidden files +- improved duplicate file workflow +- improved batch rename workflow when a selection exists +- removed the wild load option (`-w`) +- removed quick notes (added plugin `notes`) +- fix #225 (thanks @KlzXS) +- fix `tar`/`bsdtar` always creating tar archives (and not by suffix) +- fix single mouse click to select file not working +- fix symlink to dir removed on batch rename +- fix detail mode not set with program option `-S` + +------------------------------------------------------------------------------- + +nnn v2.6 +2019-08-06 + +- new plugins + - view image or browse a directory of images in terminal + - show image thumbnails + - PDF and text file reader + - calculate and verify checksum of selection or file + - append (and play) selection/dir/file music in MOC + - variable bitrate mp3 ringtone generator + - split current file or join selection +- better experience on Termux (and touch based devices) + - mouse scrolling support (with ncursesw6.0 and above) + - tap/left click to visit parent, toggle nav-as-you-type mode +- light mode set as default +- show status bar and use reverse video in light mode +- changed program options + - `-d`: detail mode + - `-H`: show hidden files + - `-l` is retired +- support `XDG_CONFIG_HOME` +- support <kbd>/</kbd> as an additional Leader key when filter is on +- sort by file extension +- use zip/unzip/tar if atool/bsdtar not found +- support duplicate file (key <kbd>^R</kbd>, same as rename file) +- new config option `NNN_SSHFS_OPTS` to specify `sshfs` options +- restrict opening 0 byte files (`NNN_RESTRICT_0B` is obsolete) +- critical defects fixed + - fix #276 - crash with variable length inotify event handling + - fix #285 - hang after deleting/moving current directory + - fix #274 - a broken prompt on empty input with libreadline + - fix #304 - list selection from another instance +- `cmatrix` as locker fallback +- wait for user input after running a command from prompt +- scrolloff set to 3 from 5 + +------------------------------------------------------------------------------- + +nnn v2.5 +2019-05-27 + +- mouse support +- new location for config files - `~/.config/nnn` + - plugin dir location: `~/.config/nnn/plugins` + - selection file `.nnncp` is now `~/.config/nnn/.selection` +- plugins: + - pdfview: view a PDF in pager + - nmount: (un)mount a storage device + - ndiff: file and directory diff for selection + - hexview: view a file in hex + - imgresize: batch resize images to desktop resolution + - ipinfo: check your IP address and whois information + - transfer: upload a file to transfer.in + - pastebin: paste the contents of a text file to paste.ubuntu.com + - boom: play random music from a directory + - nwal: set an image as wallpaper using nitrogen + - pywal: set selected image as wallpaper, change terminal color scheme + - getplugs: update plugins +- SSHFS support +- support `bsdtar`, simplify `patool` integration +- native batch rename support (`vidir` dependency dropped) +- changes to support [configuration](https://github.com/jarun/nnn/wiki/nnn-as-default-file-manager) as the default file manager +- per-context detail/light mode +- case-insensitive version compare +- shortcut to visit `/` - <kbd>`</kbd> (backtick) +- vim-like scrolloff support +- <kbd>^D</kbd> & <kbd>^U</kbd>: scroll half page, <kbd>PgDn</kbd> & <kbd>PdUp</kbd>: scroll full page +- fix selection across contexts +- recognize <kbd>Home</kbd> and <kbd>End</kbd> keys at prompt for editing +- fix broken program option `-b` +- POSIX-compliant user-scripts (wherever possible) +- `NNN_SCRIPT` is retired (replaced by plugins) + +------------------------------------------------------------------------------- + +nnn v2.4 +2019-03-19 + +- FreeDesktop.org compliant trashing +- mark selected entries with `+` +- _wild_ mode (option `-w`, key <kbd>^W</kbd>) for _nav-as-you-type_ +- POSIX-compliant GUI app launcher with drop-down menu (key <kbd>=</kbd>) +- new scripts: + - upload image to imgur + - send selection to Android using kdeconnect-cli +- show permissions in detail mode +- cp, mv progress bar for Linux (needs advcpmv) [BSD, macOS shows on <kbd>^T</kbd>] +- make libreadline an optional dep (reduces memory usage) +- minimize the number of redraws +- handle screen resize gracefully +- option `-d` to show hidden files (`NNN_SHOW_HIDDEN` is removed) +- additional key <kbd>K</kbd> to toggle selection +- change visit start dir key to <kbd>@</kbd> +- option `-C` to disable colors removed +- per-context initial directory replaced by program start dir +- marker msg when spawning new shell removed +- rename debug file to `nnndbg` + +------------------------------------------------------------------------------- + +nnn v2.3 +2019-02-19 + +- file picker mode +- repo of user-contributed scripts +- substring search for filters (option `-s`) +- version sort (option `-n`) +- disk usage calculation abort with <kbd>^C</kbd> +- create sym/hard link(s) to files in selection +- archiving of selection +- show dir symlinks along with dirs in top +- fixed CJK character handling at prompts +- key `N` (1 <= N <= 4) to switch to context N +- bring back `NNN_OPENER` to specify file opener +- env var `NNN_NOTE` and keybind <kbd>^N</kbd> for quick notes +- handle multiple arguments in VISUAL/EDITOR +- show the current directory being scanned in `du` mode +- select all files (<kbd>Y</kbd>) +- show command prompt (<kbd>^P</kbd>) +- key <kbd>,</kbd> replaces <kbd>`</kbd> as alternative Leader Key +- keybind for visit pinned directory is now <kbd>^B</kbd> +- additional key <kbd>^V</kbd> to run or select custom script +- use libreadline for command prompt +- reduce delay on <kbd>Esc</kbd> press +- config option to avoid unexpected behaviour on 0-byte file open (see #187) +- rename config option `DISABLE_FILE_OPEN_ON_NAV` to `NNN_RESTRICT_NAV_OPEN` +- keys removed - <kbd>$</kbd>, <kbd>^</kbd>, <kbd>Backspace</kbd>, <kbd>^H</kbd>, <kbd>^P</kbd>, <kbd>^M</kbd>, <kbd>^W</kbd>, <kbd>`</kbd> + +------------------------------------------------------------------------------- + +nnn v2.2 +2019-01-01 + +What's in? +- (neo)vim plugin [nnn.vim](https://github.com/mcchrish/nnn.vim) +- macOS fixes + - Fix issues with file copy, move, remove + - Handle <kbd>Del</kbd> in rename prompt + - Pass correct `file` option to identify mime +- Support selection across directories and contexts +- Offer option `force` before file remove +- Keys <kbd>Tab</kbd>, <kbd>^I</kbd> to go to next active context +- Per-context directory color specified by `$NNN_CONTEXT_COLORS` + - Option `-c` is removed +- Option `-C` to disable colors +- Choose script to run from a script directory +- Run a command (or launch an application) +- Run file as executable (key <kbd>C</kbd>) +- Documentation on lftp integration for remote file transfers +- Support a _combined_ set of arguments to `$EDITOR`, `$PAGER` and `$SHELL` +- Handle > 2 GB files on 32-bit ARM +- Env var `$DISABLE_FILE_OPEN_ON_NAV` to disable file open on <kbd>Right</kbd> or <kbd>l</kbd> +- `NUL`-terminated file paths in selection list instead of `LF` +- Better support for Termux and Cygwin environments +- Remapped keys + - <kbd>^I</kbd> - go to next active context + - <kbd>^T</kbd> - toggle _navigate-as-you-type_ + +------------------------------------------------------------------------------- + +nnn v2.1 +2018-11-23 + +What's in? +- Inclusion in several distros including Arch Linux official repo +- Multiple contexts (_aka_ tabs _aka_ workspaces) [max 4] +- Copy, move, remove selected files, remove current file +- [Leader key](https://github.com/jarun/nnn#leader-key) (like vim) +- In-built GUI app launcher with up to 2 arguments (key <kbd>o</kbd>) +- List copy selection (key <kbd>y</kbd>) +- Env var `NNN_NO_AUTOSELECT` to disable dir auto-select +- Key <kbd>Esc</kbd> exits prompt, <kbd>^L</kbd> clears prompt +- Program runtime help revamped +- Static code analysis integration +- gcc-8 warnings fixed +- Remapped keys: + - <kbd>^W</kbd> - go to pinned dir + - <kbd>^X</kbd> - delete current entry + - <kbd>^Q</kbd> - quit program +- `nlay` is retired (functionality built into `nnn`) +- `chdir` prompt is retired +- Env var `NNN_NO_X` retired, selection now works out of the box +- Only single-char bookmark keys (to work with Leader key) + +------------------------------------------------------------------------------- + +nnn v2.0 +2018-10-19 + +What's in? +- Mode to show apparent size (key `S`) +- Script to integrate `patool` instead of `atool` +- Support `bashlock` (OS X) and `lock` (BSD) as terminal locker +- Symbol `@/` for symlink to dir +- Dependency on `libreadline` removed + +------------------------------------------------------------------------------- + +nnn v1.9 +2018-08-10 + +What's in? +- Support unlimited number of scripts +- Pass currently selected filename as first argument to custom scripts +- Support directory auto-select in _navigate-as-you-type_ mode +- Show selection name in archive name prompt +- Support Cygwin opener +- Better support on RHEL 25 with earlier version on curses +- Sample script for `fzy` integration +- Now available on OpenBSD +- Disabled package generation for Ubuntu 17.10 + +------------------------------------------------------------------------------- + +nnn v1.8 +2018-05-02 + +What's in? +- Run a custom script +- Archive selected file/directory +- Show number of cherry-picked files in multi-copy mode +- Env var `NNN_SHOW_HIDDEN` to show hidden files by default +- Additional information in help screen +- Give preference to env var VISUAL, if defined, over EDITOR +- New/changed/remapped shortcuts + - <kbd>^]</kbd> - spawn a new shell in current directory + - <kbd>r</kbd> - edit directory entries in vidir + - <kbd>R</kbd> - run a custom script + - <kbd>^I</kbd> - toggle navigate-as-you-type mode + - <kbd>L</kbd> - lock the current terminal (Linux-only) +- All Ctrl shortcuts enabled in navigate-as-you-type mode +- Fix: GUI programs closing when parent terminal is closed +- Recognize `~`, `-` and `&` at bookmark prompt +- Recognize ruby (.rb) files as text files +- Efficient integer-only file size calculation +- Official inclusion on openSUSE and Fedora +- Package generation for Ubuntu 18.04 + +------------------------------------------------------------------------------- + +nnn v1.7 +2018-02-28 + +What's in? +- Batch rename/move/delete files in vidir from [moreutils](https://joeyh.name/code/moreutils/) +- Copy multiple file paths +- Copy file paths when X is unavailable +- Optionally quote individual file paths with single quotes on copy +- Use ISO 8601 date format in file details +- New/changed/remapped shortcuts: + - <kbd>^B</kbd> - show bookmark prompt (replaces <kbd>b</kbd>) + - <kbd>b</kbd> - pin current dir (replaces <kbd>^B</kbd>) + - <kbd>^J</kbd> - toggle du mode + - <kbd>R</kbd> - batch rename files in vidir + - <kbd>^F</kbd> - extract archive (replaces <kbd>^X</kbd>) + - <kbd>^G</kbd> - quit nnn and change dir + - <kbd>^X</kbd> - quit nnn (replaces <kbd>^Q</kbd>) +- Extra shortcuts enabled in nav-as-you-type mode: + - <kbd>^K</kbd>, <kbd>^Y</kbd> (file path copy) + - <kbd>^T</kbd> (toggles quoted file path copy) + - <kbd>^R</kbd> (rename) + - <kbd>^O</kbd> (open with...) + - <kbd>^B</kbd> (show bookmark prompt) + - <kbd>^V</kbd> (visit pinned dir) + - <kbd>^J</kbd> (toggle du mode) + - <kbd>^/</kbd> (open desktop opener) + - <kbd>^F</kbd> (extract archive) + - <kbd>^L</kbd> (refresh) + - <kbd>^G</kbd> (quit nnn and change dir) + - <kbd>^X</kbd> (quit nnn) + +------------------------------------------------------------------------------- + +nnn v1.6 +2017-12-25 + +What's in? +- Shortcut `^O` to open file with custom application +- Option `-b` to open bookmarks directly at start +- Huge performance improvements around file name storing and handling +- Several large static buffers removed or reduced +- Several internal algorithms fine tuned for performance/resource usage + +------------------------------------------------------------------------------- + +nnn v1.5 +2017-10-05 + +What's in? +- File and directory creation (`n`) +- Env variable `NNN_NOWAIT` to unblock nnn when opening files (DE-specific) +- Show current entry number in status bar +- Support archive listing (`F`) and extraction (`Ctrl-X`) [using `atool`] +- Show correct file size on i386 for large files (> 2GB) + +------------------------------------------------------------------------------- + +nnn v1.4 +2017-09-04 + +What's in? +- Monitor directory changes +- In-place file rename +- Pin (`Ctrl-B`) a directory and visit (`Ctrl-V`) it anytime +- Auto-completion scripts +- Show volume capacity and free in help +- Auto-fallback to light mode if too few columns (< 35) +- PackageCore integration +- Unsupported Function keys (they never work universally): + - `F2` (rename), use `Ctrl-R` + - `F5` (refresh), use `Ctrl-L` + +------------------------------------------------------------------------------- + +nnn v1.3 +2017-07-26 + +What's in? +- Show directories in custom color (default: enabled in blue) +- Option `-e` to use exiftool instead of mediainfo +- Fixed #34: nftw(3) broken with too many open descriptors +- More concise help screen + +------------------------------------------------------------------------------- + +nnn v1.2 +2017-06-29 + +What's in? +- Use the desktop opener (xdg-open on Linux, open(1) on OS X) to open files +- Option `NNN_USE_EDITOR` to open text files in EDITOR (fallback vi) +- Bookmark support (maximum 10, key `b`) +- *Navigate-as-you-type* mode (key `Insert` or option `-i`) +- Subtree search: gnome-search-tool, fallback catfish (key `^/`) (customizable) +- Show current directory content size and file count in disk usage mode +- Add detail view mode as default, use `-l` to start in light mode +- Shortcuts `F2` and `^L` to refresh and unfilter + Note: if filter is empty, `Enter` *opens* the currently selected file now +- Help screen shows bookmarks and configuration +- Show a message when calculating disk usage +- Show the spawned shell level +- Linux only: use vlock as the locker on timeout (set using `NNN_IDLE_TIMEOUT`) + +------------------------------------------------------------------------------- + +nnn v1.1 +2017-05-12 + +News +- Introducing nlay - a highly customizable bash script to handle media type +- nnn is on [Homebrew](http://braumeister.org/formula/nnn) now +- RPM packages for CentOS 7 and Fedora 24 generated on release + +What's in? +- *Search-as-you-type* +- Unicode support +- Option `-S` to start in disk usage analyzer mode +- Show media information (using mediainfo) +- Use readline at change directory prompt +- Jump to prev directories using `cd .....` (with `.` as PWD) +- Jump to initial directory using `&` +- Show help, mediainfo and file info in PAGER +- Several optimizations + +------------------------------------------------------------------------------- + +nnn v1.0 +2017-04-13 + +Modifications +- Behaviour and navigation + - Detail view (default: disabled) with: + - file type (directory, regular, symlink etc.) + - modification time + - human-readable file size + - current item in reverse video + - number of items in current directory + - full name of currently selected file in 'bar' + - Show details of the currently selected file (stat, file) + - Disk usage analyzer mode (within the same fs, doesn't follow symlinks) + - Directories first (even with sorting) + - Sort numeric names in numeric order + - Case-insensitive alphabetic content listing instead of upper case first + - Key `-` to jump to last visited directory + - Roll over at the first and last entries of a directory (with Up/Down keys) + - Removed navigation restriction with relative paths (and let permissions handle it) + - Sort entries by file size (largest to smallest) + - Shortcut to invoke file name copier (set using environment variable `NNN_COPIER`) +- File association + - Set `NNN_OPENER` to let a desktop opener handle it all. E.g.: + export NNN_OPENER=xdg-open + export NNN_OPENER=gnome-open + export NNN_OPENER=gvfs-open + - Selective file associations (ignored if `NNN_OPENER` is set): + - Associate plain text files (determined using file) with vi + - Associate common audio and video mimes with mpv + - Associate PDF files with [zathura](https://pwmt.org/projects/zathura/) + - Removed `less` as default file opener (there is no universal standalone opener utility) + - You can customize further (see [how to change file associations](#change-file-associations)) + - `NNN_FALLBACK_OPENER` is the last line of defense: + - If the executable in static file association is missing + - If a file type was not handled in static file association + - This may be the best option to set your desktop opener to + - To enable the desktop file manager key, set `NNN_DE_FILE_MANAGER`. E.g.: + export NNN_DE_FILE_MANAGER=thunar +- Optimization + - All redundant buffer removal + - All frequently used local chunks now static + - Removed some redundant string allocation and manipulation + - Simplified some roundabout procedures + - Compiler warnings fixed + - strip the final binary + +------------------------------------------------------------------------------- diff --git a/nnn/LICENSE b/nnn/LICENSE @@ -0,0 +1,27 @@ +BSD 2-Clause License + +Copyright (c) 2014-2016, Lazaros Koromilas <lostd@2f30.org> +Copyright (c) 2014-2016, Dimitris Papastamos <sin@2f30.org> +Copyright (c) 2016-2022, Arun Prakash Jana <engineerarun@gmail.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/nnn/Makefile b/nnn/Makefile @@ -0,0 +1,309 @@ +VERSION = $(shell grep -m1 VERSION $(SRC) | cut -f 2 -d'"') + +PREFIX ?= /usr/local +MANPREFIX ?= $(PREFIX)/share/man +DESKTOPPREFIX ?= $(PREFIX)/share/applications +DESKTOPICONPREFIX ?= $(PREFIX)/share/icons/hicolor +STRIP ?= strip +PKG_CONFIG ?= pkg-config +INSTALL ?= install +CP ?= cp + +CFLAGS_OPTIMIZATION ?= -O3 + +O_DEBUG := 0 # debug binary +O_NORL := 0 # no readline support +O_PCRE := 0 # link with PCRE library +O_NOLC := 0 # no locale support +O_NOMOUSE := 0 # no mouse support +O_NOBATCH := 0 # no built-in batch renamer +O_NOFIFO := 0 # no FIFO previewer support +O_CTX8 := 0 # enable 8 contexts +O_ICONS := 0 # support icons-in-terminal +O_NERD := 0 # support icons-nerdfont +O_QSORT := 0 # use Alexey Tourbin's QSORT implementation +O_BENCH := 0 # benchmark mode (stops at first user input) +O_NOSSN := 0 # disable session support +O_NOUG := 0 # disable user, group name in status bar +O_NOX11 := 0 # disable X11 integration +O_MATCHFLTR := 0 # allow filters without matches +O_NOSORT := 0 # disable sorting entries on dir load + +# User patches +O_GITSTATUS := 0 # add git status to detail view +O_NAMEFIRST := 0 # print file name first, add uid and guid to detail view +O_RESTOREPREVIEW := 0 # add preview pipe to close and restore preview pane + +# convert targets to flags for backwards compatibility +ifneq ($(filter debug,$(MAKECMDGOALS)),) + O_DEBUG := 1 +endif +ifneq ($(filter norl,$(MAKECMDGOALS)),) + O_NORL := 1 +endif +ifneq ($(filter nolc,$(MAKECMDGOALS)),) + O_NORL := 1 + O_NOLC := 1 +endif + +ifeq ($(strip $(O_DEBUG)),1) + CPPFLAGS += -DDEBUG + CFLAGS += -g +endif + +ifeq ($(strip $(O_NORL)),1) + CPPFLAGS += -DNORL +else ifeq ($(strip $(O_STATIC)),1) + CPPFLAGS += -DNORL +else + LDLIBS += -lreadline +endif + +ifeq ($(strip $(O_PCRE)),1) + CPPFLAGS += -DPCRE + LDLIBS += -lpcre +endif + +ifeq ($(strip $(O_NOLC)),1) + ifeq ($(strip $(O_ICONS)),1) +$(info *** Ignoring O_NOLC since O_ICONS is set ***) + else ifeq ($(strip $(O_NERD)),1) +$(info *** Ignoring O_NOLC since O_NERD is set ***) + else + CPPFLAGS += -DNOLC + endif +endif + +ifeq ($(strip $(O_NOMOUSE)),1) + CPPFLAGS += -DNOMOUSE +endif + +ifeq ($(strip $(O_NOBATCH)),1) + CPPFLAGS += -DNOBATCH +endif + +ifeq ($(strip $(O_NOFIFO)),1) + CPPFLAGS += -DNOFIFO +endif + +ifeq ($(strip $(O_CTX8)),1) + CPPFLAGS += -DCTX8 +endif + +ifeq ($(strip $(O_ICONS)),1) + CPPFLAGS += -DICONS +endif + +ifeq ($(strip $(O_NERD)),1) + CPPFLAGS += -DNERD +endif + +ifeq ($(strip $(O_QSORT)),1) + CPPFLAGS += -DTOURBIN_QSORT +endif + +ifeq ($(strip $(O_BENCH)),1) + CPPFLAGS += -DBENCH +endif + +ifeq ($(strip $(O_NOSSN)),1) + CPPFLAGS += -DNOSSN +endif + +ifeq ($(strip $(O_NOUG)),1) + CPPFLAGS += -DNOUG +endif + +ifeq ($(strip $(O_NOX11)),1) + CPPFLAGS += -DNOX11 +endif + +ifeq ($(strip $(O_MATCHFLTR)),1) + CPPFLAGS += -DMATCHFLTR +endif + +ifeq ($(strip $(O_NOSORT)),1) + CPPFLAGS += -DNOSORT +endif + +ifeq ($(shell $(PKG_CONFIG) ncursesw && echo 1),1) + CFLAGS_CURSES ?= $(shell $(PKG_CONFIG) --cflags ncursesw) + LDLIBS_CURSES ?= $(shell $(PKG_CONFIG) --libs ncursesw) +else ifeq ($(shell $(PKG_CONFIG) ncurses && echo 1),1) + CFLAGS_CURSES ?= $(shell $(PKG_CONFIG) --cflags ncurses) + LDLIBS_CURSES ?= $(shell $(PKG_CONFIG) --libs ncurses) +else + LDLIBS_CURSES ?= -lncurses +endif + +CFLAGS += -std=c11 -Wall -Wextra -Wshadow +CFLAGS += $(CFLAGS_OPTIMIZATION) +CFLAGS += $(CFLAGS_CURSES) + +LDLIBS += $(LDLIBS_CURSES) -lpthread + +# static compilation needs libgpm development package +ifeq ($(strip $(O_STATIC)),1) + LDFLAGS += -static + LDLIBS += -lgpm +endif + +DISTFILES = src nnn.1 Makefile README.md LICENSE +SRC = src/nnn.c +HEADERS = src/nnn.h +BIN = nnn +DESKTOPFILE = misc/desktop/nnn.desktop +LOGOSVG = misc/logo/logo.svg +LOGO64X64 = misc/logo/logo-64x64.png + +GITSTATUS = patches/gitstatus +NAMEFIRST = patches/namefirst +RESTOREPREVIEW = patches/restorepreview + +# test if we are on Mac OS X and get X.Y.Z OS version with system binary /usr/bin/sw_vers +MACOS_VERSION := $(strip $(shell command -v sw_vers >/dev/null && [ "`sw_vers -productName`" = "Mac OS X" ] && sw_vers -productVersion)) +# if Mac OS X detected, test if its version is below 10.12.0 relying on "sort -c" returning "disorder" message if the input is not sorted +ifneq ($(MACOS_VERSION),) + MACOS_BELOW_1012 := $(if $(strip $(shell printf '10.12.0\n%s' "$(MACOS_VERSION)" | sort -ct. -k1,1n -k2,2n -k3,3n 2>&1)),1) +endif +# if Mac OS X version is below 10.12.0, compile in the replacement clock_gettime and define MACOS_BELOW_1012 so that it's included in nnn.c +ifneq ($(MACOS_BELOW_1012),) + GETTIME_C = misc/macos-legacy/mach_gettime.c + GETTIME_H = misc/macos-legacy/mach_gettime.h + SRC += $(GETTIME_C) + HEADERS += $(GETTIME_H) + CPPFLAGS += -DMACOS_BELOW_1012 +endif + +all: $(BIN) + +$(BIN): $(SRC) $(HEADERS) Makefile + @$(MAKE) --silent prepatch + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(GETTIME_C) $< $(LDLIBS) + @$(MAKE) --silent postpatch + +# targets for backwards compatibility +debug: $(BIN) +norl: $(BIN) +nolc: $(BIN) + +install-desktop: $(DESKTOPFILE) + $(INSTALL) -m 0755 -d $(DESTDIR)$(DESKTOPPREFIX) + $(INSTALL) -m 0644 $(DESKTOPFILE) $(DESTDIR)$(DESKTOPPREFIX) + $(INSTALL) -m 0755 -d $(DESTDIR)$(DESKTOPICONPREFIX)/scalable/apps + $(INSTALL) -m 0644 $(LOGOSVG) $(DESTDIR)$(DESKTOPICONPREFIX)/scalable/apps/nnn.svg + $(INSTALL) -m 0755 -d $(DESTDIR)$(DESKTOPICONPREFIX)/64x64/apps + $(INSTALL) -m 0644 $(LOGO64X64) $(DESTDIR)$(DESKTOPICONPREFIX)/64x64/apps/nnn.png + +uninstall-desktop: + $(RM) $(DESTDIR)$(DESKTOPPREFIX)/$(DESKTOPFILE) + $(RM) $(DESTDIR)$(DESKTOPICONPREFIX)/scalable/apps/nnn.svg + $(RM) $(DESTDIR)$(DESKTOPICONPREFIX)/64x64/apps/nnn.png + +install: all + $(INSTALL) -m 0755 -d $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 $(BIN) $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 -d $(DESTDIR)$(MANPREFIX)/man1 + $(INSTALL) -m 0644 $(BIN).1 $(DESTDIR)$(MANPREFIX)/man1 + +uninstall: + $(RM) $(DESTDIR)$(PREFIX)/bin/$(BIN) + $(RM) $(DESTDIR)$(MANPREFIX)/man1/$(BIN).1 + +strip: $(BIN) + $(STRIP) $^ + +upx: $(BIN) + $(STRIP) $^ + upx -qqq $^ + +static: + # regular static binary + make O_STATIC=1 strip + mv $(BIN) $(BIN)-static + # static binary with icons-in-terminal support + make O_STATIC=1 O_ICONS=1 strip + mv $(BIN) $(BIN)-icons-static + # static binary with patched nerd font support + make O_STATIC=1 O_NERD=1 strip + mv $(BIN) $(BIN)-nerd-static + +musl: + cp misc/musl/musl-static-ubuntu.sh . + ./musl-static-ubuntu.sh 1 + rm ./musl-static-ubuntu.sh + +dist: + mkdir -p nnn-$(VERSION) + $(CP) -r $(DISTFILES) nnn-$(VERSION) + tar -cf - nnn-$(VERSION) | gzip > nnn-$(VERSION).tar.gz + $(RM) -r nnn-$(VERSION) + +sign: + git archive -o nnn-$(VERSION).tar.gz --format tar.gz --prefix=nnn-$(VERSION)/ v$(VERSION) + gpg --detach-sign --yes nnn-$(VERSION).tar.gz + rm -f nnn-$(VERSION).tar.gz + +upload-local: sign static musl + $(eval ID=$(shell curl -s 'https://api.github.com/repos/jarun/nnn/releases/tags/v$(VERSION)' | jq .id)) + # upload sign file + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=nnn-$(VERSION).tar.gz.sig' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/pgp-signature' \ + --upload-file nnn-$(VERSION).tar.gz.sig + # upx compress all static binaries + upx -qqq $(BIN)-static + upx -qqq $(BIN)-icons-static + upx -qqq $(BIN)-nerd-static + # upload static binary + tar -zcf $(BIN)-static-$(VERSION).x86_64.tar.gz $(BIN)-static + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=$(BIN)-static-$(VERSION).x86_64.tar.gz' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/x-sharedlib' \ + --upload-file $(BIN)-static-$(VERSION).x86_64.tar.gz + # upload icons-in-terminal compiled static binary + tar -zcf $(BIN)-icons-static-$(VERSION).x86_64.tar.gz $(BIN)-icons-static + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=$(BIN)-icons-static-$(VERSION).x86_64.tar.gz' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/x-sharedlib' \ + --upload-file $(BIN)-icons-static-$(VERSION).x86_64.tar.gz + # upload patched nerd font compiled static binary + tar -zcf $(BIN)-nerd-static-$(VERSION).x86_64.tar.gz $(BIN)-nerd-static + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=$(BIN)-nerd-static-$(VERSION).x86_64.tar.gz' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/x-sharedlib' \ + --upload-file $(BIN)-nerd-static-$(VERSION).x86_64.tar.gz + # upload musl static binary + tar -zcf $(BIN)-musl-static-$(VERSION).x86_64.tar.gz $(BIN)-musl-static + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=$(BIN)-musl-static-$(VERSION).x86_64.tar.gz' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/x-sharedlib' \ + --upload-file $(BIN)-musl-static-$(VERSION).x86_64.tar.gz + +clean: + $(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz $(BIN)-icons-static $(BIN)-icons-static-$(VERSION).x86_64.tar.gz $(BIN)-nerd-static $(BIN)-nerd-static-$(VERSION).x86_64.tar.gz $(BIN)-musl-static $(BIN)-musl-static-$(VERSION).x86_64.tar.gz + +prepatch: +ifeq ($(strip $(O_NAMEFIRST)),1) + patch --forward --strip=1 --input=$(NAMEFIRST)/mainline.diff +ifeq ($(strip $(O_GITSTATUS)),1) + patch --forward --strip=1 --input=$(GITSTATUS)/namefirst.diff +endif +else ifeq ($(strip $(O_GITSTATUS)),1) + patch --forward --strip=1 --input=$(GITSTATUS)/mainline.diff +endif +ifeq ($(strip $(O_RESTOREPREVIEW)),1) + patch --forward --strip=1 --input=$(RESTOREPREVIEW)/mainline.diff +endif + +postpatch: +ifeq ($(strip $(O_NAMEFIRST)),1) +ifeq ($(strip $(O_GITSTATUS)),1) + patch --reverse --strip=1 --input=$(GITSTATUS)/namefirst.diff +endif + patch --reverse --strip=1 --input=$(NAMEFIRST)/mainline.diff +else ifeq ($(strip $(O_GITSTATUS)),1) + patch --reverse --strip=1 --input=$(GITSTATUS)/mainline.diff +endif +ifeq ($(strip $(O_RESTOREPREVIEW)),1) + patch --reverse --strip=1 --input=$(RESTOREPREVIEW)/mainline.diff +endif + +skip: ; + +.PHONY: all install uninstall strip static dist sign upload-local clean install-desktop uninstall-desktop diff --git a/nnn/README.md b/nnn/README.md @@ -0,0 +1,162 @@ +<h3 align="center"><img src="misc/logo/logo-128x128.png" alt="nnn"><br>nnn - <i>Supercharge your productivity!</i></h3> + +<p align="center"> +<a href="https://github.com/jarun/nnn/releases/latest"><img src="https://img.shields.io/github/release/jarun/nnn.svg?maxAge=600&label=rel" alt="Latest release" /></a> +<a href="https://repology.org/project/nnn/versions"><img src="https://repology.org/badge/tiny-repos/nnn.svg?header=repos" alt="Availability"></a> +<a href="https://circleci.com/gh/jarun/workflows/nnn"><img src="https://img.shields.io/circleci/project/github/jarun/nnn.svg?label=circle%20ci" alt="CircleCI Status" /></a> +<a href="https://github.com/jarun/nnn/actions"><img src="https://github.com/jarun/nnn/workflows/ci/badge.svg?branch=master" alt="GitHub CI Status" /></a> +<a href="https://en.wikipedia.org/wiki/Privacy-invasive_software"><img src="https://img.shields.io/badge/privacy-✓-crimson?maxAge=2592000" alt="Privacy Awareness" /></a> +<a href="https://github.com/jarun/nnn/blob/master/LICENSE"><img src="https://img.shields.io/badge/©-BSD%202--Clause-important.svg?maxAge=2592000" alt="License" /></a> +</p> + +<p align="center"><a href="http://i.imgur.com/kOld6HT.gif"><img src="https://i.imgur.com/NUsSA2u.jpg"></a></p> + +<h3 align="center">[<a +href="https://github.com/jarun/nnn#features">Features</a>] [<a +href="https://github.com/jarun/nnn#quickstart">Quickstart</a>] [<a +href="https://github.com/jarun/nnn/tree/master/plugins#nnn-plugins">Plugins</a>] [<a +href="https://github.com/jarun/nnn/wiki">Wiki</a>]</h3> + +`nnn` (_n³_) is a full-featured terminal file manager. It's tiny, nearly 0-config and [incredibly fast](https://github.com/jarun/nnn/wiki/Performance). + +It is designed to be unobtrusive with smart workflows to match the trains of thought. + +`nnn` can analyze disk usage, batch rename, launch apps and pick files. The plugin repository has tons of plugins to extend the capabilities further e.g. [live previews](https://github.com/jarun/nnn/wiki/Live-previews), (un)mount disks, find & list, file/dir diff, upload files. A [patch framework](https://github.com/jarun/nnn/tree/master/patches) hosts sizable user-submitted patches which are subjective in nature. + +Independent (neo)vim plugins - [nnn.vim](https://github.com/mcchrish/nnn.vim), [vim-floaterm nnn wrapper](https://github.com/voldikss/vim-floaterm#nnn) and [nnn.nvim](https://github.com/luukvbaal/nnn.nvim) (neovim exclusive). + +Runs on the Pi, [Termux](https://www.youtube.com/embed/AbaauM7gUJw) (Android), Linux, macOS, BSD, Haiku, Cygwin, WSL, across DEs or a strictly CLI env. + +[_(there's more)_](https://github.com/jarun/nnn/wiki/Basic-use-cases#the_nnn-magic) + +## Features + +- Quality + - Privacy-aware (no unconfirmed user data collection) + - POSIX-compliant, follows Linux kernel coding style + - Highly optimized, static analysis integrated code +- Frugal + - Typically needs less than 3.5MB resident memory + - Works with 8 colors (and xterm 256 colors) + - Disk-IO sensitive (few disk reads and writes) + - No FPU usage (all integer maths, even for file size) + - Minimizes screen refresh with fast line redraws + - Tiny binary (typically around 100KB) + - 1-column mode for smaller terminals and form factors + - Hackable - compile in/out features and dependencies +- Portable + - Language-agnostic plugins + - Static binary available (no need to install) + - Minimal library deps, easy to compile + - No config file, minimal config with sensible defaults + - Plugin to backup configuration + - Widely available on many packagers + - Touch enabled, handheld-friendly shortcuts + - Unicode support +- Modes + - Light (default), detail + - Disk usage analyzer (block/apparent) + - File picker, (neo)vim plugin +- Navigation + - *Type-to-nav* mode with automatic matching dir entry + - Contexts (_aka_ tabs/workspaces) with custom colors + - Sessions, bookmarks with hotkeys; mark and visit a dir + - Remote mounts (needs `sshfs`, `rclone`) + - Familiar shortcuts (arrows, <kbd>~</kbd>, <kbd>-</kbd>, <kbd>@</kbd>), quick look-up + - `cd` on quit (*easy* shell integration) + - Auto-proceed on file open and selection +- Search + - Instant filtering with *search-as-you-type* + - Regex (POSIX/PCRE) and string (default) filters + - Subtree search plugin to open or edit files +- Sort + - Ordered pure numeric names by default (visit `/proc`) + - Case-insensitive version (_aka_ natural) sort + - By name, access/change/mod (default) time, size, extn + - Reverse sort + - Directory-specific ordering +- Mimes + - Preview hovered files in FIFO-based previewer + - Open with desktop opener or specify a custom opener + - File-specific colors (or minimal _dirs in context color_) + - Icons (customize and compile-in) + - Plugin for image, video and audio thumbnails + - Create, list, extract (to), mount (FUSE based) archives + - Option to open all text files in `$EDITOR` +- Convenience + - Detailed file stats and mime information + - Run plugins and custom commands with hotkeys + - FreeDesktop compliant trash utility integration + - Cross-dir file/all/range selection + - Create (with parents), rename, duplicate files and dirs + - Batch renamer for selection or dir + - List input stream of file paths from stdin or plugin + - Copy (as), move (as), delete, archive, link selection + - Dir updates, notification on `cp`, `mv`, `rm` completion + - Copy file paths to system clipboard on select + - Launch apps, run commands, spawn a shell, toggle exe + - Access context paths/files at prompt or spawned shell + - Lock terminal after configurable idle timeout + - Capture and show output of a program in help screen + - Basic support for screen readers and braille displays + +## Quickstart + +1. [Install](https://github.com/jarun/nnn/wiki/Usage) `nnn` and the deps you need. +2. The desktop opener is default. Use `-e` to open text files in the terminal. Optionally [open detached](https://github.com/jarun/nnn/wiki/Basic-use-cases#detached-text). +3. Configure [`cd` on quit](https://github.com/jarun/nnn/wiki/Basic-use-cases#configure-cd-on-quit). +4. [Sync subshell `$PWD`](https://github.com/jarun/nnn/wiki/Basic-use-cases#sync-subshell-pwd) to `nnn`. +5. [Install plugins](https://github.com/jarun/nnn/tree/master/plugins#installation). +6. Use `-x` to sync selection to clipboard, show notis on `cp`, `mv`, `rm` and set xterm title. +7. For a CLI-only environment, set [`NNN_OPENER`](https://github.com/jarun/nnn/wiki/Usage#configuration) to [`nuke`](https://github.com/jarun/nnn/blob/master/plugins/nuke). Use option `-c`. +8. Bid `ls` goodbye! `alias ls='nnn -de'` :sunglasses: +9. Visit the [Live previews](https://github.com/jarun/nnn/wiki/Live-previews) and [Troubleshooting](https://github.com/jarun/nnn/wiki/Troubleshooting) Wiki pages. + +Don't memorize! Arrows, <kbd>/</kbd>, <kbd>q</kbd> suffice. <kbd>Tab</kbd> creates and/or cycles contexts. <kbd>?</kbd> lists shortcuts. + +[![](https://i.imgur.com/TN3xYQz.jpg)](https://www.youtube.com/embed/-knZwdd1ScU) + +[![Wiki](https://img.shields.io/badge/RTFM-nnn%20Wiki-important?maxAge=2592000)](https://github.com/jarun/nnn/wiki) + +## Videos + +- [nnn file manager on Termux (Android)](https://www.youtube.com/embed/AbaauM7gUJw) +- [NNN File Manager](https://www.youtube.com/embed/1QXU4XSqXNo) +- [This Week in Linux 114 - TuxDigital](https://www.youtube.com/watch?v=5W9ja0DQjSY&t=2059s) +- [nnn file manager basics - Linux](https://www.youtube.com/embed/il2Fm-KJJfM) +- [I'M GOING TO USE THE NNN FILE BROWSER! 😮](https://www.youtube.com/embed/U2n5aGqou9E) +- [NNN: Is This Terminal File Manager As Good As People Say?](https://www.youtube.com/embed/KuJHo-aO_FA) +- [nnn - A File Manager (By Uoou, again.)](https://www.youtube.com/embed/cnzuzcCPYsk) + +## Elsewhere + +- [AddictiveTips](https://www.addictivetips.com/ubuntu-linux-tips/navigate-linux-filesystem/) +- [ArchWiki](https://wiki.archlinux.org/index.php/Nnn) +- [FOSSMint](https://www.fossmint.com/nnn-linux-terminal-file-browser/) +- [gHacks Tech News](https://www.ghacks.net/2019/11/01/nnn-is-an-excellent-command-line-based-file-manager-for-linux-macos-and-bsds/) +- Hacker News [[1](https://news.ycombinator.com/item?id=18520898)] [[2](https://news.ycombinator.com/item?id=19850656)] +- [It's FOSS](https://itsfoss.com/nnn-file-browser-linux/) +- [Linux Format Issue 265; Manage files with nnn](https://linuxformat.com/archives?issue=265) +- LinuxLinks [[1](https://www.linuxlinks.com/nnn-fast-and-flexible-file-manager/)] [[2](https://www.linuxlinks.com/bestconsolefilemanagers/)] [[3](https://www.linuxlinks.com/excellent-system-tools-nnn-portable-terminal-file-manager/)] +- [Linux Magazine; FOSSPicks](https://www.linux-magazine.com/Issues/2017/205/FOSSPicks/(offset)/15) +- [Make Tech Easier](https://www.maketecheasier.com/nnn-file-manager-terminal/) +- [Open Source For You](https://www.opensourceforu.com/2019/12/nnn-this-feature-rich-terminal-file-manager-will-enhance-your-productivity/) +- [PCLinuxOS Magazine Issue June 2021](https://pclosmag.com/html/Issues/202106/page08.html) +- [Suckless Rocks](https://suckless.org/rocks/) +- [Ubuntu Full Circle Magazine Issue 135; Review: nnn](https://fullcirclemagazine.org/issue-135/) +- [Using and Administering Linux: Volume 2: Zero to SysAdmin: Advanced Topics](https://books.google.com/books?id=MqjDDwAAQBAJ&pg=PA32) +- [Wikipedia](https://en.wikipedia.org/wiki/Nnn_(file_manager)) + +## Developers + +- [Arun Prakash Jana](https://github.com/jarun) (Copyright © 2016-2022) +- [0xACE](https://github.com/0xACE) +- [Anna Arad](https://github.com/annagrram) +- [KlzXS](https://github.com/KlzXS) +- [Léo Villeveygoux](https://github.com/leovilok) +- [Luuk van Baal](https://github.com/luukvbaal) +- [N-R-K](https://github.com/N-R-K) +- [Sijmen J. Mulder](https://github.com/sjmulder) +- and other contributors + +Visit the [ToDo list](https://github.com/jarun/nnn/issues/1292) to contribute or see the features in progress. diff --git a/nnn/misc/CONTRIBUTING.md b/nnn/misc/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing +Contributions to nnn are welcome! There's always an open issue with the current ToDo list, which contains the proposed features for the next release you can get your hands on. Any small changes or ideas should go in there, rather than in a separate issue. + +Before suggesting changes, please read a bit about [the design principles nnn follows](https://github.com/jarun/nnn/wiki#design), and make sure you aren't breaking any of them. + +## Coding standard + +`nnn` follows the Linux kernel coding style closely. The C source code uses TABs and the plugins use 4 spaces for indentation. + +CI runs `shellcheck` on plugins. Please watch out for any failures if you are modifying/adding a plugin. + +## Resources +The [wiki](https://github.com/jarun/nnn/wiki/Developer-guides) has some resources for developers you might be interested in: building, debugging... + +## Communication +* [Gitter chat](https://gitter.im/jarun/nnn) +* [GitHub team](https://github.com/nnn-devs) (if you plan on contributing regularly, ask for an invitation). diff --git a/nnn/misc/auto-completion/bash/nnn-completion.bash b/nnn/misc/auto-completion/bash/nnn-completion.bash @@ -0,0 +1,73 @@ +# +# Rudimentary Bash completion definition for nnn. +# +# Author: +# Arun Prakash Jana <engineerarun@gmail.com> +# + +_nnn () +{ + COMPREPLY=() + local IFS=$'\n' + local cur=$2 prev=$3 + local -a opts + opts=( + -a + -A + -b + -c + -C + -d + -D + -e + -E + -f + -g + -H + -i + -J + -K + -l + -n + -o + -p + -P + -Q + -r + -R + -s + -S + -t + -T + -u + -U + -V + -x + -h + ) + if [[ $prev == -b ]]; then + local bookmarks=$(echo $NNN_BMS | awk -F: -v RS=\; '{print $1}') + COMPREPLY=( $(compgen -W "$bookmarks" -- "$cur") ) + elif [[ $prev == -l ]]; then + return 1 + elif [[ $prev == -p ]]; then + COMPREPLY=( $(compgen -f -d -- "$cur") ) + elif [[ $prev == -P ]]; then + local plugins=$(echo $NNN_PLUG | awk -F: -v RS=\; '{print $1}') + COMPREPLY=( $(compgen -W "$plugins" -- "$cur") ) + elif [[ $prev == -s ]]; then + local sessions_dir=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/sessions + COMPREPLY=( $(cd "$sessions_dir" && compgen -f -d -- "$cur") ) + elif [[ $prev == -t ]]; then + return 1 + elif [[ $prev == -T ]]; then + local keys=$(echo "a d e r s t v" | awk -v RS=' ' '{print $0}') + COMPREPLY=( $(compgen -W "$keys" -- "$cur") ) + elif [[ $cur == -* ]]; then + COMPREPLY=( $(compgen -W "${opts[*]}" -- "$cur") ) + else + COMPREPLY=( $(compgen -f -d -- "$cur") ) + fi +} + +complete -o filenames -F _nnn nnn diff --git a/nnn/misc/auto-completion/fish/nnn.fish b/nnn/misc/auto-completion/fish/nnn.fish @@ -0,0 +1,45 @@ +# +# Fish completion definition for nnn. +# +# Author: +# Arun Prakash Jana <engineerarun@gmail.com> +# + +if test -n "$XDG_CONFIG_HOME" + set sessions_dir $XDG_CONFIG_HOME/.config/nnn/sessions +else + set sessions_dir $HOME/.config/nnn/sessions +end + +complete -c nnn -s a -d 'auto-create NNN_FIFO' +complete -c nnn -s A -d 'disable dir auto-select' +complete -c nnn -s b -r -d 'bookmark key to open' -x -a '(echo $NNN_BMS | awk -F: -v RS=\; \'{print $1"\t"$2}\')' +complete -c nnn -s c -d 'cli-only opener' +complete -c nnn -s C -d 'color by context' +complete -c nnn -s d -d 'start in detail mode' +complete -c nnn -s D -d 'dirs in context color' +complete -c nnn -s e -d 'open text files in $VISUAL/$EDITOR/vi' +complete -c nnn -s E -d 'use EDITOR for undetached edits' +complete -c nnn -s f -d 'use readline history file' +complete -c nnn -s g -d 'regex filters' +complete -c nnn -s H -d 'show hidden files' +complete -c nnn -s i -d 'show current file info' +complete -c nnn -s J -d 'no auto-proceed on select' +complete -c nnn -s K -d 'detect key collision' +complete -c nnn -s l -r -d 'lines to move per scroll' +complete -c nnn -s n -d 'start in type-to-nav mode' +complete -c nnn -s o -d 'open files only on Enter' +complete -c nnn -s p -r -d 'copy selection to file' -a '-\tstdout' +complete -c nnn -s P -r -d 'plugin key to run' -x -a '(echo $NNN_PLUG | awk -F: -v RS=\; \'{print $1"\t"$2}\')' +complete -c nnn -s Q -d 'disable quit confirmation' +complete -c nnn -s r -d 'show cp, mv progress (Linux-only)' +complete -c nnn -s R -d 'disable rollover at edges' +complete -c nnn -s s -r -d 'load session by name' -x -a '@\t"last session" (ls $sessions_dir)' +complete -c nnn -s S -d 'persistent session' +complete -c nnn -s t -r -d 'timeout in seconds to lock' +complete -c nnn -s T -r -d 'a d e r s t v' +complete -c nnn -s u -d 'use selection (no prompt)' +complete -c nnn -s U -d 'show user and group' +complete -c nnn -s V -d 'show program version and exit' +complete -c nnn -s x -d 'notis, sel to system clipboard, xterm title' +complete -c nnn -s h -d 'show program help' diff --git a/nnn/misc/auto-completion/zsh/_nnn b/nnn/misc/auto-completion/zsh/_nnn @@ -0,0 +1,46 @@ +#compdef nnn +# +# Completion definition for nnn. +# +# Author: +# Arun Prakash Jana <engineerarun@gmail.com> +# + +setopt localoptions noshwordsplit noksharrays +local -a args +args=( + '(-a)-a[auto-create NNN_FIFO]' + '(-A)-A[disable dir auto-select]' + '(-b)-b[bookmark key to open]:key char' + '(-c)-c[cli-only opener]' + '(-C)-C[color by context]' + '(-d)-d[start in detail mode]' + '(-D)-D[dirs in context color]' + '(-e)-e[open text files in $VISUAL/$EDITOR/vi]' + '(-E)-E[use EDITOR for undetached edits]' + '(-f)-f[use readline history file]' + '(-g)-g[regex filters]' + '(-H)-H[show hidden files]' + '(-i)-i[show current file info]' + '(-J)-J[no auto-proceed on select]' + '(-K)-K[detect key collision]' + '(-l)-l[lines to move per scroll]:val' + '(-n)-n[start in type-to-nav mode]' + '(-o)-o[open files only on Enter]' + '(-p)-p[copy selection to file]:file name' + '(-P)-P[plugin key to run]:key char' + '(-Q)-Q[disable quit confirmation]' + '(-r)-r[show cp, mv progress (Linux-only)]' + '(-R)-R[disable rollover at edges]' + '(-s)-s[load session]:session name' + '(-S)-S[persistent session]' + '(-t)-t[timeout to lock]:seconds' + '(-T)-T[a d e r s t v]:key' + '(-u)-u[use selection (no prompt)]' + '(-U)-U[show user and group]' + '(-V)-V[show program version and exit]' + '(-x)-x[notis, sel to system clipboard, xterm title]' + '(-h)-h[show program help]' + '*:filename:_files' +) +_arguments -S -s $args diff --git a/nnn/misc/desktop/nnn.desktop b/nnn/misc/desktop/nnn.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=nnn +Comment=Terminal file manager +Exec=nnn +Terminal=true +Icon=nnn +MimeType=inode/directory +Categories=System;FileTools;FileManager;ConsoleOnly +Keywords=File;Manager;Management;Explorer;Launcher diff --git a/nnn/misc/haiku/Makefile b/nnn/misc/haiku/Makefile @@ -0,0 +1,250 @@ +VERSION = $(shell grep -m1 VERSION $(SRC) | cut -f 2 -d'"') + +PREFIX ?= /boot/system/non-packaged +MANPREFIX ?= $(PREFIX)/documentation/man +STRIP ?= strip +PKG_CONFIG ?= pkg-config +INSTALL ?= install +CP ?= cp + +CFLAGS_OPTIMIZATION ?= -O3 + +O_DEBUG := 0 # debug binary +O_NORL := 0 # no readline support +O_PCRE := 0 # link with PCRE library +O_NOLC := 0 # no locale support +O_NOMOUSE := 0 # no mouse support +O_NOBATCH := 0 # no built-in batch renamer +O_NOFIFO := 0 # no FIFO previewer support +O_CTX8 := 0 # enable 8 contexts +O_ICONS := 0 # support icons-in-terminal +O_NERD := 0 # support icons-nerdfont +O_QSORT := 0 # use Alexey Tourbin's QSORT implementation +O_BENCH := 0 # benchmark mode (stops at first user input) +O_NOSSN := 0 # disable session support +O_NOUG := 0 # disable user, group name in status bar +O_NOX11 := 0 # disable X11 integration +O_MATCHFLTR := 0 # allow filters without matches + +# User patches +O_GITSTATUS := 0 # add git status to detail view +O_NAMEFIRST := 0 # print file name first, add uid and guid to detail view + +ifeq ($(strip $(O_GITSTATUS)),1) + LDLIBS += -lgit2 +endif + +# convert targets to flags for backwards compatibility +ifneq ($(filter debug,$(MAKECMDGOALS)),) + O_DEBUG := 1 +endif +ifneq ($(filter norl,$(MAKECMDGOALS)),) + O_NORL := 1 +endif +ifneq ($(filter nolc,$(MAKECMDGOALS)),) + O_NORL := 1 + O_NOLC := 1 +endif + +ifeq ($(strip $(O_DEBUG)),1) + CPPFLAGS += -DDEBUG + CFLAGS += -g + LDLIBS += -lrt +endif + +ifeq ($(strip $(O_NORL)),1) + CPPFLAGS += -DNORL +else ifeq ($(strip $(O_STATIC)),1) + CPPFLAGS += -DNORL +else + LDLIBS += -lreadline +endif + +ifeq ($(strip $(O_PCRE)),1) + CPPFLAGS += -DPCRE + LDLIBS += -lpcre +endif + +ifeq ($(strip $(O_NOLC)),1) + ifeq ($(strip $(O_ICONS)),1) +$(info *** Ignoring O_NOLC since O_ICONS is set ***) + else ifeq ($(strip $(O_NERD)),1) +$(info *** Ignoring O_NOLC since O_NERD is set ***) + else + CPPFLAGS += -DNOLC + endif +endif + +ifeq ($(strip $(O_NOMOUSE)),1) + CPPFLAGS += -DNOMOUSE +endif + +ifeq ($(strip $(O_NOBATCH)),1) + CPPFLAGS += -DNOBATCH +endif + +ifeq ($(strip $(O_NOFIFO)),1) + CPPFLAGS += -DNOFIFO +endif + +ifeq ($(strip $(O_CTX8)),1) + CPPFLAGS += -DCTX8 +endif + +ifeq ($(strip $(O_ICONS)),1) + CPPFLAGS += -DICONS +endif + +ifeq ($(strip $(O_NERD)),1) + CPPFLAGS += -DNERD +endif + +ifeq ($(strip $(O_QSORT)),1) + CPPFLAGS += -DTOURBIN_QSORT +endif + +ifeq ($(strip $(O_BENCH)),1) + CPPFLAGS += -DBENCH +endif + +ifeq ($(strip $(O_NOSSN)),1) + CPPFLAGS += -DNOSSN +endif + +ifeq ($(strip $(O_NOUG)),1) + CPPFLAGS += -DNOUG +endif + +ifeq ($(strip $(O_NOX11)),1) + CPPFLAGS += -DNOX11 +endif + +ifeq ($(strip $(O_MATCHFLTR)),1) + CPPFLAGS += -DMATCHFLTR +endif + +ifeq ($(shell $(PKG_CONFIG) ncursesw && echo 1),1) + CFLAGS_CURSES ?= $(shell $(PKG_CONFIG) --cflags ncursesw) + LDLIBS_CURSES ?= $(shell $(PKG_CONFIG) --libs ncursesw) +else ifeq ($(shell $(PKG_CONFIG) ncurses && echo 1),1) + CFLAGS_CURSES ?= $(shell $(PKG_CONFIG) --cflags ncurses) + LDLIBS_CURSES ?= $(shell $(PKG_CONFIG) --libs ncurses) +else + LDLIBS_CURSES ?= -lncurses +endif + +ifeq ($(shell uname -s), Haiku) + LDLIBS_HAIKU ?= -lstdc++ -lgnu -lbe + SRC_HAIKU ?= misc/haiku/nm.cpp + OBJS_HAIKU ?= misc/haiku/nm.o +endif + +CFLAGS += -std=c11 -Wall -Wextra -Wshadow +CFLAGS += $(CFLAGS_OPTIMIZATION) +CFLAGS += $(CFLAGS_CURSES) + +LDLIBS += $(LDLIBS_CURSES) -lpthread $(LDLIBS_HAIKU) +# static compilation needs libgpm development package +ifeq ($(strip $(O_STATIC)),1) + LDFLAGS += -static + LDLIBS += -lgpm +endif + +DISTFILES = src nnn.1 Makefile README.md LICENSE +SRC = src/nnn.c +HEADERS = src/nnn.h +BIN = nnn +OBJS := nnn.o $(OBJS_HAIKU) + +GITSTATUS = patches/gitstatus +NAMEFIRST = patches/namefirst + +all: $(BIN) + +ifeq ($(shell uname -s), Haiku) +$(OBJS_HAIKU): $(SRC_HAIKU) + $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $< +endif + +nnn.o: $(SRC) $(HEADERS) + $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $< + +$(BIN): $(OBJS) + @$(MAKE) --silent prepatch + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + @$(MAKE) --silent postpatch + +# targets for backwards compatibility +debug: $(BIN) +norl: $(BIN) +nolc: $(BIN) + +install: all + $(INSTALL) -m 0755 -d $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 $(BIN) $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 -d $(DESTDIR)$(MANPREFIX)/man1 + $(INSTALL) -m 0644 $(BIN).1 $(DESTDIR)$(MANPREFIX)/man1 + +uninstall: + $(RM) $(DESTDIR)$(PREFIX)/bin/$(BIN) + $(RM) $(DESTDIR)$(MANPREFIX)/man1/$(BIN).1 + +strip: $(BIN) + $(STRIP) $^ + +static: + # regular static binary + make O_STATIC=1 strip + mv $(BIN) $(BIN)-static + +dist: + mkdir -p nnn-$(VERSION) + $(CP) -r $(DISTFILES) nnn-$(VERSION) + mkdir -p nnn-$(VERSION)/misc + $(CP) -r misc/haiku nnn-$(VERSION)/misc + tar -cf - nnn-$(VERSION) | gzip > nnn-$(VERSION).tar.gz + $(RM) -r nnn-$(VERSION) + +sign: + git archive -o nnn-$(VERSION).tar.gz --format tar.gz --prefix=nnn-$(VERSION)/ v$(VERSION) + gpg --detach-sign --yes nnn-$(VERSION).tar.gz + rm -f nnn-$(VERSION).tar.gz + +upload-local: sign static + $(eval ID=$(shell curl -s 'https://api.github.com/repos/jarun/nnn/releases/tags/v$(VERSION)' | jq .id)) + # upload sign file + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=nnn-$(VERSION).tar.gz.sig' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/pgp-signature' \ + --upload-file nnn-$(VERSION).tar.gz.sig + tar -zcf $(BIN)-static-$(VERSION).x86_64.tar.gz $(BIN)-static + # upload static binary + curl -XPOST 'https://uploads.github.com/repos/jarun/nnn/releases/$(ID)/assets?name=$(BIN)-static-$(VERSION).x86_64.tar.gz' \ + -H 'Authorization: token $(NNN_SIG_UPLOAD_TOKEN)' -H 'Content-Type: application/x-sharedlib' \ + --upload-file $(BIN)-static-$(VERSION).x86_64.tar.gz + +clean: + $(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz + +prepatch: +ifeq ($(strip $(O_NAMEFIRST)),1) + patch --forward --strip=1 --input=$(NAMEFIRST)/mainline.diff +ifeq ($(strip $(O_GITSTATUS)),1) + patch --forward --strip=1 --input=$(GITSTATUS)/namefirst.diff +endif +else ifeq ($(strip $(O_GITSTATUS)),1) + patch --forward --strip=1 --input=$(GITSTATUS)/mainline.diff +endif + +postpatch: +ifeq ($(strip $(O_NAMEFIRST)),1) +ifeq ($(strip $(O_GITSTATUS)),1) + patch --reverse --strip=1 --input=$(GITSTATUS)/namefirst.diff +endif + patch --reverse --strip=1 --input=$(NAMEFIRST)/mainline.diff +else ifeq ($(strip $(O_GITSTATUS)),1) + patch --reverse --strip=1 --input=$(GITSTATUS)/mainline.diff +endif + +skip: ; + +.PHONY: all install uninstall strip static dist sign upload-local clean diff --git a/nnn/misc/haiku/haiku_interop.h b/nnn/misc/haiku/haiku_interop.h @@ -0,0 +1,14 @@ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct haiku_nm_t *haiku_nm_h; +haiku_nm_h haiku_init_nm(); +void haiku_close_nm(haiku_nm_h hnd); +int haiku_watch_dir(haiku_nm_h hnd, const char *path); +int haiku_stop_watch(haiku_nm_h hnd); +int haiku_is_update_needed(haiku_nm_h hnd); + +#ifdef __cplusplus +} +#endif diff --git a/nnn/misc/haiku/nm.cpp b/nnn/misc/haiku/nm.cpp @@ -0,0 +1,83 @@ +#include <Directory.h> +#include <Looper.h> +#include <NodeMonitor.h> +#include <MessageFilter.h> + +#include "haiku_interop.h" + +filter_result dir_mon_flt(BMessage *message, BHandler **hnd, BMessageFilter *fltr) { + (void) hnd; + (void) fltr; + + if (message->what == B_NODE_MONITOR) { + int32 val; + message->FindInt32("opcode", &val); + + switch (val) { + case B_ENTRY_CREATED: + case B_ENTRY_MOVED: + case B_ENTRY_REMOVED: + return B_DISPATCH_MESSAGE; + } + } + + return B_SKIP_MESSAGE; +} + +class DirectoryListener : public BLooper { +public: + bool recv_reset() { + Lock(); + bool val = _ev_on; + _ev_on = false; + Unlock(); + + return val; + } +private: + void MessageReceived(BMessage * message) override { + Lock(); + _ev_on = true; + Unlock(); + BLooper::MessageReceived(message); + } + + bool _ev_on = false; +}; + +struct haiku_nm_t { + haiku_nm_t() { + dl = new DirectoryListener(); + flt = new BMessageFilter(B_PROGRAMMED_DELIVERY, B_LOCAL_SOURCE, dir_mon_flt); + dl->AddCommonFilter(flt); + dl->Run(); + } + + DirectoryListener *dl; + BMessageFilter *flt; + node_ref nr; +}; + +haiku_nm_h haiku_init_nm() { + return new haiku_nm_t(); +} + +void haiku_close_nm(haiku_nm_h hnd) { + delete hnd->flt; + // This is the way of deleting a BLooper + hnd->dl->PostMessage(B_QUIT_REQUESTED); + delete hnd; +} +int haiku_watch_dir(haiku_nm_h hnd, const char *path) { + BDirectory dir(path); + dir.GetNodeRef(&(hnd->nr)); + + return watch_node(&(hnd->nr), B_WATCH_DIRECTORY, nullptr, hnd->dl); +} +int haiku_stop_watch(haiku_nm_h hnd) { + return watch_node(&(hnd->nr), B_STOP_WATCHING, nullptr, hnd->dl); +} + +int haiku_is_update_needed(haiku_nm_h hnd) { + return hnd->dl->recv_reset(); +} diff --git a/nnn/misc/haiku/nnn-master.recipe b/nnn/misc/haiku/nnn-master.recipe @@ -0,0 +1,58 @@ +SUMMARY="A blazing-fast lightweight terminal file manager" +DESCRIPTION="nnn is a full-featured terminal file manager. It's tiny and \ +nearly 0-config with an incredible performance. + +nnn is also a du analyzer, an app launcher, a batch renamer and a file picker. \ +The plugin repository has tons of plugins and documentation to extend the \ +capabilities further. You can plug new functionality and play with a \ +custom keybind instantly. There's an independent (neo)vim plugin. + +It runs smoothly on the Raspberry Pi, Termux on Android, Linux, macOS, BSD, \ +Cygwin, WSL, Haiku and works seamlessly with DEs and GUI utilities. + +Visit the Wiki for concepts, program usage, how-tos and troubleshooting." +HOMEPAGE="https://github.com/jarun/nnn" +COPYRIGHT="2016-2022 Arun Prakash Jana" +LICENSE="BSD (2-clause)" +REVISION="1" +SOURCE_URI="git://github.com/jarun/nnn.git" + +ARCHITECTURES="!x86_gcc2 x86_64" +SECONDARY_ARCHITECTURES="x86" + +PROVIDES=" + nnn$secondaryArchSuffix = $portVersion + cmd:nnn = $portVersion + " +REQUIRES=" + haiku$secondaryArchSuffix + file$secondaryArchSuffix + lib:libncurses$secondaryArchSuffix + lib:libreadline$secondaryArchSuffix + " + +BUILD_REQUIRES=" + haiku${secondaryArchSuffix}_devel + devel:libncurses$secondaryArchSuffix + devel:libreadline$secondaryArchSuffix + " +BUILD_PREREQUIRES=" + cmd:g++$secondaryArchSuffix + cmd:gcc$secondaryArchSuffix + cmd:install + cmd:ld$secondaryArchSuffix + cmd:make + cmd:pkg_config$secondaryArchSuffix + " + +BUILD() +{ + make -f misc/haiku/Makefile $jobArgs +} + +INSTALL() +{ + make -f misc/haiku/Makefile install PREFIX=$prefix + + addResourcesToBinaries misc/haiku/nnn.rdef $prefix/bin/nnn +} diff --git a/nnn/misc/haiku/nnn.rdef b/nnn/misc/haiku/nnn.rdef @@ -0,0 +1,140 @@ + +// How to apply this resource definition file manually (Haiku only): +// First, make sure that nnn is compiled. +// Next, run "rc nnn.rdef -o nnn.rsrc" to compile this resource file. +// Then, run "xres -o nnn nnn.rsrc" to apply the resource file to nnn. +// Finally, run "mimeset -f nnn" to refresh Haiku's MIME database. + +resource app_signature "application/x-vnd.Jarun-nnn"; + +resource app_version { + major = 4, + middle = 4, + minor = 0, + + variety = B_APPV_DEVELOPMENT, + internal = 0, + + short_info = "nnn", + long_info = "A blazing-fast lightweight terminal file manager" +}; + +resource app_flags B_MULTIPLE_LAUNCH | B_BACKGROUND_APP | B_ARGV_ONLY; + +resource file_types message { + "types" = "application/x-vnd.Be-directory" +}; + +resource vector_icon { + $"6E6369660805FF032E343602011202B8E0C339BAC3B985CFB8B7E549F5FF4994" + $"000000FFFF0000020112023745BA3659FABA87063B846942D03049EBF80000FF" + $"FF0000020112033BE54D39FE49B857E23A493A4A91FF489000000000AF0000FF" + $"00FF020112022D299A3B3A49BCE2FF2ED4134A65A94C096A0000FFFF00000201" + $"1202365425358147B6C63037AFB14669664AB9F30000FFFF0000020112023673" + $"5235AC4EB7FB7138A81D481E6A48C2C50000FFFF00000F0241BE60B360BE60B3" + $"60BE5FB360BE3EB364BE46B362BE2AB369BE17B371BE1FB36DBE03B379BDF3B3" + $"85BDFCB37EBDE3B38EBDCEB3A0BDD9B398BDB8B3B1BD8BB3DDBDA4B3C5BD58B4" + $"0EBCCDB4A0BD17B450BC93B4DBBC36B539BC61B50DBC35B537BC32B536BC34B5" + $"37BBE6B50ABB0FB48BBB7EB4CEBA9EB448B9E8B3DDBA36B40AB9C1B3C5B987B3" + $"A5B9A0B3B3B97AB39FB967B394B971B399B961B391B954B38CB95DB390B94FB3" + $"89B942B385B94CB388B93FB382B92FB37EB939B381B926B37DB8FFB379B900B3" + $"79B8E9B379B87FB3A4B8B1B37DB84CB3C8B82FB418B838B3FBB81FB453B826B4" + $"89B825B466B827B4CDB841B5F9B82FB534B848B656B854B6F9B84EB6B1B85AB7" + $"41B85CB776B85CB782B85CB75BB83AB7A7B860B772B814B7DBB767B88FB7CEB8" + $"29B6E7B90DB65CB9D1B694B955B626BA4CB62ABB5AB62CBAB9B629BC54B606BC" + $"9DB634BC1BB5CDBD3EB4B1BEF5B597BDA1B445BF96B3B0C089B3DDC033B31AC1" + $"A4B318C3DAB2D2C2D1B371C536B61FC639B4ABC60FB641C680B698C6F2B65CC6" + $"CFB698C6F2B69AC6F2B69AC6F2B71EC742B838C6F9B7BDC733B8B2C6BEB96CC5" + $"E2B919C65DB99BC59FBA11C4C4B9D8C530BA78C400BB14C34ABAB4C3A1BB75C2" + $"F2BD20C209BC0BC29ABDDEC1A6BED6C108BE5244BED6C121BED8C151BED9C139" + $"BECCC24CBE29C3E9BE9AC307BDC3C4B6BC1DC63ABD84C4F7BB43C700BA57C7FA" + $"BAB9C781B9F5C872B99DC966B9BCC8EBB961CA48B95ECBBBB955CB0830CBFDB9" + $"65CC7FB962CC3DBFC3CC7F60CC7FC621CC7FCC7ECBDECC7ACA9BCC7CCB3CCC6F" + $"C6D6CA63BE51CB9AC1F5C98736C731B8ADC899BA16C5F2B76EC2C4B648C477B6" + $"A1C212B625BFF7B63643B625C013B634C04A28C02FB631C027B5E2BFE0B546C0" + $"04B593BFAFB4D8BF5EB436BF81B47BBF4DB415BF30B3E0BF3FB3F8BF28B3D3BF" + $"18B3BBBF22B3C9BF11B3B4BEFFB39DBF0CB3ABBEF8B398BEE2B385BEF1B390BE" + $"DAB380BEC5B375BED1B37ABEB9B36EBE96B364BE96B364BE96B364BE95B364BE" + $"95B364BE93B364BE93B364BE93B364BE93B364BE60B360BE60B360BE60B36002" + $"23BCA6C6D2BCA6C6D2BE0FC58CBEDFC443BE70C523BF5BC34EBFA3C159BF97C2" + $"6DBFAAC0AEBF80BFC2BF99BFDABF7ABFBBBF34BFE33FBFC7BEA9C048BCC3C154" + $"BDD9C0C2BA90C277B95BC467BA2FC2CFB926C4CCB8C5C56FB8E2C542B83EC636" + $"B702C644B780C68FB6CEC624B6A8C5B6B6B4C5FDB6A1C587B660C581B69CC584" + $"B510C56EB3DDC3A7B426C4C2B3ACC2EDB465C0E9B3E0C1E1B481C0B1B55ABF67" + $"B4F0C005B642BE10B6C7BCE0B689BD92B6F5BC5DB6F6BB5AB6F5BC55B6F7BA1A" + $"B7F5B921B6F5BA1EB8CDB84DB928B776B928B7CEB928B757B90CB5E9B91BB6A4" + $"B8EBB45DB8FFB446B8EBB446B907B446BAA6B53A31B4B4BB85B5C0BC54B62FBC" + $"47B62FBC6128BD60B52BBCDBB5BABDF2B491BE6BB42BBE5EB428BE78B42EBF27" + $"B59ABEC3B4C2BF5DB613BFCBB704BF95B68DBFDEB703C005B702BFF2B702C0F0" + $"B6F1C29CB70FC229B6F9C42AB761C6A0B93DC57CB818C7F0BA8FC9A0BE92C8CC" + $"BC13CACFC21CCBAECA9DCBA3C6F5CBAFCAFBCBB15ECBB0CB56C5D85EBA295EC0" + $"015EBA21CB0FBA61C99A32CA68BA97C8CBBCA6C6D2BAF4C858BCA6C6D20215C8" + $"1C5EC81C5EC80FCB23C7DAC9A7C805CA9DC79EC84BC6E7C4FBC747C6A251C353" + $"C5B5C054C61BC1ACC54FBEF9C4A1BD7F4DBDEFC457BD12C3C3BC5FC40EBCB4C3" + $"79BC0DC2D8BB87C32B36C285BB4BC1C8BAF0C22BBB19C195BADDC120BAC444BA" + $"D2C13EBADEC177BB1CC15CBAF6C19EBB66C1B9BC18C1B5BBBAC1BCBC74C184BD" + $"41C1ABBCD8C15CBDA9C0C1BE85C11DBE17C0683EBF5CBFD2BFF2BF64BF69BFCD" + $"BF80BFC2BF7EBFBFBF99BFDABFA3C159BFAAC0AEBF97C26DBEE1C443BF5BC34E" + $"BE70C523BCA6C6D2BE0FC58CBAF4C858BA61C99ABA97C8CB32CA68BA295EBA21" + $"CB0FBECF5EC81C5EC3755EC81C5E021CBE6BB42BBE6BB42BBE5EB428BD60B52B" + $"BDF2B491BCDBB5BABC54B62FBC6128BC47B62FBAA6B53ABB85B5C031B4B4B8FF" + $"B446B907B446B8EBB446B90CB5E9B8EBB45DB91BB6A4B928B776B928B757B928" + $"B7CEB7F5B921B8CDB84DB6F5BA1EB6F6BB5AB6F7BA1AB6F5BC55B6C7BCE0B6F5" + $"BC5DB689BD92B55ABF67B642BE10B4F0C005B465C0E9B481C0B1B3E0C1E1B3DD" + $"C3A7B3ACC2EEB426C4C2B660C581B510C56EB680C583B697C58AB68FC583B74F" + $"C477B90545B815C32CB9FCC041BC10BD70BB07BEB8BC94BCCCBD99BBBBBD18BC" + $"36BE19BB41BF0DBAA0BE96BADFBF49BA82BFBBBA5FBF82BA6BBFF3BA52C05CBA" + $"59C028BA50C090BA6143BA94C0C2BA75C103BA9FC120BAC4C110BAB6C125BAC5" + $"C130BAC7C12ABAC6C0BBB986BFCEB704C043B846BFCDB704BFCBB704BFCDB704" + $"BF95B68BBF27B59ABF5DB613BEC4B4C2BE6BB42BBE78B42EBE6BB42B0207BFCE" + $"B704BFCEB704C063B85EC18FBB10C0F9B9B7C1C2BB1DC2BCBB74C28DBB61C6B9" + $"BBACC6A0B93DCB98BF33C57CB818C29CB70FC42AB761C229B6F9C005B702C0F0" + $"B6F1BFF3B702BFCEB704BFDFB703BFCEB7040207BCA6C6D2BCA6C6D2474ACB64" + $"C69E5149CB91C805CBAECA9DCBABC963CBAFCAFBCBB15ECBB0CB56C5D85EBA29" + $"5EC0015EBA21CB0FBA61C99A32CA68BA97C8CBBCA6C6D2BAF4C858BCA6C6D206" + $"04BFB67CC579B67CC579B7C1C49BB900C2AFB8F9C22FB906C32EB6F5C5CDB841" + $"C6ABB5AA4DB67CC5790211BD5BC297BD5BC297BDC5C279BEE5C244BE49C25EBF" + $"83C22BC0E8C21FC02EC21FC19DC21FC2AAC26CC234C238C323C29CC3C4C33EC3" + $"81C2E1C40BC399C45BC485C43EC405C478C502C487C626C487C58DC487C7BCC4" + $"87CAE7C487C9514ACAE746CAE7C2EFCAE746C96B46C67346C7EF46C5A0C1DDC4" + $"ABC20BC508C1AFC44FC0C2C421C151C421C097C421C040C424C06D4BC018C428" + $"BFBFC434BFEEC42CBFBFC66FBFBFCAE7BFBFC8ACBEF3CAE7BD5BCAE7BE27CAE7" + $"BD5BC821BD5BC297BD5BC55BBD5BC2970211BE27C363BE27C363BE91C345BFB1" + $"C310BF16C32AC050C2F7C1B4C2EBC0FAC2EBC269C2EBC376C338C300C304C3EF" + $"C368C490C40AC44DC3ADC4D7C465C527C551C50AC4D1C544C5CE4EC6F24EC659" + $"4EC8454ECAE74EC9964CCAE748CAE74ACAE748C9C548C77F48C8A148C6B9C2A9" + $"C5D4C2D7C62BC27BC57FC18EC553C21DC553C163C553C10CC557C1394EC0E4C5" + $"5A42C565C0BAC55E42C73A42CAE742C91140CAE73CCAE73ECAE73CC866BE27C3" + $"63BE27C5E4BE27C3630206B669C28FB669C28FB669C25DB613C207B642C21EB5" + $"8645B4F5C338B507C245B4F1C374B4F7C3BEB4F3C3B2B503C3DAB59DC370B507" + $"C3D7B632C309B669C28FB669C2CAB669C28F0207BAE6BC94BAE6BC94BC1EBB5D" + $"BD8EB8BBBD8EB948BD8EB89BBD1AB854BD5AB86DBBF7B7E7B988B975BA7DB85F" + $"B902BA0EB763BDFCB82DBAE5B751BE42B7F5BEBCB6C13FB92ABE1DBAE6BC94B9" + $"EDBD8CBAE6BC940207B9EABBF2B9EABBF2BA4CBB90BABFBABCBABFBAE9BABFBA" + $"B2BA9BBA9CBAAEBAA3BA40BA79B97BBAF7B9C8BA9EB952BB27B85BBCCCB85BBC" + $"B7B85BBCE3B8FCBC9EB89BBCCFB95EBC6CB9EABBF2B99CBC3FB9EABBF2020FC0" + $"E42CC0E42CC0BFB7C942B801C09DB7E0C07BB825C096B86CC080B84DC0ADB889" + $"C0F8B893C0D4B899C2D4B862C515B9BAC413B8E0C617BA94C789BD20C6D6BBD2" + $"C7CFBDA0C8EFC1C5C869BF62C977C428CA19CA85C9F2C73ACA1BCAABCA4FCADD" + $"CA30CACCCA6FCAEDCAB5CAD7CA97CAEBCAD4CAC4CAE5CA7DCAE7CAA1CABDC722" + $"59C19ACA3FC408C92DBF2BC83CBCC0C89FBD76C786BB6DC599B920C6BEBA17C4" + $"73B827C0E42CC2EEB794C0E42C020AC70ABC69C70ABC69C1BB3BC864C462C353" + $"50C8CFC440C982C3CEC930C411C983C3CDC984C3CCC983C3CCC985C3CAC987C3" + $"C7C986C3C9C9D4C384CA37C2CDCA13C32DCA59C272CA1945CA19C220C9B33FC8" + $"5ABC98C9B33DC85ABC98C859BC98C859BC98C7F2BC76C70ABC69C785BC69C70A" + $"BC690220C6E2C242C6E2C242C740C242C7A5C216C782C234C7CCC1F8C7DFC1AB" + $"C7DFC1D6C7DFC141C6C8C10CC782C10CC690C10CC622C10CC659C10CC622C0A1" + $"C622BFC8C622C035C659BFC8C6C8BFC8C690BFC8C705BFC8C75FBFA5C738BFBD" + $"C785BF8DC798BF35C798BF67C798BEE3C6EBBEB9C75FBEB9C6ADBEB9C635BED9" + $"C66FBEC3C5FABEEEC587BF1BC5BFBF03C555BEB4C4F0BDE5C523BE4CC532BDBA" + $"C5E5BD79C584BD98C64ABD5AC709BD4AC6ABBD4AC774BD4AC814BD6CC7CDBD54" + $"C85EBD84C8C6BDCEC89ABDA5C8F1BDF5C922BE5CC911BE24C935BE91C93DBF04" + $"C93DBECAC93DBF3EC916BFA5C931BF73C8FEBFD4C89AC038C8D4C005C8EEC05D" + $"58C0D7C92CC092C975C117C988C1B0C988C160C988C1FAC966C27CC97DC23EC9" + $"4EC2BBC8F1C321C927C2F1C8BCC34DC821C38BC878C370C7CCC3A5C6E7C3B4C7" + $"63C3B4C68DC3B4C5C5C399C62EC3AAC562C384C4D6C348C511C36AC4F5C2D4C5" + $"34C1EAC515C25EC58CC210C616C236C5D7C229C653C23EC6E2C242C698C242C6" + $"E2C2420F0A000100000A010101000A020102000A030103000A040104000A0501" + $"05000A060106000A010107000A000108000A000109000A07010A000A00010B00" + $"0A00010C000A01010D000A00010E00" +}; diff --git a/nnn/misc/logo/logo-128x128.png b/nnn/misc/logo/logo-128x128.png Binary files differ. diff --git a/nnn/misc/logo/logo-64x64.png b/nnn/misc/logo/logo-64x64.png Binary files differ. diff --git a/nnn/misc/logo/logo.svg b/nnn/misc/logo/logo.svg @@ -0,0 +1,302 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="63.999996" + height="63.999996" + viewBox="0 0 16.933332 16.933333" + version="1.1" + id="svg8" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" + sodipodi:docname="logo.svg" + inkscape:export-filename="logo-64x64.png" + inkscape:export-xdpi="96.000008" + inkscape:export-ydpi="96.000008"> + <title + id="title1733">n³ logo</title> + <defs + id="defs2"> + <linearGradient + inkscape:collect="always" + id="linearGradient4258"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop4254" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop4256" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3891"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop3887" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop3889" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3746"> + <stop + style="stop-color:#000000;stop-opacity:0" + offset="0" + id="stop3742" /> + <stop + id="stop3750" + offset="0.68665242" + style="stop-color:#000000;stop-opacity:0" /> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="1" + id="stop3744" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3628"> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="0" + id="stop3624" /> + <stop + style="stop-color:#000000;stop-opacity:0" + offset="1" + id="stop3626" /> + </linearGradient> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3628" + id="radialGradient4190" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.10643412,0.08213184,-0.08695471,0.11268405,2.5028417,-19.507024)" + cx="80.745285" + cy="31.199144" + fx="80.745285" + fy="31.199144" + r="17.514044" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3628" + id="radialGradient4200" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.1591284,-0.22144644,0.05150741,0.03701252,-10.674157,4.5498963)" + cx="63.691868" + cy="24.91503" + fx="63.691868" + fy="24.91503" + r="16.80954" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3746" + id="radialGradient4206" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.08312121,-0.16218012,0.2763752,0.1416496,-5.8976633,2.3429863)" + cx="109.63785" + cy="27.735554" + fx="109.63785" + fy="27.735554" + r="14.917971" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3891" + id="radialGradient4208" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.77009845,-0.00589491,0.0032956,0.4305302,1.4732687,-112.12628)" + cx="10.145939" + cy="261.29977" + fx="10.145939" + fy="261.29977" + r="7.9343286" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient4258" + id="radialGradient4260" + cx="38.219185" + cy="-11.155752" + fx="38.219185" + fy="-11.155752" + r="2.5266471" + gradientTransform="matrix(0.41700468,-0.5564057,0.25660676,0.1923169,-8.5901303,12.321222)" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3891" + id="radialGradient4271" + cx="36.437771" + cy="11.167927" + fx="36.437771" + fy="11.167927" + r="0.92064488" + gradientTransform="matrix(0.79726776,-1.0594042,0.6692442,0.50364803,-33.972333,27.585938)" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="43.627536" + inkscape:cy="44.431591" + inkscape:document-units="mm" + inkscape:current-layer="layer2" + showgrid="true" + units="px" + inkscape:snap-bbox="true" + inkscape:snap-bbox-midpoints="false" + inkscape:bbox-nodes="true" + inkscape:object-nodes="false" + inkscape:snap-global="true" + inkscape:snap-nodes="true" + inkscape:window-width="1918" + inkscape:window-height="1060" + inkscape:window-x="0" + inkscape:window-y="18" + inkscape:window-maximized="0" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showguides="false"> + <inkscape:grid + type="xygrid" + id="grid815" + empspacing="2" + dotted="true" + originx="0" + originy="0" /> + <inkscape:grid + type="xygrid" + id="grid1158" + spacingx="16.933333" + spacingy="16.933333" + empcolor="#3fff3f" + empopacity="0.25098039" + enabled="true" + color="#52ff3f" + opacity="0.1254902" + originx="0" + originy="0" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>n³ logo</dc:title> + <dc:creator> + <cc:Agent> + <dc:title>Léo Villeveygoux</dc:title> + </cc:Agent> + </dc:creator> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="fg" + transform="translate(1.6666665e-6,16.933336)" + style="display:inline"> + <path + sodipodi:nodetypes="ccccccccccccccccscccsccccccccccccccccscccccccccccccscccccsccsccccc" + inkscape:connector-curvature="0" + id="path4096" + d="m 7.5563837,-16.683514 c -6.37e-4,4e-5 -0.06582,0.008 -0.08984,0.0137 -0.04805,0.0106 -0.07759,0.0228 -0.101563,0.0332 -0.04795,0.0209 -0.07078,0.0351 -0.0918,0.0488 -0.04203,0.0274 -0.06833,0.0495 -0.0957,0.0723 -0.05474,0.0455 -0.10969,0.0967 -0.175781,0.16016 -0.132182,0.127 -0.300521,0.29823 -0.492188,0.50195 -0.146719,0.15595 -0.277344,0.28701 -0.388672,0.39649 -0.0057,-0.003 -0.0078,-0.004 -0.01367,-0.008 -0.197393,-0.11237 -0.466421,-0.2688 -0.753907,-0.4414 -0.292561,-0.17566 -0.563176,-0.33604 -0.765625,-0.45313 -0.101224,-0.0585 -0.185684,-0.10714 -0.25,-0.14258 -0.03216,-0.0177 -0.05714,-0.0313 -0.08398,-0.0449 -0.01342,-0.007 -0.02769,-0.0122 -0.04883,-0.0215 -0.01057,-0.005 -0.02139,-0.0114 -0.04492,-0.0195 -0.01177,-0.004 -0.02683,-0.008 -0.05078,-0.0137 -0.02395,-0.006 -0.124654,-0.0156 -0.125,-0.0156 -0.05674,0 -0.203301,0.0121 -0.333985,0.10937 -0.130683,0.0973 -0.184045,0.2292 -0.205078,0.30469 -0.04207,0.15099 -0.02811,0.20268 -0.02539,0.29102 0.0054,0.17668 0.02754,0.44302 0.07031,0.95703 0.02009,0.24144 0.03761,0.47757 0.05078,0.66211 0.01317,0.18453 0.02149,0.35714 0.02149,0.32617 -2.5e-5,-0.0716 0.0084,-0.0136 -0.08984,0.12305 -0.09824,0.13666 -0.280681,0.34121 -0.548828,0.60546 -0.331583,0.32677 -0.546477,0.51342 -0.689453,0.83204 -0.142977,0.31862 -0.12766,0.605 -0.128907,1.01953 -0.0019,0.65046 0.02339,0.50097 -0.0957,0.84179 -0.144761,0.41429 -0.289011,0.6739003 -0.884765,1.5566403 -0.27946103,0.41409 -0.54683003,0.82021 -0.66601603,1.04493 -0.389514,0.73442 -0.574283,1.51442 -0.394531,2.20507 0.234069,0.89938 1.04359803,1.46551 2.00976503,1.57227 0.08937,0.18231 0.157509,0.38673 0.316407,0.48242 h 0.002 c 0.344505,0.20741 0.757366,0.16421 1.074218,0.0137 0.316853,-0.15053 0.584433,-0.40119 0.802735,-0.72265 0.11807,-0.17388 0.279753,-0.46209 0.423828,-0.73828 0.267813,-0.5134 0.424453,-0.75918 0.673828,-0.98438 0.249375,-0.2252 0.638049,-0.4572 1.359375,-0.83203 0.492658,-0.25601 0.795852,-0.45655 1.134766,-0.66797 -3.2e-5,0.0656 0.0067,0.12864 0.0039,0.18945 -0.02949,0.65113 -0.155996,1.13801 -0.449219,1.72266 -0.266512,0.53141 -0.429772,0.70223 -1.361329,1.54102 -0.567672,0.51114 -0.922548,0.84685 -1.177734,1.16015 -0.255186,0.31331 -0.40187,0.6268 -0.484375,0.94336 -0.153164,0.58764 -0.184601,1.08693002 -0.164063,1.55274002 L 4.2498067,-3.675972e-6 H 16.933332 L 16.919662,-1.2538237 c -0.0264,-2.50458 -0.581284,-5.74387 -1.388672,-8.16016 -0.567382,-1.6980103 -1.186678,-2.8095203 -2.121093,-3.7480403 -0.824138,-0.82775 -1.808365,-1.36141 -2.937501,-1.58985 -0.460672,-0.0932 -1.2090663,-0.0911 -1.8593753,-0.0488 l 0.216796,-0.0137 -0.277343,-0.60742 c -0.129838,-0.28446 -0.245022,-0.52486 -0.335938,-0.70312 -0.04546,-0.0891 -0.0838,-0.16151 -0.121094,-0.22461 -0.01865,-0.0315 -0.03654,-0.0597 -0.06445,-0.0977 -0.01396,-0.019 -0.02894,-0.0405 -0.0625,-0.0742 -0.01678,-0.0169 -0.03866,-0.0381 -0.07617,-0.0644 -0.01876,-0.0132 -0.04084,-0.0276 -0.07227,-0.043 -0.03143,-0.0154 -0.12448,-0.0429 -0.125,-0.043 h -0.0039 l -0.0019,-0.002 c -5.15e-4,-10e-5 -0.132175,-0.01 -0.132812,-0.01 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + inkscape:connector-curvature="0" + id="path4098" + d="m 6.4080867,-3.7690437 c 0.938534,-0.84507 1.188659,-1.11552 1.48007,-1.69657 0.320921,-0.63988 0.47318,-1.22312 0.505483,-1.9363 0.02009,-0.44367 -0.02591,-0.99383 -0.08833,-1.05625 -0.01983,-0.0198 -0.09943,0.0146 -0.196664,0.0851 -0.362858,0.26298 -0.902924,0.58065 -1.625708,0.95624 -1.458811,0.75805 -1.708252,0.98331 -2.259881,2.04078 -0.136798,0.26224 -0.312882,0.57127 -0.391298,0.68675 -0.348405,0.51305 -0.842285,0.74905 -1.167444,0.55329 -0.136991,-0.0825 -0.203307,-0.18758 -0.23386,-0.37066 -0.0206,-0.1234 -0.02893,-0.12948 -0.189186,-0.13815 -0.869164,-0.047 -1.47390803,-0.49276 -1.66510103,-1.22739 -0.125725,-0.48307 0.0075,-1.1762 0.35022,-1.82239 0.076,-0.14329 0.36220403,-0.59246 0.63600503,-0.99816 0.601277,-0.89093 0.78603,-1.21845 0.946654,-1.6781303 0.120217,-0.34405 0.12288,-0.36238 0.124815,-1.01425 0.0025,-0.83103 -0.0022,-0.82067 0.662064,-1.47528 0.558243,-0.55015 0.795236,-0.87983 0.795158,-1.10615 -2.8e-5,-0.0813 -0.0332,-0.54616 -0.07371,-1.033 -0.08546,-1.02692 -0.08733,-1.08802 -0.03345,-1.08802 0.02217,0 0.515642,0.2854 1.096601,0.63421 0.58096,0.34881 1.083868,0.63511 1.117573,0.63621 0.03371,10e-4 0.347056,-0.30174 0.696336,-0.67299 0.374566,-0.39812 0.657987,-0.67043 0.690965,-0.66387 0.03324,0.007 0.230015,0.39256 0.485181,0.95161 l 0.429268,0.94049 0.146868,-0.01 c 0.609325,-0.0396 1.4254303,-0.0214 1.7210693,0.0384 1.033646,0.20912 1.909331,0.68342 2.667218,1.44463 0.872014,0.87585 1.440968,1.88647 1.994305,3.5424503 0.784909,2.34901 1.335797,5.56889 1.361403,7.99842 l 0.0076,0.71920002 H 4.7566417 c -0.01888,-0.4282 0.0079,-0.85904002 0.147789,-1.39575002 0.139882,-0.53671 0.378578,-0.83142 1.503656,-1.84447 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /> + <path + inkscape:connector-curvature="0" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;fill:url(#radialGradient4190);fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + d="m 14.019296,-0.52863368 c -0.03236,-0.37147 -0.05679,-0.71947002 -0.168981,-1.35909002 -0.15856,-0.90396 -0.383199,-2.00523 -0.634071,-3.1042 -0.25087,-1.09897 -0.528185,-2.19572 -0.792716,-3.09077 -0.264533,-0.89505 -0.516175,-1.58828 -0.715719,-1.8805 -0.190129,-0.2784503 -0.377891,-0.5254103 -0.572058,-0.7420803 -0.194167,-0.21666 -0.394798,-0.40292 -0.611333,-0.5612 -0.216532,-0.15828 -0.448667,-0.28843 -0.7058983,-0.39171 -0.131273,-0.0527 -0.29119,-0.0793 -0.436667,-0.11834 0.07747,0.0695 0.156587,0.13071 0.227892,0.22944 0.09939,0.19294 0.158695,0.41322 0.167431,0.65319 0.0087,0.23998 -0.03275,0.49928 -0.135908,0.77102 -0.103153,0.2717303 -0.267553,0.5556703 -0.503845,0.8438703 -0.233789,0.28516 -0.540714,0.57454 -0.925525,0.86093 0.03184,-0.0131 0.08412,-0.0497 0.09353,-0.0403 0.06242,0.0624 0.108457,0.6126 0.08837,1.05627 -0.0323,0.71318 -0.184474,1.29644 -0.505395,1.93632 -0.291411,0.58105 -0.541995,0.85146 -1.48053,1.69653 -1.125078,1.01305 -1.363385,1.30814 -1.503267,1.84485 -0.139889,0.53671 -0.166675,0.96758002 -0.147795,1.39578002 z" + id="path4106" /> + <path + inkscape:connector-curvature="0" + id="path4116" + d="m 7.5855797,-16.154034 c -0.03298,-0.007 -0.316348,0.26592 -0.690914,0.66404 -0.34928,0.37125 -0.662888,0.67383 -0.696598,0.67283 -0.0337,-10e-4 -0.536801,-0.28733 -1.117761,-0.63614 -0.58096,-0.34881 -1.074404,-0.63407 -1.096574,-0.63407 -0.05388,0 -0.05187,0.0609 0.03359,1.08779 0.04051,0.48684 0.07387,0.95171 0.0739,1.03301 7.7e-5,0.22632 -0.237058,0.55625 -0.795301,1.1064 -0.664264,0.65461 -0.659475,0.64381 -0.661975,1.47484 -0.0019,0.65187 -0.0048,0.67036 -0.125057,1.01441 -0.160624,0.4596803 -0.345435,0.7870003 -0.946712,1.6779303 -0.273801,0.4057 -0.55962,0.8551 -0.63562003,0.99839 -0.34272,0.64619 -0.476091,1.33956 -0.350366,1.82263 0.191193,0.73463 0.79585103,1.18032 1.66501403,1.22732 0.08533,0.005 0.123824,0.005 0.147795,0.0264 0.474897,-0.71432 0.990332,-1.57106 1.609204,-2.52181 0.642554,-0.98713 1.335583,-2.00634 2.022099,-2.85977 0.343258,-0.4267203 0.685028,-0.8121303 1.018026,-1.1312003 0.332998,-0.31907 0.65726,-0.57187 0.965832,-0.73381 0.154287,-0.081 0.304578,-0.13915 0.450102,-0.17156 0.145524,-0.0324 0.286173,-0.0389 0.421163,-0.0165 0.13499,0.0223 0.264371,0.0733 0.387056,0.15658 0.04214,0.0286 0.07884,0.0836 0.119373,0.11989 0.01427,0.004 0.02839,0.008 0.04289,0.0114 l -0.920357,-2.49752 -0.0047,5.1e-4 -0.428914,-0.94051 c -0.255166,-0.55905 -0.452002,-0.94436 -0.485242,-0.95136 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;fill:url(#radialGradient4200);fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /> + <path + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;fill:url(#radialGradient4206);fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + d="m 8.5043867,-14.262674 1.165911,2.68461 c 0.133392,0.0334 0.6612493,0.21144 0.7796483,0.25898 2.649479,0.14717 5.883653,2.4870503 2.584913,-1.46978 -0.757881,-0.76121 -1.633374,-1.23575 -2.667019,-1.44487 -0.29564,-0.0598 -1.1120183,-0.0778 -1.7213433,-0.0382 z" + id="path4122" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + <path + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;fill:url(#radialGradient4208);fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + d="m 6.4080867,-3.7690437 c 3.9106623,-2.0517737 6.5564953,-2.316357 9.7896183,-0.1353155 0.116365,0.9327131 0.184394,1.8392243 0.193006,2.6563355 l 0.0076,0.71920002 H 4.7566417 c -0.01888,-0.4282 0.0079,-0.85904002 0.147789,-1.39575002 0.139882,-0.53671 0.378578,-0.83142 1.503656,-1.84447 z" + id="path4124" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + <path + style="display:inline;opacity:0.33200001;fill:url(#radialGradient4271);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 2.3150987,-4.6632687 c 0.84336,-0.578776 1.653646,-2.182813 1.670183,-1.852083 0.01654,0.330729 -0.496094,2.645833 -1.35599,2.067057 -0.859896,-0.578776 -0.314193,-0.214974 -0.314193,-0.214974 z" + id="path4262" + inkscape:connector-curvature="0" /> + <path + sodipodi:nodetypes="ccscccsccscsccccc" + inkscape:connector-curvature="0" + d="m 6.8791644,-6.5799967 c 0.277182,-0.0747 0.617361,-0.14533 1.020535,-0.21177 0.411575,-0.0665 0.856747,-0.0997 1.335516,-0.0997 0.470371,0 0.8609466,0.0664 1.1717256,0.19932 0.310781,0.12456 0.554366,0.30725 0.730754,0.54808 0.18479,0.23253 0.314982,0.51489 0.390577,0.8470599 0.0756,0.32387 0.113393,0.68512 0.113393,1.08374 v 3.1548598 h -1.5875 v -2.9555598 c 0,-0.5481 -0.06013,-0.94256 -0.1803996,-1.18338 -0.120264,-0.2408299 -0.364802,-0.3612399 -0.733616,-0.3612399 -0.112248,0 -0.224495,0.004 -0.336742,0.0125 -0.10423,0.008 -0.216478,0.0208 -0.336743,0.0373 v 4.4503697 h -1.5875 z" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.11111069px;line-height:80.00000119%;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono Bold';letter-spacing:0px;word-spacing:0px;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:0.23356323px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="path4273" /> + <path + sodipodi:nodetypes="ccscccsccscsccccc" + inkscape:connector-curvature="0" + d="m 7.4083317,-6.0508267 c 0.277182,-0.0747 0.617361,-0.14533 1.020535,-0.21177 0.411575,-0.0665 0.856747,-0.0997 1.335516,-0.0997 0.4703713,0 0.8609463,0.0664 1.1717253,0.19932 0.310781,0.12456 0.554366,0.30725 0.730754,0.54808 0.18479,0.23253 0.314982,0.5148899 0.390577,0.8470599 0.0756,0.32387 0.113393,0.68512 0.113393,1.08374 v 2.6257298 h -1.5875 v -2.2633598 c 0,-0.51127 -0.06013,-0.87922 -0.180399,-1.10385 -0.120264,-0.22465 -0.364802,-0.33697 -0.7336163,-0.33697 -0.112248,0 -0.224495,0.004 -0.336742,0.0117 -0.10423,0.007 -0.216478,0.0194 -0.336743,0.0348 v 3.6577098 h -1.5875 z" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.11111069px;line-height:80.00000119%;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono Bold';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.23356323px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="path4158" /> + <path + inkscape:connector-curvature="0" + id="path4160" + d="m 2.2657997,-6.5970811 c 2.39e-4,-0.13158 -0.102021,-0.29549 -0.222752,-0.3571395 -0.367285,-0.18754 -0.694817,0.1635095 -0.738403,0.7914195 -0.01108,0.15956 -0.0093,0.31791 0.0038,0.35187 0.0276,0.071 0.04155,0.0639 0.42986,-0.20266 0.388306,-0.26657 0.527201,-0.43437 0.527446,-0.58349 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /> + <path + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;opacity:0.44600004;fill:url(#radialGradient4260);fill-opacity:1;stroke:none;stroke-width:3.37220645;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + d="m 5.2460787,-10.568226 c 0.812188,-0.809811 1.765374,-2.190004 1.765374,-2.556274 0,-0.0838 -0.135642,-0.203925 -0.301459,-0.267015 -0.752294,-0.286133 -1.733457,0.02549 -2.369685,0.748534 -0.347846,0.396158 -0.900949,0.953943 -1.423035,3.0038925 -0.04718,0.1852661 -0.424024,0.9086143 0.377177,0.4985965 0.801204,-0.4100179 1.308944,-0.7869297 1.951628,-1.427734 z" + id="path4252" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ssccscs" /> + <path + inkscape:connector-curvature="0" + id="path4162" + d="m 4.5914317,-10.992111 c 0.254897,-0.254151 0.554045,-0.687311 0.554045,-0.802261 0,-0.0263 -0.04257,-0.064 -0.09461,-0.0838 -0.2361,-0.0898 -0.544028,0.008 -0.743702,0.23492 -0.109168,0.12433 -0.747614,1.164311 -0.747614,1.217801 0,0.06 0.167933,0.0101 0.419382,-0.11858 0.25145,-0.12868 0.410799,-0.24697 0.612499,-0.44808 z" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.05833328;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" /> + <path + inkscape:connector-curvature="0" + id="path4164" + d="m 9.2282597,-13.755752 a 0.26460981,0.26460981 0 1 0 0.05273,0.52539 c 1.2306523,-0.12455 2.0604593,0.20175 2.7285163,0.76758 0.668056,0.56582 1.165386,1.39097 1.630859,2.255861 0.179199,0.3329602 0.577259,1.4975302 0.927734,3.0839804 0.350476,1.5864495 0.671479,3.6240594 0.771485,5.8124992 a 0.26490778,0.26490778 0 1 0 0.529297,-0.0234 c -0.101686,-2.2251898 -0.428845,-4.2894697 -0.785157,-5.9023392 -0.356311,-1.6128702 -0.723527,-2.7469202 -0.978515,-3.2207004 -0.472697,-0.878311 -0.99272,-1.763511 -1.753906,-2.408211 -0.761187,-0.6447 -1.771873,-1.02737 -3.1230473,-0.89062 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:80.00000119%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.5291667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + sodipodi:nodetypes="ccccccccccc" + inkscape:connector-curvature="0" + id="path4275" + d="m 13.309647,-10.683461 c -3.5259213,0.8942402 -2.467588,6.4504898 0.896553,5.2949099 0.277432,-0.0861 0.529922,-0.20786 0.742188,-0.38476 l 0.0059,-0.006 0.0078,-0.008 c 0.200661,-0.17837 0.36145,-0.4014 0.457031,-0.65235 0.08874,-0.23296 -0.07914,-0.44979 -0.07914,-0.7037295 -0.264584,-1.5875002 -0.264584,-2.1166602 -1.157186,-3.4173504 -0.0012,-3.9e-4 -0.0027,3.9e-4 -0.0039,0 -0.266042,-0.0881 -0.551145,-0.12305 -0.86914,-0.12305 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.11111069px;line-height:80.00000119%;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono Bold';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.16429496;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> + <path + inkscape:connector-curvature="0" + id="path4168" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.11111069px;line-height:80.00000119%;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono Bold';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.16429496;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + d="m 13.205251,-6.7970011 q 0.368361,0 0.506496,-0.11511 0.149645,-0.1151195 0.149645,-0.2762795 0,-0.4144001 -0.725209,-0.4144001 h -0.425918 v -0.8403301 h 0.425918 q 0.241736,0 0.391383,-0.0921 0.149647,-0.0921 0.149647,-0.28779 0,-0.32231 -0.448939,-0.32231 -0.241738,0 -0.471962,0.0806 -0.230226,0.0806 -0.448941,0.17266 l -0.391382,-0.80578 q 0.253249,-0.16115 0.63312,-0.27626 0.391383,-0.1266202 0.759743,-0.1266202 0.414405,0 0.690677,0.0921 0.28778,0.0921 0.46045,0.2532602 0.172659,0.14964 0.241735,0.36836 0.06909,0.20721 0.06909,0.43744 0,0.2187 -0.103625,0.41439 -0.09207,0.18419 -0.322314,0.3798801 0.333821,0.14965 0.471953,0.4144 0.149648,0.25326 0.149648,0.5640601 0,0.2877795 -0.09208,0.5295095 -0.09207,0.24173 -0.299287,0.42592 -0.207203,0.17268 -0.541028,0.27628 -0.333827,0.10365 -0.817301,0.10365 -0.345337,0 -0.748232,-0.0691 -0.391384,-0.0806 -0.621608,-0.20719 l 0.241736,-0.9093995 q 0.345339,0.1496595 0.587075,0.1956995 0.241736,0.0345 0.529518,0.0345 z" /> + </g> +</svg> diff --git a/nnn/misc/macos-legacy/mach_gettime.c b/nnn/misc/macos-legacy/mach_gettime.c @@ -0,0 +1,42 @@ +#include "mach_gettime.h" +#include <mach/mach_time.h> + +#define MT_NANO (+1.0E-9) +#define MT_GIGA UINT64_C(1000000000) + +// TODO create a list of timers, +static double mt_timebase = 0.0; +static uint64_t mt_timestart = 0; + +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + kern_return_t retval = KERN_SUCCESS; + + if (clk_id == TIMER_ABSTIME) { + if (!mt_timestart) { // only one timer, initialized on the first call to the TIMER + mach_timebase_info_data_t tb; + mach_timebase_info(&tb); + mt_timebase = tb.numer; + mt_timebase /= tb.denom; + mt_timestart = mach_absolute_time(); + } + + double diff = (mach_absolute_time() - mt_timestart) * mt_timebase; + tp->tv_sec = diff * MT_NANO; + tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA); + } else { // other clk_ids are mapped to the coresponding mach clock_service + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), clk_id, &cclock); + retval = clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + } + + return retval; +} + +/* Copyright (c) 2015-2018 Alf Watt - Open Source - https://opensource.org/licenses/MIT */ diff --git a/nnn/misc/macos-legacy/mach_gettime.h b/nnn/misc/macos-legacy/mach_gettime.h @@ -0,0 +1,28 @@ +#ifndef mach_time_h +#define mach_time_h + +#include <sys/types.h> +#include <sys/_types/_timespec.h> +#include <mach/mach.h> +#include <mach/clock.h> + +/* The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR + being appropriate or not. + http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html */ + +// XXX only supports a single timer +#define TIMER_ABSTIME -1 +#define CLOCK_REALTIME CALENDAR_CLOCK +#define CLOCK_MONOTONIC SYSTEM_CLOCK + +typedef int clockid_t; + +/* the mach kernel uses struct mach_timespec, so struct timespec + is loaded from <sys/_types/_timespec.h> for compatability */ +// struct timespec { time_t tv_sec; long tv_nsec; }; + +int clock_gettime(clockid_t clk_id, struct timespec *tp); + +#endif + +/* Copyright (c) 2015-2018 Alf Watt - Open Source - https://opensource.org/licenses/MIT */ diff --git a/nnn/misc/musl/musl-static-ubuntu.sh b/nnn/misc/musl/musl-static-ubuntu.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env sh + +# Statically compile nnn with netbsd-curses, musl-fts and musl libc on Ubuntu +# +# netbsd-curses: https://github.com/sabotage-linux/netbsd-curses +# musl-fts: https://github.com/void-linux/musl-fts +# musl libc: https://www.musl-libc.org/ +# +# Dependencies: git +# +# Usage: musl-static-ubuntu.sh [no_run] +# # optional argument - do not to execute the binary after compilation +# +# Notes: +# - run the script within the top-level nnn directory +# - installs musl & gits netbsd-curses, musl-fts libs +# +# Tested on Ubuntu 20.04 x86_64 +# Author: Arun Prakash Jana + +# Exit on first failure +set -e + +# Output binary name +BIN=nnn-musl-static + +# Install musl +sudo apt install -y --no-install-recommends musl musl-dev musl-tools + +# Get netbsd-curses +[ ! -d "./netbsd-curses" ] && git clone https://github.com/sabotage-linux/netbsd-curses + +# Enter the library dir +cd netbsd-curses + +# Get the last known working version +git checkout v0.3.2 + +# Compile the static netbsd-curses libraries +if [ ! -d "./libs" ]; then + mkdir libs +else + rm -vf libs/* +fi +make CC=musl-gcc CFLAGS=-O3 LDFLAGS=-static all-static -j$(($(nproc)+1)) +cp -v libcurses/libcurses.a libterminfo/libterminfo.a libs/ + +# Get musl-fts library +cd .. +[ ! -d "./musl-fts" ] && git clone https://github.com/void-linux/musl-fts --depth=1 + +# Compile the static musl-fts library +cd musl-fts +./bootstrap.sh +./configure +make CC=musl-gcc CFLAGS=-O3 LDFLAGS=-static -j$(($(nproc)+1)) + +# Compile nnn +cd .. +[ -e "./netbsd-curses" ] || rm "$BIN" +musl-gcc -O3 -DNORL -DNOMOUSE -std=c11 -Wall -Wextra -Wshadow -I./netbsd-curses/libcurses -I./musl-fts -o "$BIN" src/nnn.c -Wl,-Bsymbolic-functions -lpthread -L./netbsd-curses/libs -lcurses -lterminfo -static -L./musl-fts/.libs -lfts +strip "$BIN" + +if [ -z "$1" ]; then + # Run the binary with it selected + ./"$BIN" -d "$BIN" +fi diff --git a/nnn/misc/natool/natool b/nnn/misc/natool/natool @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# ############################################################################# +# natool: a wrapper script to patool to list, extract and create archives +# +# usage: natool [-a] [-l] [-x] [archive] [file/dir] +# +# Examples: +# - create archive : natool -a archive.7z archive_dir +# - list archive : natool -l archive.7z +# - extract archive: natool -x archive.7z +# +# Brief: +# natool is written to integrate patool (instead of the default atool) with nnn +# A copies of this file should be dropped somewhere in $PATH as atool +# +# Author: Arun Prakash Jana +# Email: engineerarun@gmail.com +# Homepage: https://github.com/jarun/nnn +# Copyright © 2019 Arun Prakash Jana +# ############################################################################# + +import sys +from subprocess import Popen, PIPE + +if len(sys.argv) < 3: + print('usage: natool [-a] [-l] [-x] [archive] [file/dir]') + sys.exit(0) + +if sys.argv[1] == '-a': + cmd = ['patool', '--non-interactive', 'create', sys.argv[2]] + cmd.extend(sys.argv[3:]) +elif sys.argv[1] == '-l': + cmd = ['patool', '--non-interactive', 'list'] + cmd.extend(sys.argv[2:]) +elif sys.argv[1] == '-x': + cmd = ['patool', '--non-interactive', 'extract'] + cmd.extend(sys.argv[2:]) +else: + sys.exit(0) + +pipe = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) +out, err = pipe.communicate() +print(out.decode()) +print(err.decode()) diff --git a/nnn/misc/packagecore/packagecore.yaml b/nnn/misc/packagecore/packagecore.yaml @@ -0,0 +1,141 @@ +name: nnn +maintainer: Arun Prakash Jana <engineerarun@gmail.com> +license: BSD 2-Clause +summary: The unorthodox terminal file manager. +homepage: https://github.com/jarun/nnn +commands: + install: + - make PREFIX="/usr" strip install DESTDIR="${BP_DESTDIR}" +packages: + centos7.5: + builddeps: + - make + - gcc + - pkgconfig + - ncurses-devel + - readline-devel + deps: + - ncurses + - readline + commands: + pre: + - yum install epel-release + centos7.6: + builddeps: + - make + - gcc + - pkgconfig + - ncurses-devel + - readline-devel + deps: + - ncurses + - readline + commands: + pre: + - yum install epel-release + centos7.7: + builddeps: + - make + - gcc + - pkgconfig + - ncurses-devel + - readline-devel + deps: + - ncurses + - readline + commands: + pre: + - yum install epel-release + centos8.0: + builddeps: + - make + - gcc + - pkgconfig + - ncurses-devel + - readline-devel + deps: + - ncurses + - readline + commands: + pre: + - yum install epel-release + debian9: + builddeps: + - make + - gcc + - pkg-config + - libncursesw5-dev + - libreadline-dev + deps: + - libncursesw5 + - readline-common + debian10: + builddeps: + - make + - gcc + - pkg-config + - libncurses-dev + - libreadline-dev + deps: + - libncursesw6 + - readline-common + fedora32: + builddeps: + - make + - gcc + - pkg-config + - ncurses-devel + - readline-devel + deps: + - ncurses + - readline +# opensuse15.2: +# builddeps: +# - make +# - gcc +# - pkg-config +# - readline-devel +# - ncurses-devel +# deps: +# - libncurses6 +# - libreadline7 +# opensuse.tumbleweed: +# builddeps: +# - make +# - gcc +# - pkg-config +# - readline-devel +# - ncurses-devel +# deps: +# - libncurses6 +# - libreadline8 + ubuntu16.04: + builddeps: + - make + - gcc + - pkg-config + - libncursesw5-dev + - libreadline6-dev + deps: + - libncursesw5 + - libreadline6 + ubuntu18.04: + builddeps: + - make + - gcc + - pkg-config + - libncursesw5-dev + - libreadline-dev + deps: + - libncursesw5 + - libreadline7 + ubuntu20.04: + builddeps: + - make + - gcc + - pkg-config + - libncurses-dev + - libreadline-dev + deps: + - libncursesw6 + - libreadline8 diff --git a/nnn/misc/quitcd/quitcd.bash_zsh b/nnn/misc/quitcd/quitcd.bash_zsh @@ -0,0 +1,28 @@ +n () +{ + # Block nesting of nnn in subshells + if [ -n $NNNLVL ] && [ "${NNNLVL:-0}" -ge 1 ]; then + echo "nnn is already running" + return + fi + + # The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set) + # To cd on quit only on ^G, either remove the "export" as in: + # NNN_TMPFILE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.lastd" + # (or, to a custom path: NNN_TMPFILE=/tmp/.lastd) + # or, export NNN_TMPFILE after nnn invocation + export NNN_TMPFILE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.lastd" + + # Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn + # stty start undef + # stty stop undef + # stty lwrap undef + # stty lnext undef + + nnn "$@" + + if [ -f "$NNN_TMPFILE" ]; then + . "$NNN_TMPFILE" + rm -f "$NNN_TMPFILE" > /dev/null + fi +} diff --git a/nnn/misc/quitcd/quitcd.csh b/nnn/misc/quitcd/quitcd.csh @@ -0,0 +1,14 @@ +# NOTE: set NNN_TMPFILE correctly if you use 'XDG_CONFIG_HOME' + +# The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set) +# To cd on quit only on ^G, set NNN_TMPFILE after nnn invocation +# A custom path can also be set e.g. set NNN_TMPFILE=/tmp/.lastd +set NNN_TMPFILE=~/.config/nnn/.lastd + +# Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn +# stty start undef +# stty stop undef +# stty lwrap undef +# stty lnext undef + +alias n 'nnn; source "$NNN_TMPFILE"; rm -f "$NNN_TMPFILE"' diff --git a/nnn/misc/quitcd/quitcd.fish b/nnn/misc/quitcd/quitcd.fish @@ -0,0 +1,37 @@ +# Rename this file to match the name of the function +# e.g. ~/.config/fish/functions/n.fish +# or, add the lines to the 'config.fish' file. + +function n --wraps nnn --description 'support nnn quit and change directory' + # Block nesting of nnn in subshells + if test -n "$NNNLVL" + if [ (expr $NNNLVL + 0) -ge 1 ] + echo "nnn is already running" + return + end + end + + # The behaviour is set to cd on quit (nnn checks if NNN_TMPFILE is set) + # To cd on quit only on ^G, remove the "-x" as in: + # set NNN_TMPFILE "$XDG_CONFIG_HOME/nnn/.lastd" + # (or, to a custom path: set NNN_TMPFILE "/tmp/.lastd") + # or, export NNN_TMPFILE after nnn invocation + if test -n "$XDG_CONFIG_HOME" + set -x NNN_TMPFILE "$XDG_CONFIG_HOME/nnn/.lastd" + else + set -x NNN_TMPFILE "$HOME/.config/nnn/.lastd" + end + + # Unmask ^Q (, ^V etc.) (if required, see `stty -a`) to Quit nnn + # stty start undef + # stty stop undef + # stty lwrap undef + # stty lnext undef + + nnn $argv + + if test -e $NNN_TMPFILE + source $NNN_TMPFILE + rm $NNN_TMPFILE + end +end diff --git a/nnn/misc/test/benchmark.sh b/nnn/misc/test/benchmark.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# Usage: ./misc/test/benchmark.sh ./nnn /tmp/testdir1 ./testdir2 ... +# +# Don't forget to build nnn in benchmark mode: make O_BENCH=1 + +# Use a test dir filled with genfiles.sh to get interesting output +# (or maybe /usr/lib/) + +LANG=C + +TIME_VAL=${TIME_VAL:-"real"} + +SAMPLES=${SAMPLES:-100} + +EXE=$1 + +bench_val () { + (time "$1" "$2") 2>&1 |\ + awk '$1=="'"$TIME_VAL"'"{match($2, /[0-9]*\.[0-9]*/) ; print substr($2, RSTART, RLENGTH)}' +} + +bench_dir () { + i=$SAMPLES + printf "$2" + while [ $((i--)) -gt 0 ] ; do + printf "\t%s" "$(bench_val "$1" "$2")" + done + printf "\n" +} + +shift + +for dir in "$@" ; do + bench_dir "$EXE" "$dir" +done + diff --git a/nnn/misc/test/genfiles.sh b/nnn/misc/test/genfiles.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Generates 100000 files in the current directory + +i=1; while [ $i -le 100000 ]; do + mktemp -p . -t 'XXXXXXXXXXXXXXXXXXXXX' + i=$(( i + 1 )) +done diff --git a/nnn/misc/test/mktest.sh b/nnn/misc/test/mktest.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# Create test files and directories + +test -e outdir && { + echo "Remove 'outdir' and try again" + exit 1 +} + +mkdir -p outdir && cd outdir || exit 1 + +echo 'It works!' > normal.txt +echo 'Με δουλέβει;' > 'κοινό.txt' +ln -sf normal.txt ln-normal.txt +ln -sf normal.txt ln-normal +mkdir -p normal-dir +ln -sf normal-dir ln-normal-dir +ln -sf nowhere ln-nowhere +mkfifo mk-fifo +touch no-access && chmod 000 no-access +mkdir -p no-access-dir && chmod 000 no-access-dir +ln -sf ../normal.txt normal-dir/ln-normal.txt +ln -sf ../normal.txt normal-dir/ln-normal +echo 'int main(void) { *((char *)0) = 0; }' > ill.c +make ill > /dev/null +echo 'test/ill' > ill.sh +mkdir -p empty-dir +mkdir -p cage +echo 'chmod 000 test/cage' > cage/lock.sh +echo 'chmod 755 test/cage' > cage-unlock.sh +mkdir -p cage/lion +echo 'chmod 000 test/cage' > cage/lion/lock.sh +mkdir -p korean +touch 'korean/[ENG sub] PRODUCE48 울림ㅣ김채원ㅣ행복 나눠주는 천사소녀 @자기소개_1분 PR 180615 EP.0-Cgnmr6Fd82' +touch 'korean/[ENG sub] PRODUCE48 [48스페셜] 윙크요정, 내꺼야!ㅣ김채원(울림) 180615 EP.0-K7ulTiuJZK8.mp4' +touch 'korean/[FULL ENG SUB] 181008 SALEWA x IZ_ONE Long Padding Photoshoot Behind Film-[오늘의 시구] 아이즈원 (IZONE) 장원영&미야와키 사쿠라! 시구 시타! (10.06)-VmDl5eBJ3x0.mkv' +touch 'korean/IZ_ONE (아이즈원) - 1st Mini Album [COLOR_IZ] Highlight Medley-w9V2xFrYIgk.web' +touch 'korean/IZ_ONE (아이즈원) - 1st Mini Album [COLOR_IZ] MV TEASER 1-uhnJLBNBNto.mkv' +touch 'korean/IZ_ONE CHU [1회] ′순도 100%′ 우리즈원 숙소 생활 ★최초 공개★ 181025 EP.1-pcutrQN1Sbg.mkv' +touch 'korean/IZ_ONE CHU [1회_예고] 아이즈원 데뷔 준비 과정 ★독점 공개★ 아이즈원 츄 이번주 (목) 밤 11시 첫방송 181025' +touch 'korean/IZ_ONE CHU [1회] 도치기현 1호 이모 팬과의 만남! 181025 EP.1-5kYoReT5x44.mp4' +touch 'korean/IZ_ONE CHU [1회] ′12명 소녀들의 새로운 시작′ 앞으로 아이즈원 잘 부탁해♥ 181025 EP.1-RVNvgbdLQLQ' +touch 'korean/IZ_ONE CHU [1회] ′앗..그것만은!′ 자비없는 합숙생활 폭로전 181025 EP.1-AmP5KzpoI38.mkv' +touch 'korean/IZ_ONE CHU [1회] 휴게소 간식 내기 노래 맞히기 게임 181025 EP.1-LyNDKflpWYE.mp4' +touch 'korean/IZ_ONE CHU [1회] 2018 아이즈원 걸크러시능력시험 (feat. 치타쌤) 181025 EP.1-9qHWpbo0eB8.mp4' +touch 'korean/IZ_ONE CHU [1회] ′돼지요′ 아니죠, ′되지요′ 맞습니다! (feat. 꾸라먹방) 181025EP.1-WDLFqMWiKn' +touch 'korean/IZ_ONE CHU [1회] ′두근두근′ 첫 MT를 앞둔 비글력 만렙의 아이즈원 181025 EP.1' +mkdir -p unicode +touch 'unicode/Malgudi Days - मालगुडी डेज - E05. Swami and Friends - स्वामी और उसके दोस्त (Part 1)' +touch 'unicode/Malgudi Days - मालगुडी डेज - E05. Swami and Friends - स्वामी और उसके दोस्त (Part 2)' +touch 'unicode/Malgudi Days - मालगुडी डेज - E05. Swami and Friends - स्वामी और उसके दोस्त (Part 3)' +chmod +x 'unicode/Malgudi Days - मालगुडी डेज - E05. Swami and Friends - स्वामी और उसके दोस्त (Part 2)' +touch 'unicode/Führer' +touch 'unicode/Eso eso aamar ghare eso ♫ এসো এসো আমার ঘরে এসো ♫ Swagatalakshmi Dasgupta' +touch 'max_chars_filename_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' diff --git a/nnn/misc/test/plot-bench.py b/nnn/misc/test/plot-bench.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# +# Usage: ./plot-bench.py datafile +# (where datafile is the output of benchmark.sh) + +import matplotlib.pyplot as plt +import sys + +def bench_file_to_lists(infile): + return [[float(entry) for entry in line.split('\t')[1:]] for line in infile.readlines()] + +def plot_data(data): + fig = plt.figure() + ax = fig.add_axes([0,0,1,1]) + ax.violinplot(data) + plt.savefig("plot.svg") + +filename = sys.argv[1] + +plot_data(bench_file_to_lists(open(filename))) diff --git a/nnn/nnn.1 b/nnn/nnn.1 @@ -0,0 +1,583 @@ +.Dd Nov 23, 2021 +.Dt NNN 1 +.Os +.Sh NAME +.Nm nnn +.Nd The unorthodox terminal file manager. +.Sh SYNOPSIS +.Nm +.Op Ar -aAcCdDeEfgHJKnQrRSuUVxh +.Op Ar -b key +.Op Ar -F val +.Op Ar -l val +.Op Ar -p file +.Op Ar -P key +.Op Ar -s name +.Op Ar -t secs +.Op Ar -T key +.Op Ar PATH +.Sh DESCRIPTION +.Nm +.Pq Nnn's Not Noice +is a performance-optimized, feature-packed fork of noice +.Em http://git.2f30.org/noice/ +with seamless desktop integration, simplified navigation, +.Em type-to-nav +mode with dir auto-enter, disk usage analyzer mode, bookmarks, +contexts, application launcher, familiar navigation shortcuts, +subshell spawning and much more. It remains a simple and +efficient file manager that stays out of your way. +.Pp +.Nm +opens the current working directory by default if +.Ar PATH +is not specified. +.Sh KEYBINDS +.Pp +Press +.Ql \&? +in +.Nm +to see the list of keybinds. +.Sh OPTIONS +.Pp +.Nm +supports the following options: +.Pp +.Fl a + auto-setup temporary NNN_FIFO (described in ENVIRONMENT section) +.Pp +.Fl A + disable directory auto-enter in type-to-nav mode +.Pp +.Fl "b key" + specify bookmark key to open +.Pp +.Fl c + indicates that the opener is a cli-only opener (overrides -e) +.Pp +.Fl C + 8-color scheme - color directories by context, disable file colors +.Pp +.Fl d + detail mode +.Pp +.Fl D + show directories in context color with \fBNNN_FCOLORS\fR set +.Pp +.Fl e + open text files in $VISUAL (else $EDITOR, fallback vi) [preferably CLI] +.Pp +.Fl E + use $EDITOR for internal undetached edits +.Pp +.Fl f + use readline history file +.Pp +.Fl "F val" + fifo notification mode + 0: notify as previewer, 1: notify as explorer +.Pp +.Fl g + use regex filters instead of substring match +.Pp +.Fl H + show hidden files +.Pp +.Fl i + show current file information in info bar (may be slow) +.Pp +.Fl J + disable auto-proceed on selection + (eg. selecting an entry will no longer move cursor to the next entry) +.Pp +.Fl K + test for keybind collision +.Pp +.Fl "l val" + number of lines to move per mouse wheel scroll +.Pp +.Fl n + start in type-to-nav mode +.Pp +.Fl o + open files only on Enter key +.Pp +.Fl "p file" + copy (or \fIpick\fR) selection to file, or stdout if file='-' +.Pp +.Fl "P key" + specify plugin key to run +.Pp +.Fl Q + disable confirmation on quit with multiple contexts active +.Pp +.Fl r + show cp, mv progress + (Linux-only, needs \fIadvcpmv\fR; '^T' shows the progress on BSD/macOS) +.Pp +.Fl R + disable rollover at edges (eg. pressing \fIdown\fR while on the last + entry will no longer move cursor to the first entry and vice\-versa) +.Pp +.Fl "s name" + load a session by name +.Pp +.Fl S + persistent session +.Pp +.Fl "t secs" + idle timeout in seconds to lock terminal +.Pp +.Fl "T key" + sort order + keys: 'a'u / 'd'u / 'e'xtension / 'r'everse / 's'ize / 't'ime / 'v'ersion + capitalize to reverse (except 'r') +.Pp +.Fl u + use selection if available, don't prompt to choose between selection and hovered entry +.Pp +.Fl U + show user and group names in status bar +.Pp +.Fl V + show version and exit +.Pp +.Fl x + show notifications on selection cp, mv, rm completion (requires \fI.ntfy\fR plugin) + copy path to system clipboard on selection (requires \fI.cbcp\fR plugin) + show xterm title (if non-picker mode) +.Pp +.Fl h + show program help and exit +.Sh CONFIGURATION +There is no configuration file. Associated files are at +.Pp +.Pa ${XDG_CONFIG_HOME:-$HOME/.config}/nnn/ +.Pp +Configuration is done using a few optional (set if you need) environment +variables. See ENVIRONMENT section. +.Pp +.Nm +uses \fIxdg-open\fR (on Linux), \fIopen(1)\fR (on macOS), \fIcygstart\fR on +(Cygwin) and \fIopen\fR on (Haiku) as the desktop opener. It's also possible +to specify a custom opener. See ENVIRONMENT section. +.Sh CONTEXTS +Open multiple locations with 4 contexts. The status is shown in the top left +corner: +.Pp +- the current context is in reverse video +.br +- other active contexts are underlined +.br +- rest are inactive +.Pp +A new context copies the state of the previous context. Each context can have +its own color. See ENVIRONMENT section. +.Sh SESSIONS +Sessions are a way to save and restore states of work. A session stores the +settings and contexts. Sessions can be loaded at runtime or with a program +option. +.Pp +- When a session is loaded at runtime, the last working state is saved +automatically to a dedicated "auto session" session file. Session option +\fIrestore\fR would restore the "auto session". +.br +- The persistent session option is global. If it is used, the last active session +will be updated with the final state at program quit. +.br +- The "auto session" is used in persistent session mode if no session is active. +.br +- Listing input stream and opening a bookmark by key have a higher priority to +session options (-s/-S). +.Pp +All the session files are located by session name in the directory +.Pp +\fB${XDG_CONFIG_HOME:-$HOME/.config}/nnn/sessions\fR +.Pp +"@" is the "auto session" file. +.Sh FILTERS +Filters are strings (or regex patterns) to find matching entries in the current +directory instantly (\fIsearch-as-you-type\fR). Matches are case-insensitive by +default. The last filter in each context is persisted at runtime or in saved +sessions. +.Pp +Special keys at filter prompt: +.Bd -literal +-------- + --------------------------------------- + Key | Function +-------- + --------------------------------------- + ^char | Usual keybind functionality + Esc | Exit filter prompt but skip dir refresh + Alt+Esc | Exit filter prompt and refresh dir +-------- + --------------------------------------- +.Ed +.Pp +Special keys at \fBempty filter prompt\fR: +.Bd -literal +------ + --------------------------------------- + Key | Function +------ + --------------------------------------- + ? | Show help and config screen + / | Toggle between string and regex + : | Toggle case-sensitivity + ^L | Clear filter (\fIif prompt is non-empty\fR) + | OR apply last filter + Bksp | Stay at filter prompt and refresh dir + Del | Stay at filter prompt and refresh dir +------ + --------------------------------------- +.Ed +.Pp +Additional special keys at \fBempty filter prompt\fR +in \fBtype-to-nav\fR mode: +.Bd -literal +------ + ------------------------ + Key | Function +------ + ------------------------ + ' | Go to first non-dir file + + | Toggle auto-proceed on open + , | Mark CWD + - | Go to last visited dir + . | Show hidden files + ; | Run a plugin by its key + = | Launch a GUI application + > | Export file list + @ | Visit start dir + ] | Show command prompt + ` | Visit / + ~ | Go HOME +------ + ------------------------ +.Ed +.Pp +Common regex use cases: +.Pp +(1) To list all matches starting with the filter expression, + start the expression with a '^' (caret) symbol. +.br +(2) Type '\\.mkv' to list all MKV files. +.br +(3) Use '.*' to match any character (\fIsort of\fR fuzzy search). +.br +(4) Exclude filenames having 'nnn' (compiled with PCRE lib): '^(?!nnn)' +.Pp +In the \fItype-to-nav\fR mode directories are opened in filter +mode, allowing continuous navigation. +.br +When there's a unique match and it's a directory, +.Nm +auto enters it in this mode. Use the relevant program option to disable this +behaviour. +.Sh SELECTION +.Nm +allows file selection across directories and contexts! +.Pp +There are 3 groups of keybinds to add files to selection: +.Pp +(1) hovered file selection toggle + - deselects if '+' is visible before the entry, else adds to selection +.br +(2) add a range of files to selection + - repeat the range key on the same entry twice to clear selection completely +.br +(3) add all files in the current directory to selection +.Pp +A selection can be edited, copied, moved, removed, archived or linked. +.Pp +Absolute paths of the selected files are copied to \fB.selection\fR file in +the config directory. The selection file is shared between multiple program +instances. Selection from multiple instances are not merged. The last instance +writing to the file overwrites earlier contents. If you have 2 instances of +.Nm +\fIopen\fR in 2 panes of a terminal multiplexer, you can select in one pane and +use the selection in the other pane. The selection gets cleared in the +.Nm +instance where the selection was made on mv/rm (but not on cp). +.Pp +.Nm +clears the selection after a successful operation with the selection. Plugins +are allowed to define the behaviour individually. +.Pp +To edit the selection use the _edit selection_ key. Editing doesn't end the +selection mode. You can add more files to the selection and edit the list +again. If no file is selected in the current session, this option attempts +to list the selection file. +.Sh FIND AND LIST +There are two ways to search and list: +.Pp +- feed a list of file paths as input +.br +- search using a plugin (e.g. \fIfinder\fR) and list the results +.Pp +File paths must be NUL-separated ('\\0'). Paths and can be relative to the +current directory or absolute. Invalid paths in the input are ignored. Input +limit is 65,536 paths or 256 MiB of data. +.Pp +To list the input stream, start +.Nm +by writing to its standard input. E.g., to list files in current +directory larger than +1M: +.Bd -literal + find -maxdepth 1 -size +1M -print0 | nnn +.Ed +.Pp +or redirect a list from a file: +.Bd -literal + nnn < files.txt +.Ed +.Pp +Handy bash/zsh shell function to list files by mime-type in current directory: +.Bd -literal + # to show video files, run: list video + + list () + { + find . -maxdepth 1 | file -if- | grep "$1" | awk -F: '{printf "%s\0", $1}' | nnn + } +.Ed +.Pp +A temporary directory will be created containing symlinks to the given +paths. Any action performed on these symlinks will be performed only on their +targets, after which they might become invalid. +.Pp +Right arrow or 'l' on a symlink in the listing dir takes to the target +file. Press '-' to return to the listing dir. Press 'Enter' to open the symlink. +.Pp +Listing input stream can be scripted. It can be extended to pick (option -p) +selected entries from the listed results. +.Sh BOOKMARKS +There are 2 ways (can be used together) to manage bookmarks. +.Pp +(1) Bookmark keys: See \fINNN_BMS\fR under \fIENVIORNMENT\fR section on how to + set bookmark keys. +.Pp +(2) Symlinked bookmarks: Symlinked bookmarks can be created at runtime + with the \fIB\fR key. They can also be manually created by adding symlinks + to bookmarked locations under the bookmarks directory in the nnn config + directory (~/.config/nnn/bookmarks). +.Pp +Pressing \fIb\fR will list all the bookmark keys set in NNN_BMS. Pressing +\fIEnter\fR at this prompt will take to the symlink bookmark directory, if +NNN_BMS is not set, it happens directly on bookmarks key. +.Sh UNITS +The minimum file size unit is byte (B). The rest are K, M, G, T, P, E, Z, Y +(powers of 1024), same as the default units in \fIls\fR. +.Sh ENVIRONMENT +The SHELL, VISUAL (else EDITOR) and PAGER environment variables are +used. A single combination of arguments is supported for SHELL and PAGER. +.Pp +\fBNNN_OPTS:\fR binary options to +.Nm +.Bd -literal + export NNN_OPTS="cEnrx" +.Ed +.Pp +\fBNNN_OPENER:\fR specify a custom file opener. +.Bd -literal + export NNN_OPENER=nuke + + NOTE: 'nuke' is a file opener available in the plugin repository. +.Ed +.Pp +\fBNNN_BMS:\fR bookmark string as \fIkey_char:location\fR pairs +separated by \fI;\fR: +.Bd -literal + export NNN_BMS="d:$HOME/Docs;u:/home/user/Cam Uploads;D:$HOME/Downloads/" +.Ed +.Pp +These bookmarks are listed in the help and config screen (key ?). +.Pp +\fBNNN_PLUG:\fR directly executable plugins as \fIkey_char:plugin\fR pairs +separated by \fI;\fR: +.Bd -literal + export NNN_PLUG='f:finder;o:fzopen;p:mocplay;d:diffs;t:nmount;v:imgview' + + NOTES: + 1. To run a plugin directly, press \fI;\fR followed by the key. + 2. Alternatively, combine with \fIAlt\fR (i.e. \fIAlt+key\fR). + 3. To skip directory refresh after running a plugin, prefix with \fB-\fR. + + export NNN_PLUG='p:-plugin' +.Ed +.Pp + To assign keys to arbitrary non-background cli commands and invoke like + plugins, add \fI!\fR before the command. +.Bd -literal + export NNN_PLUG='x:!chmod +x $nnn;g:!git log;s:!smplayer $nnn' + + To pick and run an unassigned plugin, press \fBEnter\fR at the plugin prompt. + To run a plugin at startup, use the option `-P` followed by the plugin key. + + NOTES: + 1. Use single quotes for $NNN_PLUG so $nnn is not interpreted + 2. (Again) add \fB!\fR before the command + 3. To disable directory refresh after running a \fIcommand as plugin\fR, + prefix with \fB-!\fR + 4. To skip user confirmation after command execution, suffix with \fB*\fR + Note: Do not use \fB*\fR with programs those run and exit e.g. cat + + export NNN_PLUG='y:-!sync*' + + 5. To run a \fIGUI app as plugin\fR, add a \fB&\fR after \fB!\fR + Note: \fI$nnn\fR must be the last argument in this case. + + export NNN_PLUG='m:-!&mousepad $nnn' + + 6. To show the output of run-and-exit commands which do not need user input, + add \fB|\fR (pipe) after \fB!\fR + Note: This option is incompatible with \fB&\fR (terminal output is masked + for GUI programs) and ignores \fB*\fR (output is already paged for user) + + export NNN_PLUG='m:-!|mediainfo $nnn;t:-!|tree -ps;l:-!|ls -lah --group-directories-first' + + EXAMPLES: + ------------------------------------ + ------------------------------------------------- + Key:Command | Description + ------------------------------------ + ------------------------------------------------- + c:!convert $nnn png:- | xclip -sel \ | Copy image to clipboard + clipboard -t image/png* | + e:-!sudo -E vim $nnn* | Edit file as root in vim + g:-!git diff | Show git diff + h:-!hx $nnn* | Open hovered file in hx hex editor + k:-!fuser -kiv $nnn* | Interactively kill process(es) using hovered file + l:-!git log | Show git log + n:-!vi /home/user/Dropbox/dir/note* | Take quick notes in a synced file/dir of notes + p:-!less -iR $nnn* | Page through hovered file in less + s:-!&smplayer -minigui $nnn | Play hovered media file, even unfinished download + x:!chmod +x $nnn | Make the hovered file executable + y:-!sync* | Flush cached writes + ------------------------------------ + ------------------------------------------------- + + Online docs: https://github.com/jarun/nnn/tree/master/plugins +.Ed +.Pp +\fBNNN_ORDER:\fR directory-specific sort key. +.Bd -literal + export NNN_ORDER='t:/home/user/Downloads;S:/tmp' + + NOTE: Sort keys can be a/d/e/r/s/t/v (see program option -T). + Capitalize to reverse (except 'r'). + Path must be absolute. +.Ed +.Pp +\fBNNN_COLORS:\fR string of color numbers for each context, e.g.: +.Bd -literal + # 8 color numbers: + # 0-black, 1-red, 2-green, 3-yellow, 4-blue (default), 5-magenta, 6-cyan, 7-white + export NNN_COLORS='1234' + + # xterm 256 color numbers (converted to hex, 2 symbols per context): + # see https://user-images.githubusercontent.com/1482942/93023823-46a6ba80-f5e1-11ea-9ea3-6a3c757704f4.png + export NNN_COLORS='#0a1b2c3d' + + # both (256 followed by 8 as fallback, separated by ';') + export NNN_COLORS='#0a1b2c3d;1234' + + NOTE: If only 256 colors are specified and the terminal doesn't support, default is used. +.Ed +.Pp +\fBNNN_FCOLORS:\fR specify file-type specific colors: +.Bd -literal + export NNN_FCOLORS='c1e2272e006033f7c6d6abc4' + + Specify file-specific colors in xterm 256 color hex numbers (2 symbols per color). + Order is strict, use 00 to omit/use default terminal color. Defaults: + + ------------------------- + --- + ------------- + Order | Hex | Color + ------------------------- + --- + ------------- + Block device | c1 | DarkSeaGreen1 + Char device | e2 | Yellow1 + Directory | 27 | DeepSkyBlue1 + Executable | 2e | Green1 + Regular | 00 | Normal + Hard link | 60 | Plum4 + Symbolic link | 33 | Cyan1 + Missing OR file details | f7 | Grey62 + Orphaned symbolic link | c6 | DeepPink1 + FIFO | d6 | Orange1 + Socket | ab | MediumOrchid1 + Unknown OR 0B regular/exe | c4 | Red1 + ------------------------- + --- + ------------- + + If the terminal supports xterm 256 colors or more, file-specific colors will be rendered. + To force the 8-color scheme use option -C. + If xterm 256 colors aren't supported, 8-color scheme will be used. +.Ed +.Pp +\fBNNN_ARCHIVE:\fR archive extensions to be handled silently (default: bzip2, (g)zip, tar). +.Bd -literal + export NNN_ARCHIVE="\\\\.(7z|bz2|gz|tar|tgz|zip)$" + + NOTE: Non-default formats may require a third-party utility. +.Ed +.Pp +\fBNNN_SSHFS:\fR specify custom sshfs command with options: +.Bd -literal + export NNN_SSHFS='sshfs -o reconnect,idmap=user,cache_timeout=3600' + + NOTE: The options must be comma-separated without any space between them. +.Ed +.Pp +\fBNNN_RCLONE:\fR pass additional options to rclone command: +.Bd -literal + export NNN_RCLONE='rclone mount --read-only --no-checksum' + + NOTE: The options must be preceded by "rclone" and max 5 flags are supported. +.Ed +.Pp +\fBNNN_TRASH:\fR trash (instead of \fIrm -rf\fR) files to desktop Trash. +.Bd -literal + export NNN_TRASH=n + # n=1: trash-cli, n=2: gio trash +.Ed +.Pp +\fBNNN_SEL:\fR absolute path to custom selection file. +.Pp +\fBNNN_FIFO:\fR path of a named pipe to write the hovered file path: +.Bd -literal + export NNN_FIFO='/tmp/nnn.fifo' + + NOTES: + 1. Overridden by a temporary path with -a option. + 2. If the FIFO file doesn't exist it will be created, + but not removed (unless it is generated by -a option). + + Online docs: https://github.com/jarun/nnn/wiki/Live-previews +.Ed +.Pp +\fBNNN_LOCKER:\fR terminal locker program. +.Bd -literal + export NNN_LOCKER='bmon -p wlp1s0' + export NNN_LOCKER='cmatrix' +.Ed +.Pp +\fBNNN_TMPFILE:\fR \fIalways\fR cd on quit and write the command in the file specified. +.Bd -literal + export NNN_TMPFILE='/tmp/.lastd' +.Ed +.Pp +\fBNNN_HELP:\fR run a program and show the output on top of the program help page. +.Bd -literal + export NNN_HELP='fortune' +.Ed +.Pp +\fBNNN_MCLICK:\fR key emulated by a middle mouse click. +.Bd -literal + export NNN_MCLICK='^R' + + NOTE: Only the first character is considered if not a \fICtrl+key\fR combo. +.Ed +.Pp +\fBnnn:\fR this is a special variable. +.Bd -literal + Set to the hovered file name before starting the command prompt or spawning a shell. +.Ed +.Pp +\fBNO_COLOR:\fR disable ANSI color output (overridden by \fBNNN_COLORS\fR). +.Sh AUTHORS +.An Arun Prakash Jana Aq Mt engineerarun@gmail.com , +.An Lazaros Koromilas Aq Mt lostd@2f30.org , +.An Dimitris Papastamos Aq Mt sin@2f30.org . +.Sh HOME +.Em https://github.com/jarun/nnn diff --git a/nnn/patches/README.md b/nnn/patches/README.md @@ -0,0 +1,16 @@ +<h1 align="center">User Patch Framework</h1> + +This directory contains sizable user submitted patches that were rejected from mainline as they tend to be more subjective in nature. + +The patches will be adapted on each release when necessary (v4.1 onwards). Each patch can be applied through its respective make variable during compilation. In case inter-patch merge conflicts occur, a compatability patch is provided and will automatically be applied. + +## List of patches +| Patch (a-z) | Description | Make var | +| --- | --- | --- | +| gitstatus | Add git status column to the detail view. Provides command line flag `-G` to show column in normal mode. | `O_GITSTATUS` | +| namefirst | Print filenames first in the detail view. Print user/group columns when a directory contains different users/groups. | `O_NAMEFIRST` | +| restorepreview | Add pipe to close and restore [`preview-tui`](https://github.com/jarun/nnn/blob/master/plugins/preview-tui) for internal undetached edits (<kbd>e</kbd> key)| `O_RESTOREPREVIEW` | + +To apply a patch, use the corresponding make variable, e.g.: + + make O_NAMEFIRST=1 diff --git a/nnn/patches/gitstatus/mainline.diff b/nnn/patches/gitstatus/mainline.diff @@ -0,0 +1,240 @@ +# Description: Add git status column to detail mode. Provides additional +# command line flag -G which will render the git status +# column also in normal mode. Vim plugin users may consider +# adding the -G flag to their command override. +# +# Authors: Luuk van Baal + +diff --git a/src/nnn.c b/src/nnn.c +index 1028906a..c80314de 100644 +--- a/src/nnn.c ++++ b/src/nnn.c +@@ -262,6 +262,25 @@ + #define FREE 0 + #define CAPACITY 1 + ++/* Git icons */ ++#ifdef NERD ++#define GIT_ADD "" ++#define GIT_DEL "" ++#define GIT_IGN "" ++#define GIT_MOD "" ++#define GIT_NEW "" ++#define GIT_NON "-" ++#define GIT_UPD "ﮮ" ++#else ++#define GIT_ADD "A" ++#define GIT_DEL "D" ++#define GIT_IGN "!" ++#define GIT_MOD "M" ++#define GIT_NEW "?" ++#define GIT_NON "-" ++#define GIT_UPD "U" ++#endif ++ + /* TYPE DEFINITIONS */ + typedef unsigned int uint_t; + typedef unsigned char uchar_t; +@@ -286,6 +305,7 @@ typedef struct entry { + uid_t uid; /* 4 bytes */ + gid_t gid; /* 4 bytes */ + #endif ++ char git_status[2][5]; + } *pEntry; + + /* Selection marker */ +@@ -342,6 +362,7 @@ typedef struct { + uint_t cliopener : 1; /* All-CLI app opener */ + uint_t waitedit : 1; /* For ops that can't be detached, used EDITOR */ + uint_t rollover : 1; /* Roll over at edges */ ++ uint_t normalgit : 1; /* Show git status in normal mode */ + } settings; + + /* Non-persistent program-internal states (alphabeical order) */ +@@ -391,7 +412,17 @@ typedef struct { + } session_header_t; + #endif + ++typedef struct { ++ char status[2]; ++ char path[PATH_MAX]; ++} git_status_t; ++ + /* GLOBALS */ ++struct { ++ bool show; ++ size_t len; ++ git_status_t *statuses; ++} git_statuses; + + /* Configuration, contexts */ + static settings cfg = { +@@ -422,6 +453,7 @@ static settings cfg = { + 0, /* cliopener */ + 0, /* waitedit */ + 1, /* rollover */ ++ 0, /* normalgit */ + }; + + static context g_ctx[CTX_MAX] __attribute__ ((aligned)); +@@ -3839,6 +3871,56 @@ static int get_kv_key(kv *kvarr, char *val, uchar_t max, uchar_t id) + return -1; + } + ++static size_t get_git_statuses(const char *path) ++{ ++ static char gitrev[] = "git rev-parse --show-toplevel 2>/dev/null"; ++ char workdir[PATH_MAX], *ret; ++ FILE *fp = popen(gitrev, "r"); ++ ++ git_statuses.show = FALSE; ++ ret = fgets(workdir, PATH_MAX, fp); ++ pclose(fp); ++ if (!ret) ++ return 0; ++ ++ static char gitstat[] = "git -c core.quotePath= status --porcelain --no-renames --ignored=matching -u "; ++ char pathspec[PATH_MAX], status[PATH_MAX]; ++ size_t i = -1; ++ workdir[xstrlen(workdir) - 1] = '\0'; ++ snprintf(pathspec, PATH_MAX, "%s\"%s\"%s 2>/dev/null", gitstat, path, cfg.showhidden ? "" : "/*"); ++ fp = popen(pathspec, "r"); ++ ++ while (fgets(status, PATH_MAX, fp)) { ++ size_t pathindex = (status[3] == '"') ? 4 : 3; ++ status[xstrlen(status) - pathindex + 2] = '\0'; ++ git_statuses.statuses = xrealloc(git_statuses.statuses, sizeof(git_status_t) * (++i + 1)); ++ git_statuses.statuses[i].status[0] = status[0]; ++ git_statuses.statuses[i].status[1] = status[1]; ++ mkpath(workdir, status + pathindex, git_statuses.statuses[i].path); ++ } ++ ++ pclose(fp); ++ return (i + 1); ++} ++ ++static void set_git_status(char status[][5], uint_t nr) ++{ ++ for (int j = 0; j < 2; j++) { ++ if (status[j][0] == '-') ++ switch (git_statuses.statuses[nr].status[j]) { ++ case ' ': xstrsncpy(status[j], GIT_NON, 4); break; ++ case 'M': xstrsncpy(status[j], GIT_MOD, 4); break; ++ case 'A': xstrsncpy(status[j], GIT_ADD, 4); break; ++ case '?': xstrsncpy(status[j], GIT_NEW, 4); break; ++ case '!': xstrsncpy(status[j], GIT_IGN, 4); break; ++ case 'D': xstrsncpy(status[j], GIT_DEL, 4); break; ++ case 'U': xstrsncpy(status[j], GIT_UPD, 4); break; ++ } ++ } ++ if (git_statuses.statuses[nr].status[1] != '!') ++ git_statuses.show = TRUE; ++} ++ + static void resetdircolor(int flags) + { + /* Directories are always shown on top, clear the color when moving to first file */ +@@ -4176,6 +4258,10 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel) + + uchar_t color_pair = get_color_pair_name_ind(ent, &ind, &attrs); + ++ if (git_statuses.show && (cfg.showdetail || cfg.normalgit)) ++ printw("%*s%s%s", (cfg.normalgit && !cfg.showdetail) ? 1 : 0, "", ++ ent->git_status[0], ent->git_status[1]); ++ + addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' '); + + if (g_state.oldcolor) +@@ -5617,6 +5703,11 @@ static int dentfill(char *path, struct entry **ppdents) + attron(COLOR_PAIR(cfg.curctx + 1)); + } + ++ char linkpath[PATH_MAX]; ++ if ((git_statuses.len = get_git_statuses(path))) ++ if (!realpath(path, linkpath)) ++ printwarn(NULL); ++ + #if _POSIX_C_SOURCE >= 200112L + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + #endif +@@ -5815,6 +5906,29 @@ static int dentfill(char *path, struct entry **ppdents) + #endif + } + ++ if (git_statuses.len) { ++ char dentpath[PATH_MAX]; ++ size_t pathlen = mkpath(linkpath, dentp->name, dentpath); ++ dentp->git_status[0][0] = dentp->git_status[1][0] = '-'; ++ dentp->git_status[0][1] = dentp->git_status[1][1] = '\0'; ++ ++ if (dentp->flags & DIR_OR_DIRLNK) { ++ char prefix[PATH_MAX]; ++ memccpy(prefix, dentpath, '\0', PATH_MAX); ++ prefix[pathlen - 1] = '/'; ++ ++ for (size_t i = 0; i < git_statuses.len; ++i) ++ if (is_prefix(git_statuses.statuses[i].path, prefix, pathlen)) ++ set_git_status(dentp->git_status, i); ++ } else { ++ for (size_t i = 0; i < git_statuses.len; ++i) ++ if (!xstrcmp(git_statuses.statuses[i].path, dentpath)) { ++ set_git_status(dentp->git_status, i); ++ break; ++ } ++ } ++ } ++ + ++ndents; + } while ((dp = readdir(dirp))); + +@@ -6360,11 +6474,12 @@ static int adjust_cols(int n) + #endif + if (cfg.showdetail) { + /* Fallback to light mode if less than 35 columns */ +- if (n < 36) ++ if (n < 38) + cfg.showdetail ^= 1; + else /* 2 more accounted for below */ +- n -= 32; +- } ++ n -= (git_statuses.show ? 34 : 32); ++ } else if (cfg.normalgit && git_statuses.show) ++ n -= 3; + + /* 2 columns for preceding space and indicator */ + return (n - 2); +@@ -8126,6 +8241,7 @@ static void usage(void) + " -F val fifo mode [0:preview 1:explore]\n" + #endif + " -g regex filters\n" ++ " -G always show git status\n" + " -H show hidden files\n" + " -i show current file info\n" + " -J no auto-proceed on select\n" +@@ -8266,6 +8382,7 @@ static void cleanup(void) + free(hostname); + } + #endif ++ free(git_statuses.statuses); + free(selpath); + free(plgpath); + free(cfgpath); +@@ -8310,7 +8427,7 @@ int main(int argc, char *argv[]) + + while ((opt = (env_opts_id > 0 + ? env_opts[--env_opts_id] +- : getopt(argc, argv, "aAb:cCdDeEfF:gHiJKl:nop:P:QrRs:St:T:uUVxh"))) != -1) { ++ : getopt(argc, argv, "aAb:cCdDeEfF:gGHiJKl:nop:P:QrRs:St:T:uUVxh"))) != -1) { + switch (opt) { + #ifndef NOFIFO + case 'a': +@@ -8361,6 +8478,9 @@ int main(int argc, char *argv[]) + cfg.regex = 1; + filterfn = &visible_re; + break; ++ case 'G': ++ cfg.normalgit = 1; ++ break; + case 'H': + cfg.showhidden = 1; + break; diff --git a/nnn/patches/gitstatus/namefirst.diff b/nnn/patches/gitstatus/namefirst.diff @@ -0,0 +1,243 @@ +# Description: Add git status column to detail mode. Provides additional +# command line flag -G which will render the git status +# column also in normal mode. Vim plugin users may consider +# adding the -G flag to their command override. +# Compatibility patch for the namefirst patch. +# +# Authors: Luuk van Baal + +diff --git a/src/nnn.c b/src/nnn.c +index af586056..9ebfb203 100644 +--- a/src/nnn.c ++++ b/src/nnn.c +@@ -262,6 +262,25 @@ + #define FREE 0 + #define CAPACITY 1 + ++/* Git icons */ ++#ifdef NERD ++#define GIT_ADD "" ++#define GIT_DEL "" ++#define GIT_IGN "" ++#define GIT_MOD "" ++#define GIT_NEW "" ++#define GIT_NON "-" ++#define GIT_UPD "ﮮ" ++#else ++#define GIT_ADD "A" ++#define GIT_DEL "D" ++#define GIT_IGN "!" ++#define GIT_MOD "M" ++#define GIT_NEW "?" ++#define GIT_NON "-" ++#define GIT_UPD "U" ++#endif ++ + /* TYPE DEFINITIONS */ + typedef unsigned int uint_t; + typedef unsigned char uchar_t; +@@ -286,6 +305,7 @@ typedef struct entry { + uid_t uid; /* 4 bytes */ + gid_t gid; /* 4 bytes */ + #endif ++ char git_status[2][5]; + } *pEntry; + + /* Selection marker */ +@@ -342,6 +362,7 @@ typedef struct { + uint_t cliopener : 1; /* All-CLI app opener */ + uint_t waitedit : 1; /* For ops that can't be detached, used EDITOR */ + uint_t rollover : 1; /* Roll over at edges */ ++ uint_t normalgit : 1; /* Show git status in normal mode */ + } settings; + + /* Non-persistent program-internal states (alphabeical order) */ +@@ -395,7 +416,17 @@ static struct { + ushort_t maxnameln, maxsizeln, maxuidln, maxgidln, maxentln, uidln, gidln, printguid; + } dtls; + ++typedef struct { ++ char status[2]; ++ char path[PATH_MAX]; ++} git_status_t; ++ + /* GLOBALS */ ++struct { ++ bool show; ++ size_t len; ++ git_status_t *statuses; ++} git_statuses; + + /* Configuration, contexts */ + static settings cfg = { +@@ -426,6 +457,7 @@ static settings cfg = { + 0, /* cliopener */ + 0, /* waitedit */ + 1, /* rollover */ ++ 0, /* normalgit */ + }; + + static context g_ctx[CTX_MAX] __attribute__ ((aligned)); +@@ -3847,6 +3879,56 @@ static int get_kv_key(kv *kvarr, char *val, uchar_t max, uchar_t id) + return -1; + } + ++static size_t get_git_statuses(const char *path) ++{ ++ static char gitrev[] = "git rev-parse --show-toplevel 2>/dev/null"; ++ char workdir[PATH_MAX], *ret; ++ FILE *fp = popen(gitrev, "r"); ++ ++ git_statuses.show = FALSE; ++ ret = fgets(workdir, PATH_MAX, fp); ++ pclose(fp); ++ if (!ret) ++ return 0; ++ ++ static char gitstat[] = "git -c core.quotePath= status --porcelain --no-renames --ignored=matching -u "; ++ char pathspec[PATH_MAX], status[PATH_MAX]; ++ size_t i = -1; ++ workdir[xstrlen(workdir) - 1] = '\0'; ++ snprintf(pathspec, PATH_MAX, "%s\"%s\"%s 2>/dev/null", gitstat, path, cfg.showhidden ? "" : "/*"); ++ fp = popen(pathspec, "r"); ++ ++ while (fgets(status, PATH_MAX, fp)) { ++ size_t pathindex = (status[3] == '"') ? 4 : 3; ++ status[xstrlen(status) - pathindex + 2] = '\0'; ++ git_statuses.statuses = xrealloc(git_statuses.statuses, sizeof(git_status_t) * (++i + 1)); ++ git_statuses.statuses[i].status[0] = status[0]; ++ git_statuses.statuses[i].status[1] = status[1]; ++ mkpath(workdir, status + pathindex, git_statuses.statuses[i].path); ++ } ++ ++ pclose(fp); ++ return (i + 1); ++} ++ ++static void set_git_status(char status[][5], uint_t nr) ++{ ++ for (int j = 0; j < 2; j++) { ++ if (status[j][0] == '-') ++ switch (git_statuses.statuses[nr].status[j]) { ++ case ' ': xstrsncpy(status[j], GIT_NON, 4); break; ++ case 'M': xstrsncpy(status[j], GIT_MOD, 4); break; ++ case 'A': xstrsncpy(status[j], GIT_ADD, 4); break; ++ case '?': xstrsncpy(status[j], GIT_NEW, 4); break; ++ case '!': xstrsncpy(status[j], GIT_IGN, 4); break; ++ case 'D': xstrsncpy(status[j], GIT_DEL, 4); break; ++ case 'U': xstrsncpy(status[j], GIT_UPD, 4); break; ++ } ++ } ++ if (git_statuses.statuses[nr].status[1] != '!') ++ git_statuses.show = TRUE; ++} ++ + static void resetdircolor(int flags) + { + /* Directories are always shown on top, clear the color when moving to first file */ +@@ -4157,6 +4239,9 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel) + int attrs = 0, namelen; + uchar_t color_pair = get_color_pair_name_ind(ent, &ind, &attrs); + ++ if (git_statuses.show && (cfg.showdetail || cfg.normalgit)) ++ printw(" %s%s", ent->git_status[0], ent->git_status[1]); ++ + addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' '); + + if (g_state.oldcolor) +@@ -5623,6 +5708,11 @@ static int dentfill(char *path, struct entry **ppdents) + attron(COLOR_PAIR(cfg.curctx + 1)); + } + ++ char linkpath[PATH_MAX]; ++ if ((git_statuses.len = get_git_statuses(path))) ++ if (!realpath(path, linkpath)) ++ printwarn(NULL); ++ + #if _POSIX_C_SOURCE >= 200112L + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + #endif +@@ -5821,6 +5911,29 @@ static int dentfill(char *path, struct entry **ppdents) + #endif + } + ++ if (git_statuses.len) { ++ char dentpath[PATH_MAX]; ++ size_t pathlen = mkpath(linkpath, dentp->name, dentpath); ++ dentp->git_status[0][0] = dentp->git_status[1][0] = '-'; ++ dentp->git_status[0][1] = dentp->git_status[1][1] = '\0'; ++ ++ if (dentp->flags & DIR_OR_DIRLNK) { ++ char prefix[PATH_MAX]; ++ memccpy(prefix, dentpath, '\0', PATH_MAX); ++ prefix[pathlen - 1] = '/'; ++ ++ for (size_t i = 0; i < git_statuses.len; ++i) ++ if (is_prefix(git_statuses.statuses[i].path, prefix, pathlen)) ++ set_git_status(dentp->git_status, i); ++ } else { ++ for (size_t i = 0; i < git_statuses.len; ++i) ++ if (!xstrcmp(git_statuses.statuses[i].path, dentpath)) { ++ set_git_status(dentp->git_status, i); ++ break; ++ } ++ } ++ } ++ + ++ndents; + } while ((dp = readdir(dirp))); + +@@ -6357,7 +6470,8 @@ static int adjust_cols(int n) + cfg.showdetail ^= 1; + else /* 2 more accounted for below */ + n -= (dtls.maxentln - 2 - dtls.maxnameln); +- } ++ } else if (cfg.normalgit && git_statuses.show) ++ n -= 3; + + /* 2 columns for preceding space and indicator */ + return (n - 2); +@@ -6512,7 +6626,7 @@ static void redraw(char *path) + } + #endif + } +- dtls.maxentln = dtls.maxnameln + dtls.maxsizeln + (dtls.printguid ? (dtls.maxuidln + dtls.maxgidln + 29) : 26); ++ dtls.maxentln = dtls.maxnameln + dtls.maxsizeln + (dtls.printguid ? (dtls.maxuidln + dtls.maxgidln + 3) : 0) + (git_statuses.show ? 29 : 26); + } + + ncols = adjust_cols(ncols); +@@ -8132,6 +8246,7 @@ static void usage(void) + " -F val fifo mode [0:preview 1:explore]\n" + #endif + " -g regex filters\n" ++ " -G always show git status\n" + " -H show hidden files\n" + " -i show current file info\n" + " -J no auto-proceed on select\n" +@@ -8272,6 +8387,7 @@ static void cleanup(void) + free(hostname); + } + #endif ++ free(git_statuses.statuses); + free(selpath); + free(plgpath); + free(cfgpath); +@@ -8316,7 +8432,7 @@ int main(int argc, char *argv[]) + + while ((opt = (env_opts_id > 0 + ? env_opts[--env_opts_id] +- : getopt(argc, argv, "aAb:cCdDeEfF:gHiJKl:nop:P:QrRs:St:T:uUVxh"))) != -1) { ++ : getopt(argc, argv, "aAb:cCdDeEfF:gGHiJKl:nop:P:QrRs:St:T:uUVxh"))) != -1) { + switch (opt) { + #ifndef NOFIFO + case 'a': +@@ -8367,6 +8483,9 @@ int main(int argc, char *argv[]) + cfg.regex = 1; + filterfn = &visible_re; + break; ++ case 'G': ++ cfg.normalgit = 1; ++ break; + case 'H': + cfg.showhidden = 1; + break; diff --git a/nnn/patches/namefirst/mainline.diff b/nnn/patches/namefirst/mainline.diff @@ -0,0 +1,225 @@ +# Description: Prints filenames first in the detail view. Prints user/group +# columns when a directory contains different users/groups. +# +# Author: Luuk van Baal + +diff --git a/src/nnn.c b/src/nnn.c +index 88263beb..55f32e73 100644 +--- a/src/nnn.c ++++ b/src/nnn.c +@@ -390,6 +390,10 @@ typedef struct { + } session_header_t; + #endif + ++static struct { ++ ushort_t maxnameln, maxsizeln, maxuidln, maxgidln, maxentln, uidln, gidln, printguid; ++} dtls; ++ + /* GLOBALS */ + + /* Configuration, contexts */ +@@ -1083,10 +1087,12 @@ static char *getpwname(uid_t uid) + static char *namecache; + + if (uidcache != uid) { ++ if (dtls.maxuidln && !dtls.printguid) dtls.printguid = 1; + struct passwd *pw = getpwuid(uid); + + uidcache = uid; + namecache = pw ? pw->pw_name : NULL; ++ dtls.uidln = xstrlen(namecache ? namecache : xitoa(uid)); + } + + return namecache ? namecache : xitoa(uid); +@@ -1098,10 +1104,12 @@ static char *getgrname(gid_t gid) + static char *grpcache; + + if (gidcache != gid) { ++ if (dtls.maxgidln && !dtls.printguid) dtls.printguid = 1; + struct group *gr = getgrgid(gid); + + gidcache = gid; + grpcache = gr ? gr->gr_name : NULL; ++ dtls.gidln = xstrlen(grpcache ? grpcache : xitoa(gid)); + } + + return grpcache ? grpcache : xitoa(gid); +@@ -3829,14 +3837,13 @@ static void resetdircolor(int flags) + * Max supported str length: NAME_MAX; + */ + #ifdef NOLC +-static char *unescape(const char *str, uint_t maxcols) ++static size_t unescape(const char *str, uint_t maxcols) + { + char * const wbuf = g_buf; + char *buf = wbuf; +- +- xstrsncpy(wbuf, str, maxcols); ++ size_t len = xstrsncpy(wbuf, str, maxcols); + #else +-static wchar_t *unescape(const char *str, uint_t maxcols) ++static size_t unescape(const char *str, uint_t maxcols) + { + wchar_t * const wbuf = (wchar_t *)g_buf; + wchar_t *buf = wbuf; +@@ -3861,7 +3868,7 @@ static wchar_t *unescape(const char *str, uint_t maxcols) + ++buf; + } + +- return wbuf; ++ return len; + } + + static off_t get_size(off_t size, off_t *pval, int comp) +@@ -4122,33 +4129,7 @@ static uchar_t get_color_pair_name_ind(const struct entry *ent, char *pind, int + static void printent(const struct entry *ent, uint_t namecols, bool sel) + { + char ind = '\0'; +- int attrs; +- +- if (cfg.showdetail) { +- int type = ent->mode & S_IFMT; +- char perms[6] = {' ', ' ', (char)('0' + ((ent->mode >> 6) & 7)), +- (char)('0' + ((ent->mode >> 3) & 7)), +- (char)('0' + (ent->mode & 7)), '\0'}; +- +- addch(' '); +- attrs = g_state.oldcolor ? (resetdircolor(ent->flags), A_DIM) +- : (fcolors[C_MIS] ? COLOR_PAIR(C_MIS) : 0); +- if (attrs) +- attron(attrs); +- +- /* Print details */ +- print_time(&ent->sec); +- +- printw("%s%9s ", perms, (type == S_IFREG || type == S_IFDIR) +- ? coolsize(cfg.blkorder ? (blkcnt_t)ent->blocks << blk_shift : ent->size) +- : (type = (uchar_t)get_detail_ind(ent->mode), (char *)&type)); +- +- if (attrs) +- attroff(attrs); +- } +- +- attrs = 0; +- ++ int attrs = 0, namelen; + uchar_t color_pair = get_color_pair_name_ind(ent, &ind, &attrs); + + addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' '); +@@ -4173,15 +4154,40 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel) + ++namecols; + + #ifndef NOLC +- addwstr(unescape(ent->name, namecols)); ++ addwstr((namelen = unescape(ent->name, namecols), (wchar_t *)g_buf)); + #else +- addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1)); ++ addstr((namelen = unescape(ent->name, MIN(namecols, ent->nlen) + 1), (char *)g_buf)); + #endif + +- if (attrs) ++ if (!sel && attrs) + attroff(attrs); + if (ind) + addch(ind); ++ if (cfg.showdetail) { ++ int type = ent->mode & S_IFMT; ++ char perms[6] = {(char)('0' + ((ent->mode >> 6) & 7)), ++ (char)('0' + ((ent->mode >> 3) & 7)), ++ (char)('0' + (ent->mode & 7)), ' ', ' ', '\0'}, *size = NULL; ++ ++ if (attrs) ++ attron(attrs); ++ if (!g_state.oldcolor && (type == S_IFDIR || (type == S_IFLNK && ent->flags & DIR_OR_DIRLNK))) ++ attroff(A_BOLD); ++ size_t sizelen = (type == S_IFREG || type == S_IFDIR) ? xstrlen(size = coolsize(cfg.blkorder ? ent->blocks << blk_shift : ent->size)) : 1; ++ printw("%*c%*s%s%s", 1 + MIN(namecols, dtls.maxnameln + (size_t)(ind ? 0 : 1)) - namelen, ' ', ++ dtls.maxsizeln - sizelen, "", size ? size : (type = (uchar_t)get_detail_ind(ent->mode), (char *)&type), " "); ++#ifndef NOUG ++ if (g_state.uidgid && dtls.printguid) { ++ addstr(getpwname(ent->uid)); ++ printw("%*c%s", dtls.maxuidln + 1 - dtls.uidln, ' ', getgrname(ent->gid)); ++ printw("%*c", dtls.maxgidln + 2 - dtls.gidln, ' '); ++ } ++#endif ++ addstr(perms); ++ print_time(&ent->sec); ++ } ++ if (attrs) ++ attroff(attrs); + } + + static void savecurctx(char *path, char *curname, int nextctx) +@@ -6250,18 +6256,6 @@ static void statusbar(char *path) + tocursor(); + } + +-static inline void markhovered(void) +-{ +- if (cfg.showdetail && ndents) { /* Reversed block for hovered entry */ +- tocursor(); +-#ifdef ICONS_ENABLED +- addstr(MD_ARROW_FORWARD); +-#else +- addch(' ' | A_REVERSE); +-#endif +- } +-} +- + static int adjust_cols(int n) + { + /* Calculate the number of cols available to print entry name */ +@@ -6269,11 +6263,10 @@ static int adjust_cols(int n) + n -= (g_state.oldcolor ? 0 : 1 + xstrlen(ICON_PADDING_LEFT) + xstrlen(ICON_PADDING_RIGHT)); + #endif + if (cfg.showdetail) { +- /* Fallback to light mode if less than 35 columns */ +- if (n < 36) ++ if (n < (dtls.maxentln + 1 - dtls.maxnameln)) + cfg.showdetail ^= 1; + else /* 2 more accounted for below */ +- n -= 32; ++ n -= (dtls.maxentln - 2 - dtls.maxnameln); + } + + /* 2 columns for preceding space and indicator */ +@@ -6310,8 +6303,6 @@ static void draw_line(int ncols) + /* Must reset e.g. no files in dir */ + if (dir) + attroff(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); +- +- markhovered(); + } + + static void redraw(char *path) +@@ -6419,6 +6410,21 @@ static void redraw(char *path) + + onscreen = MIN(onscreen + curscroll, ndents); + ++ if (cfg.showdetail) { ++ ushort_t lenbuf = dtls.maxnameln = dtls.maxsizeln = dtls.maxuidln = dtls.maxgidln = dtls.printguid = 0; ++ for (i = curscroll; i < onscreen; ++i) { ++ if ((lenbuf = pdents[i].nlen - 1) > dtls.maxnameln) dtls.maxnameln = lenbuf; ++ if ((lenbuf = xstrlen(coolsize(cfg.blkorder ? pdents[i].blocks << blk_shift : pdents[i].size))) > dtls.maxsizeln) dtls.maxsizeln = lenbuf; ++#ifndef NOUG ++ if (g_state.uidgid) { ++ if ((getpwname(pdents[i].uid), dtls.uidln) > dtls.maxuidln) dtls.maxuidln = dtls.uidln; ++ if ((getgrname(pdents[i].gid), dtls.gidln) > dtls.maxgidln) dtls.maxgidln = dtls.gidln; ++ } ++#endif ++ } ++ dtls.maxentln = dtls.maxnameln + dtls.maxsizeln + (dtls.printguid ? (dtls.maxuidln + dtls.maxgidln + 29) : 26); ++ } ++ + ncols = adjust_cols(ncols); + + int len = scanselforpath(path, FALSE); +@@ -6449,7 +6455,7 @@ static void redraw(char *path) + #endif + } + +- markhovered(); ++ statusbar(path); + } + + static bool cdprep(char *lastdir, char *lastname, char *path, char *newpath) diff --git a/nnn/patches/restorepreview/mainline.diff b/nnn/patches/restorepreview/mainline.diff @@ -0,0 +1,269 @@ +# Description: Adds preview pipe to enable closing and re-opening the preview +# pane when running an undetached editor. If you are using vim +# you might experience incorrectly resized window. Consider adding +# the following to your vimrc: +# autocmd VimEnter * :silent exec "!kill -s WINCH $PPID" +# +# Authors: Luuk van Baal + +diff --git a/src/nnn.c b/src/nnn.c +index 4243f2cc..b893f573 100644 +--- a/src/nnn.c ++++ b/src/nnn.c +@@ -370,7 +370,8 @@ typedef struct { + uint_t stayonsel : 1; /* Disable auto-proceed on select */ + uint_t trash : 2; /* Trash method 0: rm -rf, 1: trash-cli, 2: gio trash */ + uint_t uidgid : 1; /* Show owner and group info */ +- uint_t reserved : 7; /* Adjust when adding/removing a field */ ++ uint_t previewer : 1; /* Run state of previewer */ ++ uint_t reserved : 6; /* Adjust when adding/removing a field */ + } runstate; + + /* Contexts or workspaces */ +@@ -518,6 +519,9 @@ static char g_tmpfpath[TMP_LEN_MAX] __attribute__ ((aligned)); + /* Buffer to store plugins control pipe location */ + static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned)); + ++/* Buffer to store preview plugins control pipe location */ ++static char g_ppipepath[TMP_LEN_MAX] __attribute__ ((aligned)); ++ + /* Non-persistent runtime states */ + static runstate g_state; + +@@ -692,12 +696,13 @@ static const char * const messages[] = { + #define NNN_FCOLORS 5 + #define NNNLVL 6 + #define NNN_PIPE 7 +-#define NNN_MCLICK 8 +-#define NNN_SEL 9 +-#define NNN_ARCHIVE 10 +-#define NNN_ORDER 11 +-#define NNN_HELP 12 /* strings end here */ +-#define NNN_TRASH 13 /* flags begin here */ ++#define NNN_PPIPE 8 ++#define NNN_MCLICK 9 ++#define NNN_SEL 10 ++#define NNN_ARCHIVE 11 ++#define NNN_ORDER 12 ++#define NNN_HELP 13 /* strings end here */ ++#define NNN_TRASH 14 /* flags begin here */ + + static const char * const env_cfg[] = { + "NNN_OPTS", +@@ -708,6 +713,7 @@ static const char * const env_cfg[] = { + "NNN_FCOLORS", + "NNNLVL", + "NNN_PIPE", ++ "NNN_PPIPE", + "NNN_MCLICK", + "NNN_SEL", + "NNN_ARCHIVE", +@@ -854,7 +860,7 @@ static int set_sort_flags(int r); + static void statusbar(char *path); + static bool get_output(char *file, char *arg1, char *arg2, int fdout, bool multi, bool page); + #ifndef NOFIFO +-static void notify_fifo(bool force); ++static void notify_fifo(bool force, bool closepreview); + #endif + + /* Functions */ +@@ -3061,7 +3067,7 @@ try_quit: + } else { + #ifndef NOFIFO + if (!g_state.fifomode) +- notify_fifo(TRUE); /* Send hovered path to NNN_FIFO */ ++ notify_fifo(TRUE, FALSE); /* Send hovered path to NNN_FIFO */ + #endif + escaped = TRUE; + settimeout(); +@@ -5160,15 +5166,20 @@ static bool run_cmd_as_plugin(const char *file, char *runfile, uchar_t flags) + + static bool plctrl_init(void) + { +- size_t len; ++ size_t len, lenbuf; ++ pid_t pid = getpid(); + + /* g_tmpfpath is used to generate tmp file names */ + g_tmpfpath[tmpfplen - 1] = '\0'; +- len = xstrsncpy(g_pipepath, g_tmpfpath, TMP_LEN_MAX); ++ len = lenbuf = xstrsncpy(g_pipepath, g_tmpfpath, TMP_LEN_MAX); + g_pipepath[len - 1] = '/'; +- len = xstrsncpy(g_pipepath + len, "nnn-pipe.", TMP_LEN_MAX - len) + len; +- xstrsncpy(g_pipepath + len - 1, xitoa(getpid()), TMP_LEN_MAX - len); ++ xstrsncpy(g_ppipepath, g_pipepath, TMP_LEN_MAX); ++ len += xstrsncpy(g_pipepath + len, "nnn-pipe.", TMP_LEN_MAX - len); ++ xstrsncpy(g_pipepath + len - 1, xitoa(pid), TMP_LEN_MAX - len); ++ len = xstrsncpy(g_ppipepath + lenbuf, "nnn-ppipe.", TMP_LEN_MAX - lenbuf) + lenbuf; ++ xstrsncpy(g_ppipepath + len - 1, xitoa(pid), TMP_LEN_MAX - len); + setenv(env_cfg[NNN_PIPE], g_pipepath, TRUE); ++ setenv(env_cfg[NNN_PPIPE], g_ppipepath, TRUE); + + return EXIT_SUCCESS; + } +@@ -5197,6 +5208,21 @@ static ssize_t read_nointr(int fd, void *buf, size_t count) + return len; + } + ++void *previewpipe(void *arg __attribute__ ((unused))) ++{ ++ int fd, buf; ++ ++ mkfifo(g_ppipepath, 0600); ++ fd = open(g_ppipepath, O_RDONLY); ++ ++ if (read(fd, &buf, 1) == 1) ++ g_state.previewer = buf; ++ ++ close(fd); ++ unlink(g_ppipepath); ++ return NULL; ++} ++ + static char *readpipe(int fd, char *ctxnum, char **path) + { + char ctx, *nextpath = NULL; +@@ -5862,7 +5888,7 @@ static void populate(char *path, char *lastname) + } + + #ifndef NOFIFO +-static void notify_fifo(bool force) ++static void notify_fifo(bool force, bool closepreview) + { + if (!fifopath) + return; +@@ -5878,6 +5904,12 @@ static void notify_fifo(bool force) + } + } + ++ if (closepreview) { ++ if (write(fifofd, "close\n", 6) != 6) ++ xerror(); ++ return; ++ } ++ + static struct entry lastentry; + + if (!force && !memcmp(&lastentry, &pdents[cur], sizeof(struct entry))) +@@ -5910,7 +5942,7 @@ static void send_to_explorer(int *presel) + if (fd > 1) + close(fd); + } else +- notify_fifo(TRUE); /* Send opened path to NNN_FIFO */ ++ notify_fifo(TRUE, FALSE); /* Send opened path to NNN_FIFO */ + } + #endif + +@@ -5943,7 +5975,7 @@ static void move_cursor(int target, int ignore_scrolloff) + + #ifndef NOFIFO + if (!g_state.fifomode) +- notify_fifo(FALSE); /* Send hovered path to NNN_FIFO */ ++ notify_fifo(FALSE, FALSE); /* Send hovered path to NNN_FIFO */ + #endif + } + +@@ -6565,7 +6597,7 @@ static bool browse(char *ipath, const char *session, int pkey) + pEntry pent; + enum action sel; + struct stat sb; +- int r = -1, presel, selstartid = 0, selendid = 0; ++ int r = -1, presel, selstartid = 0, selendid = 0, previewkey = 0; + const uchar_t opener_flags = (cfg.cliopener ? F_CLI : (F_NOTRACE | F_NOSTDIN | F_NOWAIT)); + bool watch = FALSE, cd = TRUE; + ino_t inode = 0; +@@ -6819,7 +6851,7 @@ nochange: + move_cursor(r, 1); + #ifndef NOFIFO + else if ((event.bstate == BUTTON1_PRESSED) && !g_state.fifomode) +- notify_fifo(TRUE); /* Send clicked path to NNN_FIFO */ ++ notify_fifo(TRUE, FALSE); /* Send clicked path to NNN_FIFO */ + #endif + /* Handle right click selection */ + if (event.bstate == BUTTON3_PRESSED) { +@@ -6979,7 +7011,14 @@ nochange: + && strstr(g_buf, "text") + #endif + ) { ++ if (g_state.previewer) ++ notify_fifo(FALSE, TRUE); + spawn(editor, newpath, NULL, NULL, F_CLI); ++ if (g_state.previewer) { ++ pkey = previewkey; ++ goto run_plugin; ++ } ++ + if (cfg.filtermode) { + presel = FILTER; + clearfilter(); +@@ -7291,8 +7330,14 @@ nochange: + copycurname(); + goto nochange; + case SEL_EDIT: ++ if (g_state.previewer) ++ notify_fifo(FALSE, TRUE); + if (!g_state.picker) + spawn(editor, newpath, NULL, NULL, F_CLI); ++ if (g_state.previewer) { ++ pkey = previewkey; ++ goto run_plugin; ++ } + continue; + default: /* SEL_LOCK */ + lock_terminal(); +@@ -7661,6 +7706,7 @@ nochange: + cd = FALSE; + goto begin; + } ++run_plugin: + case SEL_PLUGIN: + /* Check if directory is accessible */ + if (!xdiraccess(plgpath)) { +@@ -7686,6 +7732,12 @@ nochange: + goto nochange; + } + ++ if (xstrcmp(tmp, "preview-tui") == 0) { ++ previewkey = r; ++ pthread_t tid; ++ pthread_create(&tid, NULL, previewpipe, NULL); ++ } ++ + if (tmp[0] == '-' && tmp[1]) { + ++tmp; + r = FALSE; /* Do not refresh dir after completion */ +@@ -7740,7 +7792,13 @@ nochange: + case SEL_SHELL: // fallthrough + case SEL_LAUNCH: // fallthrough + case SEL_PROMPT: ++ if (g_state.previewer) ++ notify_fifo(FALSE, TRUE); + r = handle_cmd(sel, newpath); ++ if (g_state.previewer) { ++ pkey = previewkey; ++ goto run_plugin; ++ } + + /* Continue in type-to-nav mode, if enabled */ + if (cfg.filtermode) +@@ -8282,8 +8340,10 @@ static void cleanup(void) + if (g_state.autofifo) + unlink(fifopath); + #endif +- if (g_state.pluginit) ++ if (g_state.pluginit){ + unlink(g_pipepath); ++ unlink(g_ppipepath); ++ } + #ifdef DEBUG + disabledbg(); + #endif +@@ -8787,7 +8847,7 @@ int main(int argc, char *argv[]) + + #ifndef NOFIFO + if (!g_state.fifomode) +- notify_fifo(FALSE); ++ notify_fifo(FALSE, TRUE); + if (fifofd != -1) + close(fifofd); + #endif diff --git a/nnn/plugins/.cbcp b/nnn/plugins/.cbcp @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +# Description: Copy selection to system clipboard as newline-separated entries +# Dependencies: +# - tr +# - xclip/xsel (Linux) +# - pbcopy (macOS) +# - termux-clipboard-set (Termux) +# - clip.exe (WSL) +# - clip (Cygwin) +# - wl-copy (Wayland) +# - clipboard (Haiku) +# +# Limitation: breaks if a filename has newline in it +# +# Note: For a space-separated list: +# xargs -0 < "$SELECTION" +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if type xsel >/dev/null 2>&1; then + # Linux + tr '\0' '\n' < "$selection" | xsel -bi +elif type xclip >/dev/null 2>&1; then + # Linux + tr '\0' '\n' < "$selection" | xclip -sel clip +elif type pbcopy >/dev/null 2>&1; then + # macOS + tr '\0' '\n' < "$selection" | pbcopy +elif type termux-clipboard-set >/dev/null 2>&1; then + # Termux + tr '\0' '\n' < "$selection" | termux-clipboard-set +elif type clip.exe >/dev/null 2>&1; then + # WSL + tr '\0' '\n' < "$selection" | clip.exe +elif type clip >/dev/null 2>&1; then + # Cygwin + tr '\0' '\n' < "$selection" | clip +elif type wl-copy >/dev/null 2>&1; then + # Wayland + tr '\0' '\n' < "$selection" | wl-copy +elif type clipboard >/dev/null 2>&1; then + # Haiku + tr '\0' '\n' < "$selection" | clipboard --stdin +fi diff --git a/nnn/plugins/.iconlookup b/nnn/plugins/.iconlookup @@ -0,0 +1,428 @@ +#!/usr/bin/env sh + +# Description: Print icons in front of list of directories/files + +# Dependencies: awk + +# Usage +# 1. Set colors and/or icons to your liking +# 2. Pipe any directory listing to iconlookup and it will output prepended icons +# 3. preview-tui uses the script to prepend icon to directory listings +# 4. Aditionally you can consider adding it to your PATH and/or FZF_DEFAULT_COMMAND to +# make it work with various fzf plugins (make sure you also add --ansi to your FZF_DEFAULT_OPTS) + +# Shell: POSIX compliant + +# Author: Luuk van Baal (https://github.com/luukvbaal/iconlookup) + +icon_lookup() { +awk 'BEGIN { +# Set your ANSI colorscheme below (https://en.wikipedia.org/wiki/ANSI_escape_code#Colors). +# Default uses standard nnn icon colors, 8 and 24-bit nord themes are commented out. + colordepth=8 #colordepth=8 #colordepth=24 + color_dirtxt=39 #color_dirtxt=111 #color_dirtxt="129;161;193" + color_filetxt=15 #color_filetxt=111 #color_filetxt="129;161;193" + color_default=39 #color_default=111 #color_default="129;161;193" + color_video=93 #color_video=110 #color_video="136;192;208" + color_audio=220 #color_audio=150 #color_audio="163;190;140" + color_image=82 #color_image=150 #color_image="163;190;140" + color_docs=202 #color_docs=173 #color_docs="208;135;112" + color_archive=209 #color_archive=179 #color_archive="235;203;139" + color_c=81 #color_c=150 #color_c="163;190;140" + color_java=32 #color_java=139 #color_java="180;142;173" + color_js=47 #color_js=109 #color_js="143;188;187" + color_react=39 #color_react=111 #color_react="129;161;193" + color_css=199 #color_css=110 #color_css="136;192;208" + color_python=227 #color_python=68 #color_python="94;129;172" + color_lua=19 #color_lua=167 #color_lua="191;97;106" + color_document=15 #color_document=173 #color_document="208;135;112" + color_fsharp=31 #color_fsharp=179 #color_fsharp="180;142;173" + color_ruby=160 #color_ruby=150 #color_ruby="163;190;140" + color_scala=196 #color_scala=139 #color_scala="143;188;187" + color_shell=47 #color_shell=109 #color_shell="143;188;187" + color_vim=28 #color_vim=109 #color_vim="143;188;187" + +# icons[][1] contains icon and icons[][2] contains color + icons["directory"][1] = ""; icons["directory"][2] = color_default + icons["file"][1] = ""; icons["file"][2] = color_default + icons["exec"][1] = ""; icons["exec"][2] = color_default + icons["manual"][1] = ""; icons["manual"][2] = color_docs + icons["pipe"][1] = "ﳣ"; icons["pipe"][2] = color_default + icons["socket"][1] = "ﳧ"; icons["socket"][2] = color_default + icons["door"][1] = "➡"; icons["door"][2] = color_default + +# top level and common icons + icons[".git"][1] = ""; icons[".git"][2] = color_default + icons["desktop"][1] = "ﲾ"; icons["desktop"][2] = color_default + icons["briefcase"][1] = ""; icons["briefcase"][2] = color_default + icons["document"][1] = ""; icons["document"][2] = color_default + icons["downloads"][1] = ""; icons["downloads"][2] = color_default + icons["music"][1] = ""; icons["music"][2] = color_default + icons["musicfile"][1] = ""; icons["musicfile"][2] = color_audio + icons["pictures"][1] = ""; icons["pictures"][2] = color_default + icons["picturefile"][1] = ""; icons["picturefile"][2] = color_image + icons["public"][1] = ""; icons["public"][2] = color_default + icons["templates"][1] = "陼"; icons["templates"][2] = color_default + icons["videos"][1] = ""; icons["videos"][2] = color_default + icons["videofile"][1] = "ﳜ"; icons["videofile"][2] = color_video + icons["changelog"][1] = ""; icons["changelog"][2] = color_docs + icons["configure"][1] = ""; icons["configure"][2] = color_default + icons["license"][1] = ""; icons["license"][2] = color_docs + icons["makefile"][1] = ""; icons["makefile"][2] = color_default + icons["archive"][1] = ""; icons["archive"][2] = color_archive + icons["script"][1] = ""; icons["script"][2] = color_shell + icons["cplusplus"][1] = ""; icons["cplusplus"][2] = color_c + icons["java"][1] = ""; icons["java"][2] = color_java + icons["clojure"][1] = ""; icons["clojure"][2] = color_default + icons["js"][1] = ""; icons["js"][2] = color_js + icons["linux"][1] = ""; icons["linux"][2] = color_default + icons["fsharp"][1] = ""; icons["fsharp"][2] = color_fsharp + icons["ruby"][1] = ""; icons["ruby"][2] = color_ruby + icons["c"][1] = ""; icons["c"][2] = color_c + icons["chess"][1] = ""; icons["chess"][2] = color_default + icons["haskell"][1] = ""; icons["haskell"][2] = color_vim + icons["html"][1] = ""; icons["html"][2] = color_default + icons["react"][1] = ""; icons["react"][2] = color_react + icons["python"][1] = ""; icons["python"][2] = color_python + icons["database"][1] = ""; icons["database"][2] = color_default + icons["worddoc"][1] = ""; icons["worddoc"][2] = color_document + icons["playlist"][1] = "蘿"; icons["playlist"][2] = color_audio + icons["opticaldisk"][1] = ""; icons["opticaldisk"][2] = color_archive + +# numbers + icons["1"][1] = icons["manual"][1]; icons["1"][2] = icons["manual"][2] + icons["7z"][1] = icons["archive"][1]; icons["7z"][2] = icons["archive"][2] + +# a + icons["a"][1] = icons["manual"][1]; icons["a"][2] = icons["manual"][2] + icons["apk"][1] = icons["archive"][1]; icons["apk"][2] = icons["archive"][2] + icons["asm"][1] = icons["file"][1]; icons["asm"][2] = icons["file"][2] + icons["aup"][1] = icons["musicfile"][1]; icons["aup"][2] = icons["musicfile"][2] + icons["avi"][1] = icons["videofile"][1]; icons["avi"][2] = icons["videofile"][2] + +# b + icons["bat"][1] = icons["script"][1]; icons["bat"][2] = icons["script"][2] + icons["bin"][1] = ""; icons["bin"][2] = color_default + icons["bmp"][1] = icons["picturefile"][1]; icons["bmp"][2] = icons["picturefile"][2] + icons["bz2"][1] = icons["archive"][1]; icons["bz2"][2] = icons["archive"][2] + +# c + icons["cplusplus"][1] = icons["cplusplus"][1]; icons["cplusplus"][2] = icons["cplusplus"][2] + icons["cabal"][1] = icons["haskell"][1]; icons["cab"][2] = icons["haskell"][2] + icons["cab"][1] = icons["archive"][1]; icons["cab"][2] = icons["archive"][2] + icons["cbr"][1] = icons["archive"][1]; icons["cbr"][2] = icons["archive"][2] + icons["cbz"][1] = icons["archive"][1]; icons["cbz"][2] = icons["archive"][2] + icons["cc"][1] = icons["cplusplus"][1]; icons["cc"][2] = icons["cplusplus"][2] + icons["class"][1] = icons["java"][1]; icons["class"][2] = icons["java"][2] + icons["clj"][1] = icons["clojure"][1]; icons["clj"][2] = icons["clojure"][2] + icons["cljc"][1] = icons["clojure"][1]; icons["cljc"][2] = icons["clojure"][2] + icons["cljs"][1] = icons["clojure"][1]; icons["cljs"][2] = icons["clojure"][2] + icons["cmake"][1] = icons["makefile"][1]; icons["cmake"][2] = icons["makefile"][2] + icons["coffee"][1] = ""; icons["coffee"][2] = color_default + icons["conf"][1] = icons["configure"][1]; icons["conf"][2] = icons["configure"][2] + icons["cpio"][1] = icons["archive"][1]; icons["cpio"][2] = icons["archive"][2] + icons["cpp"][1] = icons["cplusplus"][1]; icons["cpp"][2] = icons["cplusplus"][2] + icons["css"][1] = ""; icons["css"][2] = color_css + icons["cue"][1] = icons["playlist"][1]; icons["cue"][2] = icons["playlist"][2] + icons["cvs"][1] = icons["configure"][1]; icons["cvs"][2] = icons["configure"][2] + icons["cxx"][1] = icons["cplusplus"][1]; icons["cxx"][2] = icons["cplusplus"][2] + +# d + icons["db"][1] = icons["database"][1]; icons["db"][2] = icons["database"][2] + icons["deb"][1] = ""; icons["deb"][2] = color_archive + icons["diff"][1] = ""; icons["diff"][2] = color_default + icons["dll"][1] = icons["script"][1]; icons["dll"][2] = icons["script"][2] + icons["doc"][1] = icons["worddoc"][1]; icons["doc"][2] = icons["worddoc"][2] + icons["docx"][1] = icons["worddoc"][1]; icons["docx"][2] = icons["worddoc"][2] + +# e + icons["ejs"][1] = icons["js"][1]; icons["ejs"][2] = icons["js"][2] + icons["elf"][1] = icons["linux"][1]; icons["elf"][2] = icons["linux"][2] + icons["epub"][1] = icons["manual"][1]; icons["epub"][2] = icons["manual"][2] + icons["exe"][1] = icons["exec"][1]; icons["exe"][2] = icons["exec"][2] + +# f + icons["fsharp"][1] = icons["fsharp"][1]; icons["fsharp"][2] = icons["fsharp"][2] + icons["flac"][1] = icons["musicfile"][1]; icons["flac"][2] = icons["musicfile"][2] + icons["fen"][1] = icons["chess"][1]; icons["fen"][2] = icons["chess"][2] + icons["flv"][1] = icons["videofile"][1]; icons["flv"][2] = icons["videofile"][2] + icons["fs"][1] = icons["fsharp"][1]; icons["fs"][2] = icons["fsharp"][2] + icons["fsi"][1] = icons["fsharp"][1]; icons["fsi"][2] = icons["fsharp"][2] + icons["fsscript"][1] = icons["fsharp"][1]; icons["fsscript"][2] = icons["fsharp"][2] + icons["fsx"][1] = icons["fsharp"][1]; icons["fsx"][2] = icons["fsharp"][2] + +# g + icons["gem"][1] = icons["ruby"][1]; icons["gem"][2] = icons["ruby"][2] + icons["gif"][1] = icons["picturefile"][1]; icons["gif"][2] = icons["picturefile"][2] + icons["go"][1] = "ﳑ"; icons["go"][2] = color_default + icons["gz"][1] = icons["archive"][1]; icons["gz"][2] = icons["archive"][2] + icons["gzip"][1] = icons["archive"][1]; icons["gzip"][2] = icons["archive"][2] + +# h + icons["h"][1] = icons["c"][1]; icons["h"][2] = icons["c"][2] + icons["hh"][1] = icons["cplusplus"][1]; icons["hh"][2] = icons["cplusplus"][2] + icons["hpp"][1] = icons["cplusplus"][1]; icons["hpp"][2] = icons["cplusplus"][2] + icons["hs"][1] = icons["haskell"][1]; icons["hs"][2] = icons["haskell"][2] + icons["htaccess"][1] = icons["configure"][1]; icons["htaccess"][2] = icons["configure"][2] + icons["htpasswd"][1] = icons["configure"][1]; icons["htpasswd"][2] = icons["configure"][2] + icons["htm"][1] = icons["html"][1]; icons["htm"][2] = icons["html"][2] + icons["hxx"][1] = icons["cplusplus"][1]; icons["hxx"][2] = icons["cplusplus"][2] + +# i + icons["ico"][1] = icons["picturefile"][1]; icons["ico"][2] = icons["picturefile"][2] + icons["img"][1] = icons["opticaldisk"][1]; icons["img"][2] = icons["opticaldisk"][2] + icons["ini"][1] = icons["configure"][1]; icons["ini"][2] = icons["configure"][2] + icons["iso"][1] = icons["opticaldisk"][1]; icons["iso"][2] = icons["opticaldisk"][2] + +# j + icons["jar"][1] = icons["java"][1]; icons["jar"][2] = icons["java"][2] + icons["java"][1] = icons["java"][1]; icons["java"][2] = icons["java"][2] + icons["jl"][1] = icons["configure"][1]; icons["jl"][2] = icons["configure"][2] + icons["jpeg"][1] = icons["picturefile"][1]; icons["jpeg"][2] = icons["picturefile"][2] + icons["jpg"][1] = icons["picturefile"][1]; icons["jpg"][2] = icons["picturefile"][2] + icons["json"][1] = "ﬥ"; icons["json"][2] = color_js + icons["jsx"][1] = icons["react"][1]; icons["jsx"][2] = icons["react"][2] + +# k + +# l + icons["lha"][1] = icons["archive"][1]; icons["lha"][2] = icons["archive"][2] + icons["lhs"][1] = icons["haskell"][1]; icons["lhs"][2] = icons["haskell"][2] + icons["ilog"][1] = icons["document"][1]; icons["ilog"][2] = icons["document"][2] + icons["lua"][1] = ""; icons["lua"][2] = color_lua + icons["lzh"][1] = icons["archive"][1]; icons["lzh"][2] = icons["archive"][2] + icons["lzma"][1] = icons["archive"][1]; icons["lzma"][2] = icons["archive"][2] + +# m + icons["m"][1] = "ﴜ"; icons["mat"][2] = color_c + icons["m4a"][1] = icons["musicfile"][1]; icons["m4a"][2] = icons["musicfile"][2] + icons["m4v"][1] = icons["videofile"][1]; icons["m4v"][2] = icons["videofile"][2] + icons["mat"][1] = ""; icons["mat"][2] = color_c + icons["markdown"][1] = ""; icons["markdown"][2] = color_docs + icons["md"][1] = ""; icons["md"][2] = color_docs + icons["mk"][1] = icons["makefile"][1]; icons["mk"][2] = icons["makefile"][2] + icons["mkv"][1] = icons["videofile"][1]; icons["mkv"][2] = icons["videofile"][2] + icons["mov"][1] = icons["videofile"][1]; icons["mov"][2] = icons["videofile"][2] + icons["mp3"][1] = icons["musicfile"][1]; icons["mp3"][2] = icons["musicfile"][2] + icons["mp4"][1] = icons["videofile"][1]; icons["mp4"][2] = icons["videofile"][2] + icons["mpeg"][1] = icons["videofile"][1]; icons["mpeg"][2] = icons["videofile"][2] + icons["mpg"][1] = icons["videofile"][1]; icons["mpg"][2] = icons["videofile"][2] + icons["msi"][1] = ""; icons["msi"][2] = color_default + +# n + icons["nix"][1] = ""; icons["nix"][2] = color_fsharp + +# o + icons["o"][1] = icons["manual"][1]; icons["o"][2] = icons["manual"][2] + icons["ogg"][1] = icons["musicfile"][1]; icons["ogg"][2] = icons["musicfile"][2] + icons["odownload"][1] = icons["download"][1]; icons["odownload"][2] = icons["download"][2] + icons["out"][1] = icons["linux"][1]; icons["out"][2] = icons["linux"][2] + +# p + icons["part"][1] = icons["download"][1]; icons["part"][2] = icons["download"][2] + icons["patch"][1] = icons["diff"][1]; icons["patch"][2] = icons["diff"][2] + icons["pdf"][1] = ""; icons["pdf"][2] = color_docs + icons["pgn"][1] = icons["chess"][1]; icons["pgn"][2] = icons["chess"][2] + icons["php"][1] = ""; icons["php"][2] = color_default + icons["png"][1] = icons["picturefile"][1]; icons["png"][2] = icons["picturefile"][2] + icons["ppt"][1] = ""; icons["ppt"][2] = color_default + icons["pptx"][1] = ""; icons["pptx"][2] = color_default + icons["psb"][1] = ""; icons["psb"][2] = color_default + icons["psd"][1] = ""; icons["psd"][2] = color_default + icons["py"][1] = icons["python"][1]; icons["py"][2] = icons["python"][2] + icons["pyc"][1] = icons["python"][1]; icons["pyc"][2] = icons["python"][2] + icons["pyd"][1] = icons["python"][1]; icons["pyd"][2] = icons["python"][2] + icons["pyo"][1] = icons["python"][1]; icons["pyo"][2] = icons["python"][2] + +# q + +# r + icons["rar"][1] = icons["archive"][1]; icons["rar"][2] = icons["archive"][2] + icons["rc"][1] = icons["configure"][1]; icons["rc"][2] = icons["configure"][2] + icons["rom"][1] = ""; icons["rom"][2] = color_default + icons["rpm"][1] = icons["archive"][1]; icons["rpm"][2] = icons["archive"][2] + icons["rss"][1] = "參"; icons["rss"][2] = color_default + icons["rtf"][1] = ""; icons["rtf"][2] = color_default + +# s + icons["sass"][1] = ""; icons["sass"][2] = color_css + icons["scss"][1] = ""; icons["scss"][2] = color_css + icons["so"][1] = icons["manual"][1]; icons["so"][2] = icons["manual"][2] + icons["scala"][1] = ""; icons["scala"][2] = color_scala + icons["sh"][1] = icons["script"][1]; icons["sh"][2] = icons["script"][2] + icons["slim"][1] = icons["script"][1]; icons["slim"][2] = icons["script"][2] + icons["sln"][1] = ""; icons["sln"][2] = color_default + icons["sql"][1] = icons["database"][1]; icons["sql"][2] = icons["database"][2] + icons["srt"][1] = ""; icons["srt"][2] = color_default + icons["isub"][1] = ""; icons["isub"][2] = color_default + icons["svg"][1] = icons["picturefile"][1]; icons["svg"][2] = icons["picturefile"][2] + +# t + icons["tar"][1] = icons["archive"][1]; icons["tar"][2] = icons["archive"][2] + icons["tex"][1] = ""; icons["tex"][2] = color_default + icons["tgz"][1] = icons["archive"][1]; icons["tgz"][2] = icons["archive"][2] + icons["ts"][1] = ""; icons["ts"][2] = color_js + icons["tsx"][1] = icons["react"][1]; icons["tsx"][2] = icons["react"][2] + icons["txt"][1] = icons["document"][1]; icons["txt"][2] = icons["document"][2] + icons["txz"][1] = icons["archive"][1]; icons["txz"][2] = icons["archive"][2] + +# u + +# v + icons["vid"][1] = icons["videofile"][1]; icons["vid"][2] = icons["videofile"][2] + icons["vim"][1] = ""; icons["vim"][2] = color_vim + icons["vimrc"][1] = ""; icons["vimrc"][2] = color_vim + icons["vtt"][1] = ""; icons["vtt"][2] = color_default +# w + icons["wav"][1] = icons["musicfile"][1]; icons["wav"][2] = icons["musicfile"][2] + icons["webm"][1] = icons["videofile"][1]; icons["webm"][2] = icons["videofile"][2] + icons["wma"][1] = icons["videofile"][1]; icons["wma"][2] = icons["videofile"][2] + icons["wmv"][1] = icons["videofile"][1]; icons["wmv"][2] = icons["videofile"][2] + +# x + icons["xbps"][1] = icons["archive"][1]; icons["xbps"][2] = color_archive + icons["xcf"][1] = icons["picturefile"][1]; icons["xcf"][2] = color_image + icons["xhtml"][1] = icons["html"][1]; icons["xhtml"][2] = icons["html"][2] + icons["xls"][1] = ""; icons["xls"][2] = color_default + icons["xlsx"][1] = ""; icons["xlsx"][2] = color_default + icons["xml"][1] = icons["html"][1]; icons["xml"][2] = icons["html"][2] + icons["xz"][1] = icons["archive"][1]; icons["xz"][2] = icons["archive"][2] + +# y + icons["yaml"][1] = icons["configure"][1]; icons["yaml"][2] = icons["configure"][2] + icons["yml"][1] = icons["configure"][1]; icons["yml"][2] = icons["configure"][2] +# z + icons["zip"][1] = icons["archive"][1]; icons["zip"][2] = icons["archive"][2] + icons["zsh"][1] = icons["script"][1]; icons["zsh"][2] = icons["script"][2] + icons["zst"][1] = icons["archive"][1]; icons["zst"][2] = icons["archive"][2] + + FS = "." + limit = ENVIRON["limit"] + switch (colordepth) { + case "4": + escape="\033[" + break; + case "8": + escape="\033[38;5;" + break; + case "24": + escape="\033[38;2;" + break; + } + bstr = ENVIRON["beforestr"] +} +{ + # dont print cwd . and leading ./ from tree -f + if ($0 ~/^\.$/) + next + ent = ($0 ~/^\.\//) ? substr($0, 3, length($0) - 2) : $0 + ext = $NF + + # Print icons, set color and bold directories by using ansi escape codes + if (ext in icons) + printcolor(icons[ext][1], icons[ext][2], color_filetxt, ent, "10") + else + switch (substr(ent, length(ent), 1)) { + case "/": + printcolor(icons["directory"][1], color_default, color_dirtxt, ent, "1") + break; + case "*": + printcolor(icons["exe"][1], color_default, color_filetxt, ent, "10") + break; + case "|": + printcolor(icons["pipe"][1], color_default, color_filetxt, ent, "10") + break; + case "=": + printcolor(icons["socket"][1], color_default, color_filetxt, ent, "10") + break; + case ">": + printcolor(icons["door"][1], color_default, color_filetxt, ent, "10") + break; + default: + printcolor(icons["file"][1], color_default, color_filetxt, ent, "10") + } +} +function printcolor(i, c, d, n, b) { + if (limit != "" && length(n) + 2 > limit) + n = substr(n, 1, limit - 2) + printf "\033[0m" + printf "%s%s%s;%sm%s %s%sm%s\n", bstr, escape, c, b, i, escape, d, n +}' +printf '\033[0m' +} + +print_begin() { + printf '%s\n' "$1" | sed 's/\\n/\n/g' +} + +print_end() { + printf '%s\n' "$1" | sed 's/\\n/\n/g' +} + +print_help() { + printf 'Icon Lookup\n +Usage: + iconlookup [options] + iconlookup [-bBe] [string] + iconlookup -l [number] + iconlookup (-h | --help) + + Prepend icons to list of files based on extension or appended indicator by ls/tree "-F" flag ("/" for directory, "*" for executable etc.) + +Options: + -h --help -? Show this screen. + -b --before Prepend str before icon. + -B --begin Prepend str before output. + -e --end Append str after output. + -l --limit Limit line length to [number] characters.' +} + +while :; do + case $1 in + -h|-\?|--help) + print_help + exit ;; + -B|--begin) + if [ -n "$2" ]; then + print_begin "$2" + fi + shift ;; + -e|--end) + if [ -n "$2" ]; then + end=1 + endstr="$2" + fi + shift ;; + -b|--before) + if [ -n "$2" ]; then + export beforestr="$2" + fi + shift ;; + -l|--limit) + if [ -n "$2" ]; then + export limit="$2" + shift + else + printf 'ERROR: "--limit" requires a non-empty option argument.\n' + exit + fi ;; + --) + shift + break ;; + -?*) + printf 'WARNING: Unknown option ignored: %s\n' "$1" ;; + *) break ;; + esac + shift +done + +if [ ! -t 0 ]; then + [ -n "$beforestr" ] && limit="$((limit - ${#beforestr}))" + icon_lookup +else + printf 'ERROR: no data provided...\nExpecting a directory listing in stdin\n' +fi + +if [ -n "$end" ]; then + print_end "$endstr" +fi diff --git a/nnn/plugins/.nmv b/nnn/plugins/.nmv @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +# Description: An almost fully POSIX compliant batch file renamer +# +# Note: nnn auto-detects and invokes this plugin if available +# Whitespace is used as delimiter for read. +# The plugin doesn't support filenames with leading or trailing whitespace +# To use NNN_LIST your shell must support readlink(1) +# +# Capabilities: +# 1. Basic file rename +# 2. Detects order change +# 3. Can move files +# 4. Can remove files +# 5. Switch number pairs to swap filenames +# +# Shell: bash +# Author: KlzXS + +EDITOR="${EDITOR:-vi}" +TMPDIR="${TMPDIR:-/tmp}" +NNN_INCLUDE_HIDDEN="${NNN_INCLUDE_HIDDEN:-0}" +VERBOSE="${VERBOSE:-0}" +RECURSIVE="${RECURSIVE:-0}" + +case "$NNN_TRASH" in + 1) + RM_UTIL="trash-put" ;; + 2) + RM_UTIL="gio trash" ;; + *) + RM_UTIL="rm -ri" ;; +esac + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +exit_status=0 + +dst_file=$(mktemp "$TMPDIR/.nnnXXXXXX") + +if [ -s "$selection" ]; then + printf "Rename 'c'urrent / 's'election? " + read -r resp + + if ! [ "$resp" = "c" ] && ! [ "$resp" = "s" ]; then + exit 1 + fi +fi + +if [ "$resp" = "s" ]; then + arr=$(tr '\0' '\n' < "$selection") +else + findcmd="find . ! -name ." + + if [ "$RECURSIVE" -eq 0 ]; then + findcmd="$findcmd -prune" + fi + + if [ "$NNN_INCLUDE_HIDDEN" -eq 0 ]; then + findcmd="$findcmd ! -name \".*\"" + fi + + if [ -z "$NNN_LIST" ]; then + findcmd="$findcmd -print" + else + findcmd="$findcmd -printf "'"'"$NNN_LIST/%P\n"'"' + fi + + arr=$(eval "$findcmd" | sort) +fi + +lines=$(printf "%s\n" "$arr" | wc -l) +width=${#lines} + +printf "%s" "$arr" | awk '{printf("%'"${width}"'d %s\n", NR, $0)}' > "$dst_file" + +items=("~") +while IFS='' read -r line; do + if [ -n "$NNN_LIST" ]; then + line=$(readlink "$line" || printf "%s" "$line") + fi + + items+=("$line"); +done < <(printf "%s\n" "$arr") + +$EDITOR "$dst_file" + +while read -r num name; do + if [ -z "$name" ]; then + if [ -z "$num" ]; then + continue + fi + + printf "%s: unable to parse line, aborting\n" "$0" + exit 1 + fi + + # check if $num is an integer + if [ ! "$num" -eq "$num" ] 2> /dev/null; then + printf "%s: unable to parse line, aborting\n" "$0" + exit 1 + fi + + src=${items[$num]} + + if [ -z "$src" ]; then + printf "%s: unknown item number %s\n" "$0" "$num" > /dev/stderr + continue + elif [ "$name" != "$src" ]; then + if [ -z "$name" ]; then + continue + fi + + if [ ! -e "$src" ] && [ ! -L "$src" ]; then + printf "%s: %s does not exit\n" "$0" "$src" > /dev/stderr + + unset "items[$num]" + continue + fi + + # handle swaps + if [ -e "$name" ] || [ -L "$name" ]; then + tmp="$name~" + c=0 + + while [ -e "$tmp" ] || [ -L "$tmp" ]; do + c=$((c+1)) + tmp="$tmp~$c" + done + + if mv "$name" "$tmp"; then + if [ "$VERBOSE" -ne 0 ]; then + printf "'%s' -> '%s'\n" "$name" "$tmp" + fi + else + printf "%s: failed to rename %s to %s: %s\n" "$0" "$name" "$tmp" "$!" > /dev/stderr + exit_status=1 + fi + + for key in "${!items[@]}"; do + if [ "${items[$key]}" = "$name" ]; then + items[$key]="$tmp" + fi + done + fi + + dir=$(dirname "$name") + if [ ! -d "$dir" ] && ! mkdir -p "$dir"; then + printf "%s: failed to create directory tree %s\n" "$0" "$dir" > /dev/stderr + exit_status=1 + elif ! mv -i "$src" "$name"; then + printf "%s: failed to rename %s to %s: %s\n" "$0" "$name" "$tmp" "$!" > /dev/stderr + exit_status=1 + else + if [ -d "$name" ]; then + for key in "${!items[@]}"; do + items[$key]=$(printf "%s" "${items[$key]}" | sed "s|^$src\(\$\|\/\)|$name\1|") + done + + if [ "$VERBOSE" -ne 0 ]; then + printf "'%s' => '%s'\n" "$src" "$name" + fi + else + true + if [ "$VERBOSE" -ne 0 ]; then + printf "'%s' -> '%s'\n" "$src" "$name" + fi + fi + fi + fi + + unset "items[$num]" +done <"$dst_file" + +unset "items[0]" +for item in "${items[@]}"; do + $RM_UTIL "$item" +done + +rm "$dst_file" +exit $exit_status diff --git a/nnn/plugins/.nnn-plugin-helper b/nnn/plugins/.nnn-plugin-helper @@ -0,0 +1,38 @@ +#!/usr/bin/env sh + +# Description: Helper script for plugins +# +# Shell: POSIX compliant +# Author: Anna Arad + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +export selection + +## Set CUR_CTX to 1 to open directory in current context +CUR_CTX=0 +export CUR_CTX + +## Ask nnn to switch to directory $1 in context $2. +## If $2 is not provided, the function asks explicitly. +nnn_cd () { + dir="$1" + + if [ -z "$NNN_PIPE" ]; then + echo "No pipe file found" 1>&2 + return + fi + + if [ -n "$2" ]; then + context=$2 + elif [ $CUR_CTX -ne 1 ]; then + printf "Choose context 1-4 (blank for current): " + read -r context + fi + + printf "%s" "${context:-0}c$dir" > "$NNN_PIPE" +} + +cmd_exists () { + type "$1" > /dev/null 2>&1 + echo $? +} diff --git a/nnn/plugins/.ntfy b/nnn/plugins/.ntfy @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# Description: Show a notification +# +# Details: nnn invokes this plugin to show notification when a cp/mv/rm operation is complete. +# +# Dependencies: notify-send (Ubuntu)/ntfy (https://github.com/dschep/ntfy)/osascript (macOS)/notify (Haiku) +# +# Shell: POSIX compliant +# Author: Anna Arad + +OS="$(uname)" + +if type notify-send >/dev/null 2>&1; then + notify-send nnn "Done!" +elif [ "$OS" = "Darwin" ]; then + osascript -e 'display notification "Done!" with title "nnn"' +elif type ntfy >/dev/null 2>&1; then + ntfy -t nnn send "Done!" +elif [ "$OS" = "Haiku" ]; then + notify --title "nnn" "Done!" +fi diff --git a/nnn/plugins/README.md b/nnn/plugins/README.md @@ -0,0 +1,357 @@ +<h1 align="center">nnn plugins</h1> + +<p align="center"><img src="https://i.imgur.com/SpT0L2W.png" /></p> +<p align="center"><i>read ebooks with plugin gutenread (Android)</i></p> + +## Introduction + +Plugins extend the capabilities of `nnn`. They are _executable_ scripts (or binaries) `nnn` can communicate with and trigger. This mechanism fits perfectly with the fundamental design to keep the core file manager lean and fast, by delegating repetitive (but not necessarily file manager-specific) tasks to the plugins which can be run with custom hotkeys. + +`nnn` is _**language-agnostic**_ when it comes to plugins. You can write a plugin in any (scripting) language you are comfortable in! + +## List of plugins + +| Plugin (a-z) | Description [Clears selection<sup>1</sup>] | Lang | Dependencies | +| --- | --- | --- | --- | +| [autojump](autojump) | Navigate to dir/path | sh | [jump](https://github.com/gsamokovarov/jump)/autojump/<br>zoxide/z (needs fzf) | +| [boom](boom) | Play random music from dir | sh | [moc](http://moc.daper.net/) | +| [bulknew](bulknew) | Create multiple files/dirs at once | bash | sed, xargs, mktemp | +| [cdpath](cdpath) | `cd` to the directory from `CDPATH` | sh | fzf | +| [chksum](chksum) | Create and verify checksums [✓] | sh | md5sum,<br>sha256sum | +| [cmusq](cmusq) | Queue/play files/dirs in cmus player [✓] | sh | cmus, pgrep | +| [diffs](diffs) | Diff for selection (limited to 2 for directories) [✓] | sh | vimdiff, mktemp | +| [dragdrop](dragdrop) | Drag/drop files from/into nnn | sh | [dragon](https://github.com/mwh/dragon) | +| [dups](dups) | List non-empty duplicate files in current dir | bash | find, md5sum,<br>sort uniq xargs | +| [finder](finder) | Run custom find command (**stored in histfile**) and list | sh | - | +| [fixname](fixname) | Clean filename to be more shell-friendly [✓] | bash | sed | +| [fzcd](fzcd) | Fuzzy search multiple dirs (or `$PWD`) and visit file | sh | fzf, (find) | +| [fzhist](fzhist) | Fuzzy-select a cmd from history, edit in `$EDITOR` and run | sh | fzf, mktemp | +| [fzopen](fzopen) | Fuzzy find file(s) in subtree to edit/open/pick | sh | fzf, xdg-open/open | +| [fzplug](fzplug) | Fuzzy find, preview and run other plugins | sh | fzf | +| [getplugs](getplugs) | Update plugins to installed `nnn` version | sh | curl | +| [gitroot](gitroot) | Cd to the root of current git repo | sh | git | +| [gpge](gpge) | Encrypt/decrypt files using GPG [✓] | sh | gpg | +| [gutenread](gutenread) | Browse, download, read from Project Gutenberg | sh | curl, unzip, w3m<br>[epr](https://github.com/wustho/epr) (optional) | +| [gsconnect](gsconnect) | GNOME's implementation of kdeconnect [✓] | sh | gsconnect | +| [imgresize](imgresize) | Batch resize images in dir to screen resolution | sh | [imgp](https://github.com/jarun/imgp) | +| [imgur](imgur) | Upload an image to imgur (from [imgur-screenshot](https://github.com/jomo/imgur-screenshot)) | bash | - | +| [imgview](imgview) | View (thumbnail)images, set wallpaper, [rename](https://github.com/jarun/nnn/wiki/Basic-use-cases#browse-rename-images) and [more](https://wiki.archlinux.org/index.php/Sxiv#Assigning_keyboard_shortcuts)| sh | _see in-file docs_ | +| [ipinfo](ipinfo) | Fetch external IP address and whois information | sh | curl, whois | +| [kdeconnect](kdeconnect) | Send selected files to an Android device [✓] | sh | kdeconnect-cli | +| [launch](launch) | GUI application launcher | sh | fzf | +| [mimelist](mimelist) | List files by mime in subtree | sh | - | +| [moclyrics](moclyrics) | Show lyrics of the track playing in moc | sh | [ddgr](https://github.com/jarun/ddgr), [moc](http://moc.daper.net/) | +| [mocq](mocq) | Queue/play selection/dir/file in moc [✓] | sh | [moc](http://moc.daper.net/) | +| [mp3conv](mp3conv) | Extract audio from multimedia as mp3 | sh | ffmpeg | +| [mtpmount](mtpmount) | Toggle mount of MTP device (eg. Android) | sh | gvfs-mtp | +| [nbak](nbak) | Backs up `nnn` config | sh | tar, awk, mktemp | +| [nmount](nmount) | Toggle mount status of a device as normal user | sh | pmount, udisks2 | +| [nuke](nuke) | Sample file opener (CLI-only by default) | sh | _see in-file docs_ | +| [oldbigfile](oldbigfile) | List large files by access time | sh | find, sort | +| [organize](organize) | Auto-organize files in directories by file type [✓] | sh | file | +| [pdfread](pdfread) | Read a PDF or text file aloud | sh | pdftotext, mpv,<br>pico2wave | +| [preview-tabbed](preview-tabbed) | Preview files with Tabbed/xembed | bash | _see in-file docs_ | +| [preview-tui](preview-tui) | Preview with Tmux/kitty/[QuickLook](https://github.com/QL-Win/QuickLook)/xterm/`$TERMINAL` | sh | _see in-file docs_ | +| [pskill](pskill) | Fuzzy list by name and kill process or zombie | sh | fzf, ps, sudo/doas | +| [renamer](renamer) | Batch rename selection or files in dir [✓] | sh | [qmv](https://www.nongnu.org/renameutils/)/[vidir](https://joeyh.name/code/moreutils/) | +| [ringtone](ringtone) | Create a variable bitrate mp3 ringtone from file | sh | date, ffmpeg | +| [rsynccp](rsynccp) | Gives copy-paste verbose progress percentage [✓] | sh | rsync | +| [splitjoin](splitjoin) | Split file or join selection [✓] | sh | split, cat | +| [suedit](suedit) | Edit file using superuser permissions | sh | sudoedit/sudo/doas | +| [togglex](togglex) | Toggle executable mode for selection [✓] | sh | chmod | +| [umounttree](umounttree) | Unmount a remote mountpoint from within | sh | fusermount | +| [upload](upload) | Upload to Firefox Send or ix.io (text) or file.io (bin) | sh | [ffsend](https://github.com/timvisee/ffsend), curl, jq, tr | +| [wallpaper](wall) | Set wallpaper or change colorscheme | sh | nitrogen/pywal | +| [x2sel](x2sel) | Copy file list from system clipboard to selection | sh | _see in-file docs_ | +| [xdgdefault](xdgdefault) | Set the default app for the hovered file type | sh | xdg-utils, fzf/dmenu | + +Note: + +1. A plugin has to explicitly request `nnn` to clear the selection e.g. after operating on the selected files. + +### Table of contents + +- [Installation](#installation) +- [Configuration](#configuration) + - [Skip directory refresh after running a plugin](#skip-directory-refresh-after-running-a-plugin) +- [Running commands as plugin](#running-commands-as-plugin) + - [Skip user confirmation after command execution](#skip-user-confirmation-after-command-execution) + - [Run a GUI app as plugin](#run-a-gui-app-as-plugin) + - [Page non-interactive command output](#page-non-interactive-command-output) + - [Some useful key-command examples](#some-useful-key-command-examples) +- [Access level of plugins](#access-level-of-plugins) +- [Create your own plugins](#create-your-own-plugins) + - [Send data to `nnn`](#send-data-to-nnn) + - [Get notified on file hover](#get-notified-on-file-hover) +- [Examples](#examples) +- [Contributing plugins](#contributing-plugins) + +## Installation + +The following command installs or updates (after backup) all plugins: + +```sh +curl -Ls https://raw.githubusercontent.com/jarun/nnn/master/plugins/getplugs | sh +``` + +Plugins are installed to `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`. + +## Configuration + +Set environment variable `NNN_PLUG` to assign keybinds and invoke plugins directly using the plugin shortcut (<kbd>;</kbd>) followed by the assigned key character. E.g., with the below config: + +```sh +export NNN_PLUG='f:finder;o:fzopen;p:mocplay;d:diffs;t:nmount;v:imgview' +``` + +plugin `finder` can be invoked with the keybind <kbd>;f</kbd>, `fzopen` can be run with <kbd>;o</kbd> and so on... The key vs. plugin pairs are shown in the help and config screen. + +Alternatively, combine with <kbd>Alt</kbd> (i.e. <kbd>Alt+key</kbd>). + +To pick and run an unassigned plugin, press <kbd>Enter</kbd> (to _enter_ the plugin dir) at the plugin prompt. + +To run a plugin at startup, use the option `-P` followed by the plugin key. + +If the plugins list gets too long, try breaking them up into sections: + +``` +NNN_PLUG_PERSONAL='g:personal/convert2zoom;p:personal/echo' +NNN_PLUG_WORK='j:work/prettyjson;d:work/foobar' +NNN_PLUG_INLINE='e:!go run $nnn*' +NNN_PLUG_DEFAULT='1:ipinfo;p:preview-tui;o:fzz;b:nbak' +NNN_PLUG="$NNN_PLUG_PERSONAL;$NNN_PLUG_WORK;$NNN_PLUG_DEFAULT;$NNN_PLUG_INLINE" +export NNN_PLUG +``` + +Note: +- `'g:personal/convert2zoom'` will look in the personal sub-folder inside the plugin folder. +- `'b:boom;b:bulknew` will result in only the first definition of *b* (`b:boom`) being used. +- A keybinding definition of more than 1 character will prevent nnn from starting. + + +#### Skip directory refresh after running a plugin + +`nnn` refreshes the directory after running a plugin to reflect any changes by the plugin. To disable this add a `-` before the plugin name: + +```sh +export NNN_PLUG='p:-plugin' +``` + +## Running commands as plugin + +To assign keys to arbitrary non-background cli commands and invoke like plugins, add `!` (underscore) before the command. + +```sh +export NNN_PLUG='x:!chmod +x $nnn;g:!git log;s:!smplayer $nnn' +``` + +Now <kbd>;x</kbd> can be used to make a file executable, <kbd>;g</kbd> can be used to the git log of a git project directory, <kbd>;s</kbd> can be used to preview a partially downloaded media file. + +#### Skip user confirmation after command execution + +`nnn` waits for user confirmation (the prompt `Press Enter to continue`) after it executes a command as plugin (unlike plugins which can add a `read` to wait). To skip this, add a `*` after the command. + +```sh +export NNN_PLUG='s:!smplayer $nnn*;n:-!vim /home/vaio/Dropbox/Public/synced_note*' +``` + +Now there will be no prompt after <kbd>;s</kbd> and <kbd>;n</kbd>. + +Note: Do not use `*` with programs those run and exit e.g. cat. + +#### Run a GUI app as plugin + +To run a GUI app as plugin, add a `&` after `!`. + +```sh +export NNN_PLUG='m:-!&mousepad $nnn' +``` + +Note: `$nnn` must be the last argument in this case. + +#### Page non-interactive command output + +To show the output of run-and-exit commands which do not need user input, add `|` (pipe) after `!`. + +```sh +export NNN_PLUG='m:-!|mediainfo $nnn;t:-!|tree -ps;l:-!|ls -lah --group-directories-first' +``` + +This option is incompatible with `&` (terminal output is masked for GUI programs) and ignores `*` (output is already paged for user). + +Notes: + +1. Use single quotes for `$NNN_PLUG` so `$nnn` is not interpreted +2. (_Again_) add `!` before the command +3. To disable directory refresh after running a _command as plugin_, prefix with `-!` + +#### Some useful key-command examples + +| Key:Command | Description | +|---|---| +| `c:!convert $nnn png:- \| xclip -sel clipboard -t image/png*` | Copy image to clipboard | +| `e:-!sudo -E vim $nnn*` | Edit file as root in vim | +| `g:-!git diff` | Show git diff | +| `h:-!hx $nnn*` | Open hovered file in [hx](https://github.com/krpors/hx) hex editor | +| `k:-!fuser -kiv $nnn*` | Interactively kill process(es) using hovered file | +| `l:-!git log` | Show git log | +| `n:-!vi /home/user/Dropbox/dir/note*` | Take quick notes in a synced file/dir of notes | +| `p:-!less -iR $nnn*` | Page through hovered file in less | +| `s:-!&smplayer -minigui $nnn` | Play hovered media file, even unfinished download | +| `x:!chmod +x $nnn` | Make the hovered file executable | +| `y:-!sync*` | Flush cached writes | + +## Access level of plugins + +When `nnn` executes a plugin, it does the following: +- Changes to the directory where the plugin is to be run (`$PWD` pointing to the active directory) +- Passes three arguments to the script: + 1. `$1`: The hovered file's name. + 2. `$2`: The working directory (might differ from `$PWD` in case of symlinked paths; non-canonical). + 3. `$3`: The picker mode output file (`-` for stdout) if `nnn` is executed as a file picker. +- Sets the environment variable `NNN_PIPE` used to control `nnn` active directory. +- Sets the environment variable `NNN_INCLUDE_HIDDEN` to `1` if hidden files are active, `0` otherwise. +- Exports the [special variables](https://github.com/jarun/nnn/wiki/Concepts#special-variables). + +Plugins can also read the `.selection` file in the config directory. + +## Create your own plugins + +Plugins can be written in any scripting language. However, POSIX-compliant shell scripts runnable in `sh` are preferred. + +**Make the file executable**. Drop it in the plugin directory. Optionally add a hotkey in `$NNN_PLUG` for frequent usage. + +#### Send data to `nnn` +`nnn` provides a mechanism for plugins to send data to `nnn` to control its active directory or invoke the list mode. +The way to do so is by writing to the pipe pointed by the environment variable `NNN_PIPE`. +The plugin should write a single string in the format `(<->)<ctxcode><opcode><data>` without a newline at the end. For example, `1c/etc`. + +The optional `-` at the **beginning of the stream** instructs `nnn` to clear the selection. +In cases where the data transfer to `nnn` has to happen while the selection file is being read (e.g. in a loop), the plugin should +create a tmp copy of the selection file, inform `nnn` to clear the selection and then do the subsequent processing with the tmp file. + +The `ctxcode` indicates the context to change the active directory of. + +| Context code | Meaning | +|:---:| --- | +| `+` | smart context (next inactive else current) | +| `0` | current context | +| `1`-`4` | context number | + +The `opcode` indicates the operation type. + +| Opcode | Operation | +|:---:| --- | +| `c` | change directory | +| `l` | list files in list mode | +| `p` | picker file overwritten | + +For convenience, we provided a helper script named `.nnn-plugin-helper` and a function named `nnn_cd` to ease this process. `nnn_cd` receives the path to change to as the first argument, and the context as an optional second argument. +If a context is not provided, it is asked for explicitly. To skip this and choose the current context, set the `CUR_CTX` variable in `.nnn-plugin-helper` (or in the specific plugin after sourcing `.nnn-plugin-helper`) to 1. +Usage examples can be found in the Examples section below. + +#### Get notified on file hover + +If `NNN_FIFO` is set, `nnn` will open it and write every hovered files. This can be used in plugins and external scripts, e.g. to implement file previews. + +Don't forget to fork in the background to avoid blocking `nnn`. + +For more details on configuration and usage of the preview plugins, visit [Live Previews](https://github.com/jarun/nnn/wiki/Live-previews). + +## Examples + +There are many plugins provided by `nnn` which can be used as examples. Here are a few simple selected examples. + +#### Show the git log of changes to the particular file along with the code for a quick and easy review. + +```sh +#!/usr/bin/env sh + +git log -p -- "$1" +``` + +#### Change to directory in clipboard using helper script + +```sh +#!/usr/bin/env sh + +. $(dirname $0)/.nnn-plugin-helper + +nnn_cd "$(xsel -ob)" +``` + +#### Change directory to the location of a link using helper script with specific context (current) + +```sh +#!/usr/bin/env sh + +. $(dirname $0)/.nnn-plugin-helper + +nnn_cd "$(dirname $(readlink -fn $1))" 0 +``` + +#### Change to arbitrary directory without helper script + +```sh +#!/usr/bin/env sh + +printf "cd to: " +read -r dir + +printf "%s" "0c$dir" > "$NNN_PIPE" +``` + +#### Send every hovered file to X selection + +```sh +#!/usr/bin/env sh + +if [ -z "$NNN_FIFO" ] ; then + exit 1 +fi + +while read FILE ; do + printf "%s" "$FILE" | xsel +done < "$NNN_FIFO" & +disown +``` + +#### Quick find (using `fd`) + +```sh +#!/usr/bin/env sh + +. "$(dirname "$0")"/.nnn-plugin-helper + +printf "pattern: " +read -r pattern + +if [ -n "$pattern" ]; then + printf "%s" "+l" > "$NNN_PIPE" + eval "fd -HI $pattern -0" > "$NNN_PIPE" +fi +``` + +#### Quick grep (using `rg`) + +```sh +#!/usr/bin/env sh + +. "$(dirname "$0")"/.nnn-plugin-helper + +printf "pattern: " +read -r pattern + +if [ -n "$pattern" ]; then + printf "%s" "+l" > "$NNN_PIPE" + eval "rg -l0 --hidden -S $pattern" > "$NNN_PIPE" +fi +``` + +## Contributing plugins + +1. Add informative sections like _Description_, _Notes_, _Dependencies_, _Shell_, _Author_ etc. in the plugin. +2. Add an entry in the table above. +3. Keep non-portable commands (like `notify-send`) commented so users from any other OS/DE aren't surprised. +4. The plugin file should be executable. +5. If your plugin stores data, use `${XDG_CACHE_HOME:-$HOME/.cache}/nnn`. Document it _in-file_. diff --git a/nnn/plugins/autojump b/nnn/plugins/autojump @@ -0,0 +1,61 @@ +#!/usr/bin/env sh + +# Description: Navigate to directory using jump/autojump/zoxide/z +# +# Dependencies: +# - jump - https://github.com/gsamokovarov/jump +# - OR autojump - https://github.com/wting/autojump +# - OR zoxide - https://github.com/ajeetdsouza/zoxide +# - OR z - https://github.com/rupa/z (z requires fzf) +# - OR z (fish) - https://github.com/jethrokuan/z (z requires fzf) +# +# Note: The dependencies STORE NAVIGATION PATTERNS +# +# Shell: POSIX compliant +# Authors: Marty Buchaus, Dave Snider, Tim Adler, Nick Waywood + +if [ ! -p "$NNN_PIPE" ]; then + printf 'ERROR: NNN_PIPE is not set!' + read -r _ + exit 2 +fi + +if type jump >/dev/null 2>&1; then + printf "jump to : " + IFS= read -r line + # shellcheck disable=SC2086 + odir="$(jump cd ${line})" + printf "%s" "0c$odir" > "$NNN_PIPE" +elif type autojump >/dev/null 2>&1; then + printf "jump to : " + read -r dir + odir="$(autojump "$dir")" + printf "%s" "0c$odir" > "$NNN_PIPE" +elif type zoxide >/dev/null 2>&1; then + if type fzf >/dev/null 2>&1; then + odir="$(zoxide query -i --)" + printf "%s" "0c$odir" > "$NNN_PIPE" + else + printf "jump to : " + read -r dir + odir="$(zoxide query -- "$dir")" + printf "%s" "0c$odir" > "$NNN_PIPE" + fi +else + # rupa/z uses $_Z_DATA, jethrokuan/z (=port of z for fish) uses $Z_DATA + datafile="${_Z_DATA:-${Z_DATA:-$HOME/.z}}" + if type fzf >/dev/null 2>&1 && [ -f "$datafile" ]; then + # Read the data from z's file instead of calling + # z so the data doesn't need to be processed twice + sel=$(awk -F "|" '{print $1}' "$datafile" | fzf | awk '{$1=$1};1') + + # NOTE: Uncomment this line and comment out the line above if + # you want to see the weightings of the dir's in the fzf pane + # sel=$(awk -F "|" '{printf "%s %s\n", $2, $1}' "$datafile" | fzf | sed 's/^[0-9,.]* *//' | awk '{$1=$1};1') + + printf "%s" "0c$sel" > "$NNN_PIPE" + else + printf "No supported autojump script [jump/autojump/zoxide/z (needs fzf)] found" + read -r _ + fi +fi diff --git a/nnn/plugins/boom b/nnn/plugins/boom @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +# Description: Play random music (MP3, FLAC, M4A, WEBM, WMA) from current dir. +# +# Dependencies: mocp (or custom) +# +# Note: You may want to set GUIPLAYER. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +GUIPLAYER="${GUIPLAYER}" +NUMTRACKS="${NUMTRACKS:-100}" + +if [ -n "$GUIPLAYER" ]; then + find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | shuf -n "$NUMTRACKS" | xargs -d "\n" "$GUIPLAYER" > /dev/null 2>&1 & + + # detach the player + sleep 1 +elif type mocp >/dev/null 2>&1; then + cmd=$(pgrep -x mocp 2>/dev/null) + ret=$cmd + + if [ -z "$ret" ]; then + # start MOC server + mocp -S + mocp -o shuffle + else + # mocp running, check if it's playing + state=$(mocp -i | grep "State:" | cut -d' ' -f2) + if [ "$state" = 'PLAY' ]; then + # add up to 100 random audio files + find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | head -n "$NUMTRACKS" | xargs -d "\n" mocp -a + exit + fi + fi + + # clear MOC playlist + mocp -c + mocp -o shuffle + + # add up to 100 random audio files + find . -type f \( -iname "*.mp3" -o -iname "*.flac" -o -iname "*.m4a" -o -iname "*.webm" -o -iname "*.wma" \) | head -n "$NUMTRACKS" | xargs -d "\n" mocp -a + + # start playing + mocp -p +else + printf "moc missing" + read -r _ +fi diff --git a/nnn/plugins/bulknew b/nnn/plugins/bulknew @@ -0,0 +1,32 @@ +#!/usr/bin/env sh + +# Description: Allows for creation of multiple files/dirs simultaneously +# Creates a tmp file to write each entry in a separate line +# +# Note: Only relative paths are supported. Absolute paths are ignored +# Leading and trailing whitespace in path names is also ignored +# +# Shell: POSIX compliant +# Author: KlzXS + +EDITOR="${EDITOR:-vi}" +TMPDIR="${TMPDIR:-/tmp}" + +printf "'f'ile / 'd'ir? " +read -r resp + +if [ "$resp" = "f" ]; then + #shellcheck disable=SC2016 + cmd='mkdir -p "$(dirname "{}")" && touch "{}"' +elif [ "$resp" = "d" ]; then + cmd='mkdir -p {}' +else + exit 1 +fi + +tmpfile=$(mktemp "$TMPDIR/.nnnXXXXXX") +$EDITOR "$tmpfile" + +sed "/^\//d" "$tmpfile" | xargs -n1 -I{} sh -c "$cmd" + +rm "$tmpfile" diff --git a/nnn/plugins/cdpath b/nnn/plugins/cdpath @@ -0,0 +1,55 @@ +#!/usr/bin/env sh + +# Description: 'cd' to the directory from CDPATH +# +# Details: If the CDPATH environmet variable is not set, the default value of +# ${XDG_CONFIG_HOME:-$HOME/.config}/nnn/bookmarks will be used. +# You can create this directory and fill it with symbolic links to your +# favorite directories. It's a good idea to add it to CDPATH so that it +# could also be used from the command line outside of nnn. +# The fzf search is done on the directory basename (the first column). +# +# This plugin is an extended version of the bookmarks plugin. +# If you set your CDPATH to ${XDG_CACHE_HOME:-$HOME/.cache}/nnn/bookmarks +# or to the value of BOOKMARKS_DIR, you can use it as a bookmarks replacement. +# +# Shell: POSIX compliant +# Author: Yuri Kloubakov + +. "$(dirname "$0")"/.nnn-plugin-helper + +# Get a list of (symbolic links to) directories for every element of CDPATH +get_dirs() { + IFS=':' + for path in $CDPATH; do + for entry in "$path"/*; do + if [ -d "$entry" ]; then + name=$(basename "$entry" | grep -o '^.\{1,24\}') + if [ -h "$entry" ]; then + slink=$(ls -dl -- "$entry") + entry=${slink#*" $entry -> "} + fi + printf "%-24s :%s\n" "${name}" "$entry" + fi + done + done +} + +abort() { + echo "$1" + read -r _ + exit 1 +} + +if [ -z "$CDPATH" ]; then + CDPATH="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/bookmarks" + [ -d "$CDPATH" ] || abort "CDPATH is not set and there is no \"$CDPATH\" directory" +fi + +dir_list=$(get_dirs) +[ -n "$dir_list" ] || abort "There are no directories to choose from. Check your \"$CDPATH\"." + +dir=$(echo "$dir_list" | fzf --nth=1 --delimiter=':' | awk -F: 'END { print $2 }') +if [ -n "$dir" ]; then + nnn_cd "$dir" 0 +fi diff --git a/nnn/plugins/chksum b/nnn/plugins/chksum @@ -0,0 +1,72 @@ +#!/usr/bin/env sh + +# Description: Create and verify checksums +# +# Details: +# - selection: it will generate one file with the checksums and filenames +# (and with paths if they are in another directory) +# output checksum filename format: checksum_timestamp.checksum_type +# - file: if the file is a checksum, the plugin does the verification +# if the file is not a checksum, checksum will be generated for it +# the output checksum filename will be filename.checksum_type +# - directory: recursively calculates checksum for all the files in the dir +# the output checksum filename will be directory.checksum_type +# +# Shell: POSIX compliant +# Authors: ath3, Arun Prakash Jana + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +resp=f +chsum=md5 + +checksum_type() +{ + echo "possible checksums: md5, sha1, sha224, sha256, sha384, sha512" + printf "create md5 (m), sha256 (s), sha512 (S) (or type one of the above checksums) [default=m]: " + read -r chsum_resp + for chks in md5 sha1 sha224 sha256 sha384 sha512 + do + if [ "$chsum_resp" = "$chks" ]; then + chsum=$chsum_resp + return + fi + done + if [ "$chsum_resp" = "s" ]; then + chsum=sha256 + elif [ "$chsum_resp" = "S" ]; then + chsum=sha512 + fi +} + +if [ -s "$selection" ]; then + printf "work with selection (s) or current file (f) [default=f]: " + read -r resp +fi + +if [ "$resp" = "s" ]; then + checksum_type + sed 's|'"$PWD/"'||g' < "$selection" | xargs -0 -I{} ${chsum}sum {} > "checksum_$(date '+%Y%m%d%H%M').$chsum" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +elif [ -n "$1" ]; then + if [ -f "$1" ]; then + for chks in md5 sha1 sha224 sha256 sha384 sha512 + do + if echo "$1" | grep -q \.${chks}$; then + ${chks}sum -c < "$1" + read -r _ + return + fi + done + checksum_type + file=$(basename "$1").$chsum + ${chsum}sum "$1" > "$file" + elif [ -d "$1" ]; then + checksum_type + file=$(basename "$1").$chsum + find "$1" -type f -exec ${chsum}sum "{}" + > "$file" + fi +fi diff --git a/nnn/plugins/cmusq b/nnn/plugins/cmusq @@ -0,0 +1,80 @@ +#!/usr/bin/env sh + +# Description: Add selection or hovered file/directory to cmus queue +# +# Dependencies: cmus, pgrep, xdotool (optional) +# +# Notes: +# 1. If adding selection, files/dirs are added in the same order they were selected in nnn +# 2. A new window will be opened if cmus is not running already, playback will start immediately +# 3. If cmus is already running, files will be appended to the queue with no forced playback +# +# TODO: +# 1. Add cava and cmus-lyrics as optional dependencies +# 2. Start cava and/or cmus-lyrics in tmux or kitty panes next to cmus +# +# Shell: POSIX compliant +# Author: Kabouik + +# (Optional) Set preferred terminal emulator for cmus if not set in your env, +# or leave commented out to use OS default +#TERMINAL="kitty" + +if ! type cmus >/dev/null; then + printf "cmus missing" + read -r _ + exit 1 +fi + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +start_cmus() { + type xdotool >/dev/null && nnnwindow="$(xdotool getactivewindow)" + case "$TERMINAL" in + kitty | gnome-terminal | st) + nohup "$TERMINAL" -- cmus & ;; + havoc) + nohup "$TERMINAL" cmus & ;; + "") + nohup x-terminal-emulator -e cmus & ;; + *) + nohup "$TERMINAL" -e cmus & ;; + esac + # Give the new terminal some time to open + until cmus-remote -C; do sleep 0.1; done + [ -n "$nnnwindow" ] && xdotool windowactivate "$nnnwindow" +} >/dev/null 2>&1 + +fill_queue() { + if [ "$REPLY" = "s" ]; then + xargs < "$selection" -0 cmus-remote -q + elif [ -n "$1" ]; then + cmus-remote -q "$1" + fi +} + +# If active selection,then ask what to do +if [ -s "$selection" ]; then + printf "Queue [s]election or [c]urrently hovered? [default=c]: " + read -r REPLY +fi + +# If cmus is not running, start and play queue +if ! pgrep cmus >/dev/null; then + printf "cmus is not running, starting it in a new %s window.\n" "$TERMINAL" + start_cmus + fill_queue "$1" + cmus-remote -p + printf "Files added to cmus queue.\n" +else # Append to existing queue if cmus is already running + fill_queue "$1" + printf "Files appended to current cmus queue.\n" +fi + +# Change view +cmus-remote -C "view 4" + +# Clear selection +if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/nnn/plugins/diffs b/nnn/plugins/diffs @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Description: Show diff of 2 directories or multiple files in vimdiff +# +# Notes: +# 1. vim may show the warning: 'Vim: Warning: Input is not from a terminal' +# press 'Enter' to ignore and proceed. +# 2. if only one file is in selection, the hovered file is considered as the +# second file to diff with +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, ath3 + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if type nvim >/dev/null 2>&1; then + diffcmd="nvim -d" +else + diffcmd="vimdiff +0" +fi + +dirdiff() { + dir1=$(mktemp "${TMPDIR:-/tmp}"/nnn-"$(basename "$1")".XXXXXXXX) + dir2=$(mktemp "${TMPDIR:-/tmp}"/nnn-"$(basename "$2")".XXXXXXXX) + ls -A1 "$1" > "$dir1" + ls -A1 "$2" > "$dir2" + $diffcmd "$dir1" "$dir2" + rm "$dir1" "$dir2" +} + +if [ -s "$selection" ]; then + arr=$(tr '\0' '\n' < "$selection") + if [ "$(echo "$arr" | wc -l)" -gt 1 ]; then + f1="$(echo "$arr" | sed -n '1p')" + f2="$(echo "$arr" | sed -n '2p')" + if [ -d "$f1" ] && [ -d "$f2" ]; then + dirdiff "$f1" "$f2" + else + # If xargs supports the -o option, use it to get rid of: + # Vim: Warning: Input is not from a terminal + # xargs -0 -o vimdiff < $selection + + eval xargs -0 "$diffcmd" < "$selection" + fi + elif [ -n "$1" ]; then + f1="$(echo "$arr" | sed -n '1p')" + if [ -d "$f1" ] && [ -d "$1" ]; then + dirdiff "$f1" "$1" + elif [ -f "$f1" ] && [ -f "$1" ]; then + $diffcmd "$f1" "$1" + else + echo "cannot compare file with directory" + fi + else + echo "needs at least 2 files or directories selected for comparison" + fi +fi + +# Clear selection +if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/nnn/plugins/dragdrop b/nnn/plugins/dragdrop @@ -0,0 +1,77 @@ +#!/usr/bin/env sh + +# Description: Open a Drag and drop window, to drop files onto other programs. +# Also provides drag and drop window for files. +# +# Dependencies: dragon - https://github.com/mwh/dragon +# +# Notes: +# 1. Files that are dropped will be added to nnn's selection +# Some web-based files will be downloaded to current dir +# with curl and it may overwrite some existing files +# 2. The user has to mm to clear nnn's selection first +# +# Shell: POSIX compliant +# Author: 0xACE + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +resp=f +all= +if type dragon-drag-and-drop >/dev/null 2>&1; then + dnd="dragon-drag-and-drop" +elif type dragon-drop >/dev/null 2>&1; then + dnd="dragon-drop" +else + dnd="dragon" +fi + +add_file () +{ + printf '%s\0' "$@" >> "$selection" +} + +use_all () +{ + printf "mark --all (a) [default=none]: " + read -r resp + if [ "$resp" = "a" ]; then + all="--all" + else + all="" + fi +} + +if [ -s "$selection" ]; then + printf "Drop file (r). Drag selection (s), Drag current directory (d) or drag current file (f) [default=f]: " + read -r resp +else + printf "Drop file (r). Drag current directory (d) or drag current file (f) [default=f]: " + read -r resp + if [ "$resp" = "s" ]; then + resp=f + fi +fi + +if [ "$resp" = "s" ]; then + use_all + sed -z 's|'"$PWD/"'||g' < "$selection" | xargs -0 "$dnd" "$all" & +elif [ "$resp" = "d" ]; then + use_all + "$dnd" "$all" "$PWD/"* & +elif [ "$resp" = "r" ]; then + true > "$selection" + "$dnd" --print-path --target | while read -r f + do + if printf "%s" "$f" | grep '^\(https\?\|ftps\?\|s\?ftp\):\/\/' ; then + curl -LJO "$f" + add_file "$PWD/$(basename "$f")" + elif [ -e "$f" ]; then + add_file "$f" + fi + done & +else + if [ -n "$1" ] && [ -e "$1" ]; then + "$dnd" "$1" & + fi +fi + diff --git a/nnn/plugins/dups b/nnn/plugins/dups @@ -0,0 +1,70 @@ +#!/usr/bin/env sh + +# Description: List non-empty duplicates in the current dir (based on size followed by MD5) +# +# Source: https://www.commandlinefu.com/commands/view/3555/find-duplicate-files-based-on-size-first-then-md5-hash +# +# Dependencies: find md5sum sort uniq xargs gsed +# +# Notes: +# 1. If the file size exceeds $size_digits digits the file will be misplaced +# 12 digits fit files up to 931GiB +# 2. Bash compatible required for mktemp +# +# Shell: Bash +# Authors: syssyphus, KlzXS + +EDITOR="${EDITOR:-vi}" +TMPDIR="${TMPDIR:-/tmp}" + +size_digits=12 +tmpfile=$(mktemp "$TMPDIR/.nnnXXXXXX") + +printf "\ +## This is an overview of all duplicate files found. +## Comment out the files you wish to remove. You will be given an option to cancel. +## Lines with double comments (##) are ignored. +## You will have the option to remove the files with force or interactively.\n +" > "$tmpfile" + +# shellcheck disable=SC2016 +find . -size +0 -type f -printf "%${size_digits}s %p\n" | sort -rn | uniq -w"${size_digits}" -D | sed -e ' +s/^ \{0,12\}\([0-9]\{0,12\}\) \(.*\)$/printf "%s %s\\n" "$(md5sum "\2")" "d\1"/ +' | tr '\n' '\0' | xargs -0 -n1 sh -c | sort | { uniq -w32 --all-repeated=separate; echo; } | sed -ne ' +h +s/^\(.\{32\}\).* d\([0-9]*\)$/## md5sum: \1 size: \2 bytes/p +g + +:loop +N +/.*\n$/!b loop +p' | sed -e 's/^.\{32\} \(.*\) d[0-9]*$/\1/' >> "$tmpfile" + +"$EDITOR" "$tmpfile" + +printf "Remove commented files? (yes/no) [default=n]: " +read -r commented + +if [ "$commented" = "y" ]; then + sedcmd="/^##.*/d; /^[^#].*/d; /^$/d; s/^# *\(.*\)$/\1/" +else + printf "Press any key to exit" + read -r _ + exit +fi + +printf "Remove with force or interactive? (f/i) [default=i]: " +read -r force + +if [ "$force" = "f" ]; then + #shellcheck disable=SC2016 + sed -e "$sedcmd" "$tmpfile" | tr '\n' '\0' | xargs -0 -r sh -c 'rm -f "$0" "$@" </dev/tty' +else + #shellcheck disable=SC2016 + sed -e "$sedcmd" "$tmpfile" | tr '\n' '\0' | xargs -0 -r sh -c 'rm -i "$0" "$@" </dev/tty' +fi + +rm "$tmpfile" + +printf "Press any key to exit" +read -r _ diff --git a/nnn/plugins/finder b/nnn/plugins/finder @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +# Description: Run custom search and list results in smart context +# +# Note: This plugin retains search history +# +# Usage: +# Run plugin and enter e.g. "-size +10M" to list files in current +# directory larger than 10M. By default entered expressions are +# interpreted as arguments to find. Results have to be NUL +# terminated which is done by default for find. Alternatively one +# can prepend a '$' to run a custom search program such as fd or +# ripgrep. Entered expressions will be saved in history file to +# be listed as bookmarks and and can be entered by index and edited. +# +# Shell: Bash +# Author: Arun Prakash Jana, Luuk van Baal +TMPDIR="${TMPDIR:-/tmp}" +NNN_FINDHIST="${NNN_FINDHIST:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/finderbms}" +NNN_FINDHISTLEN="${NNN_FINDHISTLEN:-10000}" + +printexamples() { + printf -- "-maxdepth 1 -name pattern +-maxdepth 1 -size +100M +\$fd -0 pattern +\$fd -0 -d 2 -S +100M +\$grep -rlZ pattern +\$rg -l0 pattern +\$fzf -m | tr '\\\n' '\\\0'\n" +} + +printexprs() { + for ((i = "$1"; i < ${#fexprs[@]}; i++)); do + printf '%s\t%s\n' "$((i + 1))" "${fexprs[$i]}" + done +} + +mapexpr() { + if [ "$fexpr" -eq "$fexpr" ] 2>/dev/null; then + fexpr=${fexprs[$((fexpr - 1))]} + read -r -e -p "Search expression: " -i "$fexpr" fexpr + else + return 1 + fi +} + +readexpr() { + case "$fexpr" in + h) clear + printf "Examples:\n" + mapfile -t fexprs < <(printexamples) + printexprs 0 + read -r -p "Search expression or index: " fexpr + mapexpr + [ -n "$fexpr" ] && readexpr ;; + \$*) cmd="${fexpr:1}" ;; + *) mapexpr && readexpr + cmd="find $fexpr -print0" ;; + esac +} + +clear +[ -f "$NNN_FINDHIST" ] || printexamples > "$NNN_FINDHIST" + +mapfile -t fexprs < <(sort "$NNN_FINDHIST" | uniq -c | sort -nr | head -n5 |\ + awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}') +printf "Most used search expressions:\n" +printexprs 0 + +mapfile -t -O"$i" fexprs < <(tac "$NNN_FINDHIST" | awk '!a[$0]++' | head -n5) +printf "Most recently used search expressions:\n" +printexprs "$i" +read -r -p "Search expression or index (h for help): " fexpr + +mapexpr + +if [ -n "$fexpr" ]; then + printf "+l" > "$NNN_PIPE" + while :; do + readexpr + eval "$cmd" > "$NNN_PIPE" && break + read -r -e -p "Search expression: " -i "$fexpr" fexpr + done + if [ -n "$fexpr" ]; then + tail -n"$NNN_FINDHISTLEN" "$NNN_FINDHIST" > "$TMPDIR/finderbms" + printf "%s\n" "$fexpr" >> "$TMPDIR/finderbms" + mv "$TMPDIR/finderbms" "$NNN_FINDHIST" + fi +fi diff --git a/nnn/plugins/fixname b/nnn/plugins/fixname @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# Description: Clean filename or dirname (either hovered or selections) +# to be more shell-friendly. This script cleans +# non A-Za-z0-9._- characters. +# and replaces it with underscore (_). +# +# It supports cleaning single/double quote, newline, +# leading, trailing spaces. +# +# eg. +# to be continued (つづく).mp4 -> to_be_continued______.mp4 +# [work] stuff.txt -> _work__stuff.txt +# home's server -> home_s_server +# qwe\trty -> __qwe_rty +# +# And if there are two almost similar filenames +# like: 'asd]f' and 'asd f' both will be renamed to 'asd_f', +# to avoid overwriting, the last file will be prepended by _. +# So they will be: 'asd_f' and '_asd_f' +# +# Dependencies: sed +# +# Shell: Bash +# Author: Benawi Adha + +prompt=true +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +cleanup() { + # printf "%s" "$1" | sed -e 's/[^A-Za-z0-9._-]/_/g' + printf "%s" "$1" | sed 's/[^A-Za-z0-9._-]/_/g' | sed ':a;N;$!ba;s/\n/_/g' +} + +if [ -s "$sel" ]; then + targets=() + while IFS= read -r -d '' i || [ -n "$i" ]; do + targets+=( "$(basename "$i")" ) + done < "$sel" +else + targets=("$1") +fi + +for i in "${targets[@]}"; do + printf "%s -> %s\n" "$i" "$(cleanup "$i")"; +done + +if $prompt; then + echo + printf "Proceed [Yn]? " + read -r input + case "$input" in + y|Y|'') + ;; + *) + echo "Canceled" + exit + ;; + esac +fi + +for i in "${targets[@]}"; do + if [ "$i" != "$(cleanup "$i")" ]; then + tmp='' + if [ -e "$(cleanup "$i")" ]; then + tmp='_' + fi + mv "$i" "$tmp$(cleanup "$i")"; + fi +done + +# Clear selection +if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/nnn/plugins/fzcd b/nnn/plugins/fzcd @@ -0,0 +1,88 @@ +#!/usr/bin/env sh + +# Description: Fuzzy search multiple locations read-in from a path-list file +# (or $PWD) and open the selected file's dir in a smart context. +# Dependencies: fzf, find (only for multi-location search) +# +# Details: Paths in list file should be newline-separated absolute paths. +# Paths can be file paths; the script will scan the parent dirs. +# +# The path-list file precedence is: +# - "$1" (the hovered file) if it exists, is plain-text and the +# first line points to an existing file +# - "$LIST" if set below +# - "$2" (the current directory) [mimics plugin fzcd behaviour] +# +# The path-list file can be generated easily: +# - pick the (file)paths in picker mode to path-list file +# - OR, edit selection in nnn and save as path-list file +# +# Shell: POSIX compliant +# Author: Anna Arad, Arun Prakash Jana, KlzXS + +IFS="$(printf '\n\r')" + +. "$(dirname "$0")"/.nnn-plugin-helper + +CTX=+ +LIST="$LIST" + +if ! type fzf >/dev/null 2>&1; then + printf "fzf missing" + read -r _ + exit 1 +fi + +if [ -n "$1" ] && [ "$(file -b --mime-type "$1")" = 'text/plain' ] && [ -e "$(head -1 "$1")" ]; then + LIST="$1" +elif ! [ -s "$LIST" ]; then + sel=$(fzf) + # Show only the file and parent dir + # sel=$(fzf --delimiter / --with-nth=-2,-1 --tiebreak=begin --info=hidden) + + LIST='' +fi + +if [ -n "$LIST" ]; then + if type find >/dev/null 2>&1; then + tmpfile=$(mktemp /tmp/abc-script.XXXXXX) + + while IFS= read -r path; do + if [ -d "$path" ]; then + printf "%s\n" "$path" >> "$tmpfile" + elif [ -f "$path" ]; then + printf "%s\n" "$(dirname "$path")" >> "$tmpfile" + fi + done < "$LIST" + + sel=$(xargs -d '\n' < "$tmpfile" -I{} find {} -type f -printf "%H//%P\n" | sed '/.*\/\/\(\..*\|.*\/\..*\)/d; s:/\+:/:g' | fzf --delimiter / --tiebreak=begin --info=hidden) + # Alternative for 'fd' + # sel=$(xargs -d '\n' < "$tmpfile" fd . | fzf --delimiter / --tiebreak=begin --info=hidden) + + rm "$tmpfile" + else + printf "find missing" + read -r _ + exit 1 + fi +fi + +if [ -n "$sel" ]; then + if [ "$sel" = "." ] || { ! [ -d "$sel" ] && ! [ -f "$sel" ]; }; then + exit 0 + fi + + # Check if the selected path returned by fzf command is absolute + case $sel in + /*) nnn_cd "$sel" "$CTX" ;; + *) + # Remove "./" prefix if it exists + sel="${sel#./}" + + if [ "$PWD" = "/" ]; then + nnn_cd "/$sel" "$CTX" + else + nnn_cd "$PWD/$sel" "$CTX" + fi;; + esac +fi diff --git a/nnn/plugins/fzhist b/nnn/plugins/fzhist @@ -0,0 +1,40 @@ +#!/usr/bin/env sh + +# Description: Fuzzy find a command from history, +# edit in $EDITOR and run as a command +# +# Note: Supports only bash and fish history +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if type fzf >/dev/null 2>&1; then + fuzzy=fzf +else + exit 1 +fi + +shellname="$(basename "$SHELL")" + +if [ "$shellname" = "bash" ]; then + hist_file="$HOME/.bash_history" + entry="$("$fuzzy" < "$hist_file")" +elif [ "$shellname" = "fish" ]; then + hist_file="$HOME/.local/share/fish/fish_history" + entry="$(grep "\- cmd: " "$hist_file" | cut -c 8- | "$fuzzy")" +fi + +if [ -n "$entry" ]; then + tmpfile=$(mktemp) + echo "$entry" >> "$tmpfile" + $EDITOR "$tmpfile" + + if [ -s "$tmpfile" ]; then + $SHELL -c "$(cat "$tmpfile")" + fi + + rm "$tmpfile" + + printf "Press any key to exit" + read -r _ +fi diff --git a/nnn/plugins/fzopen b/nnn/plugins/fzopen @@ -0,0 +1,82 @@ +#!/usr/bin/env sh + +# Description: Regular mode: +# Fuzzy find a file in directory subtree. +# Opens in $VISUAL or $EDITOR if text. +# Opens other type of files with xdg-open. +# Work only with a single file selected. +# +# Picker mode: +# If picker mode output file is passed, it +# will be overwritten with any picked files. +# Leaves untouched if no file is picked. +# Works with single/multiple files selected. +# +# Dependencies: fd/find, fzf/skim, xdg-open/open (on macOS) +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" +USE_NUKE=0 + +. "$(dirname "$0")"/.nnn-plugin-helper + +if type fzf >/dev/null 2>&1; then + cmd="$FZF_DEFAULT_COMMAND" + if type fd >/dev/null 2>&1; then + [ -z "$cmd" ] && cmd="fd -t f 2>/dev/null" + else + [ -z "$cmd" ] && cmd="find . -type f 2>/dev/null" + fi + entry="$(eval "$cmd" | fzf -m)" + # To show only the file name + # entry=$(find . -type f 2>/dev/null | fzf --delimiter / --with-nth=-1 --tiebreak=begin --info=hidden) +elif type sk >/dev/null 2>&1; then + entry=$(find . -type f 2>/dev/null | sk) +else + exit 1 +fi + +# Check for picker mode +if [ "$3" ]; then + if [ "$entry" ]; then + case "$entry" in + /*) fullpath="$entry" ;; + *) fullpath="$PWD/$entry" ;; + esac + if [ "-" = "$3" ]; then + printf "%s\n" "$fullpath" + else + printf "%s\n" "$fullpath" > "$3" + fi + + # Tell `nnn` to clear its internal selection + printf "%s" "0p" > "$NNN_PIPE" + fi + + exit 0 +fi + +if [ "$USE_NUKE" -ne 0 ]; then + "$NUKE" "$entry" + exit 0 +fi + +# Open the file (works for a single file only) +cmd_file="" +cmd_open="" +if uname | grep -q "Darwin"; then + cmd_file="file -bIL" + cmd_open="open" +else + cmd_file="file -biL" + cmd_open="xdg-open" +fi + +case "$($cmd_file "$entry")" in + *text*) + "${VISUAL:-$EDITOR}" "$entry" ;; + *) + $cmd_open "$entry" >/dev/null 2>&1 ;; +esac diff --git a/nnn/plugins/fzplug b/nnn/plugins/fzplug @@ -0,0 +1,59 @@ +#!/usr/bin/env sh + +# Description: Fuzzy find and execute nnn plugins (and optionally, +# custom scripts located elsewhere). +# Description and details of plugins can be previewed +# from the fzf interface. Use `?` to toggle preview +# pane on and off, ^Up/^Dn to scroll. +# +# Dependencies: find, fzf, cat (or bat, if installed) +# +# Note: For better compatibility with as many nnn plugins as possible, +# fzplug will first execute the chosen script on the file hovered +# in nnn, and upon failure, try to run it with no target (i.e on +# an active selection, if present). +# +# Shell: POSIX compliant +# Author: Kabouik + +# Optional scripts sources + +# Leave blank or fill with the absolute path of a folder containing executable +# scripts other than nnn plugins (e.g., "$HOME/.local/share/nautilus/scripts", +# since there are numerous Nautilus script git repositories). +# Add extra variables if needed, make sure you call them in the find command. + +#CUSTOMDIR1="$HOME/.local/share/nautilus/scripts" +CUSTOMDIR1="" +CUSTOMDIR2="" + +nnnpluginsdir="$HOME/.config/nnn/plugins" + +# Preview with bat if installed +if type bat >/dev/null; then + BAT="bat --terminal-width='$(tput cols)' --decorations=always --color=always --style='${BAT_STYLE:-header,numbers}'" +fi + +plugin=$(find "$nnnpluginsdir" "$CUSTOMDIR1" "$CUSTOMDIR2" \ +-maxdepth 3 -perm -111 -type f 2>/dev/null | fzf --ansi --preview \ + "${BAT:-cat} {}" --preview-window="right:66%:wrap" --delimiter / \ + --with-nth -1 --bind="?:toggle-preview") + +# Try running the script on the hovered file, and abort +# abort if no plugin was selected (ESC or ^C pressed). +err=0 +if ! [ "$plugin" = "" ]; then + "$plugin" "$1" || err=1 +fi + +# If attempt with hovered file fails, try without any target +# (nnn selections should still be passed to the script in that case) +if [ "$err" -eq "1" ]; then + clear && "$plugin" || err=2 +fi + +# Abort and show error if both fail +if [ "$err" -eq "2" ]; then + sep="\n---\n" + printf "$sep""Failed to execute '%s'. See error above or try without fzfplug. Press return to continue. " "$plugin" && read -r _ && clear +fi diff --git a/nnn/plugins/getplugs b/nnn/plugins/getplugs @@ -0,0 +1,70 @@ +#!/usr/bin/env sh + +# Description: Update nnn plugins to installed nnn version +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, KlzXS + +CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/ +PLUGIN_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins + +merge () { + if type nvim >/dev/null 2>&1; then + nvim -d "$1" "$2" + else + vimdiff +0 "$1" "$2" + fi +} + +prompt () { + printf "%s\n" "Plugin $1 already exists and is different." + printf "Keep (k), merge (m), overwrite (o) [default: k]? " + read -r operation + + if [ "$operation" = "m" ]; then + op="merge" + elif [ "$operation" = "o" ]; then + op="cp -vRf" + else + op="true" + fi +} + +if [ "$1" = "master" ] ; then + VER="master" + ARCHIVE_URL=https://github.com/jarun/nnn/archive/master.tar.gz +elif type nnn >/dev/null 2>&1; then + VER=$(nnn -V) + ARCHIVE_URL=https://github.com/jarun/nnn/releases/download/v"$VER"/nnn-v"$VER".tar.gz +else + echo "nnn is not installed" + exit 1 +fi + +# backup any earlier plugins +if [ -d "$PLUGIN_DIR" ]; then + tar -C "$CONFIG_DIR" -czf "$CONFIG_DIR""plugins-$(date '+%Y%m%d%H%M').tar.gz" plugins/ +fi + +mkdir -p "$PLUGIN_DIR" +cd "$CONFIG_DIR" || exit 1 +curl -Ls "$ARCHIVE_URL" -o nnn-"$VER".tar.gz +tar -zxf nnn-"$VER".tar.gz + +cd nnn-"$VER"/plugins || exit 1 + +# shellcheck disable=SC2044 +# We do not use obnoxious names for plugins +for f in $(find . -maxdepth 1 \( ! -iname "." ! -iname "*.md" \)); do + if [ -f ../../plugins/"$f" ]; then + if [ "$(diff --brief "$f" ../../plugins/"$f")" ]; then + prompt "$f" + $op "$f" ../../plugins/ + fi + else + cp -vRf "$f" ../../plugins/ + fi +done +cd ../.. || exit 1 + +rm -rf nnn-"$VER"/ nnn-"$VER".tar.gz diff --git a/nnn/plugins/gitroot b/nnn/plugins/gitroot @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# Description: cd to the top level of the current git repository in the current context +# Dependencies: git +# Shell: sh +# Author: https://github.com/PatrickF1 + +root="$(git rev-parse --show-toplevel 2>/dev/null)" +if [ -n "$root" ]; then + printf "%s" "0c$root" > "$NNN_PIPE" +else + printf "Not in a git repository" + read -r _ + exit 1 +fi diff --git a/nnn/plugins/gpgd b/nnn/plugins/gpgd @@ -0,0 +1,28 @@ +#!/usr/bin/env sh + +# Description: Decrypts selected files using gpg. The contents of the +# decrypted file are stored in a file with extension .dec +# +# Note: If an appropriate private key cannot be found gpg silently +# prints a message in the background and no files are written. +# +# Shell: POSIX compliant +# Author: KlzXS + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +printf "(s)election/(c)urrent? [default=c] " +read -r resp + +if [ "$resp" = "s" ]; then + files=$(tr '\0' '\n' < "$selection") +else + files=$1 +fi + +printf "%s" "$files" | xargs -n1 -I{} gpg --decrypt --output "{}.dec" {} + +# Clear selection +if [ "$resp" = "s" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/nnn/plugins/gpge b/nnn/plugins/gpge @@ -0,0 +1,44 @@ +#!/usr/bin/env sh + +# Description: Encrypts selected files using gpg. Can encrypt +# asymmetrically (key) or symmetrically (passphrase). +# If asymmetric encryption is chosen a key can be +# chosen from the list of capable public keys using fzf. +# +# Note: Symmetric encryption only works for a single (current) file as per gpg limitations +# +# Shell: POSIX compliant +# Author: KlzXS + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +printf "(s)ymmetric, (a)symmetric? [default=a] " +read -r symmetry + +if [ "$symmetry" = "s" ]; then + gpg --symmetric "$1" +else + printf "(s)election/(c)urrent? [default=c] " + read -r resp + + if [ "$resp" = "s" ]; then + files=$(tr '\0' '\n' < "$selection") + else + files=$1 + fi + + keyids=$(gpg --list-public-keys --with-colons | grep -E "pub:(.*:){10}.*[eE].*:" | awk -F ":" '{print $5}') + + #awk needs literal $10 + #shellcheck disable=SC2016 + keyuids=$(printf "%s" "$keyids" | xargs -n1 -I{} sh -c 'gpg --list-key --with-colons "{}" | grep "uid" | awk -F ":" '\''{printf "%s %s\n", "{}", $10}'\''') + + recipient=$(printf "%s" "$keyuids" | fzf | awk '{print $1}') + + printf "%s" "$files" | xargs -n1 gpg --encrypt --recipient "$recipient" + + # Clear selection + if [ "$resp" = "s" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +fi diff --git a/nnn/plugins/gsconnect b/nnn/plugins/gsconnect @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + +#set -x +# Description: Send the selected (or hovered) files to your Android device using gsconnect daemon.js. +# GSConnect must be configured on the Android device and the PC. +# +# Shell: POSIX compliant +# Author: Darukutsu +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +gsconnect=$HOME/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io/service/daemon.js +id=$($gsconnect -l) + +if [ -s "$selection" ]; then + xargs -0 < "$selection" -I{} "$gsconnect" -d "$id" --share-file="{}" + # Clear selection + printf "-" > "$NNN_PIPE" +else + "$gsconnect" -d "$id" --share-file="$2/$1" +fi diff --git a/nnn/plugins/gutenread b/nnn/plugins/gutenread @@ -0,0 +1,49 @@ +#!/usr/bin/env sh + +# Description: Browse Project Gutenberg catalogue by popularity, then download +# and read a book of your choice. +# +# Details: Set the variable EBOOK_ID to download in html format and read in w3m. +# Clear EBOOK_ID to browse available ebooks by popularity and set it to +# the ID once you find an interesting one. +# To download and read in epub format set READER to an epub reader like +# epr: https://github.com/wustho/epr +# +# More on EBOOK_ID: +# Wuthering Heights by Emily Brontë is at https://www.gutenberg.org/ebooks/768 +# So EBOOK_ID would be 768 +# +# Downloaded ebooks are at ${XDG_CACHE_HOME:-$HOME/.cache}/nnn/gutenbooks/ +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +EBOOK_ID="${EBOOK_ID}" +DIR="${XDG_CACHE_HOME:-$HOME/.cache}/nnn/gutenbooks/$EBOOK_ID" +BROWSE_LINK="https://www.gutenberg.org/ebooks/search/?sort_order=downloads" +BROWSER="${BROWSER:-w3m}" +READER="${READER}" + +if [ -n "$EBOOK_ID" ]; then + if [ ! -e "$DIR" ]; then + mkdir -p "$DIR" + cd "$DIR" || exit 1 + + if [ -z "$READER" ]; then + curl -L -O "https://www.gutenberg.org/files/$EBOOK_ID/$EBOOK_ID-h.zip" + unzip "$EBOOK_ID"-h.zip + else + curl -L -o "$EBOOK_ID".epub "https://www.gutenberg.org/ebooks/$EBOOK_ID.epub.noimages" + fi + fi + + if [ -d "$DIR" ]; then + if [ -z "$READER" ]; then + "$BROWSER" "$DIR/$EBOOK_ID-h/$EBOOK_ID-h.htm" + else + "$READER" "$DIR/$EBOOK_ID.epub" + fi + fi +else + "$BROWSER" "$BROWSE_LINK" +fi diff --git a/nnn/plugins/imgresize b/nnn/plugins/imgresize @@ -0,0 +1,31 @@ +#!/usr/bin/env sh + +# Description: Resize images in a directory to screen resolution with imgp +# +# Dependencipes: imgp - https://github.com/jarun/imgp +# +# Notes: +# 1. Set res to avoid the desktop resolution prompt each time +# 2. MINSIZE is set to 1MB by default, adjust it if you want +# 3. imgp options used: +# a - adaptive mode +# c - convert PNG to JPG +# k - skip images matching specified hres/vres +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# set resolution (e.g. 1920x1080) +res="${RESOLUTION}" + +# set minimum image size (in bytes) to resize (default: 1MB) +MINSIZE="${MINSIZE:-1048576}" + +if [ -z "$res" ]; then + printf "desktop resolution (hxv): " + read -r res +fi + +if [ -n "$res" ] && [ -n "$MINSIZE" ]; then + imgp -ackx "$res" -s "$MINSIZE" +fi diff --git a/nnn/plugins/imgur b/nnn/plugins/imgur @@ -0,0 +1,595 @@ +#!/usr/bin/env bash + +########################################################################## +# The MIT License +# +# Copyright (c) jomo +# +# Permission is hereby granted, free of charge, +# to any person obtaining a copy of this software and +# associated documentation files (the "Software"), to +# deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom +# the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice +# shall be included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +########################################################################## + +# https://github.com/jomo/imgur-screenshot +# https://help.imgur.com/hc/en-us/articles/209592766-Tools-for-Imgur +# +# Slightly modified for `nnn` integration +# +# Shell: Bash +# Description: Upload an image file to imgur + +if [ "${1}" = "--debug" ]; then + echo "########################################" + echo "Enabling debug mode" + echo "Please remove credentials before pasting" + echo "########################################" + echo "" + uname -a + for arg in ${0} "${@}"; do + echo -n "'${arg}' " + done + echo -e "\n" + shift + set -x +fi + +current_version="v1.7.4" + +function is_mac() { + uname | grep -q "Darwin" +} + +### IMGUR-SCREENSHOT DEFAULT CONFIG #### + +# You can override the config in ~/.config/imgur-screenshot/settings.conf + +imgur_anon_id="ea6c0ef2987808e" +imgur_icon_path="${HOME}/Pictures/imgur.png" + +imgur_acct_key="" +imgur_secret="" +login="false" +album_title="" +album_id="" +credentials_file="${HOME}/.config/imgur-screenshot/credentials.conf" + +file_name_format="imgur-%Y_%m_%d-%H:%M:%S.png" # when using scrot, must end with .png! +file_dir="${HOME}/Pictures" + +upload_connect_timeout="5" +upload_timeout="120" +upload_retries="1" + +# shellcheck disable=SC2034 +if is_mac; then + screenshot_select_command="screencapture -i %img" + screenshot_window_command="screencapture -iWa %img" + screenshot_full_command="screencapture %img" + open_command="open %url" +else + screenshot_select_command="scrot -s %img" + screenshot_window_command="scrot %img" + screenshot_full_command="scrot %img" + open_command="xdg-open %url" +fi +open="true" + +mode="select" +edit_command="gimp %img" +edit="false" +exit_on_album_creation_fail="true" + +log_file="${HOME}/.imgur-screenshot.log" + +auto_delete="" +copy_url="true" +keep_file="true" +check_update="true" + +# NOTICE: if you make changes here, also edit the docs at +# https://github.com/jomo/imgur-screenshot/wiki/Config + +# You can override the config in ~/.config/imgur-screenshot/settings.conf + +############## END CONFIG ############## + +settings_path="${HOME}/.config/imgur-screenshot/settings.conf" +if [ -f "${settings_path}" ]; then + source "${settings_path}" +fi + +# dependency check +if [ "${1}" = "--check" ]; then + (type grep &>/dev/null && echo "OK: found grep") || echo "ERROR: grep not found" + if is_mac; then + if type growlnotify &>/dev/null; then + echo "OK: found growlnotify" + elif type terminal-notifier &>/dev/null; then + echo "OK: found terminal-notifier" + else + echo "ERROR: growlnotify nor terminal-notifier found" + fi + (type screencapture &>/dev/null && echo "OK: found screencapture") || echo "ERROR: screencapture not found" + (type pbcopy &>/dev/null && echo "OK: found pbcopy") || echo "ERROR: pbcopy not found" + else + (type notify-send &>/dev/null && echo "OK: found notify-send") || echo "ERROR: notify-send (from libnotify-bin) not found" + (type scrot &>/dev/null && echo "OK: found scrot") || echo "ERROR: scrot not found" + (type xclip &>/dev/null && echo "OK: found xclip") || echo "ERROR: xclip not found" + fi + (type curl &>/dev/null && echo "OK: found curl") || echo "ERROR: curl not found" + exit 0 +fi + + +# notify <'ok'|'error'> <title> <text> +function notify() { + if is_mac; then + if type growlnotify &>/dev/null; then + growlnotify --icon "${imgur_icon_path}" --iconpath "${imgur_icon_path}" --title "${2}" --message "${3}" + else + terminal-notifier -appIcon "${imgur_icon_path}" -contentImage "${imgur_icon_path}" -title "imgur: ${2}" -message "${3}" + fi + else + if [ "${1}" = "error" ]; then + notify-send -a ImgurScreenshot -u critical -c "im.error" -i "${imgur_icon_path}" -t 500 "imgur: ${2}" "${3}" + else + notify-send -a ImgurScreenshot -u low -c "transfer.complete" -i "${imgur_icon_path}" -t 500 "imgur: ${2}" "${3}" + fi + fi +} + +function take_screenshot() { + echo "Please select area" + is_mac || sleep 0.1 # https://bbs.archlinux.org/viewtopic.php?pid=1246173#p1246173 + + cmd="screenshot_${mode}_command" + cmd=${!cmd//\%img/${1}} + + if ! shot_err="$(${cmd} &>/dev/null)"; then #takes a screenshot with selection + echo "Failed to take screenshot '${1}': '${shot_err}'. For more information visit https://github.com/jomo/imgur-screenshot/wiki/Troubleshooting" | tee -a "${log_file}" + notify error "Something went wrong :(" "Information has been logged" + exit 1 + fi +} + +function check_for_update() { + # exit non-zero on HTTP error, output only the body (no stats) but output errors, follow redirects, output everything to stdout + remote_version="$(curl --compressed -fsSL --stderr - "https://api.github.com/repos/jomo/imgur-screenshot/releases" | grep -Em 1 --color 'tag_name":\s*".*"' | cut -d '"' -f 4)" + if [ -n "$remote_version" ]; then + if [ ! "${current_version}" = "${remote_version}" ] && [ -n "${current_version}" ] && [ -n "${remote_version}" ]; then + echo "Update found!" + echo "Version ${remote_version} is available (You have ${current_version})" + notify ok "Update found" "Version ${remote_version} is available (You have ${current_version}). https://github.com/jomo/imgur-screenshot" + echo "Check https://github.com/jomo/imgur-screenshot/releases/${remote_version} for more info." + elif [ -z "${current_version}" ] || [ -z "${remote_version}" ]; then + echo "Invalid empty version string" + echo "Current (local) version: '${current_version}'" + echo "Latest (remote) version: '${remote_version}'" + else + echo "Version ${current_version} is up to date." + fi + else + echo "Failed to check for latest version: ${remote_version}" + fi +} + +function check_oauth2_client_secrets() { + if [ -z "${imgur_acct_key}" ] || [ -z "${imgur_secret}" ]; then + echo "In order to upload to your account, register a new application at:" + echo "https://api.imgur.com/oauth2/addclient" + echo "Select 'OAuth 2 authorization without a callback URL'" + echo "Then, set the imgur_acct_key (Client ID) and imgur_secret in your config." + exit 1 + fi +} + +function load_access_token() { + token_expire_time=0 + # check for saved access_token and its expiration date + if [ -f "${credentials_file}" ]; then + source "${credentials_file}" + fi + current_time="$(date +%s)" + preemptive_refresh_time="$((10*60))" + expired="$((current_time > (token_expire_time - preemptive_refresh_time)))" + if [ -n "${refresh_token}" ]; then + # token already set + if [ "${expired}" -eq "0" ]; then + # token expired + refresh_access_token "${credentials_file}" + fi + else + acquire_access_token "${credentials_file}" + fi +} + +function acquire_access_token() { + check_oauth2_client_secrets + # prompt for a PIN + authorize_url="https://api.imgur.com/oauth2/authorize?client_id=${imgur_acct_key}&response_type=pin" + echo "Go to" + echo "${authorize_url}" + echo "and grant access to this application." + read -rp "Enter the PIN: " imgur_pin + + if [ -z "${imgur_pin}" ]; then + echo "PIN not entered, exiting" + exit 1 + fi + + # exchange the PIN for access token and refresh token + response="$(curl --compressed -fsSL --stderr - \ + -F "client_id=${imgur_acct_key}" \ + -F "client_secret=${imgur_secret}" \ + -F "grant_type=pin" \ + -F "pin=${imgur_pin}" \ + https://api.imgur.com/oauth2/token)" + save_access_token "${response}" "${1}" +} + +function refresh_access_token() { + check_oauth2_client_secrets + token_url="https://api.imgur.com/oauth2/token" + # exchange the refresh token for access_token and refresh_token + if ! response="$(curl --compressed -fsSL --stderr - \ + -F "client_id=${imgur_acct_key}" \ + -F "client_secret=${imgur_secret}" \ + -F "grant_type=refresh_token" \ + -F "refresh_token=${refresh_token}" \ + "${token_url}" + )"; then + # curl failed + handle_upload_error "${response}" "${token_url}" + exit 1 + fi + save_access_token "${response}" "${1}" +} + +function save_access_token() { + if ! grep -q "access_token" <<<"${1}"; then + # server did not send access_token + echo "Error: Something is wrong with your credentials:" + echo "${1}" + exit 1 + fi + + access_token="$(grep -Eo 'access_token":".*"' <<<"${1}" | cut -d '"' -f 3)" + refresh_token="$(grep -Eo 'refresh_token":".*"' <<<"${1}" | cut -d '"' -f 3)" + expires_in="$(grep -Eo 'expires_in":[0-9]*' <<<"${1}" | cut -d ':' -f 2)" + token_expire_time="$(( $(date +%s) + expires_in ))" + + # create dir if not exist + mkdir -p "$(dirname "${2}")" 2>/dev/null + touch "${2}" && chmod 600 "${2}" + cat <<EOF > "${2}" +access_token="${access_token}" +refresh_token="${refresh_token}" +token_expire_time="${token_expire_time}" +EOF +} + +function fetch_account_info() { + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/account/me)" + if grep -Eq '"success":\s*true' <<<"${response}"; then + username="$(grep -Eo '"url":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + echo "Logged in as ${username}." + echo "https://${username}.imgur.com" + else + echo "Failed to fetch info: ${response}" + fi +} + +function delete_image() { + response="$(curl --compressed -X DELETE -fsSL --stderr - -H "Authorization: Client-ID ${1}" "https://api.imgur.com/3/image/${2}")" + if grep -Eq '"success":\s*true' <<<"${response}"; then + echo "Image successfully deleted (delete hash: ${2})." >> "${3}" + else + echo "The Image could not be deleted: ${response}." >> "${3}" + fi +} + +function upload_authenticated_image() { + echo "Uploading '${1}'..." + title="$(echo "${1}" | rev | cut -d "/" -f 1 | cut -d "." -f 2- | rev)" + if [ -n "${album_id}" ]; then + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -F "title=${title}" -F "image=@\"${1}\"" -F "album=${album_id}" -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/image)" + else + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -F "title=${title}" -F "image=@\"${1}\"" -H "Authorization: Bearer ${access_token}" https://api.imgur.com/3/image)" + fi + + # JSON parser premium edition (not really) + if grep -Eq '"success":\s*true' <<<"${response}"; then + img_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + img_ext="$(grep -Eo '"link":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4 | rev | cut -d "." -f 1 | rev)" # "link" itself has ugly '\/' escaping and no https! + del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + + if [ -n "${auto_delete}" ]; then + export -f delete_image + echo "Deleting image in ${auto_delete} seconds." + nohup /bin/bash -c "sleep ${auto_delete} && delete_image ${imgur_anon_id} ${del_id} ${log_file}" & + fi + + handle_upload_success "https://i.imgur.com/${img_id}.${img_ext}" "https://imgur.com/delete/${del_id}" "${1}" + else # upload failed + err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + test -z "${err_msg}" && err_msg="${response}" + handle_upload_error "${err_msg}" "${1}" + fi +} + +function upload_anonymous_image() { + echo "Uploading '${1}'..." + title="$(echo "${1}" | rev | cut -d "/" -f 1 | cut -d "." -f 2- | rev)" + if [ -n "${album_id}" ]; then + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Client-ID ${imgur_anon_id}" -F "title=${title}" -F "image=@\"${1}\"" -F "album=${album_id}" https://api.imgur.com/3/image)" + else + response="$(curl --compressed --connect-timeout "${upload_connect_timeout}" -m "${upload_timeout}" --retry "${upload_retries}" -fsSL --stderr - -H "Authorization: Client-ID ${imgur_anon_id}" -F "title=${title}" -F "image=@\"${1}\"" https://api.imgur.com/3/image)" + fi + # JSON parser premium edition (not really) + if grep -Eq '"success":\s*true' <<<"${response}"; then + img_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + img_ext="$(grep -Eo '"link":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4 | rev | cut -d "." -f 1 | rev)" # "link" itself has ugly '\/' escaping and no https! + del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + + if [ -n "${auto_delete}" ]; then + export -f delete_image + echo "Deleting image in ${auto_delete} seconds." + nohup /bin/bash -c "sleep ${auto_delete} && delete_image ${imgur_anon_id} ${del_id} ${log_file}" & + fi + + handle_upload_success "https://i.imgur.com/${img_id}.${img_ext}" "https://imgur.com/delete/${del_id}" "${1}" + else # upload failed + err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + test -z "${err_msg}" && err_msg="${response}" + handle_upload_error "${err_msg}" "${1}" + fi +} + +function handle_upload_success() { + echo "" + echo "image link: ${1}" + echo "delete link: ${2}" + + if [ "${copy_url}" = "true" ] && [ -z "${album_title}" ]; then + if is_mac; then + echo -n "${1}" | pbcopy + else + echo -n "${1}" | xclip -selection clipboard + fi + echo "URL copied to clipboard" + fi + + # print to log file: image link, image location, delete link + echo -e "${1}\t${3}\t${2}" >> "${log_file}" + + notify ok "Upload done!" "${1}" + +# if [ ! -z "${open_command}" ] && [ "${open}" = "true" ]; then +# open_cmd=${open_command//\%url/${1}} +# open_cmd=${open_cmd//\%img/${2}} +# echo "Opening '${open_cmd}'" +# eval "${open_cmd}" +# fi +} + +function handle_upload_error() { + error="Upload failed: \"${1}\"" + echo "${error}" + echo -e "Error\t${2}\t${error}" >> "${log_file}" + notify error "Upload failed :(" "${1}" +} + +function handle_album_creation_success() { + echo "" + echo "Album link: ${1}" + echo "Delete hash: ${2}" + echo "" + + notify ok "Album created!" "${1}" + + if [ "${copy_url}" = "true" ]; then + if is_mac; then + echo -n "${1}" | pbcopy + else + echo -n "${1}" | xclip -selection clipboard + fi + echo "URL copied to clipboard" + fi + + # print to log file: album link, album title, delete hash + echo -e "${1}\t\"${3}\"\t${2}" >> "${log_file}" +} + +function handle_album_creation_error() { + error="Album creation failed: \"${1}\"" + echo -e "Error\t${2}\t${error}" >> "${log_file}" + notify error "Album creation failed :(" "${1}" + if [ ${exit_on_album_creation_fail} ]; then + exit 1 + fi +} + +while [ ${#} != 0 ]; do + case "${1}" in + -h | --help) + echo "usage: ${0} [--debug] [-c | --check | -v | -h | -u]" + echo " ${0} [--debug] [option]... [file]..." + echo "" + echo " --debug Enable debugging, must be first option" + echo " -h, --help Show this help, exit" + echo " -v, --version Show current version, exit" + echo " --check Check if all dependencies are installed, exit" + echo " -c, --connect Show connected imgur account, exit" + echo " -o, --open <true|false> Override 'open' config" + echo " -e, --edit <true|false> Override 'edit' config" + echo " -i, --edit-command <command> Override 'edit_command' config (include '%img'), sets --edit 'true'" + echo " -l, --login <true|false> Override 'login' config" + echo " -a, --album <album_title> Create new album and upload there" + echo " -A, --album-id <album_id> Override 'album_id' config" + echo " -k, --keep-file <true|false> Override 'keep_file' config" + echo " -d, --auto-delete <s> Automatically delete image after <s> seconds" + echo " -u, --update Check for updates, exit" + echo " file Upload file instead of taking a screenshot" + exit 0;; + -v | --version) + echo "${current_version}" + exit 0;; + -s | --select) + mode="select" + shift;; + -w | --window) + mode="window" + shift;; + -f | --full) + mode="full" + shift;; + -o | --open) + # shellcheck disable=SC2034 + open="${2}" + shift 2;; + -e | --edit) + edit="${2}" + shift 2;; + -i | --edit-command) + edit_command="${2}" + edit="true" + shift 2;; + -l | --login) + login="${2}" + shift 2;; + -c | --connect) + load_access_token + fetch_account_info + exit 0;; + -a | --album) + album_title="${2}" + shift 2;; + -A | --album-id) + album_id="${2}" + shift 2;; + -k | --keep-file) + keep_file="${2}" + shift 2;; + -d | --auto-delete) + auto_delete="${2}" + shift 2;; + -u | --update) + check_for_update + exit 0;; + *) + upload_files=("${@}") + break;; + esac +done + +if [ "${login}" = "true" ]; then + # load before changing directory + load_access_token +fi + + +if [ -n "${album_title}" ]; then + if [ "${login}" = "true" ]; then + response="$(curl -fsSL --stderr - \ + -F "title=${album_title}" \ + -H "Authorization: Bearer ${access_token}" \ + https://api.imgur.com/3/album)" + else + response="$(curl -fsSL --stderr - \ + -F "title=${album_title}" \ + -H "Authorization: Client-ID ${imgur_anon_id}" \ + https://api.imgur.com/3/album)" + fi + if grep -Eq '"success":\s*true' <<<"${response}"; then # Album creation successful + echo "Album '${album_title}' successfully created" + album_id="$(grep -Eo '"id":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + del_id="$(grep -Eo '"deletehash":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + handle_album_creation_success "https://imgur.com/a/${album_id}" "${del_id}" "${album_title}" + + if [ "${login}" = "false" ]; then + album_id="${del_id}" + fi + else # Album creation failed + err_msg="$(grep -Eo '"error":\s*"[^"]+"' <<<"${response}" | cut -d "\"" -f 4)" + test -z "${err_msg}" && err_msg="${response}" + handle_album_creation_error "${err_msg}" "${album_title}" + fi +fi + +if [ -z "${upload_files[*]}" ]; then + upload_files[0]="" +fi + +for upload_file in "${upload_files[@]}"; do + + if [ -z "${upload_file}" ]; then + cd "${file_dir}" || exit 1 + + # new filename with date + img_file="$(date +"${file_name_format}")" + take_screenshot "${img_file}" + else + # upload file instead of screenshot + img_file="${upload_file}" + fi + + # get full path + #cd "$(dirname "$(realpath "${img_file}")")" + #img_file="$(realpath "${img_file}")" + + # check if file exists + if ! [ -f "${img_file}" ]; then + echo "file '${img_file}' doesn't exist !" + read -r _ + exit 1 + fi + + # open image in editor if configured + if [ "${edit}" = "true" ]; then + edit_cmd=${edit_command//\%img/${img_file}} + echo "Opening editor '${edit_cmd}'" + if ! (eval "${edit_cmd}"); then + echo "Error for image '${img_file}': command '${edit_cmd}' failed, not uploading. For more information visit https://github.com/jomo/imgur-screenshot/wiki/Troubleshooting" | tee -a "${log_file}" + notify error "Something went wrong :(" "Information has been logged" + exit 1 + fi + fi + + if [ "${login}" = "true" ]; then + upload_authenticated_image "${img_file}" + else + upload_anonymous_image "${img_file}" + fi + + # delete file if configured + if [ "${keep_file}" = "false" ] && [ -z "${1}" ]; then + echo "Deleting temp file ${file_dir}/${img_file}" + rm -rf "${img_file}" + fi + + echo "" +done + + +if [ "${check_update}" = "true" ]; then + check_for_update +fi + +read -r _ diff --git a/nnn/plugins/imgview b/nnn/plugins/imgview @@ -0,0 +1,111 @@ +#!/usr/bin/env sh + +# Description: Open hovered or current directory in image viewer. +# Generates media thumbnails with optional dependencies. +# +# Dependencies: +# - imv (https://github.com/eXeC64/imv) or, +# - sxiv (https://github.com/muennich/sxiv) or, +# - nsxiv (https://github.com/nsxiv/nsxiv) or, +# - ucollage (https://github.com/ckardaris/ucollage) or, +# - lsix (https://github.com/hackerb9/lsix), or +# - viu (https://github.com/atanunq/viu), or +# - catimg (https://github.com/posva/catimg), or +# - optional: ffmpeg for audio thumbnails (album art) +# - optional: ffmpegthumbnailer for video thumbnails +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana, Luuk van Baal +# +# Consider setting NNN_PREVIEWDIR to $XDG_CACHE_HOME/nnn/previews +# if you want to keep media thumbnails on disk between reboots. +NNN_PREVIEWDIR="${NNN_PREVIEWDIR:-${TMPDIR:-/tmp}/nnn/previews}" + +exit_prompt() { + [ -n "$1" ] && printf "%s\n" "$1" + printf "%s" "Press any key to exit..." + cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg" + clear + exit +} + +make_thumbs() { + mkdir -p "$NNN_PREVIEWDIR$dir" || return + if [ "$1" -eq 3 ]; then + [ -d "$target" ] && exit_prompt "$2 can only display a single image" + mime="$(file -bL --mime-type -- "$target")" + case "$mime" in + audio/*) ffmpeg -i "$target" "$NNN_PREVIEWDIR$target.jpg" -y >/dev/null 2>&1 + ret="$NNN_PREVIEWDIR/$target.jpg" ;; + video/*) ffmpegthumbnailer -i "$target" -o "$NNN_PREVIEWDIR$target.jpg" 2> /dev/null + ret="$NNN_PREVIEWDIR/$target.jpg" ;; + *) ret="$target" ;; + esac + fi + for file in "$dir"/*; do + if [ ! -f "$NNN_PREVIEWDIR$file.jpg" ]; then + case "$(file -bL --mime-type -- "$file")" in + audio/*) [ "$1" -ne 0 ] && ffmpeg -i "$file" "$NNN_PREVIEWDIR$file.jpg" -y >/dev/null 2>&1 ;; + video/*) [ "$1" -ne 1 ] && ffmpegthumbnailer -i "$file" -o "$NNN_PREVIEWDIR$file.jpg" 2> /dev/null ;; + esac + fi + done + for file in "$NNN_PREVIEWDIR$dir"/*; do + filename="$(basename "$file" .jpg)" + [ ! -e "$dir/$filename" ] && rm "$file" 2>/dev/null + done +} + +listimages() { + find -L "$dir" "$NNN_PREVIEWDIR$dir" -maxdepth 1 -type f -print0 2>/dev/null | sort -z +} + +view_files() { + [ -f "$target" ] && count="-n $(listimages | grep -a -m 1 -ZznF "$target" | cut -d: -f1)" + case "$1" in + nsxiv) listimages | xargs -0 nsxiv -a "${count:--t}" -- ;; + sxiv) listimages | xargs -0 sxiv -a "${count:--t}" -- ;; + imv*) listimages | xargs -0 "$1" "${count:-}" -- ;; + esac +} + +target="$(readlink -f "$1")" +[ -d "$target" ] && dir="$target" || dir="${target%/*}" +if uname | grep -q "Darwin"; then + [ -f "$1" ] && open "$1" >/dev/null 2>&1 & +elif type lsix >/dev/null 2>&1; then + if [ -d "$target" ]; then + cd "$target" || exit_prompt + fi + make_thumbs "" + clear + lsix + cd "$NNN_PREVIEWDIR$dir" && lsix + exit_prompt +elif type ucollage >/dev/null 2>&1; then + type ffmpeg >/dev/null 2>&1 && make_thumbs 1 + UCOLLAGE_EXPAND_DIRS=1 ucollage "$dir" "$NNN_PREVIEWDIR$dir" || exit_prompt +elif type sxiv >/dev/null 2>&1; then + type ffmpegthumbnailer >/dev/null 2>&1 && make_thumbs 0 + view_files sxiv >/dev/null 2>&1 & +elif type nsxiv >/dev/null 2>&1; then + type ffmpegthumbnailer >/dev/null 2>&1 && make_thumbs 0 + view_files nsxiv >/dev/null 2>&1 & +elif type imv >/dev/null 2>&1; then + make_thumbs "" + view_files imv >/dev/null 2>&1 & +elif type imvr >/dev/null 2>&1; then + make_thumbs "" + view_files imvr >/dev/null 2>&1 & +elif type viu >/dev/null 2>&1; then + clear + make_thumbs 3 viu + viu -n "$ret" + exit_prompt +elif type catimg >/dev/null 2>&1; then + make_thumbs 3 catimg + catimg "$ret" + exit_prompt +else + exit_prompt "Please install sxiv/nsxiv/imv/viu/catimg/lsix." +fi diff --git a/nnn/plugins/ipinfo b/nnn/plugins/ipinfo @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +# Description: Shows the external IP address and whois information. Useful over VPNs. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +IP=$(curl -s ifconfig.me) + +whois "$IP" +echo your external IP address is "$IP" + +read -r _ diff --git a/nnn/plugins/kdeconnect b/nnn/plugins/kdeconnect @@ -0,0 +1,24 @@ +#!/usr/bin/env sh + +# Description: Send the selected files to your Android device using kdeconnect-cli. +# kdeconnect must be configured on the Android device and the PC. +# +# Shell: POSIX compliant +# Author: juacq97 + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +id=$(kdeconnect-cli -a --id-only | awk '{print $1}') +if [ -s "$selection" ]; then + kdeconnect-cli -d "$id" --share "$(cat "$selection")" + + # If you want a system notification, uncomment the next 3 lines. + #notify-send -a "Kdeconnect" "Sending $(cat "$selection")" +#else + #notify-send -a "Kdeconnect" "No file selected" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +fi diff --git a/nnn/plugins/launch b/nnn/plugins/launch @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +# Description: Independent POSIX-compliant GUI application launcher. +# Fuzzy find executables in $PATH and launch an application. +# stdin, stdout, stderr are suppressed so CLI tools exit silently. +# +# To configure launch as an independent app launcher add a keybind +# to open launch in a terminal e.g., +# +# xfce4-terminal -e "${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/launch +# +# Dependencies: fzf +# +# Usage: launch [delay] +# delay is in seconds, if omitted launch waits for 1 sec +# +# Integration with nnn: launch is installed with other plugins, nnn picks it up. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# shellcheck disable=SC2086 + +IFS=':' + +get_selection() { + if type fzf >/dev/null 2>&1; then + { IFS=':'; ls -H $PATH; } | sort | fzf + else + exit 1 + fi +} + +if selection=$( get_selection ); then + setsid "$selection" 2>/dev/null 1>/dev/null & + + if [ -n "$1" ]; then + sleep "$1" + else + sleep 1 + fi +fi diff --git a/nnn/plugins/mimelist b/nnn/plugins/mimelist @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +# Description: Find and list files by mime type in smart context +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +. "$(dirname "$0")"/.nnn-plugin-helper + +printf "mime (e.g., video/audio/image): " +read -r mime + +printf "%s" "+l" > "$NNN_PIPE" +find . | file -if- | grep "$mime" | awk -F: '{printf "%s\0", $1}' > "$NNN_PIPE" diff --git a/nnn/plugins/moclyrics b/nnn/plugins/moclyrics @@ -0,0 +1,40 @@ +#!/usr/bin/env sh + +# Description: Fetches the lyrics of the track currently playing in MOC +# +# Dependencies: ddgr (https://github.com/jarun/ddgr) +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +# Check if MOC server is running +cmd=$(pgrep -x mocp 2>/dev/null) +ret=$cmd +if [ -z "$ret" ]; then + exit +fi + +# Grab the output +out="$(mocp -i)" + +# Check if anything is playing +state=$(echo "$out" | grep "State:" | cut -d' ' -f2) +if ! [ "$state" = 'PLAY' ]; then + exit +fi + +# Try by Artist and Song Title first +ARTIST="$(echo "$out" | grep 'Artist:' | cut -d':' -f2 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//')" +TITLE="$(echo "$out" | grep 'SongTitle:' | cut -d':' -f2 | sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//')" + +if [ -n "$ARTIST" ] && [ -n "$TITLE" ]; then + ddgr -w azlyrics.com --ducky "$ARTIST" "$TITLE" +else + # Try by file name + FILENAME="$(basename "$(echo "$out" | grep 'File:' | cut -d':' -f2)")" + FILENAME="$(echo "${FILENAME%%.*}" | tr -d -)" + + if [ -n "$FILENAME" ]; then + ddgr -w azlyrics.com --ducky "$FILENAME" + fi +fi diff --git a/nnn/plugins/mocq b/nnn/plugins/mocq @@ -0,0 +1,89 @@ +#!/usr/bin/env sh + +# Description: Appends and optionally plays music in MOC +# +# Notes: +# - if selection is available, plays it, else plays the current file or directory +# - appends tracks and exits is MOC is running, else clears playlist and adds tracks +# - to let mocp shuffle tracks, set SHUFFLE=1 +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, ath3 + +IFS="$(printf '\n\r')" +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +cmd=$(pgrep -x mocp 2>/dev/null) +ret=$cmd + +SHUFFLE="${SHUFFLE:-0}" + +mocp_add () +{ + if [ "$SHUFFLE" = 1 ]; then + if [ "$resp" = "y" ]; then + arr=$(tr '\0' '\n' < "$selection") + elif [ -n "$1" ]; then + arr="$1" + fi + + for entry in $arr + do + if [ -d "$entry" ]; then + arr2=$arr2$(find "$entry" -type f \( ! -iname "*.m3u" ! -iname "*.pls" \)) + elif echo "$entry" | grep -qv '\.m3u$\|\.pls$' ; then + arr2=$(printf "%s\n%s" "$entry" "$arr2") + fi + done + + mocp -o shuffle + echo "$arr2" | xargs -d "\n" mocp -a + else + if [ "$resp" = "y" ]; then + xargs < "$selection" -0 mocp -a + else + mocp -a "$1" + fi + fi +} + +if [ ! -s "$selection" ] && [ -z "$1" ]; then + exit +fi + +if [ "$2" = "opener" ]; then + : +elif [ -s "$selection" ]; then + printf "Work with selection? Enter 'y' to confirm: " + read -r resp +fi + +if [ -z "$ret" ]; then + # mocp not running + mocp -S +else + # mocp running, check if it's playing + state=$(mocp -i | grep "State:" | cut -d' ' -f2) + + if [ "$state" = 'PLAY' ]; then + # add to playlist and exit + mocp_add "$1" + + # uncomment the line below to show mocp interface after appending + # mocp + + exit + fi +fi + +# clear selection and play +mocp -c +mocp_add "$1" "$resp" +mocp -p + +# uncomment the line below to show mocp interface after appending +# mocp + +# Clear selection +if [ "$resp" = "y" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/nnn/plugins/mp3conv b/nnn/plugins/mp3conv @@ -0,0 +1,41 @@ +#!/usr/bin/env sh + +# Description: Extract audio from multimedia files and convert to mp3 +# +# Dependencies: ffmpeg compiled with libmp3lame audio codec support +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +outdir=_mp3files + +handle_multimedia() { + mime="${1}" + file="${2}" + + case "${mime}" in + audio/* | video/*) + ffmpeg -i "${file}" -vn -codec:a libmp3lame -q:a 2 "${outdir}/${file%.*}.mp3" + ;; + *) + ;; + esac +} + +printf "Process 'a'll in directory or 'c'urrent? " +read -r resp + +if [ "$resp" = "a" ]; then + if ! [ -e "${outdir}" ]; then + mkdir "${outdir}" + fi + + for f in *; do + if [ -f "${f}" ]; then + mimestr="$( file --dereference --brief --mime-type -- "${f}" )" + handle_multimedia "${mimestr}" "${f}" + fi + done +elif [ "$resp" = "c" ] && [ -f "$1" ]; then + ffmpeg -i "${1}" -vn -codec:a libmp3lame -q:a 2 "${1%.*}.mp3" +fi diff --git a/nnn/plugins/mtpmount b/nnn/plugins/mtpmount @@ -0,0 +1,76 @@ +#!/usr/bin/env sh + +# Description: Toggle mount of MTP device (eg. Android device) +# 'l' to list mountable devices +# 'n' integer associated to device to mount +# 'q'/'Return' exit +# +# Dependencies: gvfs-mtp +# +# Notes: The MTP device should be mounted at /run/user/$UID/gvfs. +# Put /run/user/$UID/gvfs to bookmark entries (NNN_BMS) for faster access. +# Make sure the device is unlocked when mounting. +# +# When doing copy-paste into MTP device, you will get an error like this: +# cp: preserving times for './gambar1.png': Operation not supported +# That just means the file is copied but timestamp won't be preserved. +# It's like doing `cp -p localfile.txt file-to-SMB.txt`. +# +# Shell: POSIX compliant +# Author: Benawi Adha + +prompt="Device number ('l' to list): " + +IFS=' +' + +lsmtp () { + devs=$(gio mount -li | grep -e 'activation_root' | sed 's/\s*activation_root=//g') + c=1 + printf "Devices list:\n" + for i in $devs; do + printf "%s %s\\n" "$c" "$i" + c=$(( c + 1 )) + done + echo +} + +lsmtp +printf "%s" "$prompt" +read -r input + +while [ -n "$input" ] +do + if [ "$input" = "l" ]; then + lsmtp + elif [ "$input" = "q" ] || [ "$input" -eq 0 ]; then + exit + elif [ "$input" -le "$(printf '%s\n' "${devs}" | grep -c '^')" ]; then + # dev=$(printf "%s\n" "$devs" | cut -d$'\n' -f${input}) + c=1 + for i in $devs; do + dev=$i + if [ "$input" -eq $c ]; then + break + fi + c=$(( c + 1 )) + done + + if (gio mount -l | grep '^Mount([1-9]).*'"$dev" ) 1>/dev/null; then + if gio mount -u "${dev}"; then + printf "%s unmounted\n" "$dev" + fi + else + if gio mount "${dev}"; then + printf "%s mounted to /run/user/\$UID/gvfs\n" "$dev" + fi + fi + echo + else + printf "Invalid input\n" + fi + + printf "%s" "$prompt" + read -r input +done + diff --git a/nnn/plugins/nbak b/nnn/plugins/nbak @@ -0,0 +1,75 @@ +#!/usr/bin/env sh + +# Description: Backup nnn configuration +# - config dir content +# - environment config +# - shell functions and aliases +# +# Shell: POSIX compliant +# Author: Léo Villeveygoux + +nnn_aliases="n nnn" + +outdir="nnn-$(whoami)@$(hostname)" + +outfile="${outdir}.tar.bz2" + +shellname="$(basename "$SHELL")" + +conffile="config.txt" + +configdir="${XDG_CONFIG_HOME:-$HOME/.config}/nnn" + +workdir="$PWD" + +tempdir="$(mktemp -d)" + +mkdir "$tempdir/$outdir" + +if [ ! -d "$tempdir" ]; then + echo "Can't create work directory." >&2 + exit 1 +fi + +cd "$tempdir/$outdir" || exit 1 + +# Backing up config dir content +cp -r "$configdir" . || exit 1 + +# Environment config +env | sed "s/'/'\\\\''/" |\ + awk '/^NNN_/{print "export '\''"$0"'\''"}' > "$conffile" + +# Shell functions/aliases +case "$shellname" in + bash) + for name in $nnn_aliases ; do + if [ "$(bash -ic "type -t $name")" = "function" ] ; then + bash -ic "type $name" | tail -n+2 >> "$conffile" + elif bash -ic "alias $name" >/dev/null 2>&1 ; then + bash -ic "alias $name" >> "$conffile" + fi + done + ;; + zsh) + for name in $nnn_aliases ; do + if zsh -ic "functions $name" ; then + zsh -ic "functions $name" >> "$conffile" + elif zsh -ic "alias $name" ; then + echo alias "$(zsh -ic "alias $name")" >> "$conffile" + fi + done + ;; + + *) + echo "Unknown shell, skipping alias/function checking." >&2 + ;; +esac + +cd .. || exit 1 + +printf "Saving as '%s' ... " "$workdir/$outfile" + +tar caf "$workdir/$outfile" "$outdir" && echo "Done" || echo "Failed" + +cd "$workdir" && rm -rf "$tempdir" diff --git a/nnn/plugins/nmount b/nnn/plugins/nmount @@ -0,0 +1,55 @@ +#!/usr/bin/env sh + +# Description: Toggle mount status of a device using pmount +# If the device is not mounted, it will be mounted. +# If the device is mounted, it will be unmounted and powered down. +# +# Dependencies: lsblk, pmount +# +# Usage: Runs `lsblk` on 'l', exits on 'Return`. +# +# Notes: +# - The script uses Linux-specific lsblk to list block devices. Alternatives: +# macOS: "diskutil list" +# BSD: "geom disk list" +# - The script uses udisksctl (from udisks2) to power down devices. This is also Linux-specific. +# Users on non-Linux platforms can comment it and use an alterntive to power-down disks. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +prompt="device name [e.g. sdXn] ('l'ist, 'q'uit): " + +lsblk + +printf "\nEnsure you aren't still in the mounted device.\n" +printf "%s" "$prompt" +read -r dev + +while [ -n "$dev" ] +do + if [ "$dev" = "l" ]; then + lsblk + elif [ "$dev" = "q" ]; then + exit + else + if grep -qs "$dev " /proc/mounts; then + sync + if pumount "$dev" + then + echo "$dev" unmounted. + if udisksctl power-off -b /dev/"$dev" + then + echo "$dev" ejected. + fi + fi + else + pmount "$dev" + echo "$dev" mounted to "$(lsblk -n /dev/"$dev" | rev | cut -d' ' -f1 | rev)". + fi + fi + + echo + printf "%s" "$prompt" + read -r dev +done diff --git a/nnn/plugins/nnnopen b/nnn/plugins/nnnopen @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +if [[ ! -f "$1" ]]; then + exit +fi + +mime=$(file -b --mime-type "$1") +echo $mime +#texto +if [[ "${mime%/*}" = "text" ]]; then + nvim "$1" +#imaxes +elif [[ "${mime%/*}" = "image" ]]; then + sxiv "$1" +# video +if [[ "${mime%/*}" = "video" ]]; then + mpv "$1" +fi diff --git a/nnn/plugins/nuke b/nnn/plugins/nuke @@ -0,0 +1,556 @@ +#!/usr/bin/env sh + +# Description: Sample script to play files in apps by file type or mime +# +# Shell: POSIX compliant +# Usage: nuke filepath +# +# Integration with nnn: +# 1. Export the required config: +# export NNN_OPENER=/absolute/path/to/nuke +# # Otherwise, if nuke is in $PATH +# # export NNN_OPENER=nuke +# 2. Run nnn with the program option to indicate a CLI opener +# nnn -c +# # The -c program option overrides option -e +# 3. nuke can use nnn plugins (e.g. mocq is used for audio), $PATH is updated. +# +# Details: +# Inspired by ranger's scope.sh, modified for usage with nnn. +# +# Guards against accidentally opening mime types like executables, shared libs etc. +# +# Tries to play 'file' (1st argument) in the following order: +# 1. by extension +# 2. by mime (image, video, audio, pdf) +# 3. by mime (other file types) +# 4. by mime (prompt and run executables) +# +# Modification tips: +# 1. Invokes CLI utilities by default. Set GUI to 1 to enable GUI apps. +# 2. PAGER is "less -R". +# 3. Start GUI apps in bg to unblock. Redirect stdout and strerr if required. +# 4. Some CLI utilities are piped to the $PAGER, to wait and quit uniformly. +# 5. If the output cannot be paged use "read -r _" to wait for user input. +# 6. On a DE, try 'xdg-open' or 'open' in handle_fallback() as last resort. +# +# Feel free to change the utilities to your favourites and add more mimes. +# +# Defaults: +# By extension (only the enabled ones): +# most archives: list with atool, bsdtar +# rar: list with unrar +# 7-zip: list with 7z +# pdf: zathura (GUI), pdftotext, mutool, exiftool +# audio: mocq (nnn plugin using MOC), mpv, media_client (Haiku), mediainfo, exiftool +# avi|mkv|mp4: smplayer, mpv (GUI), ffmpegthumbnailer, mediainfo, exiftool +# log: vi +# torrent: rtorrent, transmission-show +# odt|ods|odp|sxw: odt2txt +# md: glow (https://github.com/charmbracelet/glow), lowdown (https://kristaps.bsd.lv/lowdown) +# htm|html|xhtml: w3m, lynx, elinks +# json: jq, python (json.tool module) +# Multimedia by mime: +# image/*: imv/sxiv/nsxiv (GUI), viu (https://github.com/atanunq/viu), img2txt, exiftool +# video/*: smplayer, mpv (GUI), ffmpegthumbnailer, mediainfo, exiftool +# audio/*: mocq (nnn plugin using MOC), mpv, media_client (Haiku), mediainfo, exiftool +# application/pdf: zathura (GUI), pdftotext, mutool, exiftool +# Other mimes: +# text/troff: man -l +# text/* | */xml: vi +# image/vnd.djvu): djvutxt, exiftool +# +# TODO: +# 1. Adapt, test and enable all mimes +# 2. Clean-up the unnecessary exit codes + +# set to 1 to enable GUI apps and/or BIN execution +GUI="${GUI:-0}" +BIN="${BIN:-0}" + +set -euf -o noclobber -o noglob -o nounset +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n + +PATH=$PATH:"${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins" +IMAGE_CACHE_PATH="$(dirname "$1")"/.thumbs + +FPATH="$1" +FNAME=$(basename "$1") +EDITOR="${VISUAL:-${EDITOR:-vi}}" +PAGER="${PAGER:-less -R}" +ext="${FNAME##*.}" +if [ -n "$ext" ]; then + ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')" +fi + +is_mac() { + uname | grep -q "Darwin" +} + +handle_pdf() { + if [ "$GUI" -ne 0 ]; then + if is_mac; then + nohup open "${FPATH}" >/dev/null 2>&1 & + elif type zathura >/dev/null 2>&1; then + nohup zathura "${FPATH}" >/dev/null 2>&1 & + else + return + fi + elif type pdftotext >/dev/null 2>&1; then + ## Preview as text conversion + pdftotext -l 10 -nopgbrk -q -- "${FPATH}" - | eval "$PAGER" + elif type mutool >/dev/null 2>&1; then + mutool draw -F txt -i -- "${FPATH}" 1-10 | eval "$PAGER" + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}" | eval "$PAGER" + else + return + fi + exit 0 +} + +handle_audio() { + if type mocp >/dev/null 2>&1 && type mocq >/dev/null 2>&1; then + mocq "${FPATH}" "opener" >/dev/null 2>&1 + elif type mpv >/dev/null 2>&1; then + mpv "${FPATH}" >/dev/null 2>&1 & + elif type media_client >/dev/null 2>&1; then + media_client play "${FPATH}" >/dev/null 2>&1 & + elif type mediainfo >/dev/null 2>&1; then + mediainfo "${FPATH}" | eval "$PAGER" + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}"| eval "$PAGER" + else + return + fi + exit 0 +} + +handle_video() { + if [ "$GUI" -ne 0 ]; then + mpv "${FPATH}" > /dev/null 2>&1 & + #if is_mac; then + # nohup open "${FPATH}" >/dev/null 2>&1 & + #elif type smplayer >/dev/null 2>&1; then + # nohup smplayer "${FPATH}" >/dev/null 2>&1 & + #elif type mpv >/dev/null 2>&1; then + # nohup mpv "${FPATH}" >/dev/null 2>&1 & + #else + # return + fi + elif type ffmpegthumbnailer >/dev/null 2>&1; then + # Thumbnail + [ -d "${IMAGE_CACHE_PATH}" ] || mkdir "${IMAGE_CACHE_PATH}" + ffmpegthumbnailer -i "${FPATH}" -o "${IMAGE_CACHE_PATH}/${FNAME}.jpg" -s 0 + viu -n "${IMAGE_CACHE_PATH}/${FNAME}.jpg" | eval "$PAGER" + elif type mediainfo >/dev/null 2>&1; then + mediainfo "${FPATH}" | eval "$PAGER" + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}"| eval "$PAGER" + else + return + fi + exit 0 +} + +# handle this extension and exit +handle_extension() { + case "${ext}" in + ## Archive + a|ace|alz|arc|arj|bz|bz2|cab|cpio|deb|gz|jar|lha|lz|lzh|lzma|lzo|\ + rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z|zip) + if type atool >/dev/null 2>&1; then + atool --list -- "${FPATH}" | eval "$PAGER" + exit 0 + elif type bsdtar >/dev/null 2>&1; then + bsdtar --list --file "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + rar) + if type unrar >/dev/null 2>&1; then + ## Avoid password prompt by providing empty password + unrar lt -p- -- "${FPATH}" | eval "$PAGER" + fi + exit 1;; + 7z) + if type 7z >/dev/null 2>&1; then + ## Avoid password prompt by providing empty password + 7z l -p -- "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + + ## PDF + pdf) + handle_pdf + exit 1;; + + ## Audio + aac|flac|m4a|mid|midi|mpa|mp2|mp3|ogg|wav|wma) + handle_audio + exit 1;; + + ## Video + avi|mkv|mp4) + handle_video + exit 1;; + + ## Log files + log) + "$EDITOR" "${FPATH}" + exit 0;; + + ## BitTorrent + torrent) + if type rtorrent >/dev/null 2>&1; then + rtorrent "${FPATH}" + exit 0 + elif type transmission-show >/dev/null 2>&1; then + transmission-show -- "${FPATH}" + exit 0 + fi + exit 1;; + + ## OpenDocument + odt|ods|odp|sxw) + if type odt2txt >/dev/null 2>&1; then + ## Preview as text conversion + odt2txt "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + + ## Markdown + md) + if type glow >/dev/null 2>&1; then + glow -sdark "${FPATH}" | eval "$PAGER" + exit 0 + elif type lowdown >/dev/null 2>&1; then + lowdown -Tterm "${FPATH}" | eval "$PAGER" + exit 0 + fi + ;; + + ## HTML + htm|html|xhtml) + ## Preview as text conversion + if type w3m >/dev/null 2>&1; then + w3m -dump "${FPATH}" | eval "$PAGER" + exit 0 + elif type lynx >/dev/null 2>&1; then + lynx -dump -- "${FPATH}" | eval "$PAGER" + exit 0 + elif type elinks >/dev/null 2>&1; then + elinks -dump "${FPATH}" | eval "$PAGER" + exit 0 + fi + ;; + + ## JSON + json) + if type jq >/dev/null 2>&1; then + jq --color-output . "${FPATH}" | eval "$PAGER" + exit 0 + elif type python >/dev/null 2>&1; then + python -m json.tool -- "${FPATH}" | eval "$PAGER" + exit 0 + fi + ;; + esac +} + +# sets the variable abs_target, this should be faster than calling printf +abspath() { + case "$1" in + /*) abs_target="$1";; + *) abs_target="$PWD/$1";; + esac +} + +# storing the result to a tmp file is faster than calling listimages twice +listimages() { + find -L "///${1%/*}" -maxdepth 1 -type f -print0 | + grep -izZE '\.(jpe?g|png|gif|webp|tiff|bmp|ico|svg)$' | + sort -z | tee "$tmp" +} + +load_dir() { + abspath "$2" + tmp="${TMPDIR:-/tmp}/nuke_$$" + trap 'rm -f $tmp' EXIT + count="$(listimages "$abs_target" | grep -a -m 1 -ZznF "$abs_target" | cut -d: -f1)" + + if [ -n "$count" ]; then + if [ "$GUI" -ne 0 ]; then + xargs -0 nohup "$1" -n "$count" -- < "$tmp" + else + xargs -0 "$1" -n "$count" -- < "$tmp" + fi + else + shift + "$1" -- "$@" # fallback + fi +} + +handle_multimedia() { + ## Size of the preview if there are multiple options or it has to be + ## rendered from vector graphics. If the conversion program allows + ## specifying only one dimension while keeping the aspect ratio, the width + ## will be used. + # local DEFAULT_SIZE="1920x1080" + + mimetype="${1}" + case "${mimetype}" in + ## SVG + # image/svg+xml|image/svg) + # convert -- "${FPATH}" "${IMAGE_CACHE_PATH}" && exit 6 + # exit 1;; + + ## DjVu + # image/vnd.djvu) + # ddjvu -format=tiff -quality=90 -page=1 -size="${DEFAULT_SIZE}" \ + # - "${IMAGE_CACHE_PATH}" < "${FPATH}" \ + # && exit 6 || exit 1;; + + ## Image + image/*) + if [ "$GUI" -ne 0 ]; then + if is_mac; then + nohup open "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type imv >/dev/null 2>&1; then + load_dir imv "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type imvr >/dev/null 2>&1; then + load_dir imvr "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type sxiv >/dev/null 2>&1; then + load_dir sxiv "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type nsxiv >/dev/null 2>&1; then + load_dir nsxiv "${FPATH}" >/dev/null 2>&1 & + exit 0 + fi + elif type viu >/dev/null 2>&1; then + viu -n "${FPATH}" | eval "$PAGER" + exit 0 + elif type img2txt >/dev/null 2>&1; then + img2txt --gamma=0.6 -- "${FPATH}" | eval "$PAGER" + exit 0 + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}" | eval "$PAGER" + exit 0 + fi + # local orientation + # orientation="$( identify -format '%[EXIF:Orientation]\n' -- "${FPATH}" )" + ## If orientation data is present and the image actually + ## needs rotating ("1" means no rotation)... + # if [[ -n "$orientation" && "$orientation" != 1 ]]; then + ## ...auto-rotate the image according to the EXIF data. + # convert -- "${FPATH}" -auto-orient "${IMAGE_CACHE_PATH}" && exit 6 + # fi + + ## `w3mimgdisplay` will be called for all images (unless overridden + ## as above), but might fail for unsupported types. + exit 7;; + + ## PDF + application/pdf) + handle_pdf + exit 1;; + + ## Audio + audio/*) + handle_audio + exit 1;; + + ## Video + video/*) + handle_video + exit 1;; + + # pdftoppm -f 1 -l 1 \ + # -scale-to-x "${DEFAULT_SIZE%x*}" \ + # -scale-to-y -1 \ + # -singlefile \ + # -jpeg -tiffcompression jpeg \ + # -- "${FPATH}" "${IMAGE_CACHE_PATH%.*}" \ + # && exit 6 || exit 1;; + + + ## ePub, MOBI, FB2 (using Calibre) + # application/epub+zip|application/x-mobipocket-ebook|\ + # application/x-fictionbook+xml) + # # ePub (using https://github.com/marianosimone/epub-thumbnailer) + # epub-thumbnailer "${FPATH}" "${IMAGE_CACHE_PATH}" \ + # "${DEFAULT_SIZE%x*}" && exit 6 + # ebook-meta --get-cover="${IMAGE_CACHE_PATH}" -- "${FPATH}" \ + # >/dev/null && exit 6 + # exit 1;; + + ## Font + # application/font*|application/*opentype) + # preview_png="/tmp/$(basename "${IMAGE_CACHE_PATH%.*}").png" + # if fontimage -o "${preview_png}" \ + # --pixelsize "120" \ + # --fontname \ + # --pixelsize "80" \ + # --text " ABCDEFGHIJKLMNOPQRSTUVWXYZ " \ + # --text " abcdefghijklmnopqrstuvwxyz " \ + # --text " 0123456789.:,;(*!?') ff fl fi ffi ffl " \ + # --text " The quick brown fox jumps over the lazy dog. " \ + # "${FPATH}"; + # then + # convert -- "${preview_png}" "${IMAGE_CACHE_PATH}" \ + # && rm "${preview_png}" \ + # && exit 6 + # else + # exit 1 + # fi + # ;; + + ## Preview archives using the first image inside. + ## (Very useful for comic book collections for example.) + # application/zip|application/x-rar|application/x-7z-compressed|\ + # application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar) + # local fn=""; local fe="" + # local zip=""; local rar=""; local tar=""; local bsd="" + # case "${mimetype}" in + # application/zip) zip=1 ;; + # application/x-rar) rar=1 ;; + # application/x-7z-compressed) ;; + # *) tar=1 ;; + # esac + # { [ "$tar" ] && fn=$(tar --list --file "${FPATH}"); } || \ + # { fn=$(bsdtar --list --file "${FPATH}") && bsd=1 && tar=""; } || \ + # { [ "$rar" ] && fn=$(unrar lb -p- -- "${FPATH}"); } || \ + # { [ "$zip" ] && fn=$(zipinfo -1 -- "${FPATH}"); } || return + # + # fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \ + # [ print(l, end='') for l in sys.stdin if \ + # (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\ + # sort -V | head -n 1) + # [ "$fn" = "" ] && return + # [ "$bsd" ] && fn=$(printf '%b' "$fn") + # + # [ "$tar" ] && tar --extract --to-stdout \ + # --file "${FPATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6 + # fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g') + # [ "$bsd" ] && bsdtar --extract --to-stdout \ + # --file "${FPATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}" + # [ "$rar" ] && unrar p -p- -inul -- "${FPATH}" "$fn" > \ + # "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$zip" ] && unzip -pP "" -- "${FPATH}" "$fe" > \ + # "${IMAGE_CACHE_PATH}" && exit 6 + # [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}" + # ;; + esac +} + +handle_mime() { + mimetype="${1}" + case "${mimetype}" in + ## Manpages + text/troff) + man -l "${FPATH}" + exit 0;; + + ## Text + text/* | */xml) + "$EDITOR" "${FPATH}" + exit 0;; + ## Syntax highlight + # if [[ "$( stat --printf='%s' -- "${FPATH}" )" -gt "${HIGHLIGHT_SIZE_MAX}" ]]; then + # exit 2 + # fi + # if [[ "$( tput colors )" -ge 256 ]]; then + # local pygmentize_format='terminal256' + # local highlight_format='xterm256' + # else + # local pygmentize_format='terminal' + # local highlight_format='ansi' + # fi + # env HIGHLIGHT_OPTIONS="${HIGHLIGHT_OPTIONS}" highlight \ + # --out-format="${highlight_format}" \ + # --force -- "${FPATH}" && exit 5 + # pygmentize -f "${pygmentize_format}" -O "style=${PYGMENTIZE_STYLE}"\ + # -- "${FPATH}" && exit 5 + # exit 2;; + + ## DjVu + image/vnd.djvu) + if type djvutxt >/dev/null 2>&1; then + ## Preview as text conversion (requires djvulibre) + djvutxt "${FPATH}" | eval "$PAGER" + exit 0 + elif type exiftool >/dev/null 2>&1; then + exiftool "${FPATH}" | eval "$PAGER" + exit 0 + fi + exit 1;; + esac +} + +handle_fallback() { + if [ "$GUI" -ne 0 ]; then + if type xdg-open >/dev/null 2>&1; then + nohup xdg-open "${FPATH}" >/dev/null 2>&1 & + exit 0 + elif type open >/dev/null 2>&1; then + nohup open "${FPATH}" >/dev/null 2>&1 & + exit 0 + fi + fi + + echo '----- File details -----' && file --dereference --brief -- "${FPATH}" + exit 1 +} + +handle_blocked() { + case "${MIMETYPE}" in + application/x-sharedlib) + exit 0;; + + application/x-shared-library-la) + exit 0;; + + application/x-executable) + exit 0;; + + application/x-shellscript) + exit 0;; + + application/octet-stream) + exit 0;; + esac +} + +handle_bin() { + case "${MIMETYPE}" in + application/x-executable|application/x-shellscript) + clear + echo '-------- Executable File --------' && file --dereference --brief -- "${FPATH}" + printf "Run executable (y/N/'a'rgs)? " + read -r answer + case "$answer" in + [Yy]* ) exec "${FPATH}";; + [Aa]* ) + printf "args: " + read -r args + exec "${FPATH}" "$args";; + [Nn]* ) exit;; + esac + esac +} + +MIMETYPE="$( file -bL --mime-type -- "${FPATH}" )" +handle_extension +handle_multimedia "${MIMETYPE}" +handle_mime "${MIMETYPE}" +[ "$BIN" -ne 0 ] && [ -x "${FPATH}" ] && handle_bin +handle_blocked "${MIMETYPE}" +handle_fallback + +exit 1 diff --git a/nnn/plugins/oldbigfile b/nnn/plugins/oldbigfile @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Description: List files bigger than input size by ascending access date. +# +# Dependencies: find sort +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +printf "Min file size (MB): " +read -r size + +find . -size +"$size"M -type f -printf '%A+ %s %p\n' | sort + +echo "Press any key to exit" +read -r _ diff --git a/nnn/plugins/organize b/nnn/plugins/organize @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Description: Organize files in directories by category +# +# Note: This plugin clears the selection as it changes the contents of the current dir +# +# Shell: POSIX compliant +# Author: th3lusive + +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +organize() { + case "$(file -biL "$1")" in + *video*) + [ ! -d "Videos" ] && mkdir "Videos" + mv "$1" "Videos/$1" + printf "Moved %s to Videos\n" "$1" ;; + + *audio*) [ ! -d "Audio" ] && mkdir "Audio" + mv "$1" "Audio/$1" + printf "Moved %s to Audio\n" "$1" ;; + + *image*) + [ ! -d "Images" ] && mkdir "Images" + mv "$1" "Images/$1" + printf "Moved %s to Images\n" "$1" ;; + + *pdf*|*document*|*epub*|*djvu*|*cb*) + [ ! -d "Documents" ] && mkdir "Documents" + mv "$1" "Documents/$1" + printf "Moved %s to Documents\n" "$1" ;; + + *text*) + [ ! -d "Plaintext" ] && mkdir "Plaintext" + mv "$1" "Plaintext/$1" + printf "Moved %s to Plaintext\n" "$1" ;; + + *tar*|*xz*|*compress*|*7z*|*rar*|*zip*) + [ ! -d "Archives" ] && mkdir "Archives" + mv "$1" "Archives/$1" + printf "Moved %s to Archives\n" "$1" ;; + + *binary*) + [ ! -d "Binaries" ] && mkdir "Binaries" + mv "$1" "Binaries/$1" + printf "Moved %s to Binaries\n" "$1" ;; + esac +} + +main() { + for file in * + do + [ -f "$file" ] && organize "$file" + done + + # Clear selection + if [ -s "$sel" ] && [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +} + +main "$@" diff --git a/nnn/plugins/pdfread b/nnn/plugins/pdfread @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +# Description: Read a text or PDF file in British English +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if [ -n "$1" ]; then + tmpf="$(basename "$1")" + tmpf="${TMPDIR:-/tmp}"/"${tmpf%.*}" + + if [ "$(head -c 4 "$1")" = "%PDF" ]; then + # Convert using pdftotext + pdftotext -nopgbrk -layout "$1" - | sed 's/\xe2\x80\x8b//g' > "$tmpf".txt + + pico2wave -w "$tmpf".wav -l en-GB "$(tr '\n' ' ' < "$tmpf".txt)" + + rm "$tmpf".txt + else + pico2wave -w "$tmpf".wav -l en-GB "$(tr '\n' ' ' < "$1")" + fi + + # to jump around and note the time + mpv "$tmpf".wav + + # flat read but better quality + # play -qV0 "$tmpf".wav treble 2 gain -l 2 + + rm "$tmpf".wav +fi diff --git a/nnn/plugins/preview-tabbed b/nnn/plugins/preview-tabbed @@ -0,0 +1,211 @@ +#!/usr/bin/env bash + +# Description: tabbed/xembed based file previewer +# +# Dependencies: +# - tabbed (https://tools.suckless.org/tabbed): xembed host +# - xterm (or urxvt or st) : xembed client for text-based preview +# - mpv (https://mpv.io): xembed client for video/audio +# - sxiv (https://github.com/muennich/sxiv) or, +# - nsxiv (https://github.com/nsxiv/nsxiv) : xembed client for images +# - zathura (https://pwmt.org/projects/zathura): xembed client for PDF +# - nnn's nuke plugin for text preview and fallback +# nuke is a fallback for 'mpv', 'sxiv'/'nsxiv', and 'zathura', but has its +# own dependencies, see the script for more information +# - vim (or any editor/pager really) +# - file +# - mktemp +# - xdotool (optional, to keep main window focused) +# +# Usage: +# - Install the dependencies. Then set a NNN_FIFO +# and set a key for the plugin, then start `nnn`: +# $ NNN_FIFO=/tmp/nnn.fifo nnn +# - Launch the plugin with the designated key from nnn +# +# Notes: +# 1. This plugin needs a "NNN_FIFO" to work. See man. +# 2. If the same NNN_FIFO is used in multiple nnn instances, there will be one +# common preview window. With different FIFO paths, they will be independent. +# +# How it works: +# We use `tabbed` [1] as a xembed [2] host, to have a single window +# owning each previewer window. So each previewer must be a xembed client. +# For text previewers, this is not an issue, as there are a lot of +# xembed-able terminal emulator (we default to `xterm`, but examples are +# provided for `urxvt` and `st`). For graphic preview this can be trickier, +# but a few popular viewers are xembed-able, we use: +# - `mpv`: multimedia player, for video/audio preview +# - `sxiv`/`nsxiv`: image viewer +# - `zathura`: PDF viewer +# - but we always fallback to `nuke` plugin +# +# [1]: https://tools.suckless.org/tabbed/ +# [2]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html +# +# Shell: Bash (job control is weakly specified in POSIX) +# Author: Léo Villeveygoux + + +XDOTOOL_TIMEOUT=2 +PAGER=${PAGER:-"vim -R"} +NUKE="${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins/nuke" + + +if type xterm >/dev/null 2>&1 ; then + TERMINAL="xterm -into" +elif type urxvt >/dev/null 2>&1 ; then + TERMINAL="urxvt -embed" +elif type st >/dev/null 2>&1 ; then + TERMINAL="st -w" +else + echo "No xembed term found" >&2 +fi + + +term_nuke () { + # $1 -> $XID, $2 -> $FILE + $TERMINAL "$1" -e "$NUKE" "$2" & +} + +start_tabbed () { + FIFO="$(mktemp -u)" + mkfifo "$FIFO" + + tabbed > "$FIFO" & + + jobs # Get rid of the "Completed" entries + + TABBEDPID="$(jobs -p %%)" + + if [ -z "$TABBEDPID" ] ; then + echo "Can't start tabbed" + exit 1 + fi + + read -r XID < "$FIFO" + + rm "$FIFO" +} + +get_viewer_pid () { + VIEWERPID="$(jobs -p %%)" +} + +kill_viewer () { + if [ -n "$VIEWERPID" ] && jobs -p | grep "$VIEWERPID" ; then + kill "$VIEWERPID" + fi +} + +sigint_kill () { + kill_viewer + kill "$TABBEDPID" + exit 0 +} + +previewer_loop () { + unset -v NNN_FIFO + # mute from now + exec >/dev/null 2>&1 + + MAINWINDOW="$(xdotool getactivewindow)" + + start_tabbed + trap sigint_kill SIGINT + + xdotool windowactivate "$MAINWINDOW" + + # Bruteforce focus stealing prevention method, + # works well in floating window managers like XFCE + # but make interaction with the preview window harder + # (uncomment to use): + #xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & + + while read -r FILE ; do + + jobs # Get rid of the "Completed" entries + + if ! jobs | grep tabbed ; then + break + fi + + if [ ! -e "$FILE" ] ; then + continue + fi + + kill_viewer + + MIME="$(file -bL --mime-type "$FILE")" + + case "$MIME" in + video/*) + if type mpv >/dev/null 2>&1 ; then + mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + audio/*) + if type mpv >/dev/null 2>&1 ; then + mpv --force-window=immediate --loop-file --wid="$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + image/*) + if type sxiv >/dev/null 2>&1 ; then + sxiv -ae "$XID" "$FILE" & + elif type nsxiv >/dev/null 2>&1 ; then + nsxiv -ae "$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + application/pdf) + if type zathura >/dev/null 2>&1 ; then + zathura -e "$XID" "$FILE" & + else + term_nuke "$XID" "$FILE" + fi + ;; + inode/directory) + $TERMINAL "$XID" -e nnn "$FILE" & + ;; + text/*) + if [ -x "$NUKE" ] ; then + term_nuke "$XID" "$FILE" + else + # shellcheck disable=SC2086 + $TERMINAL "$XID" -e $PAGER "$FILE" & + fi + ;; + *) + if [ -x "$NUKE" ] ; then + term_nuke "$XID" "$FILE" + else + $TERMINAL "$XID" -e sh -c "file '$FILE' | $PAGER -" & + fi + ;; + esac + get_viewer_pid + + # following lines are not needed with the bruteforce xdotool method + ACTIVE_XID="$(xdotool getactivewindow)" + if [ $((ACTIVE_XID == XID)) -ne 0 ] ; then + xdotool windowactivate "$MAINWINDOW" + else + timeout "$XDOTOOL_TIMEOUT" xdotool behave "$XID" focus windowactivate "$MAINWINDOW" & + fi + done + kill "$TABBEDPID" + kill_viewer +} + +if [ ! -r "$NNN_FIFO" ] ; then + echo "Can't read \$NNN_FIFO ('$NNN_FIFO')" + exit 1 +fi + +previewer_loop < "$NNN_FIFO" & +disown diff --git a/nnn/plugins/preview-tui b/nnn/plugins/preview-tui @@ -0,0 +1,481 @@ +#!/usr/bin/env sh + +# Description: Terminal based file previewer +# +# Note: This plugin needs a "NNN_FIFO" to work. See man. +# +# Dependencies: +# - Supports 5 independent methods to preview with: +# - tmux (>=3.0), or +# - kitty with allow_remote_control and listen_on set in kitty.conf, or +# - QuickLook on WSL (https://github.com/QL-Win/QuickLook), or +# - Windows Terminal (https://github.com/Microsoft/Terminal | https://aka.ms/terminal) with WSL, or +# - $TERMINAL set to a terminal (it's xterm by default). +# - less or $PAGER +# - tree or exa or ls +# - mediainfo or file +# - mktemp +# - unzip +# - tar +# - man +# - optional: bsdtar or atool for additional archive preview +# - optional: bat for code syntax highlighting +# - optional: ueberzug, kitty terminal, viu or catimg for images +# - optional: convert(ImageMagick) for playing gif preview +# - optional: ffmpegthumbnailer for video thumbnails (https://github.com/dirkvdb/ffmpegthumbnailer) +# - optional: ffmpeg for audio thumbnails +# - optional: libreoffce for opendocument/officedocument preview +# - optional: pdftoppm(poppler) for pdf thumbnails +# - optional: gnome-epub-thumbnailer for epub thumbnails (https://gitlab.gnome.org/GNOME/gnome-epub-thumbnailer) +# - optional: fontpreview for font preview (https://github.com/sdushantha/fontpreview) +# - optional: glow or lowdown for markdown +# - optional: w3m or lynx or elinks for html +# - optional: set/export ICONLOOKUP as 1 to enable file icons in front of directory previews with .iconlookup +# Icons and colors are configureable in .iconlookup +# - optional: scope.sh file viewer from ranger. +# 1. drop scope.sh executable in $PATH +# 2. set/export $USE_SCOPE as 1 +# - optional: pistol file viewer (https://github.com/doronbehar/pistol). +# 1. install pistol +# 2. set/export $USE_PISTOL as 1 +# +# Usage: +# You need to set a NNN_FIFO path and a key for the plugin with NNN_PLUG, +# then start `nnn`: +# +# $ nnn -a +# +# or +# +# $ NNN_FIFO=/tmp/nnn.fifo nnn +# +# Then launch the `preview-tui` plugin in `nnn`. +# +# If you provide the same NNN_FIFO to all nnn instances, there will be a +# single common preview window. If you provide different FIFO path (e.g. +# with -a), they will be independent. +# +# The previews will be shown in a tmux split. If that isn't possible, it +# will try to use a kitty terminal split. And as a final fallback, a +# different terminal window will be used ($TERMINAL). +# +# Tmux and kitty users can configure $SPLIT to either "h" or "v" to set a +# 'h'orizontal split or a 'v'ertical split (as in, the line that splits the +# windows will be horizontal or vertical). +# +# Kitty users need `allow_remote_control` set to `yes`, and `listen_on` set +# to e.g. "unix:$TMPDIR/kitty". To customize the window split, `enabled_layouts` +# has to be set to `all` or `splits` (the former is the default value). +# This terminal is also able to show images without extra dependencies. +# +# Iterm2 users are recommended to use viu to view images without getting pixelated. +# +# Windows Terminal users can set "Profile termination behavior" under "Profile > Advanced" settings +# to automaticaly close pane on quit when exit code is 0. +# +# Shell: POSIX compliant +# Authors: Todd Yamakawa, Léo Villeveygoux, @Recidiviste, Mario Ortiz Manero, Luuk van Baal, @WanderLanz + +#SPLIT="$SPLIT" # you can set a permanent split here +#TERMINAL="$TERMINAL" # same goes for the terminal +DEBUG_LOG=0 # set to 1 to enable logging for debug purposes +USE_SCOPE="${USE_SCOPE:-0}" +USE_PISTOL="${USE_PISTOL:-0}" +ICONLOOKUP="${ICONLOOKUP:-0}" +PAGER="${PAGER:-less -P?n -R}" +TMPDIR="${TMPDIR:-/tmp}" +BAT_STYLE="${BAT_STYLE:-numbers}" +BAT_THEME="${BAT_THEME:-ansi}" +# Consider setting NNN_PREVIEWDIR to $XDG_CACHE_HOME/nnn/previews if you want to keep previews on disk between reboots +NNN_PREVIEWDIR="${NNN_PREVIEWDIR:-$TMPDIR/nnn/previews}" +NNN_PREVIEWWIDTH="${NNN_PREVIEWWIDTH:-1920}" +NNN_PREVIEWHEIGHT="${NNN_PREVIEWHEIGHT:-1080}" +NNN_PARENT="${NNN_FIFO#*.}" +[ "$NNN_PARENT" -eq "$NNN_PARENT" ] 2>/dev/null || NNN_PARENT="" +FIFOPID="$TMPDIR/nnn-preview-tui-fifopid.$NNN_PARENT" +PREVIEWPID="$TMPDIR/nnn-preview-tui-pagerpid.$NNN_PARENT" +CURSEL="$TMPDIR/nnn-preview-tui-selection.$NNN_PARENT" +FIFO_UEBERZUG="$TMPDIR/nnn-preview-tui-ueberzug-fifo.$NNN_PARENT" + +if [ "$DEBUG_LOG" -eq 0 ]; then + DEBUG_LOGFILE="/dev/null" +else + DEBUG_LOGFILE="${TMPDIR}/preview-tui-log" +fi + +start_preview() { + [ "$PAGER" = "most" ] && PAGER="less -R" + + if [ -e "${TMUX%%,*}" ] && tmux -V | grep -q '[ -][3456789]\.'; then + TERMINAL=tmux + elif [ -n "$KITTY_LISTEN_ON" ]; then + TERMINAL=kitty + elif [ -z "$TERMINAL" ] && [ "$TERM_PROGRAM" = "iTerm.app" ]; then + TERMINAL=iterm + elif [ -n "$WT_SESSION" ]; then + TERMINAL=winterm + else + TERMINAL="${TERMINAL:-xterm}" + fi + + if [ -z "$SPLIT" ] && [ $(($(tput lines <"$TTY") * 2)) -gt "$(tput cols <"$TTY")" ]; then + SPLIT='h' + elif [ "$SPLIT" != 'h' ]; then + SPLIT='v' + fi + + case "$TERMINAL" in + tmux) # tmux splits are inverted + if [ "$SPLIT" = "v" ]; then DSPLIT="h"; else DSPLIT="v"; fi + tmux split-window -e "NNN_FIFO=$NNN_FIFO" -e "PREVIEW_MODE=1" -e TTY="$TTY" \ + -e "CURSEL=$CURSEL" -e "TMPDIR=$TMPDIR" -e "FIFOPID=$FIFOPID" \ + -e "BAT_STYLE=$BAT_STYLE" -e "BAT_THEME=$BAT_THEME" -e "PREVIEWPID=$PREVIEWPID" \ + -e "PAGER=$PAGER" -e "ICONLOOKUP=$ICONLOOKUP" -e "NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH" \ + -e "USE_SCOPE=$USE_SCOPE" -e "SPLIT=$SPLIT" -e "USE_PISTOL=$USE_PISTOL" \ + -e "NNN_PREVIEWDIR=$NNN_PREVIEWDIR" -e "NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT" \ + -e "FIFO_UEBERZUG=$FIFO_UEBERZUG" -e "QLPATH=$2" -d"$DSPLIT" "$0" "$1" ;; + kitty) # Setting the layout for the new window. It will be restored after the script ends. + kitty @ goto-layout splits + # Trying to use kitty's integrated window management as the split window. All + # environmental variables that will be used in the new window must be explicitly passed. + kitty @ launch --no-response --title "nnn preview" --keep-focus \ + --cwd "$PWD" --env "PATH=$PATH" --env "NNN_FIFO=$NNN_FIFO" \ + --env "PREVIEW_MODE=1" --env "PAGER=$PAGER" --env "TMPDIR=$TMPDIR" \ + --env "USE_SCOPE=$USE_SCOPE" --env "SPLIT=$SPLIT" --env "TERMINAL=$TERMINAL"\ + --env "PREVIEWPID=$PREVIEWPID" --env "FIFO_UEBERZUG=$FIFO_UEBERZUG" \ + --env "ICONLOOKUP=$ICONLOOKUP" --env "NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT" \ + --env "NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH" --env "NNN_PREVIEWDIR=$NNN_PREVIEWDIR" \ + --env "USE_PISTOL=$USE_PISTOL" --env "BAT_STYLE=$BAT_STYLE" \ + --env "BAT_THEME=$BAT_THEME" --env "FIFOPID=$FIFOPID" --env TTY="$TTY" \ + --env "CURSEL=$CURSEL" --location "${SPLIT}split" "$0" "$1" ;; + iterm) + command="$SHELL -c 'cd $PWD; \ + PATH=\\\"$PATH\\\" NNN_FIFO=\\\"$NNN_FIFO\\\" PREVIEW_MODE=1 PAGER=\\\"$PAGER\\\" \ + USE_SCOPE=\\\"$USE_SCOPE\\\" SPLIT=\\\"$SPLIT\\\" TERMINAL=\\\"$TERMINAL\\\" \ + PREVIEWPID=\\\"$PREVIEWPID\\\" CURSEL=\\\"$CURSEL\\\" TMPDIR=\\\"$TMPDIR\\\" \ + ICONLOOKUP=\\\"$ICONLOOKUP\\\" NNN_PREVIEWHEIGHT=\\\"$NNN_PREVIEWHEIGHT\\\" \ + NNN_PREVIEWWIDTH=\\\"$NNN_PREVIEWWIDTH\\\" NNN_PREVIEWDIR=\\\"$NNN_PREVIEWDIR\\\" \ + USE_PISTOL=\\\"$USE_PISTOL\\\" BAT_STYLE=\\\"$BAT_STYLE\\\" TTY=\\\"$TTY\\\" \ + BAT_THEME=\\\"$BAT_THEME\\\" FIFOPID=\\\"$FIFOPID\\\" \\\"$0\\\" \\\"$1\\\"'" + if [ "$SPLIT" = "h" ]; then split="horizontally"; else split="vertically"; fi + osascript <<-EOF + tell application "iTerm" + tell current session of current window + split $split with default profile command "$command" + end tell + end tell +EOF + ;; + winterm) + if [ "$SPLIT" = "h" ]; then split="H"; else split="V"; fi + cmd.exe /c wt -w 0 sp -$split bash -c "cd $PWD \; PATH='$PATH' NNN_FIFO=$NNN_FIFO \ + PREVIEW_MODE=1 TTY=$TTY CURSEL=$CURSEL TMPDIR=$TMPDIR FIFOPID=$FIFOPID \ + BAT_STYLE=$BAT_STYLE BAT_THEME=$BAT_THEME PREVIEWPID=$PREVIEWPID \ + PAGER='$PAGER' ICONLOOKUP=$ICONLOOKUP NNN_PREVIEWWIDTH=$NNN_PREVIEWWIDTH \ + USE_SCOPE=$USE_SCOPE SPLIT=$SPLIT USE_PISTOL=$USE_PISTOL \ + NNN_PREVIEWDIR=$NNN_PREVIEWDIR NNN_PREVIEWHEIGHT=$NNN_PREVIEWHEIGHT \ + FIFO_UEBERZUG=$FIFO_UEBERZUG QLPATH=$2 $0 $1" \; -w 0 mf previous + ;; + *) if [ -n "$2" ]; then + QUICKLOOK=1 QLPATH="$2" PREVIEW_MODE=1 "$0" "$1" & + else + PREVIEWPID="$PREVIEWPID" CURSEL="$CURSEL" PREVIEW_MODE=1 TTY="$TTY" \ + FIFOPID="$FIFOPID" FIFO_UEBERZUG="$FIFO_UEBERZUG" $TERMINAL -e "$0" "$1" & + fi ;; + esac +} >"$DEBUG_LOGFILE" 2>&1 + +toggle_preview() { + if exists QuickLook.exe; then + QLPATH="QuickLook.exe" + elif exists Bridge.exe; then + QLPATH="Bridge.exe" + fi + if kill "$(cat "$FIFOPID")"; then + [ -p "$NNN_PPIPE" ] && printf "0" > "$NNN_PPIPE" + kill "$(cat "$PREVIEWPID")" + pkill -f "tail --follow $FIFO_UEBERZUG" + if [ -n "$QLPATH" ] && stat "$1"; then + f="$(wslpath -w "$1")" && "$QLPATH" "$f" & + fi + else + [ -p "$NNN_PPIPE" ] && printf "1" > "$NNN_PPIPE" + start_preview "$1" "$QLPATH" + fi +} >"$DEBUG_LOGFILE" 2>&1 + +exists() { + type "$1" >/dev/null +} + +fifo_pager() { + cmd="$1" + shift + + # We use a FIFO to access $PAGER PID in jobs control + tmpfifopath="$TMPDIR/nnn-preview-tui-fifo.$$" + mkfifo "$tmpfifopath" || return + + $PAGER < "$tmpfifopath" & + printf "%s" "$!" > "$PREVIEWPID" + + ( + exec > "$tmpfifopath" + if [ "$cmd" = "pager" ]; then + if exists bat; then + bat --terminal-width="$(tput cols <"$TTY")" --decorations=always --color=always \ + --paging=never --style="$BAT_STYLE" --theme="$BAT_THEME" "$@" & + else + $PAGER "$@" & + fi + else + "$cmd" "$@" & + fi + ) + + rm "$tmpfifopath" +} 2>"$DEBUG_LOGFILE" + +# Binary file: show file info inside the pager +print_bin_info() { + printf -- "-------- \033[1;31mBinary file\033[0m --------\n" + if exists mediainfo; then + mediainfo "$1" + else + file -b "$1" + fi +} 2>"$DEBUG_LOGFILE" + +handle_mime() { + case "$2" in + image/jpeg) image_preview "$cols" "$lines" "$1" ;; + image/gif) generate_preview "$cols" "$lines" "$1" "gif" ;; + image/*) generate_preview "$cols" "$lines" "$1" "image" ;; + video/*) generate_preview "$cols" "$lines" "$1" "video" ;; + audio/*) generate_preview "$cols" "$lines" "$1" "audio" ;; + application/font*|application/*opentype|font/*) generate_preview "$cols" "$lines" "$1" "font" ;; + */*office*|*/*document*) generate_preview "$cols" "$lines" "$1" "office" ;; + application/zip) fifo_pager unzip -l "$1" ;; + text/troff) + if exists man; then + fifo_pager man -Pcat -l "$1" + else + fifo_pager pager "$1" + fi ;; + *) handle_ext "$1" "$3" "$4" ;; + esac +} + +handle_ext() { + case "$2" in + epub) generate_preview "$cols" "$lines" "$1" "epub" ;; + pdf) generate_preview "$cols" "$lines" "$1" "pdf" ;; + gz|bz2) fifo_pager tar -tvf "$1" ;; + md) if exists glow; then + fifo_pager glow -s dark "$1" + elif exists lowdown; then + fifo_pager lowdown -Tterm "$1" + else + fifo_pager pager "$1" + fi ;; + htm|html|xhtml) + if exists w3m; then + fifo_pager w3m "$1" + elif exists lynx; then + fifo_pager lynx "$1" + elif exists elinks; then + fifo_pager elinks "$1" + else + fifo_pager pager "$1" + fi ;; + 7z|a|ace|alz|arc|arj|bz|cab|cpio|deb|jar|lha|lz|lzh|lzma|lzo\ + |rar|rpm|rz|t7z|tar|tbz|tbz2|tgz|tlz|txz|tZ|tzo|war|xpi|xz|Z) + if exists atool; then + fifo_pager atool -l "$1" + elif exists bsdtar; then + fifo_pager bsdtar -tvf "$1" + fi ;; + *) if [ "$3" = "bin" ]; then + fifo_pager print_bin_info "$1" + else + fifo_pager pager "$1" + fi ;; + esac +} + +preview_file() { + clear + # Trying to use pistol if it's available. + if [ "$USE_PISTOL" -ne 0 ] && exists pistol; then + fifo_pager pistol "$1" + return + fi + + # Trying to use scope.sh if it's available. + if [ "$USE_SCOPE" -ne 0 ] && exists scope.sh; then + fifo_pager scope.sh "$1" "$cols" "$lines" "$(mktemp -d)" "True" + return + fi + + # Use QuickLook if it's available. + if [ -n "$QUICKLOOK" ]; then + stat "$1" && f="$(wslpath -w "$1")" && "$QLPATH" "$f" & + return + fi + + # Detecting the exact type of the file: the encoding, mime type, and extension in lowercase. + encoding="$(file -bL --mime-encoding -- "$1")" + mimetype="$(file -bL --mime-type -- "$1")" + ext="${1##*.}" + [ -n "$ext" ] && ext="$(printf "%s" "${ext}" | tr '[:upper:]' '[:lower:]')" + lines=$(tput lines <"$TTY") + cols=$(tput cols <"$TTY") + + # Otherwise, falling back to the defaults. + if [ -d "$1" ]; then + cd "$1" || return + if [ "$ICONLOOKUP" -ne 0 ] && [ -f "$(dirname "$0")"/.iconlookup ]; then + [ "$SPLIT" = v ] && BSTR="\n" + # shellcheck disable=SC2012 + ls -F --group-directories-first | head -n "$((lines - 3))" | "$(dirname "$0")"/.iconlookup -l "$cols" -B "$BSTR" -b " " + elif exists tree; then + fifo_pager tree --filelimit "$(find . -maxdepth 1 | wc -l)" -L 3 -C -F --dirsfirst --noreport + elif exists exa; then + exa -G --group-directories-first --colour=always + else + fifo_pager ls -F --group-directories-first --color=always + fi + elif [ "${encoding#*)}" = "binary" ]; then + handle_mime "$1" "$mimetype" "$ext" "bin" + else + handle_mime "$1" "$mimetype" "$ext" + fi +} 2>"$DEBUG_LOGFILE" + +generate_preview() { + if [ -n "$QLPATH" ] && stat "$3"; then + f="$(wslpath -w "$3")" && "$QLPATH" "$f" & + elif [ ! -f "$NNN_PREVIEWDIR/$3.jpg" ] || [ -n "$(find -L "$3" -newer "$NNN_PREVIEWDIR/$3.jpg")" ]; then + mkdir -p "$NNN_PREVIEWDIR/${3%/*}" + case $4 in + audio) ffmpeg -i "$3" -filter_complex "scale=iw*min(1\,min($NNN_PREVIEWWIDTH/iw\,ih)):-1" "$NNN_PREVIEWDIR/$3.jpg" -y ;; + epub) gnome-epub-thumbnailer "$3" "$NNN_PREVIEWDIR/$3.jpg" ;; + font) fontpreview -i "$3" -o "$NNN_PREVIEWDIR/$3.jpg" ;; + gif) if [ -p "$FIFO_UEBERZUG" ] && exists convert; then + frameprefix="$NNN_PREVIEWDIR/$3/${3##*/}" + if [ ! -d "$NNN_PREVIEWDIR/$3" ]; then + mkdir -p "$NNN_PREVIEWDIR/$3" + convert -coalesce -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$3" "$frameprefix.jpg" || + MAGICK_TMPDIR="/tmp" convert -coalesce -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$3" "$frameprefix.jpg" + fi + frames=$(($(find "$NNN_PREVIEWDIR/$3" | wc -l) - 2)) + [ $frames -lt 0 ] && return + while true; do + for i in $(seq 0 $frames); do + image_preview "$1" "$2" "$frameprefix-$i.jpg" + sleep 0.1 + done + done & + printf "%s" "$!" > "$PREVIEWPID" + return + else + exec >/dev/tty + image_preview "$1" "$2" "$3" + return + fi ;; + image) if exists convert; then + convert "$3" -flatten -resize "$NNN_PREVIEWWIDTH"x"$NNN_PREVIEWHEIGHT"\> "$NNN_PREVIEWDIR/$3.jpg" + else + image_preview "$1" "$2" "$3" && return + fi ;; + office) libreoffice --convert-to jpg "$3" --outdir "$NNN_PREVIEWDIR/${3%/*}" + filename="$(printf "%s" "${3##*/}" | cut -d. -f1)" + mv "$NNN_PREVIEWDIR/${3%/*}/$filename.jpg" "$NNN_PREVIEWDIR/$3.jpg" ;; + pdf) pdftoppm -jpeg -f 1 -singlefile "$3" "$NNN_PREVIEWDIR/$3" ;; + video) ffmpegthumbnailer -s0 -i "$3" -o "$NNN_PREVIEWDIR/$3.jpg" || rm "$NNN_PREVIEWDIR/$3.jpg" ;; + esac + fi >"$DEBUG_LOGFILE" + if [ -f "$NNN_PREVIEWDIR/$3.jpg" ]; then + image_preview "$1" "$2" "$NNN_PREVIEWDIR/$3.jpg" + else + fifo_pager print_bin_info "$3" + fi +} 2>"$DEBUG_LOGFILE" + +image_preview() { + clear + if [ "$TERMINAL" = "kitty" ]; then + # Kitty terminal users can use the native image preview method + kitty +kitten icat --silent --place "$1"x"$2"@0x0 --transfer-mode=stream --stdin=no "$3" & + elif exists ueberzug; then + ueberzug_layer "$1" "$2" "$3" && return + elif exists catimg; then + catimg "$3" & + elif exists viu; then + viu -t "$3" & + else + fifo_pager print_bin_info "$3" && return + fi + printf "%s" "$!" > "$PREVIEWPID" +} 2>"$DEBUG_LOGFILE" + +ueberzug_layer() { + printf '{"action": "add", "identifier": "nnn_ueberzug", "x": 0, "y": 0, "width": "%d", "height": "%d", "scaler": "fit_contain", "path": "%s"}\n' "$1" "$2" "$3" > "$FIFO_UEBERZUG" +} + +ueberzug_remove() { + printf '{"action": "remove", "identifier": "nnn_ueberzug"}\n' > "$FIFO_UEBERZUG" +} + +winch_handler() { + clear + kill "$(cat "$PREVIEWPID")" + if [ -p "$FIFO_UEBERZUG" ]; then + pkill -f "tail --follow $FIFO_UEBERZUG" + tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser json & + fi + preview_file "$(cat "$CURSEL")" +} 2>"$DEBUG_LOGFILE" + +preview_fifo() { + while read -r selection; do + if [ -n "$selection" ]; then + kill "$(cat "$PREVIEWPID")" + [ -p "$FIFO_UEBERZUG" ] && ueberzug_remove + [ "$selection" = "close" ] && break + preview_file "$selection" + printf "%s" "$selection" > "$CURSEL" + fi + done < "$NNN_FIFO" + sleep 0.1 # make sure potential preview by winch_handler is killed + pkill -P "$$" +} 2>"$DEBUG_LOGFILE" + +if [ "$PREVIEW_MODE" ]; then + if [ "$TERMINAL" != "kitty" ] && exists ueberzug; then + mkfifo "$FIFO_UEBERZUG" + tail --follow "$FIFO_UEBERZUG" | ueberzug layer --silent --parser json & + fi + + preview_file "$PWD/$1" + preview_fifo & + printf "%s" "$!" > "$FIFOPID" + printf "%s" "$PWD/$1" > "$CURSEL" + trap 'winch_handler; wait' WINCH + trap 'rm "$PREVIEWPID" "$CURSEL" "$FIFO_UEBERZUG" "$FIFOPID" 2>/dev/null' INT HUP EXIT + wait "$!" 2>/dev/null + exit 0 +else + if [ ! -r "$NNN_FIFO" ]; then + clear + printf "No FIFO available! (\$NNN_FIFO='%s')\nPlease read Usage in preview-tui." "$NNN_FIFO" + cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg" + elif [ "$KITTY_WINDOW_ID" ] && [ -z "$TMUX" ] && [ -z "$KITTY_LISTEN_ON" ]; then + clear + printf "\$KITTY_LISTEN_ON not set!\nPlease read Usage in preview-tui." + cfg=$(stty -g); stty raw -echo; head -c 1; stty "$cfg" + else + TTY="$(tty)" + TTY="$TTY" toggle_preview "$1" & + fi +fi diff --git a/nnn/plugins/pskill b/nnn/plugins/pskill @@ -0,0 +1,35 @@ +#!/usr/bin/env sh + +# Description: Fuzzy list and kill a (zombie) process by name +# +# Dependencies: fzf, ps +# +# Note: To kill a zombie process enter "zombie" +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +printf "Enter process name ['defunct' for zombies]: " +read -r psname + +# shellcheck disable=SC2009 +if [ -n "$psname" ]; then + if type sudo >/dev/null 2>&1; then + sucmd=sudo + elif type doas >/dev/null 2>&1; then + sucmd=doas + else + sucmd=: # noop + fi + + if type fzf >/dev/null 2>&1; then + fuzzy=fzf + else + exit 1 + fi + + cmd="$(ps -ax | grep -iw "$psname" | "$fuzzy" | sed -e 's/^[ \t]*//' | cut -d' ' -f1)" + if [ -n "$cmd" ]; then + $sucmd kill -9 "$cmd" + fi +fi diff --git a/nnn/plugins/renamer b/nnn/plugins/renamer @@ -0,0 +1,45 @@ +#!/usr/bin/env sh + +# Description: Batch rename selection or current directory with qmv or vidir +# +# Notes: +# - Try to mimic current batch rename functionality but with correct +# handling of edge cases by qmv or vidir. +# - Qmv opens with hidden files if no selection is used. Selected +# directories are shown. +# - Vidir don't show directories nor hidden files. +# +# Shell: POSIX compliant +# Author: José Neder + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if type qmv >/dev/null 2>&1; then + batchrenamesel="qmv -fdo -da" + batchrename="qmv -fdo -a" +elif type vidir >/dev/null 2>&1; then + batchrenamesel="vidir" + batchrename="vidir" +else + printf "there is not batchrename program installed." + exit +fi + +if [ -s "$selection" ]; then + printf "rename selection? " + read -r resp +fi + +if [ "$resp" = "y" ]; then + # -o flag is necessary for interactive editors + xargs -o -0 $batchrenamesel < "$selection" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +elif [ ! "$(LC_ALL=C ls -a)" = ". +.." ]; then + # On older systems that don't have ls -A + $batchrename +fi diff --git a/nnn/plugins/ringtone b/nnn/plugins/ringtone @@ -0,0 +1,36 @@ +#!/usr/bin/env sh + +# Description: Create an mp3 ringtone out of an audio file in any format +# Needs user to provide start and end where to cut the file +# Input file audio.ext results in audio_ringtone.mp3 +# +# Tip: To convert a complete media file, set start as 0 and +# the runtime of the file as end. +# +# Dependencies: date, ffmpeg +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if [ -n "$1" ]; then + printf "start (hh:mm:ss): " + read -r start + st=$(date -d "$start" +%s) || exit 1 + + printf "end (hh:mm:ss): " + read -r end + et=$(date -d "$end" +%s) || exit 1 + + if [ "$st" -ge "$et" ]; then + printf "error: start >= end " + read -r _ + exit 1 + fi + + interval=$(( et - st )) + + outfile=$(basename "$1") + outfile="${outfile%.*}"_ringtone.mp3 + + ffmpeg -i "$1" -ss "$start" -t "$interval" -vn -sn -acodec libmp3lame -q:a 2 "$outfile" +fi diff --git a/nnn/plugins/rsynccp b/nnn/plugins/rsynccp @@ -0,0 +1,26 @@ +#!/usr/bin/env sh + +# Description: Simple script to give copy-paste a progress percentage +# by utilizing rsync. +# +# LIMITATION: this won't work when pasting to MTP device. +# +# Dependencies: rsync +# +# Shell: POSIX compliant +# Author: Benawi Adha + +sel=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +# Choose one of these two schemes by commenting + +# more verbose +xargs -0 -I % rsync -ah --progress % "$PWD" < "$sel" + +# less verbose +# xargs -0 -I % rsync -ah --info=progress2 % "$PWD" < "$sel" + +# Clear selection +if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" +fi diff --git a/nnn/plugins/splitjoin b/nnn/plugins/splitjoin @@ -0,0 +1,52 @@ +#!/usr/bin/env sh + +# Description: Splits the file passed as argument or joins selection +# +# Note: Adds numeric suffix to split files +# Adds '.out suffix to the first file to be joined and saves as output file for join +# +# Shell: POSIX compliant +# Authors: Arun Prakash Jana, ath3 + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} +resp=s + +if [ -s "$selection" ]; then + printf "press 's' (split current file) or 'j' (join selection): " + read -r resp +fi + +if [ "$resp" = "j" ]; then + if [ -s "$selection" ]; then + arr=$(tr '\0' '\n' < "$selection") + if [ "$(echo "$arr" | wc -l)" -lt 2 ]; then + echo "joining needs at least 2 files" + exit + fi + for entry in $arr + do + if [ -d "$entry" ]; then + echo "cant join directories" + exit + fi + done + + file="$(basename "$(echo "$arr" | sed -n '1p' | sed -e 's/[0-9][0-9]$//')")" + sort -z < "$selection" | xargs -0 -I{} cat {} > "${file}.out" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi + fi +elif [ "$resp" = "s" ]; then + if [ -n "$1" ] && [ -f "$1" ]; then + # a single file is passed + printf "split size in MB: " + read -r size + + if [ -n "$size" ]; then + split -d -b "$size"M "$1" "$1" + fi + fi +fi diff --git a/nnn/plugins/suedit b/nnn/plugins/suedit @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Description: Edit file as superuser +# +# Shell: POSIX compliant +# Author: Anna Arad + +EDITOR="${EDITOR:-vim}" + +if type sudo >/dev/null 2>&1; then + sudo -E "$EDITOR" "$1" +elif type sudoedit >/dev/null 2>&1; then + sudoedit -E "$1" +elif type doas >/dev/null 2>&1; then + doas "$EDITOR" "$1" +fi diff --git a/nnn/plugins/togglex b/nnn/plugins/togglex @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +# Description: Toggles executable mode for selection +# +# Dependencies: chmod +# +# Note: Works _only_ with selection (nnn can toggle the mode for the hovered file) +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +if [ -s "$selection" ]; then + xargs -0 -I {} sh -c 'if [ -x "{}" ] ; then chmod -x "{}" ; else chmod +x "{}" ; fi' < "$selection" + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" > "$NNN_PIPE" + fi +fi diff --git a/nnn/plugins/umounttree b/nnn/plugins/umounttree @@ -0,0 +1,52 @@ +#!/usr/bin/env sh + +# Description: Autodetects a nnn remote mountpoint (mounted with `c`) +# from any of its subfolders and allows unmounting it +# from the subdir without navigating to the mountppoint +# or entering the remote name. Also works when hovering +# the mountpoint directly like vanilla `u`. +# +# Dependencies: fusermount +# +# Shell: POSIX compliant +# Authors: Kabouik & 0xACE +# +# TODO: +# - Avoid lazy unmount by forcing nnn context to leave the subfolder before fusermount. +# Tried `printf "%s" "0c$m" > "$NNN_PIPE"` but it breaks the nnn interface, see #854. + +err=0 +m=$HOME/.config/nnn/mounts +if [ "$PWD" = "$m" ]; then + # Allow running the script on hovered directory if user is in ~/.config/nnn/mounts + d="$1" +else + d=$(dirname "$(readlink -f "$1")" | grep -oP "^$m\K.*" | cut -d"/" -f2) +fi + +# Test if user is within $m or a subdir, abort if not +if [ "$d" = "" ]; then + clear && printf "You are not in a remote folder mounted with nnn. Press return to continue. " && read -r _ +else + # Test if $m/$d is a mountpoint and try unmounting if it is + mountpoint -q -- "$m/$d" + if [ "$?" -eq "1" ]; then + clear && printf "Parent '%s' is not a mountpoint. Press return to continue. " "$d" && read -r _ + else + cd "$m" && fusermount -uq "$m/$d" || err=1 + if [ "$err" -eq "0" ]; then + rmdir "$m/$d" && clear && printf "Parent '%s' unmounted." "$d" + else + clear && printf "Failed to unmount. Try lazy unmount? [Yy/Nn] " && read -r + fi + fi +fi + +# If unmount fails, offer lazy unmount +if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then + err=0 + cd "$m" && fusermount -uqz "$m/$d" || err=1 + if [ "$err" -eq "0" ]; then + rmdir "$m/$d" && clear && printf "Parent '%s' unmounted with lazy unmount. " "$d" + fi +fi diff --git a/nnn/plugins/upload b/nnn/plugins/upload @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +# Description: Upload to Firefox Send if ffsend is found, else +# Paste contents of a text a file http://ix.io +# Upload a binary file to file.io +# +# Dependencies: ffsend (https://github.com/timvisee/ffsend), curl, jq, tr +# +# Note: Binary file set to expire after a week +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +if [ -n "$1" ] && [ -s "$1" ]; then + if type ffsend >/dev/null 2>&1; then + ffsend -fiq u "$1" + elif [ "$(mimetype --output-format %m "$1" | awk -F '/' '{print $1}')" = "text" ]; then + curl -F "f:1=@$1" ix.io + else + # Upload the file, show the download link and wait till user presses any key + curl -s -F "file=@$1" https://file.io/?expires=1w | jq '.link' | tr -d '"' + + # To write download link to "$1".loc and exit + # curl -s -F "file=@$1" https://file.io/?expires=1w -o `basename "$1"`.loc + fi +else + printf "empty file!" +fi + +read -r _ diff --git a/nnn/plugins/wallpaper b/nnn/plugins/wallpaper @@ -0,0 +1,26 @@ +#!/usr/bin/env sh + +# Description: Set the selected image as wallpaper using nitrogen or pywal. +# +# Usage: Hover on an image and run the script to set it as wallpaper. +# +# Shell: POSIX compliant +# Author: juacq97 + +if [ -n "$1" ]; then + if [ "$(file --mime-type "$1" | awk '{print $NF}' | awk -F '/' '{print $1}')" = "image" ]; then + if type nitrogen >/dev/null 2>&1; then + nitrogen --set-zoom-fill --save "$1" + elif type wal >/dev/null 2>&1; then + wal -i "$1" + else + printf "nitrogen or pywal missing" + read -r _ + fi + + # If you want a system notification, uncomment the next 3 lines. + # notify-send -a "nnn" "Wallpaper changed!" + # else + # notify-send -a "nnn" "No image selected" + fi +fi diff --git a/nnn/plugins/x2sel b/nnn/plugins/x2sel @@ -0,0 +1,62 @@ +#!/usr/bin/env sh + +# Description: Copy system clipboard newline-separated file list to selection +# +# Dependencies: +# - tr +# - xclip/xsel (Linux) +# - pbpaste (macOS) +# - termux-clipboard-get (Termux) +# - powershell (WSL) +# - cygwim's /dev/clipboard (Cygwin) +# - wl-paste (Wayland) +# - clipboard (Haiku) +# +# Note: +# - Limitation: breaks if a filename has newline in it +# +# Shell: POSIX compliant +# Author: Léo Villeveygoux, after Arun Prakash Jana's .cbcp + +IFS="$(printf '%b_' '\n')"; IFS="${IFS%_}" # protect trailing \n + +selection=${NNN_SEL:-${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection} + +getclip () { + if type xsel >/dev/null 2>&1; then + # Linux + xsel -bo + elif type xclip >/dev/null 2>&1; then + # Linux + xclip -sel clip -o + elif type pbpaste >/dev/null 2>&1; then + # macOS + pbpaste + elif type termux-clipboard-get >/dev/null 2>&1; then + # Termux + termux-clipboard-get + elif type powershell.exe >/dev/null 2>&1; then + # WSL + powershell.exe Get-Clipboard + elif [ -r /dev/clipboard ] ; then + # Cygwin + cat /dev/clipboard + elif type wl-paste >/dev/null 2>&1; then + # Wayland + wl-paste + elif type clipboard >/dev/null 2>&1; then + # Haiku + clipboard --print + fi +} + +CLIPBOARD=$(getclip) + +# Check if clipboard actually contains a file list +for file in $CLIPBOARD ; do + if [ ! -e "$file" ] ; then + exit 1; + fi +done + +printf "%s" "$CLIPBOARD" | tr '\n' '\0' > "$selection" diff --git a/nnn/plugins/xdgdefault b/nnn/plugins/xdgdefault @@ -0,0 +1,53 @@ +#!/usr/bin/env sh + +# Description: Sets the xdg-open's default application for the current entry's file +# type. ${XDG_DATA_DIRS} and ${XDG_DATA_HOME} are set to the recommended +# defaults if unset, as specified in XDG Base Directory Specification +# - http://specifications.freedesktop.org/basedir-spec/. +# +# Dependencies: xdg-utils, fzf or dmenu (GUI) +# +# Shell: POSIX compliant +# Author: lwnctd + +# set to 1 to enable GUI apps +GUI="${GUI:-0}" + +if [ "$GUI" -ne 0 ] && command -v dmenu > /dev/null 2>& 1; then + menu="dmenu -i -l 7" +elif command -v fzf > /dev/null 2>& 1; then + menu="fzf -e --tiebreak=begin" +fi + +if [ -z "$1" ] || [ -z "$menu" ] > /dev/null 2>& 1; then + exit 1 +fi + +ftype=$(xdg-mime query filetype "$2/$1") + +if [ -z "$ftype" ]; then + exit 1 +fi + +dirs=${XDG_DATA_DIRS:-/usr/local/share:/usr/share} +dirs=${dirs}:${XDG_DATA_HOME:-$HOME/.local/share}: + +while [ -n "$dirs" ]; do + d=${dirs%%:*} + if [ -n "$d" ] && [ -d "$d"/applications ]; then + set -- "$@" "$d"/applications + fi + dirs=${dirs#*:} +done + +app=$(find "$@" -iname '*.desktop' -exec grep '^Name=' {} + \ + | sort -u -t ':' -k 1,1 \ + | sed -e 's;..*/\(..*desktop\):Name=\(..*\);\2:\1;' \ + | sort -t ':' -k 1,1 \ + | column -t -s ':' -o "$(printf '\t')" \ + | $menu \ + | cut -f 2) + +if [ -n "$app" ]; then + xdg-mime default "${app%%[[:blank:]]*}" "$ftype" +fi diff --git a/nnn/src/.clang-tidy b/nnn/src/.clang-tidy @@ -0,0 +1,15 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,readability-*,modernize-*,bugprone-*,misc-*,-misc-unused-parameters,google-runtime-int,-llvm-header-guard,fuchsia-restrict-system-includes,-clang-analyzer-valist.Uninitialized,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-alpha.*,-readability-magic-numbers,-readability-braces-around-statements,-readability-function-cognitive-complexity,-readability-isolate-declaration,-readability-suspicious-call-argument,-bugprone-easily-swappable-parameters,-bugprone-narrowing-conversions,-bugprone-reserved-identifier' +WarningsAsErrors: '*' +HeaderFilterRegex: '.*(?<!lookup3.c)$' +FormatStyle: 'file' +CheckOptions: + - key: readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-runtime-int.TypeSufix + value: '_t' + - key: fuchsia-restrict-system-includes.Includes + value: '*,-stdint.h,-stdbool.h' + - key: readability-function-size.StatementThreshold + value: '905' +... diff --git a/nnn/src/dbg.h b/nnn/src/dbg.h @@ -0,0 +1,91 @@ +/* + * BSD 2-Clause License + * + * Copyright (C) 2014-2016, Lazaros Koromilas <lostd@2f30.org> + * Copyright (C) 2014-2016, Dimitris Papastamos <sin@2f30.org> + * Copyright (C) 2016-2022, Arun Prakash Jana <engineerarun@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#ifdef DEBUG +static int DEBUG_FD; + +static int xprintf(int fd, const char *fmt, ...) +{ + char buf[BUFSIZ]; + int r; + va_list ap; + + va_start(ap, fmt); + r = vsnprintf(buf, sizeof(buf), fmt, ap); + if (r > 0 && (unsigned int)r < sizeof(buf)) + r = write(fd, buf, r); + va_end(ap); + return r; +} + +static int enabledbg(void) +{ + FILE *fp = fopen("/tmp/nnndbg", "w"); + + if (!fp) { + perror("dbg(1)"); + + fp = fopen("./nnndbg", "w"); + if (!fp) { + perror("dbg(2)"); + return -1; + } + } + + DEBUG_FD = dup(fileno(fp)); + fclose(fp); + if (DEBUG_FD == -1) { + perror("dbg(3)"); + return -1; + } + + return 0; +} + +static void disabledbg(void) +{ + close(DEBUG_FD); +} + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define DPRINTF_D(x) xprintf(DEBUG_FD, "ln " TOSTRING(__LINE__) ": " #x "=%d\n", x) +#define DPRINTF_U(x) xprintf(DEBUG_FD, "ln " TOSTRING(__LINE__) ": " #x "=%u\n", x) +#define DPRINTF_S(x) xprintf(DEBUG_FD, "ln " TOSTRING(__LINE__) ": " #x "=%s\n", x) +#define DPRINTF_P(x) xprintf(DEBUG_FD, "ln " TOSTRING(__LINE__) ": " #x "=%p\n", x) +#else +#define DPRINTF_D(x) +#define DPRINTF_U(x) +#define DPRINTF_S(x) +#define DPRINTF_P(x) +#endif /* DEBUG */ diff --git a/nnn/src/icons-in-terminal.h b/nnn/src/icons-in-terminal.h @@ -0,0 +1,3736 @@ +#ifndef ICONS_IN_TERMINAL +#define ICONS_IN_TERMINAL + +# define POWERLINE_BRANCH "\ue0a0" +# define POWERLINE_LINE_NUMBER "\ue0a1" +# define POWERLINE_READONLY "\ue0a2" +# define POWERLINE_EXTRA_COLUMN_NUMBER "\ue0a3" +# define POWERLINE_LEFT_HARD_DIVIDER "\ue0b0" +# define POWERLINE_LEFT_SOFT_DIVIDER "\ue0b1" +# define POWERLINE_RIGHT_HARD_DIVIDER "\ue0b2" +# define POWERLINE_RIGHT_SOFT_DIVIDER "\ue0b3" +# define POWERLINE_EXTRA_RIGHT_HALF_CIRCLE_THICK "\ue0b4" +# define POWERLINE_EXTRA_RIGHT_HALF_CIRCLE_THIN "\ue0b5" +# define POWERLINE_EXTRA_LEFT_HALF_CIRCLE_THICK "\ue0b6" +# define POWERLINE_EXTRA_LEFT_HALF_CIRCLE_THIN "\ue0b7" +# define POWERLINE_EXTRA_LOWER_LEFT_TRIANGLE "\ue0b8" +# define POWERLINE_EXTRA_BACKSLASH_SEPARATOR "\ue0b9" +# define POWERLINE_EXTRA_LOWER_RIGHT_TRIANGLE "\ue0ba" +# define POWERLINE_EXTRA_FORWARDSLASH_SEPARATOR "\ue0bb" +# define POWERLINE_EXTRA_UPPER_LEFT_TRIANGLE "\ue0bc" +# define POWERLINE_EXTRA_FORWARDSLASH_SEPARATOR_REDUNDANT "\ue0bd" +# define POWERLINE_EXTRA_UPPER_RIGHT_TRIANGLE "\ue0be" +# define POWERLINE_EXTRA_BACKSLASH_SEPARATOR_REDUNDANT "\ue0bf" +# define POWERLINE_EXTRA_FLAME_THICK "\ue0c0" +# define POWERLINE_EXTRA_FLAME_THIN "\ue0c1" +# define POWERLINE_EXTRA_FLAME_THICK_MIRRORED "\ue0c2" +# define POWERLINE_EXTRA_FLAME_THIN_MIRRORED "\ue0c3" +# define POWERLINE_EXTRA_PIXELATED_SQUARES_SMALL "\ue0c4" +# define POWERLINE_EXTRA_PIXELATED_SQUARES_SMALL_MIRRORED "\ue0c5" +# define POWERLINE_EXTRA_PIXELATED_SQUARES_BIG "\ue0c6" +# define POWERLINE_EXTRA_PIXELATED_SQUARES_BIG_MIRRORED "\ue0c7" +# define POWERLINE_EXTRA_ICE_WAVEFORM "\ue0c8" +# define POWERLINE_EXTRA_ICE_WAVEFORM_MIRRORED "\ue0ca" +# define POWERLINE_EXTRA_HONEYCOMB "\ue0cc" +# define POWERLINE_EXTRA_HONEYCOMB_OUTLINE "\ue0cd" +# define POWERLINE_EXTRA_LEGO_SEPARATOR "\ue0ce" +# define POWERLINE_EXTRA_LEGO_SEPARATOR_THIN "\ue0cf" +# define POWERLINE_EXTRA_LEGO_BLOCK_FACING "\ue0d0" +# define POWERLINE_EXTRA_LEGO_BLOCK_SIDEWAYS "\ue0d1" +# define POWERLINE_EXTRA_TRAPEZOID_TOP_BOTTOM "\ue0d2" +# define POWERLINE_EXTRA_TRAPEZOID_TOP_BOTTOM_MIRRORED "\ue0d4" +# define OCT_HEART "\ue000" +# define OCT_ZAP "\ue001" +# define OCT_LIGHT_BULB "\ue002" +# define OCT_REPO "\ue003" +# define OCT_REPO_FORKED "\ue004" +# define OCT_REPO_PUSH "\ue005" +# define OCT_REPO_PULL "\ue006" +# define OCT_BOOK "\ue007" +# define OCT_OCTOFACE "\ue008" +# define OCT_GIT_PULL_REQUEST "\ue009" +# define OCT_MARK_GITHUB "\ue00a" +# define OCT_CLOUD_DOWNLOAD "\ue00b" +# define OCT_CLOUD_UPLOAD "\ue00c" +# define OCT_KEYBOARD "\ue00d" +# define OCT_GIST "\ue00e" +# define OCT_FILE_CODE "\ue00f" +# define OCT_FILE_TEXT "\ue010" +# define OCT_FILE_MEDIA "\ue011" +# define OCT_FILE_ZIP "\ue012" +# define OCT_FILE_PDF "\ue013" +# define OCT_TAG "\ue014" +# define OCT_FILE_DIRECTORY "\ue015" +# define OCT_FILE_SUBMODULE "\ue016" +# define OCT_PERSON "\ue017" +# define OCT_JERSEY "\ue018" +# define OCT_GIT_COMMIT "\ue019" +# define OCT_GIT_BRANCH "\ue01a" +# define OCT_GIT_MERGE "\ue01b" +# define OCT_MIRROR "\ue01c" +# define OCT_ISSUE_OPENED "\ue01d" +# define OCT_ISSUE_REOPENED "\ue01e" +# define OCT_ISSUE_CLOSED "\ue01f" +# define OCT_STAR "\ue020" +# define OCT_COMMENT "\ue021" +# define OCT_QUESTION "\ue022" +# define OCT_ALERT "\ue023" +# define OCT_SEARCH "\ue024" +# define OCT_GEAR "\ue025" +# define OCT_RADIO_TOWER "\ue026" +# define OCT_TOOLS "\ue027" +# define OCT_SIGN_OUT "\ue028" +# define OCT_ROCKET "\ue029" +# define OCT_RSS "\ue02a" +# define OCT_CLIPPY "\ue02b" +# define OCT_SIGN_IN "\ue02c" +# define OCT_ORGANIZATION "\ue02d" +# define OCT_DEVICE_MOBILE "\ue02e" +# define OCT_UNFOLD "\ue02f" +# define OCT_CHECK "\ue030" +# define OCT_MAIL "\ue031" +# define OCT_MAIL_READ "\ue032" +# define OCT_ARROW_UP "\ue033" +# define OCT_ARROW_RIGHT "\ue034" +# define OCT_ARROW_DOWN "\ue035" +# define OCT_ARROW_LEFT "\ue036" +# define OCT_PIN "\ue037" +# define OCT_GIFT "\ue038" +# define OCT_GRAPH "\ue039" +# define OCT_TRIANGLE_LEFT "\ue03a" +# define OCT_CREDIT_CARD "\ue03b" +# define OCT_CLOCK "\ue03c" +# define OCT_RUBY "\ue03d" +# define OCT_BROADCAST "\ue03e" +# define OCT_KEY "\ue03f" +# define OCT_REPO_FORCE_PUSH "\ue040" +# define OCT_REPO_CLONE "\ue041" +# define OCT_DIFF "\ue042" +# define OCT_EYE "\ue043" +# define OCT_COMMENT_DISCUSSION "\ue044" +# define OCT_MAIL_REPLY "\ue045" +# define OCT_PRIMITIVE_DOT "\ue046" +# define OCT_PRIMITIVE_SQUARE "\ue047" +# define OCT_DEVICE_CAMERA "\ue048" +# define OCT_DEVICE_CAMERA_VIDEO "\ue049" +# define OCT_PENCIL "\ue04a" +# define OCT_INFO "\ue04b" +# define OCT_TRIANGLE_RIGHT "\ue04c" +# define OCT_TRIANGLE_DOWN "\ue04d" +# define OCT_LINK "\ue04e" +# define OCT_PLUS "\ue04f" +# define OCT_THREE_BARS "\ue050" +# define OCT_CODE "\ue051" +# define OCT_LOCATION "\ue052" +# define OCT_LIST_UNORDERED "\ue053" +# define OCT_LIST_ORDERED "\ue054" +# define OCT_QUOTE "\ue055" +# define OCT_VERSIONS "\ue056" +# define OCT_CALENDAR "\ue057" +# define OCT_LOCK "\ue058" +# define OCT_DIFF_ADDED "\ue059" +# define OCT_DIFF_REMOVED "\ue05a" +# define OCT_DIFF_MODIFIED "\ue05b" +# define OCT_DIFF_RENAMED "\ue05c" +# define OCT_HORIZONTAL_RULE "\ue05d" +# define OCT_ARROW_SMALL_RIGHT "\ue05e" +# define OCT_MILESTONE "\ue05f" +# define OCT_CHECKLIST "\ue060" +# define OCT_MEGAPHONE "\ue061" +# define OCT_CHEVRON_RIGHT "\ue062" +# define OCT_BOOKMARK "\ue063" +# define OCT_SETTINGS "\ue064" +# define OCT_DASHBOARD "\ue065" +# define OCT_HISTORY "\ue066" +# define OCT_LINK_EXTERNAL "\ue067" +# define OCT_MUTE "\ue068" +# define OCT_X "\ue069" +# define OCT_CIRCLE_SLASH "\ue06a" +# define OCT_PULSE "\ue06b" +# define OCT_SYNC "\ue06c" +# define OCT_TELESCOPE "\ue06d" +# define OCT_GIST_SECRET "\ue06e" +# define OCT_HOME "\ue06f" +# define OCT_STOP "\ue070" +# define OCT_BUG "\ue071" +# define OCT_LOGO_GITHUB "\ue072" +# define OCT_FILE_BINARY "\ue073" +# define OCT_DATABASE "\ue074" +# define OCT_SERVER "\ue075" +# define OCT_DIFF_IGNORED "\ue076" +# define OCT_ELLIPSIS "\ue077" +# define OCT_NO_NEWLINE "\ue078" +# define OCT_HUBOT "\ue079" +# define OCT_ARROW_SMALL_UP "\ue07a" +# define OCT_ARROW_SMALL_DOWN "\ue07b" +# define OCT_ARROW_SMALL_LEFT "\ue07c" +# define OCT_CHEVRON_UP "\ue07d" +# define OCT_CHEVRON_DOWN "\ue07e" +# define OCT_CHEVRON_LEFT "\ue07f" +# define OCT_TRIANGLE_UP "\ue080" +# define OCT_GIT_COMPARE "\ue081" +# define OCT_LOGO_GIST "\ue082" +# define OCT_FILE_SYMLINK_FILE "\ue083" +# define OCT_FILE_SYMLINK_DIRECTORY "\ue084" +# define OCT_SQUIRREL "\ue085" +# define OCT_GLOBE "\ue086" +# define OCT_UNMUTE "\ue087" +# define OCT_MENTION "\ue088" +# define OCT_PACKAGE "\ue089" +# define OCT_BROWSER "\ue08a" +# define OCT_TERMINAL "\ue08b" +# define OCT_MARKDOWN "\ue08c" +# define OCT_DASH "\ue08d" +# define OCT_FOLD "\ue08e" +# define OCT_INBOX "\ue08f" +# define OCT_TRASHCAN "\ue090" +# define OCT_PAINTCAN "\ue091" +# define OCT_FLAME "\ue092" +# define OCT_BRIEFCASE "\ue093" +# define OCT_PLUG "\ue094" +# define OCT_CIRCUIT_BOARD "\ue095" +# define OCT_MORTAR_BOARD "\ue096" +# define OCT_LAW "\ue097" +# define OCT_THUMBSUP "\ue098" +# define OCT_THUMBSDOWN "\ue099" +# define OCT_DESKTOP_DOWNLOAD "\ue09a" +# define OCT_BEAKER "\ue09b" +# define OCT_BELL "\ue09c" +# define OCT_WATCH "\ue09d" +# define OCT_SHIELD "\ue09e" +# define OCT_BOLD "\ue09f" +# define OCT_TEXT_SIZE "\ue0d5" +# define OCT_ITALIC "\ue0d6" +# define OCT_TASKLIST "\ue0d7" +# define OCT_VERIFIED "\ue0d8" +# define OCT_SMILEY "\ue0d9" +# define OCT_UNVERIFIED "\ue0da" +# define OCT_ELLIPSES "\ue0db" +# define OCT_FILE "\ue0dc" +# define OCT_GRABBER "\ue0dd" +# define OCT_PLUS_SMALL "\ue0de" +# define OCT_REPLY "\ue0df" +# define OCT_DEVICE_DESKTOP "\ue0e0" +# define FA_GLASS "\ue0e1" +# define FA_MUSIC "\ue0e2" +# define FA_SEARCH "\ue0e3" +# define FA_ENVELOPE_O "\ue0e4" +# define FA_HEART "\ue0e5" +# define FA_STAR "\ue0e6" +# define FA_STAR_O "\ue0e7" +# define FA_USER "\ue0e8" +# define FA_FILM "\ue0e9" +# define FA_TH_LARGE "\ue0ea" +# define FA_TH "\ue0eb" +# define FA_TH_LIST "\ue0ec" +# define FA_CHECK "\ue0ed" +# define FA_CLOSE "\ue0ee" +# define FA_SEARCH_PLUS "\ue0ef" +# define FA_SEARCH_MINUS "\ue0f0" +# define FA_POWER_OFF "\ue0f1" +# define FA_SIGNAL "\ue0f2" +# define FA_COG "\ue0f3" +# define FA_TRASH_O "\ue0f4" +# define FA_HOME "\ue0f5" +# define FA_FILE_O "\ue0f6" +# define FA_CLOCK_O "\ue0f7" +# define FA_ROAD "\ue0f8" +# define FA_DOWNLOAD "\ue0f9" +# define FA_ARROW_CIRCLE_O_DOWN "\ue0fa" +# define FA_ARROW_CIRCLE_O_UP "\ue0fb" +# define FA_INBOX "\ue0fc" +# define FA_PLAY_CIRCLE_O "\ue0fd" +# define FA_REPEAT "\ue0fe" +# define FA_REFRESH "\ue0ff" +# define FA_LIST_ALT "\ue100" +# define FA_LOCK "\ue101" +# define FA_FLAG "\ue102" +# define FA_HEADPHONES "\ue103" +# define FA_VOLUME_OFF "\ue104" +# define FA_VOLUME_DOWN "\ue105" +# define FA_VOLUME_UP "\ue106" +# define FA_QRCODE "\ue107" +# define FA_BARCODE "\ue108" +# define FA_TAG "\ue109" +# define FA_TAGS "\ue10a" +# define FA_BOOK "\ue10b" +# define FA_BOOKMARK "\ue10c" +# define FA_PRINT "\ue10d" +# define FA_CAMERA "\ue10e" +# define FA_FONT "\ue10f" +# define FA_BOLD "\ue110" +# define FA_ITALIC "\ue111" +# define FA_TEXT_HEIGHT "\ue112" +# define FA_TEXT_WIDTH "\ue113" +# define FA_ALIGN_LEFT "\ue114" +# define FA_ALIGN_CENTER "\ue115" +# define FA_ALIGN_RIGHT "\ue116" +# define FA_ALIGN_JUSTIFY "\ue117" +# define FA_LIST "\ue118" +# define FA_DEDENT "\ue119" +# define FA_INDENT "\ue11a" +# define FA_VIDEO_CAMERA "\ue11b" +# define FA_IMAGE "\ue11c" +# define FA_PENCIL "\ue11d" +# define FA_MAP_MARKER "\ue11e" +# define FA_ADJUST "\ue11f" +# define FA_TINT "\ue120" +# define FA_EDIT "\ue121" +# define FA_SHARE_SQUARE_O "\ue122" +# define FA_CHECK_SQUARE_O "\ue123" +# define FA_ARROWS "\ue124" +# define FA_STEP_BACKWARD "\ue125" +# define FA_FAST_BACKWARD "\ue126" +# define FA_BACKWARD "\ue127" +# define FA_PLAY "\ue128" +# define FA_PAUSE "\ue129" +# define FA_STOP "\ue12a" +# define FA_FORWARD "\ue12b" +# define FA_FAST_FORWARD "\ue12c" +# define FA_STEP_FORWARD "\ue12d" +# define FA_EJECT "\ue12e" +# define FA_CHEVRON_LEFT "\ue12f" +# define FA_CHEVRON_RIGHT "\ue130" +# define FA_PLUS_CIRCLE "\ue131" +# define FA_MINUS_CIRCLE "\ue132" +# define FA_TIMES_CIRCLE "\ue133" +# define FA_CHECK_CIRCLE "\ue134" +# define FA_QUESTION_CIRCLE "\ue135" +# define FA_INFO_CIRCLE "\ue136" +# define FA_CROSSHAIRS "\ue137" +# define FA_TIMES_CIRCLE_O "\ue138" +# define FA_CHECK_CIRCLE_O "\ue139" +# define FA_BAN "\ue13a" +# define FA_ARROW_LEFT "\ue13b" +# define FA_ARROW_RIGHT "\ue13c" +# define FA_ARROW_UP "\ue13d" +# define FA_ARROW_DOWN "\ue13e" +# define FA_MAIL_FORWARD "\ue13f" +# define FA_EXPAND "\ue140" +# define FA_COMPRESS "\ue141" +# define FA_PLUS "\ue142" +# define FA_MINUS "\ue143" +# define FA_ASTERISK "\ue144" +# define FA_EXCLAMATION_CIRCLE "\ue145" +# define FA_GIFT "\ue146" +# define FA_LEAF "\ue147" +# define FA_FIRE "\ue148" +# define FA_EYE "\ue149" +# define FA_EYE_SLASH "\ue14a" +# define FA_EXCLAMATION_TRIANGLE "\ue14b" +# define FA_PLANE "\ue14c" +# define FA_CALENDAR "\ue14d" +# define FA_RANDOM "\ue14e" +# define FA_COMMENT "\ue14f" +# define FA_MAGNET "\ue150" +# define FA_CHEVRON_UP "\ue151" +# define FA_CHEVRON_DOWN "\ue152" +# define FA_RETWEET "\ue153" +# define FA_SHOPPING_CART "\ue154" +# define FA_FOLDER "\ue155" +# define FA_FOLDER_OPEN "\ue156" +# define FA_ARROWS_V "\ue157" +# define FA_ARROWS_H "\ue158" +# define FA_BAR_CHART "\ue159" +# define FA_TWITTER_SQUARE "\ue15a" +# define FA_FACEBOOK_SQUARE "\ue15b" +# define FA_CAMERA_RETRO "\ue15c" +# define FA_KEY "\ue15d" +# define FA_COGS "\ue15e" +# define FA_COMMENTS "\ue15f" +# define FA_THUMBS_O_UP "\ue160" +# define FA_THUMBS_O_DOWN "\ue161" +# define FA_STAR_HALF "\ue162" +# define FA_HEART_O "\ue163" +# define FA_SIGN_OUT "\ue164" +# define FA_LINKEDIN_SQUARE "\ue165" +# define FA_THUMB_TACK "\ue166" +# define FA_EXTERNAL_LINK "\ue167" +# define FA_SIGN_IN "\ue168" +# define FA_TROPHY "\ue169" +# define FA_GITHUB_SQUARE "\ue16a" +# define FA_UPLOAD "\ue16b" +# define FA_LEMON_O "\ue16c" +# define FA_PHONE "\ue16d" +# define FA_SQUARE_O "\ue16e" +# define FA_BOOKMARK_O "\ue16f" +# define FA_PHONE_SQUARE "\ue170" +# define FA_TWITTER "\ue171" +# define FA_FACEBOOK "\ue172" +# define FA_GITHUB "\ue173" +# define FA_UNLOCK "\ue174" +# define FA_CREDIT_CARD "\ue175" +# define FA_FEED "\ue176" +# define FA_HDD_O "\ue177" +# define FA_BULLHORN "\ue178" +# define FA_BELL_O "\ue179" +# define FA_CERTIFICATE "\ue17a" +# define FA_HAND_O_RIGHT "\ue17b" +# define FA_HAND_O_LEFT "\ue17c" +# define FA_HAND_O_UP "\ue17d" +# define FA_HAND_O_DOWN "\ue17e" +# define FA_ARROW_CIRCLE_LEFT "\ue17f" +# define FA_ARROW_CIRCLE_RIGHT "\ue180" +# define FA_ARROW_CIRCLE_UP "\ue181" +# define FA_ARROW_CIRCLE_DOWN "\ue182" +# define FA_GLOBE "\ue183" +# define FA_WRENCH "\ue184" +# define FA_TASKS "\ue185" +# define FA_FILTER "\ue186" +# define FA_BRIEFCASE "\ue187" +# define FA_ARROWS_ALT "\ue188" +# define FA_GROUP "\ue189" +# define FA_CHAIN "\ue18a" +# define FA_CLOUD "\ue18b" +# define FA_FLASK "\ue18c" +# define FA_CUT "\ue18d" +# define FA_COPY "\ue18e" +# define FA_PAPERCLIP "\ue18f" +# define FA_FLOPPY_O "\ue190" +# define FA_SQUARE "\ue191" +# define FA_BARS "\ue192" +# define FA_LIST_UL "\ue193" +# define FA_LIST_OL "\ue194" +# define FA_STRIKETHROUGH "\ue195" +# define FA_UNDERLINE "\ue196" +# define FA_TABLE "\ue197" +# define FA_MAGIC "\ue198" +# define FA_TRUCK "\ue199" +# define FA_PINTEREST "\ue19a" +# define FA_PINTEREST_SQUARE "\ue19b" +# define FA_GOOGLE_PLUS_SQUARE "\ue19c" +# define FA_GOOGLE_PLUS "\ue19d" +# define FA_MONEY "\ue19e" +# define FA_CARET_DOWN "\ue19f" +# define FA_CARET_UP "\ue1a0" +# define FA_CARET_LEFT "\ue1a1" +# define FA_CARET_RIGHT "\ue1a2" +# define FA_COLUMNS "\ue1a3" +# define FA_SORT "\ue1a4" +# define FA_SORT_DESC "\ue1a5" +# define FA_SORT_ASC "\ue1a6" +# define FA_ENVELOPE "\ue1a7" +# define FA_LINKEDIN "\ue1a8" +# define FA_ROTATE_LEFT "\ue1a9" +# define FA_GAVEL "\ue1aa" +# define FA_DASHBOARD "\ue1ab" +# define FA_COMMENT_O "\ue1ac" +# define FA_COMMENTS_O "\ue1ad" +# define FA_BOLT "\ue1ae" +# define FA_SITEMAP "\ue1af" +# define FA_UMBRELLA "\ue1b0" +# define FA_CLIPBOARD "\ue1b1" +# define FA_LIGHTBULB_O "\ue1b2" +# define FA_EXCHANGE "\ue1b3" +# define FA_CLOUD_DOWNLOAD "\ue1b4" +# define FA_CLOUD_UPLOAD "\ue1b5" +# define FA_USER_MD "\ue1b6" +# define FA_STETHOSCOPE "\ue1b7" +# define FA_SUITCASE "\ue1b8" +# define FA_BELL "\ue1b9" +# define FA_COFFEE "\ue1ba" +# define FA_CUTLERY "\ue1bb" +# define FA_FILE_TEXT_O "\ue1bc" +# define FA_BUILDING_O "\ue1bd" +# define FA_HOSPITAL_O "\ue1be" +# define FA_AMBULANCE "\ue1bf" +# define FA_MEDKIT "\ue1c0" +# define FA_FIGHTER_JET "\ue1c1" +# define FA_BEER "\ue1c2" +# define FA_H_SQUARE "\ue1c3" +# define FA_PLUS_SQUARE "\ue1c4" +# define FA_ANGLE_DOUBLE_LEFT "\ue1c5" +# define FA_ANGLE_DOUBLE_RIGHT "\ue1c6" +# define FA_ANGLE_DOUBLE_UP "\ue1c7" +# define FA_ANGLE_DOUBLE_DOWN "\ue1c8" +# define FA_ANGLE_LEFT "\ue1c9" +# define FA_ANGLE_RIGHT "\ue1ca" +# define FA_ANGLE_UP "\ue1cb" +# define FA_ANGLE_DOWN "\ue1cc" +# define FA_DESKTOP "\ue1cd" +# define FA_LAPTOP "\ue1ce" +# define FA_TABLET "\ue1cf" +# define FA_MOBILE "\ue1d0" +# define FA_CIRCLE_O "\ue1d1" +# define FA_QUOTE_LEFT "\ue1d2" +# define FA_QUOTE_RIGHT "\ue1d3" +# define FA_SPINNER "\ue1d4" +# define FA_CIRCLE "\ue1d5" +# define FA_MAIL_REPLY "\ue1d6" +# define FA_GITHUB_ALT "\ue1d7" +# define FA_FOLDER_O "\ue1d8" +# define FA_FOLDER_OPEN_O "\ue1d9" +# define FA_EXPAND_ALT "\ue1da" +# define FA_COLLAPSE_ALT "\ue1db" +# define FA_SMILE_O "\ue1dc" +# define FA_FROWN_O "\ue1dd" +# define FA_MEH_O "\ue1de" +# define FA_GAMEPAD "\ue1df" +# define FA_KEYBOARD_O "\ue1e0" +# define FA_FLAG_O "\ue1e1" +# define FA_FLAG_CHECKERED "\ue1e2" +# define FA_TERMINAL "\ue1e3" +# define FA_CODE "\ue1e4" +# define FA_MAIL_REPLY_ALL "\ue1e5" +# define FA_STAR_HALF_EMPTY "\ue1e6" +# define FA_LOCATION_ARROW "\ue1e7" +# define FA_CROP "\ue1e8" +# define FA_CODE_FORK "\ue1e9" +# define FA_CHAIN_BROKEN "\ue1ea" +# define FA_QUESTION "\ue1eb" +# define FA_INFO "\ue1ec" +# define FA_EXCLAMATION "\ue1ed" +# define FA_SUPERSCRIPT "\ue1ee" +# define FA_SUBSCRIPT "\ue1ef" +# define FA_ERASER "\ue1f0" +# define FA_PUZZLE_PIECE "\ue1f1" +# define FA_MICROPHONE "\ue1f2" +# define FA_MICROPHONE_SLASH "\ue1f3" +# define FA_SHIELD "\ue1f4" +# define FA_CALENDAR_O "\ue1f5" +# define FA_FIRE_EXTINGUISHER "\ue1f6" +# define FA_ROCKET "\ue1f7" +# define FA_MAXCDN "\ue1f8" +# define FA_CHEVRON_CIRCLE_LEFT "\ue1f9" +# define FA_CHEVRON_CIRCLE_RIGHT "\ue1fa" +# define FA_CHEVRON_CIRCLE_UP "\ue1fb" +# define FA_CHEVRON_CIRCLE_DOWN "\ue1fc" +# define FA_HTML5 "\ue1fd" +# define FA_CSS3 "\ue1fe" +# define FA_ANCHOR "\ue1ff" +# define FA_UNLOCK_ALT "\ue200" +# define FA_BULLSEYE "\ue201" +# define FA_ELLIPSIS_H "\ue202" +# define FA_ELLIPSIS_V "\ue203" +# define FA_RSS_SQUARE "\ue204" +# define FA_PLAY_CIRCLE "\ue205" +# define FA_TICKET "\ue206" +# define FA_MINUS_SQUARE "\ue207" +# define FA_MINUS_SQUARE_O "\ue208" +# define FA_LEVEL_UP "\ue209" +# define FA_LEVEL_DOWN "\ue20a" +# define FA_CHECK_SQUARE "\ue20b" +# define FA_PENCIL_SQUARE "\ue20c" +# define FA_EXTERNAL_LINK_SQUARE "\ue20d" +# define FA_SHARE_SQUARE "\ue20e" +# define FA_COMPASS "\ue20f" +# define FA_CARET_SQUARE_O_DOWN "\ue210" +# define FA_CARET_SQUARE_O_UP "\ue211" +# define FA_CARET_SQUARE_O_RIGHT "\ue212" +# define FA_EUR "\ue213" +# define FA_GBP "\ue214" +# define FA_DOLLAR "\ue215" +# define FA_INR "\ue216" +# define FA_CNY "\ue217" +# define FA_ROUBLE "\ue218" +# define FA_KRW "\ue219" +# define FA_BITCOIN "\ue21a" +# define FA_FILE "\ue21b" +# define FA_FILE_TEXT "\ue21c" +# define FA_SORT_ALPHA_ASC "\ue21d" +# define FA_SORT_ALPHA_DESC "\ue21e" +# define FA_SORT_AMOUNT_ASC "\ue21f" +# define FA_SORT_AMOUNT_DESC "\ue220" +# define FA_SORT_NUMERIC_ASC "\ue221" +# define FA_SORT_NUMERIC_DESC "\ue222" +# define FA_THUMBS_UP "\ue223" +# define FA_THUMBS_DOWN "\ue224" +# define FA_YOUTUBE_SQUARE "\ue225" +# define FA_YOUTUBE "\ue226" +# define FA_XING "\ue227" +# define FA_XING_SQUARE "\ue228" +# define FA_YOUTUBE_PLAY "\ue229" +# define FA_DROPBOX "\ue22a" +# define FA_STACK_OVERFLOW "\ue22b" +# define FA_INSTAGRAM "\ue22c" +# define FA_FLICKR "\ue22d" +# define FA_ADN "\ue22e" +# define FA_BITBUCKET "\ue22f" +# define FA_BITBUCKET_SQUARE "\ue230" +# define FA_TUMBLR "\ue231" +# define FA_TUMBLR_SQUARE "\ue232" +# define FA_LONG_ARROW_DOWN "\ue233" +# define FA_LONG_ARROW_UP "\ue234" +# define FA_LONG_ARROW_LEFT "\ue235" +# define FA_LONG_ARROW_RIGHT "\ue236" +# define FA_APPLE "\ue237" +# define FA_WINDOWS "\ue238" +# define FA_ANDROID "\ue239" +# define FA_LINUX "\ue23a" +# define FA_DRIBBBLE "\ue23b" +# define FA_SKYPE "\ue23c" +# define FA_FOURSQUARE "\ue23d" +# define FA_TRELLO "\ue23e" +# define FA_FEMALE "\ue23f" +# define FA_MALE "\ue240" +# define FA_GITTIP "\ue241" +# define FA_SUN_O "\ue242" +# define FA_MOON_O "\ue243" +# define FA_ARCHIVE "\ue244" +# define FA_BUG "\ue245" +# define FA_VK "\ue246" +# define FA_WEIBO "\ue247" +# define FA_RENREN "\ue248" +# define FA_PAGELINES "\ue249" +# define FA_STACK_EXCHANGE "\ue24a" +# define FA_ARROW_CIRCLE_O_RIGHT "\ue24b" +# define FA_ARROW_CIRCLE_O_LEFT "\ue24c" +# define FA_CARET_SQUARE_O_LEFT "\ue24d" +# define FA_DOT_CIRCLE_O "\ue24e" +# define FA_WHEELCHAIR "\ue24f" +# define FA_VIMEO_SQUARE "\ue250" +# define FA_TRY "\ue251" +# define FA_PLUS_SQUARE_O "\ue252" +# define FA_SPACE_SHUTTLE "\ue253" +# define FA_SLACK "\ue254" +# define FA_ENVELOPE_SQUARE "\ue255" +# define FA_WORDPRESS "\ue256" +# define FA_OPENID "\ue257" +# define FA_BANK "\ue258" +# define FA_GRADUATION_CAP "\ue259" +# define FA_YAHOO "\ue25a" +# define FA_GOOGLE "\ue25b" +# define FA_REDDIT "\ue25c" +# define FA_REDDIT_SQUARE "\ue25d" +# define FA_STUMBLEUPON_CIRCLE "\ue25e" +# define FA_STUMBLEUPON "\ue25f" +# define FA_DELICIOUS "\ue260" +# define FA_DIGG "\ue261" +# define FA_PIED_PIPER_PP "\ue262" +# define FA_PIED_PIPER_ALT "\ue263" +# define FA_DRUPAL "\ue264" +# define FA_JOOMLA "\ue265" +# define FA_LANGUAGE "\ue266" +# define FA_FAX "\ue267" +# define FA_BUILDING "\ue268" +# define FA_CHILD "\ue269" +# define FA_PAW "\ue26a" +# define FA_SPOON "\ue26b" +# define FA_CUBE "\ue26c" +# define FA_CUBES "\ue26d" +# define FA_BEHANCE "\ue26e" +# define FA_BEHANCE_SQUARE "\ue26f" +# define FA_STEAM "\ue270" +# define FA_STEAM_SQUARE "\ue271" +# define FA_RECYCLE "\ue272" +# define FA_AUTOMOBILE "\ue273" +# define FA_CAB "\ue274" +# define FA_TREE "\ue275" +# define FA_SPOTIFY "\ue276" +# define FA_DEVIANTART "\ue277" +# define FA_SOUNDCLOUD "\ue278" +# define FA_DATABASE "\ue279" +# define FA_FILE_PDF_O "\ue27a" +# define FA_FILE_WORD_O "\ue27b" +# define FA_FILE_EXCEL_O "\ue27c" +# define FA_FILE_POWERPOINT_O "\ue27d" +# define FA_FILE_IMAGE_O "\ue27e" +# define FA_FILE_ARCHIVE_O "\ue27f" +# define FA_FILE_AUDIO_O "\ue280" +# define FA_FILE_MOVIE_O "\ue281" +# define FA_FILE_CODE_O "\ue282" +# define FA_VINE "\ue283" +# define FA_CODEPEN "\ue284" +# define FA_JSFIDDLE "\ue285" +# define FA_LIFE_BOUY "\ue286" +# define FA_CIRCLE_O_NOTCH "\ue287" +# define FA_RA "\ue288" +# define FA_EMPIRE "\ue289" +# define FA_GIT_SQUARE "\ue28a" +# define FA_GIT "\ue28b" +# define FA_HACKER_NEWS "\ue28c" +# define FA_TENCENT_WEIBO "\ue28d" +# define FA_QQ "\ue28e" +# define FA_WECHAT "\ue28f" +# define FA_PAPER_PLANE "\ue290" +# define FA_PAPER_PLANE_O "\ue291" +# define FA_HISTORY "\ue292" +# define FA_CIRCLE_THIN "\ue293" +# define FA_HEADER "\ue294" +# define FA_PARAGRAPH "\ue295" +# define FA_SLIDERS "\ue296" +# define FA_SHARE_ALT "\ue297" +# define FA_SHARE_ALT_SQUARE "\ue298" +# define FA_BOMB "\ue299" +# define FA_FUTBOL_O "\ue29a" +# define FA_TTY "\ue29b" +# define FA_BINOCULARS "\ue29c" +# define FA_PLUG "\ue29d" +# define FA_SLIDESHARE "\ue29e" +# define FA_TWITCH "\ue29f" +# define FA_YELP "\ue2a0" +# define FA_NEWSPAPER_O "\ue2a1" +# define FA_WIFI "\ue2a2" +# define FA_CALCULATOR "\ue2a3" +# define FA_PAYPAL "\ue2a4" +# define FA_GOOGLE_WALLET "\ue2a5" +# define FA_CC_VISA "\ue2a6" +# define FA_CC_MASTERCARD "\ue2a7" +# define FA_CC_DISCOVER "\ue2a8" +# define FA_CC_AMEX "\ue2a9" +# define FA_CC_PAYPAL "\ue2aa" +# define FA_CC_STRIPE "\ue2ab" +# define FA_BELL_SLASH "\ue2ac" +# define FA_BELL_SLASH_O "\ue2ad" +# define FA_TRASH "\ue2ae" +# define FA_COPYRIGHT "\ue2af" +# define FA_AT "\ue2b0" +# define FA_EYEDROPPER "\ue2b1" +# define FA_PAINT_BRUSH "\ue2b2" +# define FA_BIRTHDAY_CAKE "\ue2b3" +# define FA_AREA_CHART "\ue2b4" +# define FA_PIE_CHART "\ue2b5" +# define FA_LINE_CHART "\ue2b6" +# define FA_LASTFM "\ue2b7" +# define FA_LASTFM_SQUARE "\ue2b8" +# define FA_TOGGLE_OFF "\ue2b9" +# define FA_TOGGLE_ON "\ue2ba" +# define FA_BICYCLE "\ue2bb" +# define FA_BUS "\ue2bc" +# define FA_IOXHOST "\ue2bd" +# define FA_ANGELLIST "\ue2be" +# define FA_CC "\ue2bf" +# define FA_ILS "\ue2c0" +# define FA_MEANPATH "\ue2c1" +# define FA_BUYSELLADS "\ue2c2" +# define FA_CONNECTDEVELOP "\ue2c3" +# define FA_DASHCUBE "\ue2c4" +# define FA_FORUMBEE "\ue2c5" +# define FA_LEANPUB "\ue2c6" +# define FA_SELLSY "\ue2c7" +# define FA_SHIRTSINBULK "\ue2c8" +# define FA_SIMPLYBUILT "\ue2c9" +# define FA_SKYATLAS "\ue2ca" +# define FA_CART_PLUS "\ue2cb" +# define FA_CART_ARROW_DOWN "\ue2cc" +# define FA_DIAMOND "\ue2cd" +# define FA_SHIP "\ue2ce" +# define FA_USER_SECRET "\ue2cf" +# define FA_MOTORCYCLE "\ue2d0" +# define FA_STREET_VIEW "\ue2d1" +# define FA_HEARTBEAT "\ue2d2" +# define FA_VENUS "\ue2d3" +# define FA_MARS "\ue2d4" +# define FA_MERCURY "\ue2d5" +# define FA_INTERSEX "\ue2d6" +# define FA_TRANSGENDER_ALT "\ue2d7" +# define FA_VENUS_DOUBLE "\ue2d8" +# define FA_MARS_DOUBLE "\ue2d9" +# define FA_VENUS_MARS "\ue2da" +# define FA_MARS_STROKE "\ue2db" +# define FA_MARS_STROKE_V "\ue2dc" +# define FA_MARS_STROKE_H "\ue2dd" +# define FA_NEUTER "\ue2de" +# define FA_GENDERLESS "\ue2df" +# define FA__523 "\ue2e0" +# define FA__524 "\ue2e1" +# define FA_FACEBOOK_OFFICIAL "\ue2e2" +# define FA_PINTEREST_P "\ue2e3" +# define FA_WHATSAPP "\ue2e4" +# define FA_SERVER "\ue2e5" +# define FA_USER_PLUS "\ue2e6" +# define FA_USER_TIMES "\ue2e7" +# define FA_BED "\ue2e8" +# define FA_VIACOIN "\ue2e9" +# define FA_TRAIN "\ue2ea" +# define FA_SUBWAY "\ue2eb" +# define FA_MEDIUM "\ue2ec" +# define FA_Y_COMBINATOR "\ue2ed" +# define FA_OPTIN_MONSTER "\ue2ee" +# define FA_OPENCART "\ue2ef" +# define FA_EXPEDITEDSSL "\ue2f0" +# define FA_BATTERY "\ue2f1" +# define FA_BATTERY_3 "\ue2f2" +# define FA_BATTERY_2 "\ue2f3" +# define FA_BATTERY_1 "\ue2f4" +# define FA_BATTERY_0 "\ue2f5" +# define FA_MOUSE_POINTER "\ue2f6" +# define FA_I_CURSOR "\ue2f7" +# define FA_OBJECT_GROUP "\ue2f8" +# define FA_OBJECT_UNGROUP "\ue2f9" +# define FA_STICKY_NOTE "\ue2fa" +# define FA_STICKY_NOTE_O "\ue2fb" +# define FA_CC_JCB "\ue2fc" +# define FA_CC_DINERS_CLUB "\ue2fd" +# define FA_CLONE "\ue2fe" +# define FA_BALANCE_SCALE "\ue2ff" +# define FA_HOURGLASS_O "\ue300" +# define FA_HOURGLASS_1 "\ue301" +# define FA_HOURGLASS_2 "\ue302" +# define FA_HOURGLASS_3 "\ue303" +# define FA_HOURGLASS "\ue304" +# define FA_HAND_GRAB_O "\ue305" +# define FA_HAND_PAPER_O "\ue306" +# define FA_HAND_SCISSORS_O "\ue307" +# define FA_HAND_LIZARD_O "\ue308" +# define FA_HAND_SPOCK_O "\ue309" +# define FA_HAND_POINTER_O "\ue30a" +# define FA_HAND_PEACE_O "\ue30b" +# define FA_TRADEMARK "\ue30c" +# define FA_REGISTERED "\ue30d" +# define FA_CREATIVE_COMMONS "\ue30e" +# define FA_GG "\ue30f" +# define FA_GG_CIRCLE "\ue310" +# define FA_TRIPADVISOR "\ue311" +# define FA_ODNOKLASSNIKI "\ue312" +# define FA_ODNOKLASSNIKI_SQUARE "\ue313" +# define FA_GET_POCKET "\ue314" +# define FA_WIKIPEDIA_W "\ue315" +# define FA_SAFARI "\ue316" +# define FA_CHROME "\ue317" +# define FA_FIREFOX "\ue318" +# define FA_OPERA "\ue319" +# define FA_INTERNET_EXPLORER "\ue31a" +# define FA_TELEVISION "\ue31b" +# define FA_CONTAO "\ue31c" +# define FA_500PX "\ue31d" +# define FA_AMAZON "\ue31e" +# define FA_CALENDAR_PLUS_O "\ue31f" +# define FA_CALENDAR_MINUS_O "\ue320" +# define FA_CALENDAR_TIMES_O "\ue321" +# define FA_CALENDAR_CHECK_O "\ue322" +# define FA_INDUSTRY "\ue323" +# define FA_MAP_PIN "\ue324" +# define FA_MAP_SIGNS "\ue325" +# define FA_MAP_O "\ue326" +# define FA_MAP "\ue327" +# define FA_COMMENTING "\ue328" +# define FA_COMMENTING_O "\ue329" +# define FA_HOUZZ "\ue32a" +# define FA_VIMEO "\ue32b" +# define FA_BLACK_TIE "\ue32c" +# define FA_FONTICONS "\ue32d" +# define FA_REDDIT_ALIEN "\ue32e" +# define FA_EDGE "\ue32f" +# define FA_CREDIT_CARD_ALT "\ue330" +# define FA_CODIEPIE "\ue331" +# define FA_MODX "\ue332" +# define FA_FORT_AWESOME "\ue333" +# define FA_USB "\ue334" +# define FA_PRODUCT_HUNT "\ue335" +# define FA_MIXCLOUD "\ue336" +# define FA_SCRIBD "\ue337" +# define FA_PAUSE_CIRCLE "\ue338" +# define FA_PAUSE_CIRCLE_O "\ue339" +# define FA_STOP_CIRCLE "\ue33a" +# define FA_STOP_CIRCLE_O "\ue33b" +# define FA_SHOPPING_BAG "\ue33c" +# define FA_SHOPPING_BASKET "\ue33d" +# define FA_HASHTAG "\ue33e" +# define FA_BLUETOOTH "\ue33f" +# define FA_BLUETOOTH_B "\ue340" +# define FA_PERCENT "\ue341" +# define FA_GITLAB "\ue342" +# define FA_WPBEGINNER "\ue343" +# define FA_WPFORMS "\ue344" +# define FA_ENVIRA "\ue345" +# define FA_UNIVERSAL_ACCESS "\ue346" +# define FA_WHEELCHAIR_ALT "\ue347" +# define FA_QUESTION_CIRCLE_O "\ue348" +# define FA_BLIND "\ue349" +# define FA_AUDIO_DESCRIPTION "\ue34a" +# define FA_VOLUME_CONTROL_PHONE "\ue34b" +# define FA_BRAILLE "\ue34c" +# define FA_ASSISTIVE_LISTENING_SYSTEMS "\ue34d" +# define FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING "\ue34e" +# define FA_DEAF "\ue34f" +# define FA_GLIDE "\ue350" +# define FA_GLIDE_G "\ue351" +# define FA_SIGN_LANGUAGE "\ue352" +# define FA_LOW_VISION "\ue353" +# define FA_VIADEO "\ue354" +# define FA_VIADEO_SQUARE "\ue355" +# define FA_SNAPCHAT "\ue356" +# define FA_SNAPCHAT_GHOST "\ue357" +# define FA_SNAPCHAT_SQUARE "\ue358" +# define FA_PIED_PIPER "\ue359" +# define FA_FIRST_ORDER "\ue35a" +# define FA_YOAST "\ue35b" +# define FA_THEMEISLE "\ue35c" +# define FA_GOOGLE_PLUS_CIRCLE "\ue35d" +# define FA_FA "\ue35e" +# define FA_HANDSHAKE_O "\ue35f" +# define FA_ENVELOPE_OPEN "\ue360" +# define FA_ENVELOPE_OPEN_O "\ue361" +# define FA_LINODE "\ue362" +# define FA_ADDRESS_BOOK "\ue363" +# define FA_ADDRESS_BOOK_O "\ue364" +# define FA_ADDRESS_CARD "\ue365" +# define FA_ADDRESS_CARD_O "\ue366" +# define FA_USER_CIRCLE "\ue367" +# define FA_USER_CIRCLE_O "\ue368" +# define FA_USER_O "\ue369" +# define FA_ID_BADGE "\ue36a" +# define FA_DRIVERS_LICENSE "\ue36b" +# define FA_DRIVERS_LICENSE_O "\ue36c" +# define FA_QUORA "\ue36d" +# define FA_FREE_CODE_CAMP "\ue36e" +# define FA_TELEGRAM "\ue36f" +# define FA_THERMOMETER "\ue370" +# define FA_THERMOMETER_3 "\ue371" +# define FA_THERMOMETER_2 "\ue372" +# define FA_THERMOMETER_1 "\ue373" +# define FA_THERMOMETER_0 "\ue374" +# define FA_SHOWER "\ue375" +# define FA_BATH "\ue376" +# define FA_PODCAST "\ue377" +# define FA_WINDOW_MAXIMIZE "\ue378" +# define FA_WINDOW_MINIMIZE "\ue379" +# define FA_WINDOW_RESTORE "\ue37a" +# define FA_TIMES_RECTANGLE "\ue37b" +# define FA_TIMES_RECTANGLE_O "\ue37c" +# define FA_BANDCAMP "\ue37d" +# define FA_GRAV "\ue37e" +# define FA_ETSY "\ue37f" +# define FA_IMDB "\ue380" +# define FA_RAVELRY "\ue381" +# define FA_EERCAST "\ue382" +# define FA_MICROCHIP "\ue383" +# define FA_SNOWFLAKE_O "\ue384" +# define FA_SUPERPOWERS "\ue385" +# define FA_WPEXPLORER "\ue386" +# define FA_MEETUP "\ue387" +# define MD_ERROR "\ue388" +# define MD_ERROR_OUTLINE "\ue389" +# define MD_WARNING "\ue38a" +# define MD_ADD_ALERT "\ue38b" +# define MD_ALBUM "\ue38c" +# define MD_AV_TIMER "\ue38d" +# define MD_CLOSED_CAPTION "\ue38e" +# define MD_EQUALIZER "\ue38f" +# define MD_EXPLICIT "\ue390" +# define MD_FAST_FORWARD "\ue391" +# define MD_FAST_REWIND "\ue392" +# define MD_GAMES "\ue393" +# define MD_HEARING "\ue394" +# define MD_HIGH_QUALITY "\ue395" +# define MD_LOOP "\ue396" +# define MD_MIC "\ue397" +# define MD_MIC_NONE "\ue398" +# define MD_MIC_OFF "\ue399" +# define MD_MOVIE "\ue39a" +# define MD_LIBRARY_ADD "\ue39b" +# define MD_LIBRARY_BOOKS "\ue39c" +# define MD_LIBRARY_MUSIC "\ue39d" +# define MD_NEW_RELEASES "\ue39e" +# define MD_NOT_INTERESTED "\ue39f" +# define MD_PAUSE "\ue3a0" +# define MD_PAUSE_CIRCLE_FILLED "\ue3a1" +# define MD_PAUSE_CIRCLE_OUTLINE "\ue3a2" +# define MD_PLAY_ARROW "\ue3a3" +# define MD_PLAY_CIRCLE_FILLED "\ue3a4" +# define MD_PLAY_CIRCLE_OUTLINE "\ue3a5" +# define MD_PLAYLIST_ADD "\ue3a6" +# define MD_QUEUE "\ue3a7" +# define MD_QUEUE_MUSIC "\ue3a8" +# define MD_RADIO "\ue3a9" +# define MD_RECENT_ACTORS "\ue3aa" +# define MD_REPEAT "\ue3ab" +# define MD_REPEAT_ONE "\ue3ac" +# define MD_REPLAY "\ue3ad" +# define MD_SHUFFLE "\ue3ae" +# define MD_SKIP_NEXT "\ue3af" +# define MD_SKIP_PREVIOUS "\ue3b0" +# define MD_SNOOZE "\ue3b1" +# define MD_STOP "\ue3b2" +# define MD_SUBTITLES "\ue3b3" +# define MD_SURROUND_SOUND "\ue3b4" +# define MD_VIDEO_LIBRARY "\ue3b5" +# define MD_VIDEOCAM "\ue3b6" +# define MD_VIDEOCAM_OFF "\ue3b7" +# define MD_VOLUME_DOWN "\ue3b8" +# define MD_VOLUME_MUTE "\ue3b9" +# define MD_VOLUME_OFF "\ue3ba" +# define MD_VOLUME_UP "\ue3bb" +# define MD_WEB "\ue3bc" +# define MD_HD "\ue3bd" +# define MD_SORT_BY_ALPHA "\ue3be" +# define MD_AIRPLAY "\ue3bf" +# define MD_FORWARD_10 "\ue3c0" +# define MD_FORWARD_30 "\ue3c1" +# define MD_FORWARD_5 "\ue3c2" +# define MD_REPLAY_10 "\ue3c3" +# define MD_REPLAY_30 "\ue3c4" +# define MD_REPLAY_5 "\ue3c5" +# define MD_ADD_TO_QUEUE "\ue3c6" +# define MD_FIBER_DVR "\ue3c7" +# define MD_FIBER_NEW "\ue3c8" +# define MD_PLAYLIST_PLAY "\ue3c9" +# define MD_ART_TRACK "\ue3ca" +# define MD_FIBER_MANUAL_RECORD "\ue3cb" +# define MD_FIBER_SMART_RECORD "\ue3cc" +# define MD_MUSIC_VIDEO "\ue3cd" +# define MD_SUBSCRIPTIONS "\ue3ce" +# define MD_PLAYLIST_ADD_CHECK "\ue3cf" +# define MD_QUEUE_PLAY_NEXT "\ue3d0" +# define MD_REMOVE_FROM_QUEUE "\ue3d1" +# define MD_SLOW_MOTION_VIDEO "\ue3d2" +# define MD_WEB_ASSET "\ue3d3" +# define MD_FIBER_PIN "\ue3d4" +# define MD_BRANDING_WATERMARK "\ue3d5" +# define MD_CALL_TO_ACTION "\ue3d6" +# define MD_FEATURED_PLAY_LIST "\ue3d7" +# define MD_FEATURED_VIDEO "\ue3d8" +# define MD_NOTE "\ue3d9" +# define MD_VIDEO_CALL "\ue3da" +# define MD_VIDEO_LABEL "\ue3db" +# define MD_BUSINESS "\ue3dc" +# define MD_CALL "\ue3dd" +# define MD_CALL_END "\ue3de" +# define MD_CALL_MADE "\ue3df" +# define MD_CALL_MERGE "\ue3e0" +# define MD_CALL_MISSED "\ue3e1" +# define MD_CALL_RECEIVED "\ue3e2" +# define MD_CALL_SPLIT "\ue3e3" +# define MD_CHAT "\ue3e4" +# define MD_CLEAR_ALL "\ue3e5" +# define MD_COMMENT "\ue3e6" +# define MD_CONTACTS "\ue3e7" +# define MD_DIALER_SIP "\ue3e8" +# define MD_DIALPAD "\ue3e9" +# define MD_EMAIL "\ue3ea" +# define MD_FORUM "\ue3eb" +# define MD_IMPORT_EXPORT "\ue3ec" +# define MD_INVERT_COLORS_OFF "\ue3ed" +# define MD_LIVE_HELP "\ue3ee" +# define MD_LOCATION_OFF "\ue3ef" +# define MD_LOCATION_ON "\ue3f0" +# define MD_MESSAGE "\ue3f1" +# define MD_CHAT_BUBBLE "\ue3f2" +# define MD_CHAT_BUBBLE_OUTLINE "\ue3f3" +# define MD_NO_SIM "\ue3f4" +# define MD_PHONE "\ue3f5" +# define MD_PORTABLE_WIFI_OFF "\ue3f6" +# define MD_CONTACT_PHONE "\ue3f7" +# define MD_CONTACT_MAIL "\ue3f8" +# define MD_RING_VOLUME "\ue3f9" +# define MD_SPEAKER_PHONE "\ue3fa" +# define MD_STAY_CURRENT_LANDSCAPE "\ue3fb" +# define MD_STAY_CURRENT_PORTRAIT "\ue3fc" +# define MD_STAY_PRIMARY_LANDSCAPE "\ue3fd" +# define MD_STAY_PRIMARY_PORTRAIT "\ue3fe" +# define MD_SWAP_CALLS "\ue3ff" +# define MD_TEXTSMS "\ue400" +# define MD_VOICEMAIL "\ue401" +# define MD_VPN_KEY "\ue402" +# define MD_PHONELINK_ERASE "\ue403" +# define MD_PHONELINK_LOCK "\ue404" +# define MD_PHONELINK_RING "\ue405" +# define MD_PHONELINK_SETUP "\ue406" +# define MD_PRESENT_TO_ALL "\ue407" +# define MD_IMPORT_CONTACTS "\ue408" +# define MD_MAIL_OUTLINE "\ue409" +# define MD_SCREEN_SHARE "\ue40a" +# define MD_STOP_SCREEN_SHARE "\ue40b" +# define MD_CALL_MISSED_OUTGOING "\ue40c" +# define MD_RSS_FEED "\ue40d" +# define MD_ADD "\ue40e" +# define MD_ADD_BOX "\ue40f" +# define MD_ADD_CIRCLE "\ue410" +# define MD_ADD_CIRCLE_OUTLINE "\ue411" +# define MD_ARCHIVE "\ue412" +# define MD_BACKSPACE "\ue413" +# define MD_BLOCK "\ue414" +# define MD_CLEAR "\ue415" +# define MD_CONTENT_COPY "\ue416" +# define MD_CONTENT_CUT "\ue417" +# define MD_CONTENT_PASTE "\ue418" +# define MD_CREATE "\ue419" +# define MD_DRAFTS "\ue41a" +# define MD_FILTER_LIST "\ue41b" +# define MD_FLAG "\ue41c" +# define MD_FORWARD "\ue41d" +# define MD_GESTURE "\ue41e" +# define MD_INBOX "\ue41f" +# define MD_LINK "\ue420" +# define MD_MAIL "\ue421" +# define MD_MARKUNREAD "\ue422" +# define MD_REDO "\ue423" +# define MD_REMOVE "\ue424" +# define MD_REMOVE_CIRCLE "\ue425" +# define MD_REMOVE_CIRCLE_OUTLINE "\ue426" +# define MD_REPLY "\ue427" +# define MD_REPLY_ALL "\ue428" +# define MD_REPORT "\ue429" +# define MD_SAVE "\ue42a" +# define MD_SELECT_ALL "\ue42b" +# define MD_SEND "\ue42c" +# define MD_SORT "\ue42d" +# define MD_TEXT_FORMAT "\ue42e" +# define MD_UNDO "\ue42f" +# define MD_FONT_DOWNLOAD "\ue430" +# define MD_MOVE_TO_INBOX "\ue431" +# define MD_UNARCHIVE "\ue432" +# define MD_NEXT_WEEK "\ue433" +# define MD_WEEKEND "\ue434" +# define MD_DELETE_SWEEP "\ue435" +# define MD_LOW_PRIORITY "\ue436" +# define MD_ACCESS_ALARM "\ue437" +# define MD_ACCESS_ALARMS "\ue438" +# define MD_ACCESS_TIME "\ue439" +# define MD_ADD_ALARM "\ue43a" +# define MD_AIRPLANEMODE_INACTIVE "\ue43b" +# define MD_AIRPLANEMODE_ACTIVE "\ue43c" +# define MD_BATTERY_ALERT "\ue43d" +# define MD_BATTERY_CHARGING_FULL "\ue43e" +# define MD_BATTERY_FULL "\ue43f" +# define MD_BATTERY_STD "\ue440" +# define MD_BATTERY_UNKNOWN "\ue441" +# define MD_BLUETOOTH "\ue442" +# define MD_BLUETOOTH_CONNECTED "\ue443" +# define MD_BLUETOOTH_DISABLED "\ue444" +# define MD_BLUETOOTH_SEARCHING "\ue445" +# define MD_BRIGHTNESS_AUTO "\ue446" +# define MD_BRIGHTNESS_HIGH "\ue447" +# define MD_BRIGHTNESS_LOW "\ue448" +# define MD_BRIGHTNESS_MEDIUM "\ue449" +# define MD_DATA_USAGE "\ue44a" +# define MD_DEVELOPER_MODE "\ue44b" +# define MD_DEVICES "\ue44c" +# define MD_DVR "\ue44d" +# define MD_GPS_FIXED "\ue44e" +# define MD_GPS_NOT_FIXED "\ue44f" +# define MD_GPS_OFF "\ue450" +# define MD_LOCATION_DISABLED "\ue451" +# define MD_LOCATION_SEARCHING "\ue452" +# define MD_GRAPHIC_EQ "\ue453" +# define MD_NETWORK_CELL "\ue454" +# define MD_NETWORK_WIFI "\ue455" +# define MD_NFC "\ue456" +# define MD_WALLPAPER "\ue457" +# define MD_WIDGETS "\ue458" +# define MD_SCREEN_LOCK_LANDSCAPE "\ue459" +# define MD_SCREEN_LOCK_PORTRAIT "\ue45a" +# define MD_SCREEN_LOCK_ROTATION "\ue45b" +# define MD_SCREEN_ROTATION "\ue45c" +# define MD_SD_STORAGE "\ue45d" +# define MD_SETTINGS_SYSTEM_DAYDREAM "\ue45e" +# define MD_SIGNAL_CELLULAR_4_BAR "\ue45f" +# define MD_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_4_BAR "\ue460" +# define MD_SIGNAL_CELLULAR_NO_SIM "\ue461" +# define MD_SIGNAL_CELLULAR_NULL "\ue462" +# define MD_SIGNAL_CELLULAR_OFF "\ue463" +# define MD_SIGNAL_WIFI_4_BAR "\ue464" +# define MD_SIGNAL_WIFI_4_BAR_LOCK "\ue465" +# define MD_SIGNAL_WIFI_OFF "\ue466" +# define MD_STORAGE "\ue467" +# define MD_USB "\ue468" +# define MD_WIFI_LOCK "\ue469" +# define MD_WIFI_TETHERING "\ue46a" +# define MD_ATTACH_FILE "\ue46b" +# define MD_ATTACH_MONEY "\ue46c" +# define MD_BORDER_ALL "\ue46d" +# define MD_BORDER_BOTTOM "\ue46e" +# define MD_BORDER_CLEAR "\ue46f" +# define MD_BORDER_COLOR "\ue470" +# define MD_BORDER_HORIZONTAL "\ue471" +# define MD_BORDER_INNER "\ue472" +# define MD_BORDER_LEFT "\ue473" +# define MD_BORDER_OUTER "\ue474" +# define MD_BORDER_RIGHT "\ue475" +# define MD_BORDER_STYLE "\ue476" +# define MD_BORDER_TOP "\ue477" +# define MD_BORDER_VERTICAL "\ue478" +# define MD_FORMAT_ALIGN_CENTER "\ue479" +# define MD_FORMAT_ALIGN_JUSTIFY "\ue47a" +# define MD_FORMAT_ALIGN_LEFT "\ue47b" +# define MD_FORMAT_ALIGN_RIGHT "\ue47c" +# define MD_FORMAT_BOLD "\ue47d" +# define MD_FORMAT_CLEAR "\ue47e" +# define MD_FORMAT_COLOR_FILL "\ue47f" +# define MD_FORMAT_COLOR_RESET "\ue480" +# define MD_FORMAT_COLOR_TEXT "\ue481" +# define MD_FORMAT_INDENT_DECREASE "\ue482" +# define MD_FORMAT_INDENT_INCREASE "\ue483" +# define MD_FORMAT_ITALIC "\ue484" +# define MD_FORMAT_LINE_SPACING "\ue485" +# define MD_FORMAT_LIST_BULLETED "\ue486" +# define MD_FORMAT_LIST_NUMBERED "\ue487" +# define MD_FORMAT_PAINT "\ue488" +# define MD_FORMAT_QUOTE "\ue489" +# define MD_FORMAT_SIZE "\ue48a" +# define MD_FORMAT_STRIKETHROUGH "\ue48b" +# define MD_FORMAT_TEXTDIRECTION_L_TO_R "\ue48c" +# define MD_FORMAT_TEXTDIRECTION_R_TO_L "\ue48d" +# define MD_FORMAT_UNDERLINED "\ue48e" +# define MD_FUNCTIONS "\ue48f" +# define MD_INSERT_CHART "\ue490" +# define MD_INSERT_COMMENT "\ue491" +# define MD_INSERT_DRIVE_FILE "\ue492" +# define MD_INSERT_EMOTICON "\ue493" +# define MD_INSERT_INVITATION "\ue494" +# define MD_INSERT_LINK "\ue495" +# define MD_INSERT_PHOTO "\ue496" +# define MD_MERGE_TYPE "\ue497" +# define MD_MODE_COMMENT "\ue498" +# define MD_MODE_EDIT "\ue499" +# define MD_PUBLISH "\ue49a" +# define MD_SPACE_BAR "\ue49b" +# define MD_STRIKETHROUGH_S "\ue49c" +# define MD_VERTICAL_ALIGN_BOTTOM "\ue49d" +# define MD_VERTICAL_ALIGN_CENTER "\ue49e" +# define MD_VERTICAL_ALIGN_TOP "\ue49f" +# define MD_WRAP_TEXT "\ue4a0" +# define MD_MONEY_OFF "\ue4a1" +# define MD_DRAG_HANDLE "\ue4a2" +# define MD_FORMAT_SHAPES "\ue4a3" +# define MD_HIGHLIGHT "\ue4a4" +# define MD_LINEAR_SCALE "\ue4a5" +# define MD_SHORT_TEXT "\ue4a6" +# define MD_TEXT_FIELDS "\ue4a7" +# define MD_MONETIZATION_ON "\ue4a8" +# define MD_TITLE "\ue4a9" +# define MD_ATTACHMENT "\ue4aa" +# define MD_CLOUD "\ue4ab" +# define MD_CLOUD_CIRCLE "\ue4ac" +# define MD_CLOUD_DONE "\ue4ad" +# define MD_CLOUD_DOWNLOAD "\ue4ae" +# define MD_CLOUD_OFF "\ue4af" +# define MD_CLOUD_QUEUE "\ue4b0" +# define MD_CLOUD_UPLOAD "\ue4b1" +# define MD_FILE_DOWNLOAD "\ue4b2" +# define MD_FILE_UPLOAD "\ue4b3" +# define MD_FOLDER "\ue4b4" +# define MD_FOLDER_OPEN "\ue4b5" +# define MD_FOLDER_SHARED "\ue4b6" +# define MD_CREATE_NEW_FOLDER "\ue4b7" +# define MD_CAST "\ue4b8" +# define MD_CAST_CONNECTED "\ue4b9" +# define MD_COMPUTER "\ue4ba" +# define MD_DESKTOP_MAC "\ue4bb" +# define MD_DESKTOP_WINDOWS "\ue4bc" +# define MD_DEVELOPER_BOARD "\ue4bd" +# define MD_DOCK "\ue4be" +# define MD_GAMEPAD "\ue4bf" +# define MD_HEADSET "\ue4c0" +# define MD_HEADSET_MIC "\ue4c1" +# define MD_KEYBOARD "\ue4c2" +# define MD_KEYBOARD_ARROW_DOWN "\ue4c3" +# define MD_KEYBOARD_ARROW_LEFT "\ue4c4" +# define MD_KEYBOARD_ARROW_RIGHT "\ue4c5" +# define MD_KEYBOARD_ARROW_UP "\ue4c6" +# define MD_KEYBOARD_BACKSPACE "\ue4c7" +# define MD_KEYBOARD_CAPSLOCK "\ue4c8" +# define MD_KEYBOARD_HIDE "\ue4c9" +# define MD_KEYBOARD_RETURN "\ue4ca" +# define MD_KEYBOARD_TAB "\ue4cb" +# define MD_KEYBOARD_VOICE "\ue4cc" +# define MD_LAPTOP "\ue4cd" +# define MD_LAPTOP_CHROMEBOOK "\ue4ce" +# define MD_LAPTOP_MAC "\ue4cf" +# define MD_LAPTOP_WINDOWS "\ue4d0" +# define MD_MEMORY "\ue4d1" +# define MD_MOUSE "\ue4d2" +# define MD_PHONE_ANDROID "\ue4d3" +# define MD_PHONE_IPHONE "\ue4d4" +# define MD_PHONELINK "\ue4d5" +# define MD_PHONELINK_OFF "\ue4d6" +# define MD_ROUTER "\ue4d7" +# define MD_SCANNER "\ue4d8" +# define MD_SECURITY "\ue4d9" +# define MD_SIM_CARD "\ue4da" +# define MD_SMARTPHONE "\ue4db" +# define MD_SPEAKER "\ue4dc" +# define MD_SPEAKER_GROUP "\ue4dd" +# define MD_TABLET "\ue4de" +# define MD_TABLET_ANDROID "\ue4df" +# define MD_TABLET_MAC "\ue4e0" +# define MD_TOYS "\ue4e1" +# define MD_TV "\ue4e2" +# define MD_WATCH "\ue4e3" +# define MD_DEVICE_HUB "\ue4e4" +# define MD_POWER_INPUT "\ue4e5" +# define MD_DEVICES_OTHER "\ue4e6" +# define MD_VIDEOGAME_ASSET "\ue4e7" +# define MD_ADD_TO_PHOTOS "\ue4e8" +# define MD_ADJUST "\ue4e9" +# define MD_ASSISTANT "\ue4ea" +# define MD_ASSISTANT_PHOTO "\ue4eb" +# define MD_AUDIOTRACK "\ue4ec" +# define MD_BLUR_CIRCULAR "\ue4ed" +# define MD_BLUR_LINEAR "\ue4ee" +# define MD_BLUR_OFF "\ue4ef" +# define MD_BLUR_ON "\ue4f0" +# define MD_BRIGHTNESS_1 "\ue4f1" +# define MD_BRIGHTNESS_2 "\ue4f2" +# define MD_BRIGHTNESS_3 "\ue4f3" +# define MD_BRIGHTNESS_4 "\ue4f4" +# define MD_BRIGHTNESS_5 "\ue4f5" +# define MD_BRIGHTNESS_6 "\ue4f6" +# define MD_BRIGHTNESS_7 "\ue4f7" +# define MD_BROKEN_IMAGE "\ue4f8" +# define MD_BRUSH "\ue4f9" +# define MD_CAMERA "\ue4fa" +# define MD_CAMERA_ALT "\ue4fb" +# define MD_CAMERA_FRONT "\ue4fc" +# define MD_CAMERA_REAR "\ue4fd" +# define MD_CAMERA_ROLL "\ue4fe" +# define MD_CENTER_FOCUS_STRONG "\ue4ff" +# define MD_CENTER_FOCUS_WEAK "\ue500" +# define MD_COLLECTIONS "\ue501" +# define MD_COLOR_LENS "\ue502" +# define MD_COLORIZE "\ue503" +# define MD_COMPARE "\ue504" +# define MD_CONTROL_POINT "\ue505" +# define MD_CONTROL_POINT_DUPLICATE "\ue506" +# define MD_CROP_16_9 "\ue507" +# define MD_CROP_3_2 "\ue508" +# define MD_CROP "\ue509" +# define MD_CROP_5_4 "\ue50a" +# define MD_CROP_7_5 "\ue50b" +# define MD_CROP_DIN "\ue50c" +# define MD_CROP_FREE "\ue50d" +# define MD_CROP_LANDSCAPE "\ue50e" +# define MD_CROP_ORIGINAL "\ue50f" +# define MD_CROP_PORTRAIT "\ue510" +# define MD_CROP_SQUARE "\ue511" +# define MD_DEHAZE "\ue512" +# define MD_DETAILS "\ue513" +# define MD_EDIT "\ue514" +# define MD_EXPOSURE "\ue515" +# define MD_EXPOSURE_NEG_1 "\ue516" +# define MD_EXPOSURE_NEG_2 "\ue517" +# define MD_EXPOSURE_PLUS_1 "\ue518" +# define MD_EXPOSURE_PLUS_2 "\ue519" +# define MD_EXPOSURE_ZERO "\ue51a" +# define MD_FILTER_1 "\ue51b" +# define MD_FILTER_2 "\ue51c" +# define MD_FILTER_3 "\ue51d" +# define MD_FILTER "\ue51e" +# define MD_FILTER_4 "\ue51f" +# define MD_FILTER_5 "\ue520" +# define MD_FILTER_6 "\ue521" +# define MD_FILTER_7 "\ue522" +# define MD_FILTER_8 "\ue523" +# define MD_FILTER_9 "\ue524" +# define MD_FILTER_9_PLUS "\ue525" +# define MD_FILTER_B_AND_W "\ue526" +# define MD_FILTER_CENTER_FOCUS "\ue527" +# define MD_FILTER_DRAMA "\ue528" +# define MD_FILTER_FRAMES "\ue529" +# define MD_FILTER_HDR "\ue52a" +# define MD_FILTER_NONE "\ue52b" +# define MD_FILTER_TILT_SHIFT "\ue52c" +# define MD_FILTER_VINTAGE "\ue52d" +# define MD_FLARE "\ue52e" +# define MD_FLASH_AUTO "\ue52f" +# define MD_FLASH_OFF "\ue530" +# define MD_FLASH_ON "\ue531" +# define MD_FLIP "\ue532" +# define MD_GRADIENT "\ue533" +# define MD_GRAIN "\ue534" +# define MD_GRID_OFF "\ue535" +# define MD_GRID_ON "\ue536" +# define MD_HDR_OFF "\ue537" +# define MD_HDR_ON "\ue538" +# define MD_HDR_STRONG "\ue539" +# define MD_HDR_WEAK "\ue53a" +# define MD_HEALING "\ue53b" +# define MD_IMAGE "\ue53c" +# define MD_IMAGE_ASPECT_RATIO "\ue53d" +# define MD_ISO "\ue53e" +# define MD_LANDSCAPE "\ue53f" +# define MD_LEAK_ADD "\ue540" +# define MD_LEAK_REMOVE "\ue541" +# define MD_LENS "\ue542" +# define MD_LOOKS_3 "\ue543" +# define MD_LOOKS "\ue544" +# define MD_LOOKS_4 "\ue545" +# define MD_LOOKS_5 "\ue546" +# define MD_LOOKS_6 "\ue547" +# define MD_LOOKS_ONE "\ue548" +# define MD_LOOKS_TWO "\ue549" +# define MD_LOUPE "\ue54a" +# define MD_MONOCHROME_PHOTOS "\ue54b" +# define MD_MOVIE_CREATION "\ue54c" +# define MD_MUSIC_NOTE "\ue54d" +# define MD_NATURE "\ue54e" +# define MD_NATURE_PEOPLE "\ue54f" +# define MD_NAVIGATE_BEFORE "\ue550" +# define MD_NAVIGATE_NEXT "\ue551" +# define MD_PALETTE "\ue552" +# define MD_PANORAMA "\ue553" +# define MD_PANORAMA_FISH_EYE "\ue554" +# define MD_PANORAMA_HORIZONTAL "\ue555" +# define MD_PANORAMA_VERTICAL "\ue556" +# define MD_PANORAMA_WIDE_ANGLE "\ue557" +# define MD_PHOTO "\ue558" +# define MD_PHOTO_ALBUM "\ue559" +# define MD_PHOTO_CAMERA "\ue55a" +# define MD_PHOTO_LIBRARY "\ue55b" +# define MD_PICTURE_AS_PDF "\ue55c" +# define MD_PORTRAIT "\ue55d" +# define MD_REMOVE_RED_EYE "\ue55e" +# define MD_ROTATE_90_DEGREES_CCW "\ue55f" +# define MD_ROTATE_LEFT "\ue560" +# define MD_ROTATE_RIGHT "\ue561" +# define MD_SLIDESHOW "\ue562" +# define MD_STRAIGHTEN "\ue563" +# define MD_STYLE "\ue564" +# define MD_SWITCH_CAMERA "\ue565" +# define MD_SWITCH_VIDEO "\ue566" +# define MD_TAG_FACES "\ue567" +# define MD_TEXTURE "\ue568" +# define MD_TIMELAPSE "\ue569" +# define MD_TIMER_10 "\ue56a" +# define MD_TIMER_3 "\ue56b" +# define MD_TIMER "\ue56c" +# define MD_TIMER_OFF "\ue56d" +# define MD_TONALITY "\ue56e" +# define MD_TRANSFORM "\ue56f" +# define MD_TUNE "\ue570" +# define MD_VIEW_COMFY "\ue571" +# define MD_VIEW_COMPACT "\ue572" +# define MD_WB_AUTO "\ue573" +# define MD_WB_CLOUDY "\ue574" +# define MD_WB_INCANDESCENT "\ue575" +# define MD_WB_SUNNY "\ue576" +# define MD_COLLECTIONS_BOOKMARK "\ue577" +# define MD_PHOTO_SIZE_SELECT_ACTUAL "\ue578" +# define MD_PHOTO_SIZE_SELECT_LARGE "\ue579" +# define MD_PHOTO_SIZE_SELECT_SMALL "\ue57a" +# define MD_VIGNETTE "\ue57b" +# define MD_WB_IRIDESCENT "\ue57c" +# define MD_CROP_ROTATE "\ue57d" +# define MD_LINKED_CAMERA "\ue57e" +# define MD_ADD_A_PHOTO "\ue57f" +# define MD_MOVIE_FILTER "\ue580" +# define MD_PHOTO_FILTER "\ue581" +# define MD_BURST_MODE "\ue582" +# define MD_BEENHERE "\ue583" +# define MD_DIRECTIONS "\ue584" +# define MD_DIRECTIONS_BIKE "\ue585" +# define MD_DIRECTIONS_BUS "\ue586" +# define MD_DIRECTIONS_CAR "\ue587" +# define MD_DIRECTIONS_BOAT "\ue588" +# define MD_DIRECTIONS_SUBWAY "\ue589" +# define MD_DIRECTIONS_RAILWAY "\ue58a" +# define MD_DIRECTIONS_TRANSIT "\ue58b" +# define MD_DIRECTIONS_WALK "\ue58c" +# define MD_FLIGHT "\ue58d" +# define MD_HOTEL "\ue58e" +# define MD_LAYERS "\ue58f" +# define MD_LAYERS_CLEAR "\ue590" +# define MD_LOCAL_AIRPORT "\ue591" +# define MD_LOCAL_ATM "\ue592" +# define MD_LOCAL_ACTIVITY "\ue593" +# define MD_LOCAL_BAR "\ue594" +# define MD_LOCAL_CAFE "\ue595" +# define MD_LOCAL_CAR_WASH "\ue596" +# define MD_LOCAL_CONVENIENCE_STORE "\ue597" +# define MD_LOCAL_DRINK "\ue598" +# define MD_LOCAL_FLORIST "\ue599" +# define MD_LOCAL_GAS_STATION "\ue59a" +# define MD_LOCAL_GROCERY_STORE "\ue59b" +# define MD_LOCAL_HOSPITAL "\ue59c" +# define MD_LOCAL_HOTEL "\ue59d" +# define MD_LOCAL_LAUNDRY_SERVICE "\ue59e" +# define MD_LOCAL_LIBRARY "\ue59f" +# define MD_LOCAL_MALL "\ue5a0" +# define MD_LOCAL_MOVIES "\ue5a1" +# define MD_LOCAL_OFFER "\ue5a2" +# define MD_LOCAL_PARKING "\ue5a3" +# define MD_LOCAL_PHARMACY "\ue5a4" +# define MD_LOCAL_PHONE "\ue5a5" +# define MD_LOCAL_PIZZA "\ue5a6" +# define MD_LOCAL_PLAY "\ue5a7" +# define MD_LOCAL_POST_OFFICE "\ue5a8" +# define MD_LOCAL_PRINTSHOP "\ue5a9" +# define MD_LOCAL_DINING "\ue5aa" +# define MD_LOCAL_SEE "\ue5ab" +# define MD_LOCAL_SHIPPING "\ue5ac" +# define MD_LOCAL_TAXI "\ue5ad" +# define MD_PERSON_PIN "\ue5ae" +# define MD_MAP "\ue5af" +# define MD_MY_LOCATION "\ue5b0" +# define MD_NAVIGATION "\ue5b1" +# define MD_PIN_DROP "\ue5b2" +# define MD_PLACE "\ue5b3" +# define MD_RATE_REVIEW "\ue5b4" +# define MD_RESTAURANT_MENU "\ue5b5" +# define MD_SATELLITE "\ue5b6" +# define MD_STORE_MALL_DIRECTORY "\ue5b7" +# define MD_TERRAIN "\ue5b8" +# define MD_TRAFFIC "\ue5b9" +# define MD_DIRECTIONS_RUN "\ue5ba" +# define MD_ADD_LOCATION "\ue5bb" +# define MD_EDIT_LOCATION "\ue5bc" +# define MD_NEAR_ME "\ue5bd" +# define MD_PERSON_PIN_CIRCLE "\ue5be" +# define MD_ZOOM_OUT_MAP "\ue5bf" +# define MD_RESTAURANT "\ue5c0" +# define MD_EV_STATION "\ue5c1" +# define MD_STREETVIEW "\ue5c2" +# define MD_SUBWAY "\ue5c3" +# define MD_TRAIN "\ue5c4" +# define MD_TRAM "\ue5c5" +# define MD_TRANSFER_WITHIN_A_STATION "\ue5c6" +# define MD_APPS "\ue5c7" +# define MD_ARROW_BACK "\ue5c8" +# define MD_ARROW_DROP_DOWN "\ue5c9" +# define MD_ARROW_DROP_DOWN_CIRCLE "\ue5ca" +# define MD_ARROW_DROP_UP "\ue5cb" +# define MD_ARROW_FORWARD "\ue5cc" +# define MD_CANCEL "\ue5cd" +# define MD_CHECK "\ue5ce" +# define MD_CHEVRON_LEFT "\ue5cf" +# define MD_CHEVRON_RIGHT "\ue5d0" +# define MD_CLOSE "\ue5d1" +# define MD_EXPAND_LESS "\ue5d2" +# define MD_EXPAND_MORE "\ue5d3" +# define MD_FULLSCREEN "\ue5d4" +# define MD_FULLSCREEN_EXIT "\ue5d5" +# define MD_MENU "\ue5d6" +# define MD_MORE_HORIZ "\ue5d7" +# define MD_MORE_VERT "\ue5d8" +# define MD_REFRESH "\ue5d9" +# define MD_UNFOLD_LESS "\ue5da" +# define MD_UNFOLD_MORE "\ue5db" +# define MD_ARROW_UPWARD "\ue5dc" +# define MD_SUBDIRECTORY_ARROW_LEFT "\ue5dd" +# define MD_SUBDIRECTORY_ARROW_RIGHT "\ue5de" +# define MD_ARROW_DOWNWARD "\ue5df" +# define MD_FIRST_PAGE "\ue5e0" +# define MD_LAST_PAGE "\ue5e1" +# define MD_ADB "\ue5e2" +# define MD_BLUETOOTH_AUDIO "\ue5e3" +# define MD_DISC_FULL "\ue5e4" +# define MD_DO_NOT_DISTURB_ALT "\ue5e5" +# define MD_DO_NOT_DISTURB "\ue5e6" +# define MD_DRIVE_ETA "\ue5e7" +# define MD_EVENT_AVAILABLE "\ue5e8" +# define MD_EVENT_BUSY "\ue5e9" +# define MD_EVENT_NOTE "\ue5ea" +# define MD_FOLDER_SPECIAL "\ue5eb" +# define MD_MMS "\ue5ec" +# define MD_MORE "\ue5ed" +# define MD_NETWORK_LOCKED "\ue5ee" +# define MD_PHONE_BLUETOOTH_SPEAKER "\ue5ef" +# define MD_PHONE_FORWARDED "\ue5f0" +# define MD_PHONE_IN_TALK "\ue5f1" +# define MD_PHONE_LOCKED "\ue5f2" +# define MD_PHONE_MISSED "\ue5f3" +# define MD_PHONE_PAUSED "\ue5f4" +# define MD_SD_CARD "\ue5f5" +# define MD_SIM_CARD_ALERT "\ue5f6" +# define MD_SMS "\ue5f7" +# define MD_SMS_FAILED "\ue5f8" +# define MD_SYNC "\ue5f9" +# define MD_SYNC_DISABLED "\ue5fa" +# define MD_SYNC_PROBLEM "\ue5fb" +# define MD_SYSTEM_UPDATE "\ue5fc" +# define MD_TAP_AND_PLAY "\ue5fd" +# define MD_TIME_TO_LEAVE "\ue5fe" +# define MD_VIBRATION "\ue5ff" +# define MD_VOICE_CHAT "\ue600" +# define MD_VPN_LOCK "\ue601" +# define MD_AIRLINE_SEAT_FLAT "\ue602" +# define MD_AIRLINE_SEAT_FLAT_ANGLED "\ue603" +# define MD_AIRLINE_SEAT_INDIVIDUAL_SUITE "\ue604" +# define MD_AIRLINE_SEAT_LEGROOM_EXTRA "\ue605" +# define MD_AIRLINE_SEAT_LEGROOM_NORMAL "\ue606" +# define MD_AIRLINE_SEAT_LEGROOM_REDUCED "\ue607" +# define MD_AIRLINE_SEAT_RECLINE_EXTRA "\ue608" +# define MD_AIRLINE_SEAT_RECLINE_NORMAL "\ue609" +# define MD_CONFIRMATION_NUMBER "\ue60a" +# define MD_LIVE_TV "\ue60b" +# define MD_ONDEMAND_VIDEO "\ue60c" +# define MD_PERSONAL_VIDEO "\ue60d" +# define MD_POWER "\ue60e" +# define MD_WC "\ue60f" +# define MD_WIFI "\ue610" +# define MD_ENHANCED_ENCRYPTION "\ue611" +# define MD_NETWORK_CHECK "\ue612" +# define MD_NO_ENCRYPTION "\ue613" +# define MD_RV_HOOKUP "\ue614" +# define MD_DO_NOT_DISTURB_OFF "\ue615" +# define MD_DO_NOT_DISTURB_ON "\ue616" +# define MD_PRIORITY_HIGH "\ue617" +# define MD_PIE_CHART "\ue618" +# define MD_PIE_CHART_OUTLINED "\ue619" +# define MD_BUBBLE_CHART "\ue61a" +# define MD_MULTILINE_CHART "\ue61b" +# define MD_SHOW_CHART "\ue61c" +# define MD_CAKE "\ue61d" +# define MD_DOMAIN "\ue61e" +# define MD_GROUP "\ue61f" +# define MD_GROUP_ADD "\ue620" +# define MD_LOCATION_CITY "\ue621" +# define MD_MOOD "\ue622" +# define MD_MOOD_BAD "\ue623" +# define MD_NOTIFICATIONS "\ue624" +# define MD_NOTIFICATIONS_NONE "\ue625" +# define MD_NOTIFICATIONS_OFF "\ue626" +# define MD_NOTIFICATIONS_ACTIVE "\ue627" +# define MD_NOTIFICATIONS_PAUSED "\ue628" +# define MD_PAGES "\ue629" +# define MD_PARTY_MODE "\ue62a" +# define MD_PEOPLE "\ue62b" +# define MD_PEOPLE_OUTLINE "\ue62c" +# define MD_PERSON "\ue62d" +# define MD_PERSON_ADD "\ue62e" +# define MD_PERSON_OUTLINE "\ue62f" +# define MD_PLUS_ONE "\ue630" +# define MD_POLL "\ue631" +# define MD_PUBLIC "\ue632" +# define MD_SCHOOL "\ue633" +# define MD_SHARE "\ue634" +# define MD_WHATSHOT "\ue635" +# define MD_SENTIMENT_DISSATISFIED "\ue636" +# define MD_SENTIMENT_NEUTRAL "\ue637" +# define MD_SENTIMENT_SATISFIED "\ue638" +# define MD_SENTIMENT_VERY_DISSATISFIED "\ue639" +# define MD_SENTIMENT_VERY_SATISFIED "\ue63a" +# define MD_CHECK_BOX "\ue63b" +# define MD_CHECK_BOX_OUTLINE_BLANK "\ue63c" +# define MD_RADIO_BUTTON_UNCHECKED "\ue63d" +# define MD_RADIO_BUTTON_CHECKED "\ue63e" +# define MD_STAR "\ue63f" +# define MD_STAR_HALF "\ue640" +# define MD_STAR_BORDER "\ue641" +# define MD_3D_ROTATION "\ue642" +# define MD_ACCESSIBILITY "\ue643" +# define MD_ACCOUNT_BALANCE "\ue644" +# define MD_ACCOUNT_BALANCE_WALLET "\ue645" +# define MD_ACCOUNT_BOX "\ue646" +# define MD_ACCOUNT_CIRCLE "\ue647" +# define MD_ADD_SHOPPING_CART "\ue648" +# define MD_ALARM "\ue649" +# define MD_ALARM_ADD "\ue64a" +# define MD_ALARM_OFF "\ue64b" +# define MD_ALARM_ON "\ue64c" +# define MD_ANDROID "\ue64d" +# define MD_ANNOUNCEMENT "\ue64e" +# define MD_ASPECT_RATIO "\ue64f" +# define MD_ASSESSMENT "\ue650" +# define MD_ASSIGNMENT "\ue651" +# define MD_ASSIGNMENT_IND "\ue652" +# define MD_ASSIGNMENT_LATE "\ue653" +# define MD_ASSIGNMENT_RETURN "\ue654" +# define MD_ASSIGNMENT_RETURNED "\ue655" +# define MD_ASSIGNMENT_TURNED_IN "\ue656" +# define MD_AUTORENEW "\ue657" +# define MD_BACKUP "\ue658" +# define MD_BOOK "\ue659" +# define MD_BOOKMARK "\ue65a" +# define MD_BOOKMARK_BORDER "\ue65b" +# define MD_BUG_REPORT "\ue65c" +# define MD_BUILD "\ue65d" +# define MD_CACHED "\ue65e" +# define MD_CHANGE_HISTORY "\ue65f" +# define MD_CHECK_CIRCLE "\ue660" +# define MD_CHROME_READER_MODE "\ue661" +# define MD_CLASS "\ue662" +# define MD_CODE "\ue663" +# define MD_CREDIT_CARD "\ue664" +# define MD_DASHBOARD "\ue665" +# define MD_DELETE "\ue666" +# define MD_DESCRIPTION "\ue667" +# define MD_DNS "\ue668" +# define MD_DONE "\ue669" +# define MD_DONE_ALL "\ue66a" +# define MD_EVENT "\ue66b" +# define MD_EXIT_TO_APP "\ue66c" +# define MD_EXPLORE "\ue66d" +# define MD_EXTENSION "\ue66e" +# define MD_FACE "\ue66f" +# define MD_FAVORITE "\ue670" +# define MD_FAVORITE_BORDER "\ue671" +# define MD_FEEDBACK "\ue672" +# define MD_FIND_IN_PAGE "\ue673" +# define MD_FIND_REPLACE "\ue674" +# define MD_FLIP_TO_BACK "\ue675" +# define MD_FLIP_TO_FRONT "\ue676" +# define MD_GET_APP "\ue677" +# define MD_GRADE "\ue678" +# define MD_GROUP_WORK "\ue679" +# define MD_HELP "\ue67a" +# define MD_HIGHLIGHT_OFF "\ue67b" +# define MD_HISTORY "\ue67c" +# define MD_HOME "\ue67d" +# define MD_HOURGLASS_EMPTY "\ue67e" +# define MD_HOURGLASS_FULL "\ue67f" +# define MD_HTTPS "\ue680" +# define MD_INFO "\ue681" +# define MD_INFO_OUTLINE "\ue682" +# define MD_INPUT "\ue683" +# define MD_INVERT_COLORS "\ue684" +# define MD_LABEL "\ue685" +# define MD_LABEL_OUTLINE "\ue686" +# define MD_LANGUAGE "\ue687" +# define MD_LAUNCH "\ue688" +# define MD_LIST "\ue689" +# define MD_LOCK "\ue68a" +# define MD_LOCK_OPEN "\ue68b" +# define MD_LOCK_OUTLINE "\ue68c" +# define MD_LOYALTY "\ue68d" +# define MD_MARKUNREAD_MAILBOX "\ue68e" +# define MD_NOTE_ADD "\ue68f" +# define MD_OPEN_IN_BROWSER "\ue690" +# define MD_OPEN_IN_NEW "\ue691" +# define MD_OPEN_WITH "\ue692" +# define MD_PAGEVIEW "\ue693" +# define MD_PAYMENT "\ue694" +# define MD_PERM_CAMERA_MIC "\ue695" +# define MD_PERM_CONTACT_CALENDAR "\ue696" +# define MD_PERM_DATA_SETTING "\ue697" +# define MD_PERM_DEVICE_INFORMATION "\ue698" +# define MD_PERM_IDENTITY "\ue699" +# define MD_PERM_MEDIA "\ue69a" +# define MD_PERM_PHONE_MSG "\ue69b" +# define MD_PERM_SCAN_WIFI "\ue69c" +# define MD_PICTURE_IN_PICTURE "\ue69d" +# define MD_POLYMER "\ue69e" +# define MD_POWER_SETTINGS_NEW "\ue69f" +# define MD_PRINT "\ue6a0" +# define MD_QUERY_BUILDER "\ue6a1" +# define MD_QUESTION_ANSWER "\ue6a2" +# define MD_RECEIPT "\ue6a3" +# define MD_REDEEM "\ue6a4" +# define MD_REPORT_PROBLEM "\ue6a5" +# define MD_RESTORE "\ue6a6" +# define MD_ROOM "\ue6a7" +# define MD_SCHEDULE "\ue6a8" +# define MD_SEARCH "\ue6a9" +# define MD_SETTINGS "\ue6aa" +# define MD_SETTINGS_APPLICATIONS "\ue6ab" +# define MD_SETTINGS_BACKUP_RESTORE "\ue6ac" +# define MD_SETTINGS_BLUETOOTH "\ue6ad" +# define MD_SETTINGS_CELL "\ue6ae" +# define MD_SETTINGS_BRIGHTNESS "\ue6af" +# define MD_SETTINGS_ETHERNET "\ue6b0" +# define MD_SETTINGS_INPUT_ANTENNA "\ue6b1" +# define MD_SETTINGS_INPUT_COMPONENT "\ue6b2" +# define MD_SETTINGS_INPUT_COMPOSITE "\ue6b3" +# define MD_SETTINGS_INPUT_HDMI "\ue6b4" +# define MD_SETTINGS_INPUT_SVIDEO "\ue6b5" +# define MD_SETTINGS_OVERSCAN "\ue6b6" +# define MD_SETTINGS_PHONE "\ue6b7" +# define MD_SETTINGS_POWER "\ue6b8" +# define MD_SETTINGS_REMOTE "\ue6b9" +# define MD_SETTINGS_VOICE "\ue6ba" +# define MD_SHOP "\ue6bb" +# define MD_SHOP_TWO "\ue6bc" +# define MD_SHOPPING_BASKET "\ue6bd" +# define MD_SHOPPING_CART "\ue6be" +# define MD_SPEAKER_NOTES "\ue6bf" +# define MD_SPELLCHECK "\ue6c0" +# define MD_STARS "\ue6c1" +# define MD_STORE "\ue6c2" +# define MD_SUBJECT "\ue6c3" +# define MD_SUPERVISOR_ACCOUNT "\ue6c4" +# define MD_SWAP_HORIZ "\ue6c5" +# define MD_SWAP_VERT "\ue6c6" +# define MD_SWAP_VERTICAL_CIRCLE "\ue6c7" +# define MD_SYSTEM_UPDATE_ALT "\ue6c8" +# define MD_TAB "\ue6c9" +# define MD_TAB_UNSELECTED "\ue6ca" +# define MD_THEATERS "\ue6cb" +# define MD_THUMB_DOWN "\ue6cc" +# define MD_THUMB_UP "\ue6cd" +# define MD_THUMBS_UP_DOWN "\ue6ce" +# define MD_TOC "\ue6cf" +# define MD_TODAY "\ue6d0" +# define MD_TOLL "\ue6d1" +# define MD_TRACK_CHANGES "\ue6d2" +# define MD_TRANSLATE "\ue6d3" +# define MD_TRENDING_DOWN "\ue6d4" +# define MD_TRENDING_FLAT "\ue6d5" +# define MD_TRENDING_UP "\ue6d6" +# define MD_TURNED_IN "\ue6d7" +# define MD_TURNED_IN_NOT "\ue6d8" +# define MD_VERIFIED_USER "\ue6d9" +# define MD_VIEW_AGENDA "\ue6da" +# define MD_VIEW_ARRAY "\ue6db" +# define MD_VIEW_CAROUSEL "\ue6dc" +# define MD_VIEW_COLUMN "\ue6dd" +# define MD_VIEW_DAY "\ue6de" +# define MD_VIEW_HEADLINE "\ue6df" +# define MD_VIEW_LIST "\ue6e0" +# define MD_VIEW_MODULE "\ue6e1" +# define MD_VIEW_QUILT "\ue6e2" +# define MD_VIEW_STREAM "\ue6e3" +# define MD_VIEW_WEEK "\ue6e4" +# define MD_VISIBILITY "\ue6e5" +# define MD_VISIBILITY_OFF "\ue6e6" +# define MD_CARD_GIFTCARD "\ue6e7" +# define MD_CARD_MEMBERSHIP "\ue6e8" +# define MD_CARD_TRAVEL "\ue6e9" +# define MD_WORK "\ue6ea" +# define MD_YOUTUBE_SEARCHED_FOR "\ue6eb" +# define MD_EJECT "\ue6ec" +# define MD_CAMERA_ENHANCE "\ue6ed" +# define MD_HELP_OUTLINE "\ue6ee" +# define MD_REORDER "\ue6ef" +# define MD_ZOOM_IN "\ue6f0" +# define MD_ZOOM_OUT "\ue6f1" +# define MD_HTTP "\ue6f2" +# define MD_EVENT_SEAT "\ue6f3" +# define MD_FLIGHT_LAND "\ue6f4" +# define MD_FLIGHT_TAKEOFF "\ue6f5" +# define MD_PLAY_FOR_WORK "\ue6f6" +# define MD_GIF "\ue6f7" +# define MD_INDETERMINATE_CHECK_BOX "\ue6f8" +# define MD_OFFLINE_PIN "\ue6f9" +# define MD_ALL_OUT "\ue6fa" +# define MD_COPYRIGHT "\ue6fb" +# define MD_FINGERPRINT "\ue6fc" +# define MD_GAVEL "\ue6fd" +# define MD_LIGHTBULB_OUTLINE "\ue6fe" +# define MD_PICTURE_IN_PICTURE_ALT "\ue6ff" +# define MD_IMPORTANT_DEVICES "\ue700" +# define MD_TOUCH_APP "\ue701" +# define MD_ACCESSIBLE "\ue702" +# define MD_COMPARE_ARROWS "\ue703" +# define MD_DATE_RANGE "\ue704" +# define MD_DONUT_LARGE "\ue705" +# define MD_DONUT_SMALL "\ue706" +# define MD_LINE_STYLE "\ue707" +# define MD_LINE_WEIGHT "\ue708" +# define MD_MOTORCYCLE "\ue709" +# define MD_OPACITY "\ue70a" +# define MD_PETS "\ue70b" +# define MD_PREGNANT_WOMAN "\ue70c" +# define MD_RECORD_VOICE_OVER "\ue70d" +# define MD_ROUNDED_CORNER "\ue70e" +# define MD_ROWING "\ue70f" +# define MD_TIMELINE "\ue710" +# define MD_UPDATE "\ue711" +# define MD_WATCH_LATER "\ue712" +# define MD_PAN_TOOL "\ue713" +# define MD_EURO_SYMBOL "\ue714" +# define MD_G_TRANSLATE "\ue715" +# define MD_REMOVE_SHOPPING_CART "\ue716" +# define MD_RESTORE_PAGE "\ue717" +# define MD_SPEAKER_NOTES_OFF "\ue718" +# define MD_DELETE_FOREVER "\ue719" +# define MD_AC_UNIT "\ue71a" +# define MD_AIRPORT_SHUTTLE "\ue71b" +# define MD_ALL_INCLUSIVE "\ue71c" +# define MD_BEACH_ACCESS "\ue71d" +# define MD_BUSINESS_CENTER "\ue71e" +# define MD_CASINO "\ue71f" +# define MD_CHILD_CARE "\ue720" +# define MD_CHILD_FRIENDLY "\ue721" +# define MD_FITNESS_CENTER "\ue722" +# define MD_FREE_BREAKFAST "\ue723" +# define MD_GOLF_COURSE "\ue724" +# define MD_HOT_TUB "\ue725" +# define MD_KITCHEN "\ue726" +# define MD_POOL "\ue727" +# define MD_ROOM_SERVICE "\ue728" +# define MD_SMOKE_FREE "\ue729" +# define MD_SMOKING_ROOMS "\ue72a" +# define MD_SPA "\ue72b" +# define MD_U10FFFD "\ue72c" +# define FILE_REGEX "\ue72d" +# define FILE_ARCH_LINUX "\ue72e" +# define FILE_E "\ue72f" +# define FILE_GLYPHS "\ue730" +# define FILE_KNOCKOUT "\ue731" +# define FILE_LEAN "\ue732" +# define FILE_METAL "\ue733" +# define FILE_POVRAY "\ue734" +# define FILE_S "\ue735" +# define FILE_TT "\ue736" +# define FILE_VAGRANT "\ue737" +# define FILE_XMOS "\ue738" +# define FILE_A "\ue739" +# define FILE_CHAI "\ue73a" +# define FILE_STYLUS "\ue73b" +# define FILE_TEXTILE "\ue73c" +# define FILE_UNREAL "\ue73d" +# define FILE_PUREBASIC "\ue73e" +# define FILE_TS "\ue73f" +# define FILE_SCHEME "\ue740" +# define FILE_TEXTMATE "\ue741" +# define FILE_X10 "\ue742" +# define FILE_APL "\ue743" +# define FILE_ANSIBLE "\ue744" +# define FILE_ARTTEXT "\ue745" +# define FILE_ANTWAR "\ue746" +# define FILE_OPA "\ue747" +# define FILE_CODECOV "\ue748" +# define FILE_YANG "\ue749" +# define FILE_PM2 "\ue74a" +# define FILE_HG "\ue74b" +# define FILE_PAWN "\ue74c" +# define FILE_JULIA "\ue74d" +# define FILE_SHIPIT "\ue74e" +# define FILE_MOCHA "\ue74f" +# define FILE_NIB "\ue750" +# define FILE_SHURIKEN "\ue751" +# define FILE_ALEX "\ue752" +# define FILE_TWIG "\ue753" +# define FILE_1C "\ue754" +# define FILE_TEX "\ue755" +# define FILE_BIBTEX "\ue756" +# define FILE_MUSTACHE "\ue757" +# define FILE_GULP "\ue758" +# define FILE_GRUNT "\ue759" +# define FILE_EMBER "\ue75a" +# define FILE_GO "\ue75b" +# define FILE_JENKINS "\ue75c" +# define FILE_GNU "\ue75d" +# define FILE_COMPOSER "\ue75e" +# define FILE_METEOR "\ue75f" +# define FILE_AI "\ue760" +# define FILE_PSD "\ue761" +# define FILE_SILVERSTRIPE "\ue762" +# define FILE_MAXSCRIPT "\ue763" +# define FILE_KIVY "\ue764" +# define FILE_CRYSTAL "\ue765" +# define FILE_GRADLE "\ue766" +# define FILE_GROOVY "\ue767" +# define FILE_R "\ue768" +# define FILE_VUE "\ue769" +# define FILE_HAXE "\ue76a" +# define FILE_LISP "\ue76b" +# define FILE_E909 "\ue76c" +# define FILE_FORTRAN "\ue76d" +# define FILE_ADA "\ue76e" +# define FILE_DYALOG "\ue76f" +# define FILE_JADE "\ue770" +# define FILE_E90E "\ue771" +# define FILE_FONT "\ue772" +# define FILE_POSTCSS "\ue773" +# define FILE_SCAD "\ue774" +# define FILE_E912 "\ue775" +# define FILE_RAML "\ue776" +# define FILE_LS "\ue777" +# define FILE_SALTSTACK "\ue778" +# define FILE_TERRAFORM "\ue779" +# define FILE_ORG "\ue77a" +# define FILE_ASCIIDOC "\ue77b" +# define FILE_RIOT "\ue77c" +# define FILE_OCAML "\ue77d" +# define FILE_LUA "\ue77e" +# define FILE_NPM "\ue77f" +# define FILE_LLVM "\ue780" +# define FILE_E91E "\ue781" +# define FILE_BABEL "\ue782" +# define FILE_MARKO "\ue783" +# define FILE_FLOW "\ue784" +# define FILE_BROCCOLI "\ue785" +# define FILE_APPVEYOR "\ue786" +# define FILE_CAKEFILE "\ue787" +# define FILE_APPLE "\ue788" +# define FILE_EMACS "\ue789" +# define FILE_SKETCH "\ue78a" +# define FILE_DOXYGEN "\ue78b" +# define FILE_CF "\ue78c" +# define FILE_PASCAL "\ue78d" +# define FILE_ABAP "\ue78e" +# define FILE_ANTLR "\ue78f" +# define FILE_API "\ue790" +# define FILE_AS "\ue791" +# define FILE_ARC "\ue792" +# define FILE_ARDUINO "\ue793" +# define FILE_AUGEAS "\ue794" +# define FILE_AHK "\ue795" +# define FILE_AUTOIT "\ue796" +# define FILE_ATS "\ue797" +# define FILE_ALLOY "\ue798" +# define FILE_MANPAGE "\ue799" +# define FILE_J "\ue79a" +# define FILE_GLADE "\ue79b" +# define FILE_BOO "\ue79c" +# define FILE_BRAIN "\ue79d" +# define FILE_BRO "\ue79e" +# define FILE_BLUESPEC "\ue79f" +# define FILE_STYLELINT "\ue7a0" +# define FILE_ANT "\ue7a1" +# define FILE_CMAKE "\ue7a2" +# define FILE_CLIPS "\ue7a3" +# define FILE_MAPBOX "\ue7a4" +# define FILE_CP "\ue7a5" +# define FILE_CHUCK "\ue7a6" +# define FILE_JINJA "\ue7a7" +# define FILE_ISABELLE "\ue7a8" +# define FILE_DOGE "\ue7a9" +# define FILE_IDL "\ue7aa" +# define FILE_JAKE "\ue7ab" +# define FILE_VERILOG "\ue7ac" +# define FILE_PHALCON "\ue7ad" +# define FILE_FABFILE "\ue7ae" +# define FILE_LFE "\ue7af" +# define FILE_NMAP "\ue7b0" +# define FILE_AMPL "\ue7b1" +# define FILE_CEYLON "\ue7b2" +# define FILE_CHAPEL "\ue7b3" +# define FILE_CIRRU "\ue7b4" +# define FILE_CLARION "\ue7b5" +# define FILE_NUNJUCKS "\ue7b6" +# define FILE_MEDIAWIKI "\ue7b7" +# define FILE_POSTSCRIPT "\ue7b8" +# define FILE_TCL "\ue7b9" +# define FILE_OWL "\ue7ba" +# define FILE_JSONLD "\ue7bb" +# define FILE_SPARQL "\ue7bc" +# define FILE_SAS "\ue7bd" +# define FILE_CLEAN "\ue7be" +# define FILE_CLICK "\ue7bf" +# define FILE_NVIDIA "\ue7c0" +# define FILE_CREOLE "\ue7c1" +# define FILE_COQ "\ue7c2" +# define FILE_DIFF "\ue7c3" +# define FILE_PATCH "\ue7c4" +# define FILE_BYOND "\ue7c5" +# define FILE_CYTHON "\ue7c6" +# define FILE_DARCS "\ue7c7" +# define FILE_EAGLE "\ue7c8" +# define FILE_ECERE "\ue7c9" +# define FILE_EIFFEL "\ue7ca" +# define FILE_EM "\ue7cb" +# define FILE_FLUX "\ue7cc" +# define FILE_FACTOR "\ue7cd" +# define FILE_FANCY "\ue7ce" +# define FILE_PERL6 "\ue7cf" +# define FILE_GENTOO "\ue7d0" +# define FILE_FREGE "\ue7d1" +# define FILE_FANTOM "\ue7d2" +# define FILE_FREEMARKER "\ue7d3" +# define FILE_GAP "\ue7d4" +# define FILE_CL "\ue7d5" +# define FILE_GAMS "\ue7d6" +# define FILE_GODOT "\ue7d7" +# define FILE_GML "\ue7d8" +# define FILE_GENSHI "\ue7d9" +# define FILE_POINTWISE "\ue7da" +# define FILE_GF "\ue7db" +# define FILE_GOLO "\ue7dc" +# define FILE_GOSU "\ue7dd" +# define FILE_HARBOUR "\ue7de" +# define FILE_GRAPHQL "\ue7df" +# define FILE_GRAPHVIZ "\ue7e0" +# define FILE_HASHICORP "\ue7e1" +# define FILE_HY "\ue7e2" +# define FILE_IGORPRO "\ue7e3" +# define FILE_IO "\ue7e4" +# define FILE_IOKE "\ue7e5" +# define FILE_IDRIS "\ue7e6" +# define FILE_INFORM7 "\ue7e7" +# define FILE_INNO "\ue7e8" +# define FILE_SUBLIME "\ue7e9" +# define FILE_JUPYTER "\ue7ea" +# define FILE_KRL "\ue7eb" +# define FILE_KOTLIN "\ue7ec" +# define FILE_LABVIEW "\ue7ed" +# define FILE_LSL "\ue7ee" +# define FILE_LASSO "\ue7ef" +# define FILE_LOGTALK "\ue7f0" +# define FILE_LOOKML "\ue7f1" +# define FILE_MAKO "\ue7f2" +# define FILE_MATHEMATICA "\ue7f3" +# define FILE_MATLAB "\ue7f4" +# define FILE_E992 "\ue7f5" +# define FILE_MAX "\ue7f6" +# define FILE_MERCURY "\ue7f7" +# define FILE_MIRAH "\ue7f8" +# define FILE_MODULA2 "\ue7f9" +# define FILE_MONKEY "\ue7fa" +# define FILE_NIMROD "\ue7fb" +# define FILE_NIT "\ue7fc" +# define FILE_NIX "\ue7fd" +# define FILE_AMX "\ue7fe" +# define FILE_NETLOGO "\ue7ff" +# define FILE_NUMPY "\ue800" +# define FILE_OBJJ "\ue801" +# define FILE_OPENCL "\ue802" +# define FILE_PROCESSING "\ue803" +# define FILE_OX "\ue804" +# define FILE_SCD "\ue805" +# define FILE_STATA "\ue806" +# define FILE_STAN "\ue807" +# define FILE_SQF "\ue808" +# define FILE_SLASH "\ue809" +# define FILE_SHEN "\ue80a" +# define FILE_SELF "\ue80b" +# define FILE_SCILAB "\ue80c" +# define FILE_VHDL "\ue80d" +# define FILE_SAGE "\ue80e" +# define FILE_ROBOT "\ue80f" +# define FILE_RED "\ue810" +# define FILE_REBOL "\ue811" +# define FILE_XOJO "\ue812" +# define FILE_RDOC "\ue813" +# define FILE_RACKET "\ue814" +# define FILE_PURESCRIPT "\ue815" +# define FILE_UNO "\ue816" +# define FILE_VARNISH "\ue817" +# define FILE_PROPELLER "\ue818" +# define FILE_TURING "\ue819" +# define FILE_PONY "\ue81a" +# define FILE_POGO "\ue81b" +# define FILE_PIKE "\ue81c" +# define FILE_URWEB "\ue81d" +# define FILE_PARROT "\ue81e" +# define FILE_PAPYRUS "\ue81f" +# define FILE_PAN "\ue820" +# define FILE_OZ "\ue821" +# define FILE_OXYGENE "\ue822" +# define FILE_PROGRESS "\ue823" +# define FILE_TXL "\ue824" +# define FILE_CABAL "\ue825" +# define FILE_SYSVERILOG "\ue826" +# define FILE_PICKLE "\ue827" +# define FILE_XPAGES "\ue828" +# define FILE_XTEND "\ue829" +# define FILE_ZEPHIR "\ue82a" +# define FILE_ZIMPL "\ue82b" +# define FILE_EC "\ue82c" +# define FILE_MUPAD "\ue82d" +# define FILE_OOC "\ue82e" +# define FILE_RST "\ue82f" +# define FILE_KARMA "\ue830" +# define FILE_HACK "\ue831" +# define FILE_SHOPIFY "\ue832" +# define FILE_PUG_ALT "\ue833" +# define FILE_E9D1 "\ue834" +# define FILE_SBT "\ue835" +# define FILE_E9D3 "\ue836" +# define FILE_SCRUTINIZER "\ue837" +# define FILE_CC "\ue838" +# define FILE_BRAKEMAN "\ue839" +# define FILE_NEWRELIC "\ue83a" +# define FILE_THOR "\ue83b" +# define FILE_NUGET "\ue83c" +# define FILE_POWERSHELL "\ue83d" +# define FILE_SF "\ue83e" +# define FILE_MINECRAFT "\ue83f" +# define FILE_SQLITE "\ue840" +# define FILE_PROTRACTOR "\ue841" +# define FILE_TYPINGS "\ue842" +# define FILE_STRINGS "\ue843" +# define FILE_NANT "\ue844" +# define FILE_CSSCRIPT "\ue845" +# define FILE_CAKE "\ue846" +# define FILE_OPENOFFICE "\ue847" +# define FILE_KEYNOTE "\ue848" +# define FILE_JSX "\ue849" +# define FILE_TSX "\ue84a" +# define FILE_MODEL "\ue84b" +# define FILE_FINDER "\ue84c" +# define FILE_ACCESS "\ue84d" +# define FILE_ONENOTE "\ue84e" +# define FILE_POWERPOINT "\ue84f" +# define FILE_WORD "\ue850" +# define FILE_EXCEL "\ue851" +# define FILE_STORYIST "\ue852" +# define FILE_CSOUND "\ue853" +# define FILE_DBASE "\ue854" +# define FILE_ZBRUSH "\ue855" +# define FILE_AE "\ue856" +# define FILE_INDESIGN "\ue857" +# define FILE_PREMIERE "\ue858" +# define FILE_MAYA "\ue859" +# define FILE_E9F7 "\ue85a" +# define FILE_KHRONOS "\ue85b" +# define FILE_AUDACITY "\ue85c" +# define FILE_BLENDER "\ue85d" +# define FILE_LIGHTWAVE "\ue85e" +# define FILE_FBX "\ue85f" +# define FILE_E9FD "\ue860" +# define FILE_TYPEDOC "\ue861" +# define FILE_ALPINE "\ue862" +# define FILE_YUI "\ue863" +# define FILE_EA01 "\ue864" +# define FILE_EA02 "\ue865" +# define FILE_EA03 "\ue866" +# define FILE_NORMALIZE "\ue867" +# define FILE_NEKO "\ue868" +# define FILE_MATHJAX "\ue869" +# define FILE_LEAFLET "\ue86a" +# define FILE_GDB "\ue86b" +# define FILE_FUELUX "\ue86c" +# define FILE_EQ "\ue86d" +# define FILE_CHARTJS "\ue86e" +# define FILE_EA0C "\ue86f" +# define FILE_EA0D "\ue870" +# define FILE_EA0E "\ue871" +# define FILE_ESLINT "\ue872" +# define FILE_D3 "\ue873" +# define FILE_CORDOVA "\ue874" +# define FILE_CIRCLECI "\ue875" +# define FILE_PUG "\ue876" +# define FILE_POWERBUILDER "\ue877" +# define FILE_DYLIB "\ue878" +# define FILE_REXX "\ue879" +# define FILE_SVN "\ue87a" +# define FILE_MRUBY "\ue87b" +# define FILE_WERCKER "\ue87c" +# define FILE_YARN "\ue87d" +# define FILE_EDITORCONFIG "\ue87e" +# define FILE_SNYK "\ue87f" +# define FILE_REASON "\ue880" +# define FILE_NSIS "\ue881" +# define FILE_V8 "\ue882" +# define FILE_ROLLUP "\ue883" +# define FILE_EA21 "\ue884" +# define FILE_EA22 "\ue885" +# define FILE_EA23 "\ue886" +# define FILE_RASCAL "\ue887" +# define FILE_GN "\ue888" +# define FILE_NODEMON "\ue889" +# define FILE_ELECTRON "\ue88a" +# define FILE_1C_ALT "\ue88b" +# define FILE_SWAGGER "\ue88c" +# define FILE_BITHOUND "\ue88d" +# define FILE_POLYMER "\ue88e" +# define FILE_PLATFORMIO "\ue88f" +# define FILE_SHIPPABLE "\ue890" +# define FILE_EA2E "\ue891" +# define FILE_SEQUELIZE "\ue892" +# define FILE_REDUX "\ue893" +# define FILE_RSPEC "\ue894" +# define FILE_PHPUNIT "\ue895" +# define FILE_OCTAVE "\ue896" +# define FILE_NUCLIDE "\ue897" +# define FILE_INFOPATH "\ue898" +# define FILE_LIME "\ue899" +# define FILE_LERNA "\ue89a" +# define FILE_KITCHENCI "\ue89b" +# define FILE_JEST "\ue89c" +# define FILE_JASMINE "\ue89d" +# define FILE_HAXEDEVELOP "\ue89e" +# define FILE_GITLAB "\ue89f" +# define FILE_DRONE "\ue8a0" +# define FILE_VIRTUALBOX "\ue8a1" +# define FILE_DOCLETS "\ue8a2" +# define FILE_DELPHI "\ue8a3" +# define FILE_CODEKIT "\ue8a4" +# define FILE_CHEF "\ue8a5" +# define FILE_CAKEPHP "\ue8a6" +# define FILE_COBOL "\ue8a7" +# define FILE_BUNDLER "\ue8a8" +# define FILE_BUCK "\ue8a9" +# define FILE_BRUNCH "\ue8aa" +# define FILE_AURELIA "\ue8ab" +# define FILE_VMWARE "\ue8ac" +# define FILE_RHINO "\ue8ad" +# define FILE_EJS "\ue8ae" +# define FILE_KICAD "\ue8af" +# define FILE_HOPLON "\ue8b0" +# define FILE_ABIF "\ue8b1" +# define FILE_WATCHMAN "\ue8b2" +# define FILE_P4 "\ue8b3" +# define FILE_NANOC "\ue8b4" +# define FILE_MIRANDA "\ue8b5" +# define FILE_MINIZINC "\ue8b6" +# define FILE_MESON "\ue8b7" +# define FILE_JISON "\ue8b8" +# define FILE_FRANCA "\ue8b9" +# define FILE_DEVICETREE "\ue8ba" +# define FILE_CADDY "\ue8bb" +# define FILE_BEM "\ue8bc" +# define FILE_BAZEL "\ue8bd" +# define FILE_ANGELSCRIPT "\ue8be" +# define FILE_ESDOC "\ue8bf" +# define FILE_TWINE "\ue8c0" +# define FILE_SQUARESPACE "\ue8c1" +# define FILE_PHOENIX "\ue8c2" +# define FILE_TEST_DIR "\ue8c3" +# define FILE_WEBPACK "\ue8c4" +# define FILE_TEST_COFFEE "\ue8c5" +# define FILE_TEST_GENERIC "\ue8c6" +# define FILE_TEST_JS "\ue8c7" +# define FILE_TEST_PERL "\ue8c8" +# define FILE_TEST_PYTHON "\ue8c9" +# define FILE_TEST_REACT "\ue8ca" +# define FILE_TEST_RUBY "\ue8cb" +# define FILE_TEST_TS "\ue8cc" +# define FILE_CODESHIP "\ue8cd" +# define FILE_NXC "\ue8ce" +# define FILE_BROTLI "\ue8cf" +# define FILE_PROSELINT "\ue8d0" +# define FILE_BINTRAY "\ue8d1" +# define FILE_MJML "\ue8d2" +# define FILE_WASM "\ue8d3" +# define FILE_EA71 "\ue8d4" +# define FILE_NASM "\ue8d5" +# define FILE_EA73 "\ue8d6" +# define FILE_PEG "\ue8d7" +# define FILE_JOLIE "\ue8d8" +# define FILE_NANO "\ue8d9" +# define FILE_XAMARIN "\ue8da" +# define FILE_F012 "\ue8db" +# define FILE_TAG "\ue8dc" +# define FILE_CUCUMBER "\ue8dd" +# define FILE_VIDEO "\ue8de" +# define FILE_CONFIG "\ue8df" +# define FILE_DASHBOARD "\ue8e0" +# define FILE_PUPPET "\ue8e1" +# define FILE_TERMINAL "\ue8e2" +# define FILE_MARKDOWNLINT "\ue8e3" +# define FILE_REACT "\ue8e4" +# define FILE_F101 "\ue8e5" +# define FILE_ELM "\ue8e6" +# define FILE_BOOT "\ue8e7" +# define FILE_CLJS "\ue8e8" +# define FILE_LEIN "\ue8e9" +# define FILE_DOCKER "\ue8ea" +# define FILE_PHP "\ue8eb" +# define FILE_IONIC "\ue8ec" +# define FILE_HAML "\ue8ed" +# define FILE_F17B "\ue8ee" +# define FILE_FF "\ue8ef" +# define FILE_U1F3C1 "\ue8f0" +# define FILE_TERN "\ue8f1" +# define FILE_DEFAULT "\ue8f2" +# define FILE_SIGILS "\ue8f3" +# define FILE_NGINX "\ue8f4" +# define WEATHER_DAY_CLOUDY_GUSTS "\ue8f5" +# define WEATHER_DAY_CLOUDY_WINDY "\ue8f6" +# define WEATHER_DAY_CLOUDY "\ue8f7" +# define WEATHER_DAY_FOG "\ue8f8" +# define WEATHER_DAY_HAIL "\ue8f9" +# define WEATHER_DAY_LIGHTNING "\ue8fa" +# define WEATHER_DAY_RAIN_MIX "\ue8fb" +# define WEATHER_DAY_RAIN_WIND "\ue8fc" +# define WEATHER_DAY_RAIN "\ue8fd" +# define WEATHER_DAY_SHOWERS "\ue8fe" +# define WEATHER_DAY_SNOW "\ue8ff" +# define WEATHER_DAY_SPRINKLE "\ue900" +# define WEATHER_DAY_SUNNY_OVERCAST "\ue901" +# define WEATHER_DAY_SUNNY "\ue902" +# define WEATHER_DAY_STORM_SHOWERS "\ue903" +# define WEATHER_DAY_THUNDERSTORM "\ue904" +# define WEATHER_CLOUDY_GUSTS "\ue905" +# define WEATHER_CLOUDY_WINDY "\ue906" +# define WEATHER_CLOUDY "\ue907" +# define WEATHER_FOG "\ue908" +# define WEATHER_HAIL "\ue909" +# define WEATHER_LIGHTNING "\ue90a" +# define WEATHER_RAIN_MIX "\ue90b" +# define WEATHER_RAIN_WIND "\ue90c" +# define WEATHER_RAIN "\ue90d" +# define WEATHER_SHOWERS "\ue90e" +# define WEATHER_SNOW "\ue90f" +# define WEATHER_SPRINKLE "\ue910" +# define WEATHER_STORM_SHOWERS "\ue911" +# define WEATHER_THUNDERSTORM "\ue912" +# define WEATHER_WINDY "\ue913" +# define WEATHER_NIGHT_ALT_CLOUDY_GUSTS "\ue914" +# define WEATHER_NIGHT_ALT_CLOUDY_WINDY "\ue915" +# define WEATHER_NIGHT_ALT_HAIL "\ue916" +# define WEATHER_NIGHT_ALT_LIGHTNING "\ue917" +# define WEATHER_NIGHT_ALT_RAIN_MIX "\ue918" +# define WEATHER_NIGHT_ALT_RAIN_WIND "\ue919" +# define WEATHER_NIGHT_ALT_RAIN "\ue91a" +# define WEATHER_NIGHT_ALT_SHOWERS "\ue91b" +# define WEATHER_NIGHT_ALT_SNOW "\ue91c" +# define WEATHER_NIGHT_ALT_SPRINKLE "\ue91d" +# define WEATHER_NIGHT_ALT_STORM_SHOWERS "\ue91e" +# define WEATHER_NIGHT_ALT_THUNDERSTORM "\ue91f" +# define WEATHER_NIGHT_CLEAR "\ue920" +# define WEATHER_NIGHT_CLOUDY_GUSTS "\ue921" +# define WEATHER_NIGHT_CLOUDY_WINDY "\ue922" +# define WEATHER_NIGHT_CLOUDY "\ue923" +# define WEATHER_NIGHT_HAIL "\ue924" +# define WEATHER_NIGHT_LIGHTNING "\ue925" +# define WEATHER_NIGHT_RAIN_MIX "\ue926" +# define WEATHER_NIGHT_RAIN_WIND "\ue927" +# define WEATHER_NIGHT_RAIN "\ue928" +# define WEATHER_NIGHT_SHOWERS "\ue929" +# define WEATHER_NIGHT_SNOW "\ue92a" +# define WEATHER_NIGHT_SPRINKLE "\ue92b" +# define WEATHER_NIGHT_STORM_SHOWERS "\ue92c" +# define WEATHER_NIGHT_THUNDERSTORM "\ue92d" +# define WEATHER_CELSIUS "\ue92e" +# define WEATHER_CLOUD_DOWN "\ue92f" +# define WEATHER_CLOUD_REFRESH "\ue930" +# define WEATHER_CLOUD_UP "\ue931" +# define WEATHER_CLOUD "\ue932" +# define WEATHER_DEGREES "\ue933" +# define WEATHER_DIRECTION_DOWN_LEFT "\ue934" +# define WEATHER_DIRECTION_DOWN "\ue935" +# define WEATHER_FAHRENHEIT "\ue936" +# define WEATHER_HORIZON_ALT "\ue937" +# define WEATHER_HORIZON "\ue938" +# define WEATHER_DIRECTION_LEFT "\ue939" +# define WEATHER_F049 "\ue93a" +# define WEATHER_NIGHT_FOG "\ue93b" +# define WEATHER_REFRESH_ALT "\ue93c" +# define WEATHER_REFRESH "\ue93d" +# define WEATHER_DIRECTION_RIGHT "\ue93e" +# define WEATHER_RAINDROPS "\ue93f" +# define WEATHER_STRONG_WIND "\ue940" +# define WEATHER_SUNRISE "\ue941" +# define WEATHER_SUNSET "\ue942" +# define WEATHER_THERMOMETER_EXTERIOR "\ue943" +# define WEATHER_THERMOMETER_INTERNAL "\ue944" +# define WEATHER_THERMOMETER "\ue945" +# define WEATHER_TORNADO "\ue946" +# define WEATHER_DIRECTION_UP_RIGHT "\ue947" +# define WEATHER_DIRECTION_UP "\ue948" +# define WEATHER_F059 "\ue949" +# define WEATHER_F05A "\ue94a" +# define WEATHER_F05B "\ue94b" +# define WEATHER_F05C "\ue94c" +# define WEATHER_F05D "\ue94d" +# define WEATHER_F05E "\ue94e" +# define WEATHER_F060 "\ue94f" +# define WEATHER_F061 "\ue950" +# define WEATHER_SMOKE "\ue951" +# define WEATHER_DUST "\ue952" +# define WEATHER_SNOW_WIND "\ue953" +# define WEATHER_DAY_SNOW_WIND "\ue954" +# define WEATHER_NIGHT_SNOW_WIND "\ue955" +# define WEATHER_NIGHT_ALT_SNOW_WIND "\ue956" +# define WEATHER_DAY_SLEET_STORM "\ue957" +# define WEATHER_NIGHT_SLEET_STORM "\ue958" +# define WEATHER_NIGHT_ALT_SLEET_STORM "\ue959" +# define WEATHER_DAY_SNOW_THUNDERSTORM "\ue95a" +# define WEATHER_NIGHT_SNOW_THUNDERSTORM "\ue95b" +# define WEATHER_NIGHT_ALT_SNOW_THUNDERSTORM "\ue95c" +# define WEATHER_SOLAR_ECLIPSE "\ue95d" +# define WEATHER_LUNAR_ECLIPSE "\ue95e" +# define WEATHER_METEOR "\ue95f" +# define WEATHER_HOT "\ue960" +# define WEATHER_HURRICANE "\ue961" +# define WEATHER_SMOG "\ue962" +# define WEATHER_ALIEN "\ue963" +# define WEATHER_SNOWFLAKE_COLD "\ue964" +# define WEATHER_STARS "\ue965" +# define WEATHER_RAINDROP "\ue966" +# define WEATHER_BAROMETER "\ue967" +# define WEATHER_HUMIDITY "\ue968" +# define WEATHER_NA "\ue969" +# define WEATHER_FLOOD "\ue96a" +# define WEATHER_DAY_CLOUDY_HIGH "\ue96b" +# define WEATHER_NIGHT_ALT_CLOUDY_HIGH "\ue96c" +# define WEATHER_NIGHT_CLOUDY_HIGH "\ue96d" +# define WEATHER_NIGHT_ALT_PARTLY_CLOUDY "\ue96e" +# define WEATHER_SANDSTORM "\ue96f" +# define WEATHER_NIGHT_PARTLY_CLOUDY "\ue970" +# define WEATHER_UMBRELLA "\ue971" +# define WEATHER_DAY_WINDY "\ue972" +# define WEATHER_NIGHT_ALT_CLOUDY "\ue973" +# define WEATHER_DIRECTION_UP_LEFT "\ue974" +# define WEATHER_DIRECTION_DOWN_RIGHT "\ue975" +# define WEATHER_TIME_12 "\ue976" +# define WEATHER_TIME_1 "\ue977" +# define WEATHER_TIME_2 "\ue978" +# define WEATHER_TIME_3 "\ue979" +# define WEATHER_TIME_4 "\ue97a" +# define WEATHER_TIME_5 "\ue97b" +# define WEATHER_TIME_6 "\ue97c" +# define WEATHER_TIME_7 "\ue97d" +# define WEATHER_TIME_8 "\ue97e" +# define WEATHER_TIME_9 "\ue97f" +# define WEATHER_TIME_10 "\ue980" +# define WEATHER_TIME_11 "\ue981" +# define WEATHER_MOON_NEW "\ue982" +# define WEATHER_MOON_WAXING_CRESCENT_1 "\ue983" +# define WEATHER_MOON_WAXING_CRESCENT_2 "\ue984" +# define WEATHER_MOON_WAXING_CRESCENT_3 "\ue985" +# define WEATHER_MOON_WAXING_CRESCENT_4 "\ue986" +# define WEATHER_MOON_WAXING_CRESCENT_5 "\ue987" +# define WEATHER_MOON_WAXING_CRESCENT_6 "\ue988" +# define WEATHER_MOON_FIRST_QUARTER "\ue989" +# define WEATHER_MOON_WAXING_GIBBOUS_1 "\ue98a" +# define WEATHER_MOON_WAXING_GIBBOUS_2 "\ue98b" +# define WEATHER_MOON_WAXING_GIBBOUS_3 "\ue98c" +# define WEATHER_MOON_WAXING_GIBBOUS_4 "\ue98d" +# define WEATHER_MOON_WAXING_GIBBOUS_5 "\ue98e" +# define WEATHER_MOON_WAXING_GIBBOUS_6 "\ue98f" +# define WEATHER_MOON_FULL "\ue990" +# define WEATHER_MOON_WANING_GIBBOUS_1 "\ue991" +# define WEATHER_MOON_WANING_GIBBOUS_2 "\ue992" +# define WEATHER_MOON_WANING_GIBBOUS_3 "\ue993" +# define WEATHER_MOON_WANING_GIBBOUS_4 "\ue994" +# define WEATHER_MOON_WANING_GIBBOUS_5 "\ue995" +# define WEATHER_MOON_WANING_GIBBOUS_6 "\ue996" +# define WEATHER_MOON_THIRD_QUARTER "\ue997" +# define WEATHER_MOON_WANING_CRESCENT_1 "\ue998" +# define WEATHER_MOON_WANING_CRESCENT_2 "\ue999" +# define WEATHER_MOON_WANING_CRESCENT_3 "\ue99a" +# define WEATHER_MOON_WANING_CRESCENT_4 "\ue99b" +# define WEATHER_MOON_WANING_CRESCENT_5 "\ue99c" +# define WEATHER_MOON_WANING_CRESCENT_6 "\ue99d" +# define WEATHER_WIND_DIRECTION "\ue99e" +# define WEATHER_DAY_SLEET "\ue99f" +# define WEATHER_NIGHT_SLEET "\ue9a0" +# define WEATHER_NIGHT_ALT_SLEET "\ue9a1" +# define WEATHER_SLEET "\ue9a2" +# define WEATHER_DAY_HAZE "\ue9a3" +# define WEATHER_WIND_BEAUFORT_0 "\ue9a4" +# define WEATHER_WIND_BEAUFORT_1 "\ue9a5" +# define WEATHER_WIND_BEAUFORT_2 "\ue9a6" +# define WEATHER_WIND_BEAUFORT_3 "\ue9a7" +# define WEATHER_WIND_BEAUFORT_4 "\ue9a8" +# define WEATHER_WIND_BEAUFORT_5 "\ue9a9" +# define WEATHER_WIND_BEAUFORT_6 "\ue9aa" +# define WEATHER_WIND_BEAUFORT_7 "\ue9ab" +# define WEATHER_WIND_BEAUFORT_8 "\ue9ac" +# define WEATHER_WIND_BEAUFORT_9 "\ue9ad" +# define WEATHER_WIND_BEAUFORT_10 "\ue9ae" +# define WEATHER_WIND_BEAUFORT_11 "\ue9af" +# define WEATHER_WIND_BEAUFORT_12 "\ue9b0" +# define WEATHER_DAY_LIGHT_WIND "\ue9b1" +# define WEATHER_TSUNAMI "\ue9b2" +# define WEATHER_EARTHQUAKE "\ue9b3" +# define WEATHER_FIRE "\ue9b4" +# define WEATHER_VOLCANO "\ue9b5" +# define WEATHER_MOONRISE "\ue9b6" +# define WEATHER_MOONSET "\ue9b7" +# define WEATHER_TRAIN "\ue9b8" +# define WEATHER_SMALL_CRAFT_ADVISORY "\ue9b9" +# define WEATHER_GALE_WARNING "\ue9ba" +# define WEATHER_STORM_WARNING "\ue9bb" +# define WEATHER_HURRICANE_WARNING "\ue9bc" +# define WEATHER_MOON_ALT_WAXING_CRESCENT_1 "\ue9bd" +# define WEATHER_MOON_ALT_WAXING_CRESCENT_2 "\ue9be" +# define WEATHER_MOON_ALT_WAXING_CRESCENT_3 "\ue9bf" +# define WEATHER_MOON_ALT_WAXING_CRESCENT_4 "\ue9c0" +# define WEATHER_MOON_ALT_WAXING_CRESCENT_5 "\ue9c1" +# define WEATHER_MOON_ALT_WAXING_CRESCENT_6 "\ue9c2" +# define WEATHER_MOON_ALT_FIRST_QUARTER "\ue9c3" +# define WEATHER_MOON_ALT_WAXING_GIBBOUS_1 "\ue9c4" +# define WEATHER_MOON_ALT_WAXING_GIBBOUS_2 "\ue9c5" +# define WEATHER_MOON_ALT_WAXING_GIBBOUS_3 "\ue9c6" +# define WEATHER_MOON_ALT_WAXING_GIBBOUS_4 "\ue9c7" +# define WEATHER_MOON_ALT_WAXING_GIBBOUS_5 "\ue9c8" +# define WEATHER_MOON_ALT_WAXING_GIBBOUS_6 "\ue9c9" +# define WEATHER_MOON_ALT_FULL "\ue9ca" +# define WEATHER_MOON_ALT_WANING_GIBBOUS_1 "\ue9cb" +# define WEATHER_MOON_ALT_WANING_GIBBOUS_2 "\ue9cc" +# define WEATHER_MOON_ALT_WANING_GIBBOUS_3 "\ue9cd" +# define WEATHER_MOON_ALT_WANING_GIBBOUS_4 "\ue9ce" +# define WEATHER_MOON_ALT_WANING_GIBBOUS_5 "\ue9cf" +# define WEATHER_MOON_ALT_WANING_GIBBOUS_6 "\ue9d0" +# define WEATHER_MOON_ALT_THIRD_QUARTER "\ue9d1" +# define WEATHER_MOON_ALT_WANING_CRESCENT_1 "\ue9d2" +# define WEATHER_MOON_ALT_WANING_CRESCENT_2 "\ue9d3" +# define WEATHER_MOON_ALT_WANING_CRESCENT_3 "\ue9d4" +# define WEATHER_MOON_ALT_WANING_CRESCENT_4 "\ue9d5" +# define WEATHER_MOON_ALT_WANING_CRESCENT_5 "\ue9d6" +# define WEATHER_MOON_ALT_WANING_CRESCENT_6 "\ue9d7" +# define WEATHER_MOON_ALT_NEW "\ue9d8" +# define LINUX_ARCHLINUX "\ue9d9" +# define LINUX_CENTOS "\ue9da" +# define LINUX_DEBIAN "\ue9db" +# define LINUX_FEDORA "\ue9dc" +# define LINUX_LINUXMINT "\ue9dd" +# define LINUX_LINUXMINT_INVERSE "\ue9de" +# define LINUX_MAGEIA "\ue9df" +# define LINUX_MANDRIVA "\ue9e0" +# define LINUX_OPENSUSE "\ue9e1" +# define LINUX_REDHAT "\ue9e2" +# define LINUX_SLACKWARE "\ue9e3" +# define LINUX_SLACKWARE_INVERSE "\ue9e4" +# define LINUX_UBUNTU "\ue9e5" +# define LINUX_UBUNTU_INVERSE "\ue9e6" +# define LINUX_FREEBSD "\ue9e7" +# define LINUX_COREOS "\ue9e8" +# define LINUX_GENTOO "\ue9e9" +# define LINUX_ELEMENTARY "\ue9ea" +# define LINUX_FEDORA_INVERSE "\ue9eb" +# define LINUX_SABAYON "\ue9ec" +# define LINUX_AOSC "\ue9ed" +# define LINUX_NIXOS "\ue9ee" +# define LINUX_TUX "\ue9ef" +# define LINUX_RASPBERRY_PI "\ue9f0" +# define LINUX_MANJARO "\ue9f1" +# define LINUX_APPLE "\ue9f2" +# define LINUX_DOCKER "\ue9f3" +# define LINUX_ALPINE "\ue9f4" +# define MYICONS_0001 "\ue9f5" +# define MYICONS_0002 "\ue9f6" +# define MYICONS_0003 "\ue9f7" +# define MYICONS_0004 "\ue9f8" +# define MYICONS_0005 "\ue9f9" +# define MYICONS_0006 "\ue9fa" +# define MYICONS_0007 "\ue9fb" +# define MYICONS_0008 "\ue9fc" +# define MYICONS_0009 "\ue9fd" +# define MYICONS_000A "\ue9fe" +# define MYICONS_000B "\ue9ff" +# define MYICONS_000D "\uea00" +# define MYICONS_000E "\uea01" +# define MYICONS_0010 "\uea02" +# define MYICONS_0011 "\uea03" +# define MYICONS_0013 "\uea04" +# define MYICONS_0014 "\uea05" +# define MYICONS_ARCH_LINUX_ARROW "\uea06" +# define DEV_BING_SMALL "\uea07" +# define DEV_CSS_TRICKS "\uea08" +# define DEV_GIT "\uea09" +# define DEV_BITBUCKET "\uea0a" +# define DEV_MYSQL "\uea0b" +# define DEV_STREAMLINE "\uea0c" +# define DEV_DATABASE "\uea0d" +# define DEV_DROPBOX "\uea0e" +# define DEV_GITHUB_ALT "\uea0f" +# define DEV_GITHUB_BADGE "\uea10" +# define DEV_GITHUB "\uea11" +# define DEV_WORDPRESS "\uea12" +# define DEV_VISUALSTUDIO "\uea13" +# define DEV_JEKYLL_SMALL "\uea14" +# define DEV_ANDROID "\uea15" +# define DEV_WINDOWS "\uea16" +# define DEV_STACKOVERFLOW "\uea17" +# define DEV_APPLE "\uea18" +# define DEV_LINUX "\uea19" +# define DEV_APPSTORE "\uea1a" +# define DEV_GHOST_SMALL "\uea1b" +# define DEV_YAHOO "\uea1c" +# define DEV_CODEPEN "\uea1d" +# define DEV_GITHUB_FULL "\uea1e" +# define DEV_NODEJS_SMALL "\uea1f" +# define DEV_NODEJS "\uea20" +# define DEV_HACKERNEWS "\uea21" +# define DEV_EMBER "\uea22" +# define DEV_DOJO "\uea23" +# define DEV_DJANGO "\uea24" +# define DEV_NPM "\uea25" +# define DEV_GHOST "\uea26" +# define DEV_MODERNIZR "\uea27" +# define DEV_UNITY_SMALL "\uea28" +# define DEV_RASPBERRY_PI "\uea29" +# define DEV_BLACKBERRY "\uea2a" +# define DEV_GO "\uea2b" +# define DEV_GIT_BRANCH "\uea2c" +# define DEV_GIT_PULL_REQUEST "\uea2d" +# define DEV_GIT_MERGE "\uea2e" +# define DEV_GIT_COMPARE "\uea2f" +# define DEV_GIT_COMMIT "\uea30" +# define DEV_CSSDECK "\uea31" +# define DEV_YAHOO_SMALL "\uea32" +# define DEV_TECHCRUNCH "\uea33" +# define DEV_SMASHING_MAGAZINE "\uea34" +# define DEV_NETMAGAZINE "\uea35" +# define DEV_CODROPS "\uea36" +# define DEV_PHONEGAP "\uea37" +# define DEV_GOOGLE_DRIVE "\uea38" +# define DEV_HTML5_MULTIMEDIA "\uea39" +# define DEV_HTML5_DEVICE_ACCESS "\uea3a" +# define DEV_HTML5_CONNECTIVITY "\uea3b" +# define DEV_HTML5_3D_EFFECTS "\uea3c" +# define DEV_HTML5 "\uea3d" +# define DEV_SCALA "\uea3e" +# define DEV_JAVA "\uea3f" +# define DEV_RUBY "\uea40" +# define DEV_UBUNTU "\uea41" +# define DEV_RUBY_ON_RAILS "\uea42" +# define DEV_PYTHON "\uea43" +# define DEV_PHP "\uea44" +# define DEV_MARKDOWN "\uea45" +# define DEV_LARAVEL "\uea46" +# define DEV_MAGENTO "\uea47" +# define DEV_JOOMLA "\uea48" +# define DEV_DRUPAL "\uea49" +# define DEV_CHROME "\uea4a" +# define DEV_IE "\uea4b" +# define DEV_FIREFOX "\uea4c" +# define DEV_OPERA "\uea4d" +# define DEV_BOOTSTRAP "\uea4e" +# define DEV_SAFARI "\uea4f" +# define DEV_CSS3 "\uea50" +# define DEV_CSS3_FULL "\uea51" +# define DEV_SASS "\uea52" +# define DEV_GRUNT "\uea53" +# define DEV_BOWER "\uea54" +# define DEV_JAVASCRIPT "\uea55" +# define DEV_JAVASCRIPT_SHIELD "\uea56" +# define DEV_JQUERY "\uea57" +# define DEV_COFFEESCRIPT "\uea58" +# define DEV_BACKBONE "\uea59" +# define DEV_ANGULAR "\uea5a" +# define DEV_JQUERY_UI "\uea5b" +# define DEV_SWIFT "\uea5c" +# define DEV_SYMFONY "\uea5d" +# define DEV_SYMFONY_BADGE "\uea5e" +# define DEV_LESS "\uea5f" +# define DEV_STYLUS "\uea60" +# define DEV_TRELLO "\uea61" +# define DEV_ATLASSIAN "\uea62" +# define DEV_JIRA "\uea63" +# define DEV_ENVATO "\uea64" +# define DEV_SNAP_SVG "\uea65" +# define DEV_RAPHAEL "\uea66" +# define DEV_GOOGLE_ANALYTICS "\uea67" +# define DEV_COMPASS "\uea68" +# define DEV_ONEDRIVE "\uea69" +# define DEV_GULP "\uea6a" +# define DEV_ATOM "\uea6b" +# define DEV_CISCO "\uea6c" +# define DEV_NANCY "\uea6d" +# define DEV_JENKINS "\uea6e" +# define DEV_CLOJURE "\uea6f" +# define DEV_PERL "\uea70" +# define DEV_CLOJURE_ALT "\uea71" +# define DEV_CELLULOID "\uea72" +# define DEV_W3C "\uea73" +# define DEV_REDIS "\uea74" +# define DEV_POSTGRESQL "\uea75" +# define DEV_WEBPLATFORM "\uea76" +# define DEV_REQUIREJS "\uea77" +# define DEV_OPENSOURCE "\uea78" +# define DEV_TYPO3 "\uea79" +# define DEV_UIKIT "\uea7a" +# define DEV_DOCTRINE "\uea7b" +# define DEV_GROOVY "\uea7c" +# define DEV_NGINX "\uea7d" +# define DEV_HASKELL "\uea7e" +# define DEV_ZEND "\uea7f" +# define DEV_GNU "\uea80" +# define DEV_YEOMAN "\uea81" +# define DEV_HEROKU "\uea82" +# define DEV_MSQL_SERVER "\uea83" +# define DEV_DEBIAN "\uea84" +# define DEV_TRAVIS "\uea85" +# define DEV_DOTNET "\uea86" +# define DEV_CODEIGNITER "\uea87" +# define DEV_JAVASCRIPT_BADGE "\uea88" +# define DEV_YII "\uea89" +# define DEV_COMPOSER "\uea8a" +# define DEV_KRAKENJS_BADGE "\uea8b" +# define DEV_KRAKENJS "\uea8c" +# define DEV_MOZILLA "\uea8d" +# define DEV_FIREBASE "\uea8e" +# define DEV_SIZZLEJS "\uea8f" +# define DEV_CREATIVECOMMONS "\uea90" +# define DEV_CREATIVECOMMONS_BADGE "\uea91" +# define DEV_MITLICENCE "\uea92" +# define DEV_SENCHATOUCH "\uea93" +# define DEV_BUGSENSE "\uea94" +# define DEV_EXTJS "\uea95" +# define DEV_MOOTOOLS_BADGE "\uea96" +# define DEV_MOOTOOLS "\uea97" +# define DEV_RUBY_ROUGH "\uea98" +# define DEV_KOMODO "\uea99" +# define DEV_CODA "\uea9a" +# define DEV_BINTRAY "\uea9b" +# define DEV_TERMINAL "\uea9c" +# define DEV_CODE "\uea9d" +# define DEV_RESPONSIVE "\uea9e" +# define DEV_DART "\uea9f" +# define DEV_APTANA "\ueaa0" +# define DEV_MAILCHIMP "\ueaa1" +# define DEV_NETBEANS "\ueaa2" +# define DEV_DREAMWEAVER "\ueaa3" +# define DEV_BRACKETS "\ueaa4" +# define DEV_ECLIPSE "\ueaa5" +# define DEV_CLOUD9 "\ueaa6" +# define DEV_SCRUM "\ueaa7" +# define DEV_PROLOG "\ueaa8" +# define DEV_TERMINAL_BADGE "\ueaa9" +# define DEV_CODE_BADGE "\ueaaa" +# define DEV_MONGODB "\ueaab" +# define DEV_METEOR "\ueaac" +# define DEV_METEORFULL "\ueaad" +# define DEV_FSHARP "\ueaae" +# define DEV_RUST "\ueaaf" +# define DEV_IONIC "\ueab0" +# define DEV_SUBLIME "\ueab1" +# define DEV_APPCELERATOR "\ueab2" +# define DEV_ASTERISK "\ueab3" +# define DEV_AWS "\ueab4" +# define DEV_DIGITAL_OCEAN "\ueab5" +# define DEV_DLANG "\ueab6" +# define DEV_DOCKER "\ueab7" +# define DEV_ERLANG "\ueab8" +# define DEV_GOOGLE_CLOUD_PLATFORM "\ueab9" +# define DEV_GRAILS "\ueaba" +# define DEV_ILLUSTRATOR "\ueabb" +# define DEV_INTELLIJ "\ueabc" +# define DEV_MATERIALIZECSS "\ueabd" +# define DEV_OPENSHIFT "\ueabe" +# define DEV_PHOTOSHOP "\ueabf" +# define DEV_RACKSPACE "\ueac0" +# define DEV_REACT "\ueac1" +# define DEV_REDHAT "\ueac2" +# define DEV_SCRIPTCS "\ueac3" +# define DEV_E6BD "\ueac4" +# define DEV_E6BE "\ueac5" +# define DEV_E6BF "\ueac6" +# define DEV_E6C0 "\ueac7" +# define DEV_E6C1 "\ueac8" +# define DEV_E6C2 "\ueac9" +# define DEV_E6C3 "\ueaca" +# define DEV_SQLLITE "\ueacb" +# define DEV_VIM "\ueacc" +# define POM_CLEAN_CODE "\ueacd" +# define POM_POMODORO_DONE "\ueace" +# define POM_POMODORO_ESTIMATED "\ueacf" +# define POM_POMODORO_TICKING "\uead0" +# define POM_POMODORO_SQUASHED "\uead1" +# define POM_SHORT_PAUSE "\uead2" +# define POM_LONG_PAUSE "\uead3" +# define POM_AWAY "\uead4" +# define POM_PAIR_PROGRAMMING "\uead5" +# define POM_INTERNAL_INTERRUPTION "\uead6" +# define POM_EXTERNAL_INTERRUPTION "\uead7" +# define LINEA_ARROWS_ANTICLOCKWISE "\uead8" +# define LINEA_ARROWS_ANTICLOCKWISE_DASHED "\uead9" +# define LINEA_ARROWS_BUTTON_DOWN "\ueada" +# define LINEA_ARROWS_BUTTON_OFF "\ueadb" +# define LINEA_ARROWS_BUTTON_ON "\ueadc" +# define LINEA_ARROWS_BUTTON_UP "\ueadd" +# define LINEA_ARROWS_CHECK "\ueade" +# define LINEA_ARROWS_CIRCLE_CHECK "\ueadf" +# define LINEA_ARROWS_CIRCLE_DOWN "\ueae0" +# define LINEA_ARROWS_CIRCLE_DOWNLEFT "\ueae1" +# define LINEA_ARROWS_CIRCLE_DOWNRIGHT "\ueae2" +# define LINEA_ARROWS_CIRCLE_LEFT "\ueae3" +# define LINEA_ARROWS_CIRCLE_MINUS "\ueae4" +# define LINEA_ARROWS_CIRCLE_PLUS "\ueae5" +# define LINEA_ARROWS_CIRCLE_REMOVE "\ueae6" +# define LINEA_ARROWS_CIRCLE_RIGHT "\ueae7" +# define LINEA_ARROWS_CIRCLE_UP "\ueae8" +# define LINEA_ARROWS_CIRCLE_UPLEFT "\ueae9" +# define LINEA_ARROWS_CIRCLE_UPRIGHT "\ueaea" +# define LINEA_ARROWS_CLOCKWISE "\ueaeb" +# define LINEA_ARROWS_CLOCKWISE_DASHED "\ueaec" +# define LINEA_ARROWS_COMPRESS "\ueaed" +# define LINEA_ARROWS_DENY "\ueaee" +# define LINEA_ARROWS_DIAGONAL "\ueaef" +# define LINEA_ARROWS_DIAGONAL2 "\ueaf0" +# define LINEA_ARROWS_DOWN "\ueaf1" +# define LINEA_ARROWS_DOWN_DOUBLE "\ueaf2" +# define LINEA_ARROWS_DOWNLEFT "\ueaf3" +# define LINEA_ARROWS_DOWNRIGHT "\ueaf4" +# define LINEA_ARROWS_DRAG_DOWN "\ueaf5" +# define LINEA_ARROWS_DRAG_DOWN_DASHED "\ueaf6" +# define LINEA_ARROWS_DRAG_HORIZ "\ueaf7" +# define LINEA_ARROWS_DRAG_LEFT "\ueaf8" +# define LINEA_ARROWS_DRAG_LEFT_DASHED "\ueaf9" +# define LINEA_ARROWS_DRAG_RIGHT "\ueafa" +# define LINEA_ARROWS_DRAG_RIGHT_DASHED "\ueafb" +# define LINEA_ARROWS_DRAG_UP "\ueafc" +# define LINEA_ARROWS_DRAG_UP_DASHED "\ueafd" +# define LINEA_ARROWS_DRAG_VERT "\ueafe" +# define LINEA_ARROWS_EXCLAMATION "\ueaff" +# define LINEA_ARROWS_EXPAND "\ueb00" +# define LINEA_ARROWS_EXPAND_DIAGONAL1 "\ueb01" +# define LINEA_ARROWS_EXPAND_HORIZONTAL1 "\ueb02" +# define LINEA_ARROWS_EXPAND_VERTICAL1 "\ueb03" +# define LINEA_ARROWS_FIT_HORIZONTAL "\ueb04" +# define LINEA_ARROWS_FIT_VERTICAL "\ueb05" +# define LINEA_ARROWS_GLIDE "\ueb06" +# define LINEA_ARROWS_GLIDE_HORIZONTAL "\ueb07" +# define LINEA_ARROWS_GLIDE_VERTICAL "\ueb08" +# define LINEA_ARROWS_HAMBURGER1 "\ueb09" +# define LINEA_ARROWS_HAMBURGER_2 "\ueb0a" +# define LINEA_ARROWS_HORIZONTAL "\ueb0b" +# define LINEA_ARROWS_INFO "\ueb0c" +# define LINEA_ARROWS_KEYBOARD_ALT "\ueb0d" +# define LINEA_ARROWS_KEYBOARD_CMD "\ueb0e" +# define LINEA_ARROWS_KEYBOARD_DELETE "\ueb0f" +# define LINEA_ARROWS_KEYBOARD_DOWN "\ueb10" +# define LINEA_ARROWS_KEYBOARD_LEFT "\ueb11" +# define LINEA_ARROWS_KEYBOARD_RETURN "\ueb12" +# define LINEA_ARROWS_KEYBOARD_RIGHT "\ueb13" +# define LINEA_ARROWS_KEYBOARD_SHIFT "\ueb14" +# define LINEA_ARROWS_KEYBOARD_TAB "\ueb15" +# define LINEA_ARROWS_KEYBOARD_UP "\ueb16" +# define LINEA_ARROWS_LEFT "\ueb17" +# define LINEA_ARROWS_LEFT_DOUBLE_32 "\ueb18" +# define LINEA_ARROWS_MINUS "\ueb19" +# define LINEA_ARROWS_MOVE "\ueb1a" +# define LINEA_ARROWS_MOVE2 "\ueb1b" +# define LINEA_ARROWS_MOVE_BOTTOM "\ueb1c" +# define LINEA_ARROWS_MOVE_LEFT "\ueb1d" +# define LINEA_ARROWS_MOVE_RIGHT "\ueb1e" +# define LINEA_ARROWS_MOVE_TOP "\ueb1f" +# define LINEA_ARROWS_PLUS "\ueb20" +# define LINEA_ARROWS_QUESTION "\ueb21" +# define LINEA_ARROWS_REMOVE "\ueb22" +# define LINEA_ARROWS_RIGHT "\ueb23" +# define LINEA_ARROWS_RIGHT_DOUBLE "\ueb24" +# define LINEA_ARROWS_ROTATE "\ueb25" +# define LINEA_ARROWS_ROTATE_ANTI "\ueb26" +# define LINEA_ARROWS_ROTATE_ANTI_DASHED "\ueb27" +# define LINEA_ARROWS_ROTATE_DASHED "\ueb28" +# define LINEA_ARROWS_SHRINK "\ueb29" +# define LINEA_ARROWS_SHRINK_DIAGONAL1 "\ueb2a" +# define LINEA_ARROWS_SHRINK_DIAGONAL2 "\ueb2b" +# define LINEA_ARROWS_SHRINK_HORIZONAL2 "\ueb2c" +# define LINEA_ARROWS_SHRINK_HORIZONTAL1 "\ueb2d" +# define LINEA_ARROWS_SHRINK_VERTICAL1 "\ueb2e" +# define LINEA_ARROWS_SHRINK_VERTICAL2 "\ueb2f" +# define LINEA_ARROWS_SIGN_DOWN "\ueb30" +# define LINEA_ARROWS_SIGN_LEFT "\ueb31" +# define LINEA_ARROWS_SIGN_RIGHT "\ueb32" +# define LINEA_ARROWS_SIGN_UP "\ueb33" +# define LINEA_ARROWS_SLIDE_DOWN1 "\ueb34" +# define LINEA_ARROWS_SLIDE_DOWN2 "\ueb35" +# define LINEA_ARROWS_SLIDE_LEFT1 "\ueb36" +# define LINEA_ARROWS_SLIDE_LEFT2 "\ueb37" +# define LINEA_ARROWS_SLIDE_RIGHT1 "\ueb38" +# define LINEA_ARROWS_SLIDE_RIGHT2 "\ueb39" +# define LINEA_ARROWS_SLIDE_UP1 "\ueb3a" +# define LINEA_ARROWS_SLIDE_UP2 "\ueb3b" +# define LINEA_ARROWS_SLIM_DOWN "\ueb3c" +# define LINEA_ARROWS_SLIM_DOWN_DASHED "\ueb3d" +# define LINEA_ARROWS_SLIM_LEFT "\ueb3e" +# define LINEA_ARROWS_SLIM_LEFT_DASHED "\ueb3f" +# define LINEA_ARROWS_SLIM_RIGHT "\ueb40" +# define LINEA_ARROWS_SLIM_RIGHT_DASHED "\ueb41" +# define LINEA_ARROWS_SLIM_UP "\ueb42" +# define LINEA_ARROWS_SLIM_UP_DASHED "\ueb43" +# define LINEA_ARROWS_SQUARE_CHECK "\ueb44" +# define LINEA_ARROWS_SQUARE_DOWN "\ueb45" +# define LINEA_ARROWS_SQUARE_DOWNLEFT "\ueb46" +# define LINEA_ARROWS_SQUARE_DOWNRIGHT "\ueb47" +# define LINEA_ARROWS_SQUARE_LEFT "\ueb48" +# define LINEA_ARROWS_SQUARE_MINUS "\ueb49" +# define LINEA_ARROWS_SQUARE_PLUS "\ueb4a" +# define LINEA_ARROWS_SQUARE_REMOVE "\ueb4b" +# define LINEA_ARROWS_SQUARE_RIGHT "\ueb4c" +# define LINEA_ARROWS_SQUARE_UP "\ueb4d" +# define LINEA_ARROWS_SQUARE_UPLEFT "\ueb4e" +# define LINEA_ARROWS_SQUARE_UPRIGHT "\ueb4f" +# define LINEA_ARROWS_SQUARES "\ueb50" +# define LINEA_ARROWS_STRETCH_DIAGONAL1 "\ueb51" +# define LINEA_ARROWS_STRETCH_DIAGONAL2 "\ueb52" +# define LINEA_ARROWS_STRETCH_DIAGONAL3 "\ueb53" +# define LINEA_ARROWS_STRETCH_DIAGONAL4 "\ueb54" +# define LINEA_ARROWS_STRETCH_HORIZONTAL1 "\ueb55" +# define LINEA_ARROWS_STRETCH_HORIZONTAL2 "\ueb56" +# define LINEA_ARROWS_STRETCH_VERTICAL1 "\ueb57" +# define LINEA_ARROWS_STRETCH_VERTICAL2 "\ueb58" +# define LINEA_ARROWS_SWITCH_HORIZONTAL "\ueb59" +# define LINEA_ARROWS_SWITCH_VERTICAL "\ueb5a" +# define LINEA_ARROWS_UP "\ueb5b" +# define LINEA_ARROWS_UP_DOUBLE_33 "\ueb5c" +# define LINEA_ARROWS_UPLEFT "\ueb5d" +# define LINEA_ARROWS_UPRIGHT "\ueb5e" +# define LINEA_ARROWS_VERTICAL "\ueb5f" +# define LINEA_BASIC_LOCK_OPEN "\ueb60" +# define LINEA_BASIC_MAGIC_MOUSE "\ueb61" +# define LINEA_BASIC_MAGNIFIER "\ueb62" +# define LINEA_BASIC_MAGNIFIER_MINUS "\ueb63" +# define LINEA_BASIC_MAGNIFIER_PLUS "\ueb64" +# define LINEA_BASIC_MAIL "\ueb65" +# define LINEA_BASIC_MAIL_MULTIPLE "\ueb66" +# define LINEA_BASIC_MAIL_OPEN "\ueb67" +# define LINEA_BASIC_MAIL_OPEN_TEXT "\ueb68" +# define LINEA_BASIC_MALE "\ueb69" +# define LINEA_BASIC_MAP "\ueb6a" +# define LINEA_BASIC_MESSAGE "\ueb6b" +# define LINEA_BASIC_MESSAGE_MULTIPLE "\ueb6c" +# define LINEA_BASIC_MESSAGE_TXT "\ueb6d" +# define LINEA_BASIC_MIXER2 "\ueb6e" +# define LINEA_BASIC_INFO "\ueb6f" +# define LINEA_BASIC_IPOD "\ueb70" +# define LINEA_BASIC_JOYPAD "\ueb71" +# define LINEA_BASIC_KEY "\ueb72" +# define LINEA_BASIC_KEYBOARD "\ueb73" +# define LINEA_BASIC_LAPTOP "\ueb74" +# define LINEA_BASIC_LIFE_BUOY "\ueb75" +# define LINEA_BASIC_LIGHTBULB "\ueb76" +# define LINEA_BASIC_LINK "\ueb77" +# define LINEA_BASIC_LOCK "\ueb78" +# define LINEA_BASIC_MOUSE "\ueb79" +# define LINEA_BASIC_NOTEBOOK "\ueb7a" +# define LINEA_BASIC_NOTEBOOK_PEN "\ueb7b" +# define LINEA_BASIC_NOTEBOOK_PENCIL "\ueb7c" +# define LINEA_BASIC_PAPERPLANE "\ueb7d" +# define LINEA_BASIC_PENCIL_RULER "\ueb7e" +# define LINEA_BASIC_PENCIL_RULER_PEN "\ueb7f" +# define LINEA_BASIC_CLUBS "\ueb80" +# define LINEA_BASIC_COMPASS "\ueb81" +# define LINEA_BASIC_CUP "\ueb82" +# define LINEA_BASIC_DIAMONDS "\ueb83" +# define LINEA_BASIC_DISPLAY "\ueb84" +# define LINEA_BASIC_DOWNLOAD "\ueb85" +# define LINEA_BASIC_EXCLAMATION "\ueb86" +# define LINEA_BASIC_EYE "\ueb87" +# define LINEA_BASIC_EYE_CLOSED "\ueb88" +# define LINEA_BASIC_FEMALE "\ueb89" +# define LINEA_BASIC_FLAG1 "\ueb8a" +# define LINEA_BASIC_FLAG2 "\ueb8b" +# define LINEA_BASIC_FLOPPYDISK "\ueb8c" +# define LINEA_BASIC_FOLDER "\ueb8d" +# define LINEA_BASIC_FOLDER_MULTIPLE "\ueb8e" +# define LINEA_BASIC_GEAR "\ueb8f" +# define LINEA_BASIC_GEOLOCALIZE_01 "\ueb90" +# define LINEA_BASIC_GEOLOCALIZE_05 "\ueb91" +# define LINEA_BASIC_GLOBE "\ueb92" +# define LINEA_BASIC_GUNSIGHT "\ueb93" +# define LINEA_BASIC_HAMMER "\ueb94" +# define LINEA_BASIC_HEADSET "\ueb95" +# define LINEA_BASIC_HEART "\ueb96" +# define LINEA_BASIC_HEART_BROKEN "\ueb97" +# define LINEA_BASIC_HELM "\ueb98" +# define LINEA_BASIC_HOME "\ueb99" +# define LINEA_BASIC_PHOTO "\ueb9a" +# define LINEA_BASIC_RSS "\ueb9b" +# define LINEA_BASIC_PICTURE "\ueb9c" +# define LINEA_BASIC_PICTURE_MULTIPLE "\ueb9d" +# define LINEA_BASIC_PIN1 "\ueb9e" +# define LINEA_BASIC_PIN2 "\ueb9f" +# define LINEA_BASIC_ACCELERATOR "\ueba0" +# define LINEA_BASIC_ALARM "\ueba1" +# define LINEA_BASIC_ANCHOR "\ueba2" +# define LINEA_BASIC_ANTICLOCKWISE "\ueba3" +# define LINEA_BASIC_ARCHIVE "\ueba4" +# define LINEA_BASIC_ARCHIVE_FULL "\ueba5" +# define LINEA_BASIC_BAN "\ueba6" +# define LINEA_BASIC_BATTERY_CHARGE "\ueba7" +# define LINEA_BASIC_BATTERY_EMPTY "\ueba8" +# define LINEA_BASIC_BATTERY_FULL "\ueba9" +# define LINEA_BASIC_BATTERY_HALF "\uebaa" +# define LINEA_BASIC_BOLT "\uebab" +# define LINEA_BASIC_BOOK "\uebac" +# define LINEA_BASIC_BOOK_PEN "\uebad" +# define LINEA_BASIC_BOOK_PENCIL "\uebae" +# define LINEA_BASIC_BOOKMARK "\uebaf" +# define LINEA_BASIC_CALCULATOR "\uebb0" +# define LINEA_BASIC_CALENDAR "\uebb1" +# define LINEA_BASIC_CARDS_DIAMONDS "\uebb2" +# define LINEA_BASIC_CARDS_HEARTS "\uebb3" +# define LINEA_BASIC_CASE "\uebb4" +# define LINEA_BASIC_CHRONOMETER "\uebb5" +# define LINEA_BASIC_CLESSIDRE "\uebb6" +# define LINEA_BASIC_CLOCK "\uebb7" +# define LINEA_BASIC_CLOCKWISE "\uebb8" +# define LINEA_BASIC_CLOUD "\uebb9" +# define LINEA_BASIC_POSTCARD "\uebba" +# define LINEA_BASIC_POSTCARD_MULTIPLE "\uebbb" +# define LINEA_BASIC_PRINTER "\uebbc" +# define LINEA_BASIC_QUESTION "\uebbd" +# define LINEA_BASIC_SERVER "\uebbe" +# define LINEA_BASIC_SERVER2 "\uebbf" +# define LINEA_BASIC_SERVER_CLOUD "\uebc0" +# define LINEA_BASIC_SERVER_DOWNLOAD "\uebc1" +# define LINEA_BASIC_SERVER_UPLOAD "\uebc2" +# define LINEA_BASIC_SETTINGS "\uebc3" +# define LINEA_BASIC_SHARE "\uebc4" +# define LINEA_BASIC_SHEET "\uebc5" +# define LINEA_BASIC_SHEET_MULTIPLE "\uebc6" +# define LINEA_BASIC_SHEET_PEN "\uebc7" +# define LINEA_BASIC_SHEET_PENCIL "\uebc8" +# define LINEA_BASIC_SHEET_TXT "\uebc9" +# define LINEA_BASIC_SIGNS "\uebca" +# define LINEA_BASIC_SMARTPHONE "\uebcb" +# define LINEA_BASIC_SPADES "\uebcc" +# define LINEA_BASIC_SPREAD "\uebcd" +# define LINEA_BASIC_SPREAD_BOOKMARK "\uebce" +# define LINEA_BASIC_SPREAD_TEXT "\uebcf" +# define LINEA_BASIC_SPREAD_TEXT_BOOKMARK "\uebd0" +# define LINEA_BASIC_STAR "\uebd1" +# define LINEA_BASIC_TABLET "\uebd2" +# define LINEA_BASIC_TARGET "\uebd3" +# define LINEA_BASIC_TODO "\uebd4" +# define LINEA_BASIC_TODO_PEN "\uebd5" +# define LINEA_BASIC_TODO_PENCIL "\uebd6" +# define LINEA_BASIC_TODO_TXT "\uebd7" +# define LINEA_BASIC_TODOLIST_PEN "\uebd8" +# define LINEA_BASIC_TODOLIST_PENCIL "\uebd9" +# define LINEA_BASIC_TRASHCAN "\uebda" +# define LINEA_BASIC_TRASHCAN_FULL "\uebdb" +# define LINEA_BASIC_TRASHCAN_REFRESH "\uebdc" +# define LINEA_BASIC_TRASHCAN_REMOVE "\uebdd" +# define LINEA_BASIC_UPLOAD "\uebde" +# define LINEA_BASIC_USB "\uebdf" +# define LINEA_BASIC_VIDEO "\uebe0" +# define LINEA_BASIC_WATCH "\uebe1" +# define LINEA_BASIC_WEBPAGE "\uebe2" +# define LINEA_BASIC_WEBPAGE_IMG_TXT "\uebe3" +# define LINEA_BASIC_WEBPAGE_MULTIPLE "\uebe4" +# define LINEA_BASIC_WEBPAGE_TXT "\uebe5" +# define LINEA_BASIC_WORLD "\uebe6" +# define LINEA_ELABORATION_DOCUMENT_PREVIOUS "\uebe7" +# define LINEA_ELABORATION_DOCUMENT_REFRESH "\uebe8" +# define LINEA_ELABORATION_DOCUMENT_REMOVE "\uebe9" +# define LINEA_ELABORATION_DOCUMENT_SEARCH "\uebea" +# define LINEA_ELABORATION_DOCUMENT_STAR "\uebeb" +# define LINEA_ELABORATION_DOCUMENT_UPLOAD "\uebec" +# define LINEA_ELABORATION_FOLDER_CHECK "\uebed" +# define LINEA_ELABORATION_FOLDER_CLOUD "\uebee" +# define LINEA_ELABORATION_FOLDER_DOCUMENT "\uebef" +# define LINEA_ELABORATION_FOLDER_DOWNLOAD "\uebf0" +# define LINEA_ELABORATION_FOLDER_FLAGGED "\uebf1" +# define LINEA_ELABORATION_FOLDER_GRAPH "\uebf2" +# define LINEA_ELABORATION_FOLDER_HEART "\uebf3" +# define LINEA_ELABORATION_FOLDER_MINUS "\uebf4" +# define LINEA_ELABORATION_FOLDER_NEXT "\uebf5" +# define LINEA_ELABORATION_DOCUMENT_FLAGGED "\uebf6" +# define LINEA_ELABORATION_DOCUMENT_GRAPH "\uebf7" +# define LINEA_ELABORATION_DOCUMENT_HEART "\uebf8" +# define LINEA_ELABORATION_DOCUMENT_MINUS "\uebf9" +# define LINEA_ELABORATION_DOCUMENT_NEXT "\uebfa" +# define LINEA_ELABORATION_DOCUMENT_NOACCESS "\uebfb" +# define LINEA_ELABORATION_DOCUMENT_NOTE "\uebfc" +# define LINEA_ELABORATION_DOCUMENT_PENCIL "\uebfd" +# define LINEA_ELABORATION_DOCUMENT_PICTURE "\uebfe" +# define LINEA_ELABORATION_DOCUMENT_PLUS "\uebff" +# define LINEA_ELABORATION_FOLDER_NOACCESS "\uec00" +# define LINEA_ELABORATION_FOLDER_NOTE "\uec01" +# define LINEA_ELABORATION_FOLDER_PENCIL "\uec02" +# define LINEA_ELABORATION_FOLDER_PICTURE "\uec03" +# define LINEA_ELABORATION_FOLDER_PLUS "\uec04" +# define LINEA_ELABORATION_FOLDER_PREVIOUS "\uec05" +# define LINEA_ELABORATION_FOLDER_REFRESH "\uec06" +# define LINEA_ELABORATION_CALENDAR_EMPTY "\uec07" +# define LINEA_ELABORATION_CALENDAR_FLAGGED "\uec08" +# define LINEA_ELABORATION_CALENDAR_HEART "\uec09" +# define LINEA_ELABORATION_CALENDAR_MINUS "\uec0a" +# define LINEA_ELABORATION_CALENDAR_NEXT "\uec0b" +# define LINEA_ELABORATION_CALENDAR_NOACCESS "\uec0c" +# define LINEA_ELABORATION_CALENDAR_PENCIL "\uec0d" +# define LINEA_ELABORATION_CALENDAR_PLUS "\uec0e" +# define LINEA_ELABORATION_CALENDAR_PREVIOUS "\uec0f" +# define LINEA_ELABORATION_CALENDAR_REFRESH "\uec10" +# define LINEA_ELABORATION_CALENDAR_REMOVE "\uec11" +# define LINEA_ELABORATION_CALENDAR_SEARCH "\uec12" +# define LINEA_ELABORATION_CALENDAR_STAR "\uec13" +# define LINEA_ELABORATION_CALENDAR_UPLOAD "\uec14" +# define LINEA_ELABORATION_CLOUD_CHECK "\uec15" +# define LINEA_ELABORATION_CLOUD_DOWNLOAD "\uec16" +# define LINEA_ELABORATION_CLOUD_MINUS "\uec17" +# define LINEA_ELABORATION_CLOUD_NOACCESS "\uec18" +# define LINEA_ELABORATION_CLOUD_PLUS "\uec19" +# define LINEA_ELABORATION_CLOUD_REFRESH "\uec1a" +# define LINEA_ELABORATION_CLOUD_REMOVE "\uec1b" +# define LINEA_ELABORATION_CLOUD_SEARCH "\uec1c" +# define LINEA_ELABORATION_CLOUD_UPLOAD "\uec1d" +# define LINEA_ELABORATION_DOCUMENT_CHECK "\uec1e" +# define LINEA_ELABORATION_DOCUMENT_CLOUD "\uec1f" +# define LINEA_ELABORATION_DOCUMENT_DOWNLOAD "\uec20" +# define LINEA_ELABORATION_FOLDER_REMOVE "\uec21" +# define LINEA_ELABORATION_MAIL_HEART "\uec22" +# define LINEA_ELABORATION_FOLDER_SEARCH "\uec23" +# define LINEA_ELABORATION_FOLDER_STAR "\uec24" +# define LINEA_ELABORATION_FOLDER_UPLOAD "\uec25" +# define LINEA_ELABORATION_MAIL_CHECK "\uec26" +# define LINEA_ELABORATION_BOOKMARK_CHECCK "\uec27" +# define LINEA_ELABORATION_BOOKMARK_MINUS "\uec28" +# define LINEA_ELABORATION_BOOKMARK_PLUS "\uec29" +# define LINEA_ELABORATION_BOOKMARK_REMOVE "\uec2a" +# define LINEA_ELABORATION_BRIEFCASE_CHECK "\uec2b" +# define LINEA_ELABORATION_BRIEFCASE_DOWNLOAD "\uec2c" +# define LINEA_ELABORATION_BRIEFCASE_FLAGGED "\uec2d" +# define LINEA_ELABORATION_BRIEFCASE_MINUS "\uec2e" +# define LINEA_ELABORATION_BRIEFCASE_PLUS "\uec2f" +# define LINEA_ELABORATION_BRIEFCASE_REFRESH "\uec30" +# define LINEA_ELABORATION_BRIEFCASE_REMOVE "\uec31" +# define LINEA_ELABORATION_BRIEFCASE_SEARCH "\uec32" +# define LINEA_ELABORATION_BRIEFCASE_STAR "\uec33" +# define LINEA_ELABORATION_BRIEFCASE_UPLOAD "\uec34" +# define LINEA_ELABORATION_BROWSER_CHECK "\uec35" +# define LINEA_ELABORATION_BROWSER_DOWNLOAD "\uec36" +# define LINEA_ELABORATION_BROWSER_MINUS "\uec37" +# define LINEA_ELABORATION_BROWSER_PLUS "\uec38" +# define LINEA_ELABORATION_BROWSER_REFRESH "\uec39" +# define LINEA_ELABORATION_BROWSER_REMOVE "\uec3a" +# define LINEA_ELABORATION_BROWSER_SEARCH "\uec3b" +# define LINEA_ELABORATION_BROWSER_STAR "\uec3c" +# define LINEA_ELABORATION_BROWSER_UPLOAD "\uec3d" +# define LINEA_ELABORATION_CALENDAR_CHECK "\uec3e" +# define LINEA_ELABORATION_CALENDAR_CLOUD "\uec3f" +# define LINEA_ELABORATION_CALENDAR_DOWNLOAD "\uec40" +# define LINEA_ELABORATION_MAIL_CLOUD "\uec41" +# define LINEA_ELABORATION_MAIL_DOCUMENT "\uec42" +# define LINEA_ELABORATION_MAIL_DOWNLOAD "\uec43" +# define LINEA_ELABORATION_MAIL_FLAGGED "\uec44" +# define LINEA_ELABORATION_MAIL_NEXT "\uec45" +# define LINEA_ELABORATION_MAIL_NOACCESS "\uec46" +# define LINEA_ELABORATION_MAIL_NOTE "\uec47" +# define LINEA_ELABORATION_MAIL_PENCIL "\uec48" +# define LINEA_ELABORATION_MAIL_PICTURE "\uec49" +# define LINEA_ELABORATION_MAIL_PREVIOUS "\uec4a" +# define LINEA_ELABORATION_MAIL_REFRESH "\uec4b" +# define LINEA_ELABORATION_MAIL_REMOVE "\uec4c" +# define LINEA_ELABORATION_MAIL_SEARCH "\uec4d" +# define LINEA_ELABORATION_MAIL_STAR "\uec4e" +# define LINEA_ELABORATION_MAIL_UPLOAD "\uec4f" +# define LINEA_ELABORATION_MESSAGE_CHECK "\uec50" +# define LINEA_ELABORATION_MESSAGE_DOTS "\uec51" +# define LINEA_ELABORATION_MESSAGE_HAPPY "\uec52" +# define LINEA_ELABORATION_MESSAGE_HEART "\uec53" +# define LINEA_ELABORATION_MESSAGE_MINUS "\uec54" +# define LINEA_ELABORATION_MESSAGE_NOTE "\uec55" +# define LINEA_ELABORATION_MESSAGE_PLUS "\uec56" +# define LINEA_ELABORATION_MESSAGE_REFRESH "\uec57" +# define LINEA_ELABORATION_MESSAGE_REMOVE "\uec58" +# define LINEA_ELABORATION_MESSAGE_SAD "\uec59" +# define LINEA_ELABORATION_SMARTPHONE_CLOUD "\uec5a" +# define LINEA_ELABORATION_SMARTPHONE_HEART "\uec5b" +# define LINEA_ELABORATION_SMARTPHONE_NOACCESS "\uec5c" +# define LINEA_ELABORATION_SMARTPHONE_NOTE "\uec5d" +# define LINEA_ELABORATION_SMARTPHONE_PENCIL "\uec5e" +# define LINEA_ELABORATION_SMARTPHONE_PICTURE "\uec5f" +# define LINEA_ELABORATION_SMARTPHONE_REFRESH "\uec60" +# define LINEA_ELABORATION_SMARTPHONE_SEARCH "\uec61" +# define LINEA_ELABORATION_TABLET_CLOUD "\uec62" +# define LINEA_ELABORATION_TABLET_HEART "\uec63" +# define LINEA_ELABORATION_TABLET_NOACCESS "\uec64" +# define LINEA_ELABORATION_TABLET_NOTE "\uec65" +# define LINEA_ELABORATION_TABLET_PENCIL "\uec66" +# define LINEA_ELABORATION_TABLET_PICTURE "\uec67" +# define LINEA_ELABORATION_TABLET_REFRESH "\uec68" +# define LINEA_ELABORATION_TABLET_SEARCH "\uec69" +# define LINEA_ELABORATION_TODOLIST_2 "\uec6a" +# define LINEA_ELABORATION_TODOLIST_CHECK "\uec6b" +# define LINEA_ELABORATION_TODOLIST_CLOUD "\uec6c" +# define LINEA_ELABORATION_TODOLIST_DOWNLOAD "\uec6d" +# define LINEA_ELABORATION_TODOLIST_FLAGGED "\uec6e" +# define LINEA_ELABORATION_TODOLIST_MINUS "\uec6f" +# define LINEA_ELABORATION_TODOLIST_NOACCESS "\uec70" +# define LINEA_ELABORATION_TODOLIST_PENCIL "\uec71" +# define LINEA_ELABORATION_TODOLIST_PLUS "\uec72" +# define LINEA_ELABORATION_TODOLIST_REFRESH "\uec73" +# define LINEA_ELABORATION_TODOLIST_REMOVE "\uec74" +# define LINEA_ELABORATION_TODOLIST_SEARCH "\uec75" +# define LINEA_ELABORATION_TODOLIST_STAR "\uec76" +# define LINEA_ELABORATION_TODOLIST_UPLOAD "\uec77" +# define LINEA_ECOMMERCE_RECEIPT_KIPS "\uec78" +# define LINEA_ECOMMERCE_RECEIPT_LIRA "\uec79" +# define LINEA_ECOMMERCE_RECEIPT_NAIRA "\uec7a" +# define LINEA_ECOMMERCE_RECEIPT_PESOS "\uec7b" +# define LINEA_ECOMMERCE_RECEIPT_POUND "\uec7c" +# define LINEA_ECOMMERCE_RECEIPT_RUBLO "\uec7d" +# define LINEA_ECOMMERCE_RECEIPT_RUPEE "\uec7e" +# define LINEA_ECOMMERCE_RECEIPT_TUGRIK "\uec7f" +# define LINEA_ECOMMERCE_RECEIPT_WON "\uec80" +# define LINEA_ECOMMERCE_RECEIPT_YEN "\uec81" +# define LINEA_ECOMMERCE_RECEIPT_YEN2 "\uec82" +# define LINEA_ECOMMERCE_RECEPT_COLON "\uec83" +# define LINEA_ECOMMERCE_RUBLO "\uec84" +# define LINEA_ECOMMERCE_RUPEE "\uec85" +# define LINEA_ECOMMERCE_SAFE "\uec86" +# define LINEA_ECOMMERCE_NAIRA "\uec87" +# define LINEA_ECOMMERCE_PESOS "\uec88" +# define LINEA_ECOMMERCE_POUND "\uec89" +# define LINEA_ECOMMERCE_RECEIPT "\uec8a" +# define LINEA_ECOMMERCE_RECEIPT_BATH "\uec8b" +# define LINEA_ECOMMERCE_RECEIPT_CENT "\uec8c" +# define LINEA_ECOMMERCE_RECEIPT_DOLLAR "\uec8d" +# define LINEA_ECOMMERCE_RECEIPT_EURO "\uec8e" +# define LINEA_ECOMMERCE_RECEIPT_FRANC "\uec8f" +# define LINEA_ECOMMERCE_RECEIPT_GUARANI "\uec90" +# define LINEA_ECOMMERCE_SALE "\uec91" +# define LINEA_ECOMMERCE_SALES "\uec92" +# define LINEA_ECOMMERCE_TICKET "\uec93" +# define LINEA_ECOMMERCE_TUGRIKS "\uec94" +# define LINEA_ECOMMERCE_WALLET "\uec95" +# define LINEA_ECOMMERCE_WON "\uec96" +# define LINEA_ECOMMERCE_YEN "\uec97" +# define LINEA_ECOMMERCE_CART_CONTENT "\uec98" +# define LINEA_ECOMMERCE_CART_DOWNLOAD "\uec99" +# define LINEA_ECOMMERCE_CART_MINUS "\uec9a" +# define LINEA_ECOMMERCE_CART_PLUS "\uec9b" +# define LINEA_ECOMMERCE_CART_REFRESH "\uec9c" +# define LINEA_ECOMMERCE_CART_REMOVE "\uec9d" +# define LINEA_ECOMMERCE_CART_SEARCH "\uec9e" +# define LINEA_ECOMMERCE_CART_UPLOAD "\uec9f" +# define LINEA_ECOMMERCE_CENT "\ueca0" +# define LINEA_ECOMMERCE_COLON "\ueca1" +# define LINEA_ECOMMERCE_CREDITCARD "\ueca2" +# define LINEA_ECOMMERCE_DIAMOND "\ueca3" +# define LINEA_ECOMMERCE_DOLLAR "\ueca4" +# define LINEA_ECOMMERCE_EURO "\ueca5" +# define LINEA_ECOMMERCE_FRANC "\ueca6" +# define LINEA_ECOMMERCE_GIFT "\ueca7" +# define LINEA_ECOMMERCE_GRAPH1 "\ueca8" +# define LINEA_ECOMMERCE_GRAPH2 "\ueca9" +# define LINEA_ECOMMERCE_GRAPH3 "\uecaa" +# define LINEA_ECOMMERCE_GRAPH_DECREASE "\uecab" +# define LINEA_ECOMMERCE_GRAPH_INCREASE "\uecac" +# define LINEA_ECOMMERCE_GUARANI "\uecad" +# define LINEA_ECOMMERCE_KIPS "\uecae" +# define LINEA_ECOMMERCE_LIRA "\uecaf" +# define LINEA_ECOMMERCE_MEGAPHONE "\uecb0" +# define LINEA_ECOMMERCE_MONEY "\uecb1" +# define LINEA_ECOMMERCE_YEN2 "\uecb2" +# define LINEA_ECOMMERCE_BAG "\uecb3" +# define LINEA_ECOMMERCE_BAG_CHECK "\uecb4" +# define LINEA_ECOMMERCE_BAG_CLOUD "\uecb5" +# define LINEA_ECOMMERCE_BAG_DOWNLOAD "\uecb6" +# define LINEA_ECOMMERCE_BAG_MINUS "\uecb7" +# define LINEA_ECOMMERCE_BAG_PLUS "\uecb8" +# define LINEA_ECOMMERCE_BAG_REFRESH "\uecb9" +# define LINEA_ECOMMERCE_BAG_REMOVE "\uecba" +# define LINEA_ECOMMERCE_BAG_SEARCH "\uecbb" +# define LINEA_ECOMMERCE_BAG_UPLOAD "\uecbc" +# define LINEA_ECOMMERCE_BANKNOTE "\uecbd" +# define LINEA_ECOMMERCE_BANKNOTES "\uecbe" +# define LINEA_ECOMMERCE_BASKET "\uecbf" +# define LINEA_ECOMMERCE_BASKET_CHECK "\uecc0" +# define LINEA_ECOMMERCE_BASKET_CLOUD "\uecc1" +# define LINEA_ECOMMERCE_BASKET_DOWNLOAD "\uecc2" +# define LINEA_ECOMMERCE_BASKET_MINUS "\uecc3" +# define LINEA_ECOMMERCE_BASKET_PLUS "\uecc4" +# define LINEA_ECOMMERCE_BASKET_REFRESH "\uecc5" +# define LINEA_ECOMMERCE_BASKET_REMOVE "\uecc6" +# define LINEA_ECOMMERCE_BASKET_SEARCH "\uecc7" +# define LINEA_ECOMMERCE_BASKET_UPLOAD "\uecc8" +# define LINEA_ECOMMERCE_BATH "\uecc9" +# define LINEA_ECOMMERCE_CART "\uecca" +# define LINEA_ECOMMERCE_CART_CHECK "\ueccb" +# define LINEA_ECOMMERCE_CART_CLOUD "\ueccc" +# define LINEA_MUSIC_STOP_BUTTON "\ueccd" +# define LINEA_MUSIC_TAPE "\uecce" +# define LINEA_MUSIC_VOLUME_DOWN "\ueccf" +# define LINEA_MUSIC_VOLUME_UP "\uecd0" +# define LINEA_MUSIC_BEGINNING_BUTTON "\uecd1" +# define LINEA_MUSIC_BELL "\uecd2" +# define LINEA_MUSIC_CD "\uecd3" +# define LINEA_MUSIC_DIAPASON "\uecd4" +# define LINEA_MUSIC_EJECT_BUTTON "\uecd5" +# define LINEA_MUSIC_END_BUTTON "\uecd6" +# define LINEA_MUSIC_FASTFORWARD_BUTTON "\uecd7" +# define LINEA_MUSIC_HEADPHONES "\uecd8" +# define LINEA_MUSIC_IPOD "\uecd9" +# define LINEA_MUSIC_LOUDSPEAKER "\uecda" +# define LINEA_MUSIC_MICROPHONE "\uecdb" +# define LINEA_MUSIC_MICROPHONE_OLD "\uecdc" +# define LINEA_MUSIC_MIXER "\uecdd" +# define LINEA_MUSIC_MUTE "\uecde" +# define LINEA_MUSIC_NOTE_MULTIPLE "\uecdf" +# define LINEA_MUSIC_NOTE_SINGLE "\uece0" +# define LINEA_MUSIC_PAUSE_BUTTON "\uece1" +# define LINEA_MUSIC_PLAY_BUTTON "\uece2" +# define LINEA_MUSIC_PLAYLIST "\uece3" +# define LINEA_MUSIC_RADIO_GHETTOBLASTER "\uece4" +# define LINEA_MUSIC_RADIO_PORTABLE "\uece5" +# define LINEA_MUSIC_RECORD "\uece6" +# define LINEA_MUSIC_RECORDPLAYER "\uece7" +# define LINEA_MUSIC_REPEAT_BUTTON "\uece8" +# define LINEA_MUSIC_REWIND_BUTTON "\uece9" +# define LINEA_MUSIC_SHUFFLE_BUTTON "\uecea" +# define LINEA_SOFTWARE_PARAGRAPH_JUSTIFY_CENTER "\ueceb" +# define LINEA_SOFTWARE_PARAGRAPH_JUSTIFY_LEFT "\uecec" +# define LINEA_SOFTWARE_PARAGRAPH_JUSTIFY_RIGHT "\ueced" +# define LINEA_SOFTWARE_PARAGRAPH_SPACE_AFTER "\uecee" +# define LINEA_SOFTWARE_PARAGRAPH_SPACE_BEFORE "\uecef" +# define LINEA_SOFTWARE_PATHFINDER_EXCLUDE "\uecf0" +# define LINEA_SOFTWARE_PATHFINDER_INTERSECT "\uecf1" +# define LINEA_SOFTWARE_PATHFINDER_SUBTRACT "\uecf2" +# define LINEA_SOFTWARE_PATHFINDER_UNITE "\uecf3" +# define LINEA_SOFTWARE_PEN "\uecf4" +# define LINEA_SOFTWARE_PEN_ADD "\uecf5" +# define LINEA_SOFTWARE_PEN_REMOVE "\uecf6" +# define LINEA_SOFTWARE_PENCIL "\uecf7" +# define LINEA_SOFTWARE_POLYGONALLASSO "\uecf8" +# define LINEA_SOFTWARE_REFLECT_HORIZONTAL "\uecf9" +# define LINEA_SOFTWARE_MAGNETE "\uecfa" +# define LINEA_SOFTWARE_PAGES "\uecfb" +# define LINEA_SOFTWARE_PAINTBRUSH "\uecfc" +# define LINEA_SOFTWARE_PAINTBUCKET "\uecfd" +# define LINEA_SOFTWARE_PAINTROLLER "\uecfe" +# define LINEA_SOFTWARE_PARAGRAPH "\uecff" +# define LINEA_SOFTWARE_PARAGRAPH_ALIGN_LEFT "\ued00" +# define LINEA_SOFTWARE_PARAGRAPH_ALIGN_RIGHT "\ued01" +# define LINEA_SOFTWARE_PARAGRAPH_CENTER "\ued02" +# define LINEA_SOFTWARE_PARAGRAPH_JUSTIFY_ALL "\ued03" +# define LINEA_SOFTWARE_REFLECT_VERTICAL "\ued04" +# define LINEA_SOFTWARE_REMOVE_VECTORPOINT "\ued05" +# define LINEA_SOFTWARE_SCALE_EXPAND "\ued06" +# define LINEA_SOFTWARE_SCALE_REDUCE "\ued07" +# define LINEA_SOFTWARE_SELECTION_OVAL "\ued08" +# define LINEA_SOFTWARE_SELECTION_POLYGON "\ued09" +# define LINEA_SOFTWARE_SELECTION_RECTANGLE "\ued0a" +# define LINEA_SOFTWARE_INDENT_FIRSTLINE "\ued0b" +# define LINEA_SOFTWARE_INDENT_LEFT "\ued0c" +# define LINEA_SOFTWARE_INDENT_RIGHT "\ued0d" +# define LINEA_SOFTWARE_LASSO "\ued0e" +# define LINEA_SOFTWARE_LAYERS1 "\ued0f" +# define LINEA_SOFTWARE_LAYERS2 "\ued10" +# define LINEA_SOFTWARE_LAYOUT "\ued11" +# define LINEA_SOFTWARE_LAYOUT_2COLUMNS "\ued12" +# define LINEA_SOFTWARE_LAYOUT_3COLUMNS "\ued13" +# define LINEA_SOFTWARE_LAYOUT_4BOXES "\ued14" +# define LINEA_SOFTWARE_LAYOUT_4COLUMNS "\ued15" +# define LINEA_SOFTWARE_LAYOUT_4LINES "\ued16" +# define LINEA_SOFTWARE_LAYOUT_8BOXES "\ued17" +# define LINEA_SOFTWARE_LAYOUT_HEADER "\ued18" +# define LINEA_SOFTWARE_LAYOUT_HEADER_2COLUMNS "\ued19" +# define LINEA_SOFTWARE_LAYOUT_HEADER_3COLUMNS "\ued1a" +# define LINEA_SOFTWARE_LAYOUT_HEADER_4BOXES "\ued1b" +# define LINEA_SOFTWARE_LAYOUT_HEADER_4COLUMNS "\ued1c" +# define LINEA_SOFTWARE_LAYOUT_HEADER_COMPLEX "\ued1d" +# define LINEA_SOFTWARE_LAYOUT_HEADER_COMPLEX2 "\ued1e" +# define LINEA_SOFTWARE_LAYOUT_HEADER_COMPLEX3 "\ued1f" +# define LINEA_SOFTWARE_LAYOUT_HEADER_COMPLEX4 "\ued20" +# define LINEA_SOFTWARE_LAYOUT_HEADER_SIDELEFT "\ued21" +# define LINEA_SOFTWARE_LAYOUT_HEADER_SIDERIGHT "\ued22" +# define LINEA_SOFTWARE_LAYOUT_SIDEBAR_LEFT "\ued23" +# define LINEA_SOFTWARE_LAYOUT_SIDEBAR_RIGHT "\ued24" +# define LINEA_SOFTWARE_SELECTION_ROUNDEDRECTANGLE "\ued25" +# define LINEA_SOFTWARE_VECTOR_LINE "\ued26" +# define LINEA_SOFTWARE_SHAPE_OVAL "\ued27" +# define LINEA_SOFTWARE_SHAPE_POLYGON "\ued28" +# define LINEA_SOFTWARE_SHAPE_RECTANGLE "\ued29" +# define LINEA_SOFTWARE_SHAPE_ROUNDEDRECTANGLE "\ued2a" +# define LINEA_SOFTWARE_ADD_VECTORPOINT "\ued2b" +# define LINEA_SOFTWARE_BOX_OVAL "\ued2c" +# define LINEA_SOFTWARE_BOX_POLYGON "\ued2d" +# define LINEA_SOFTWARE_BOX_RECTANGLE "\ued2e" +# define LINEA_SOFTWARE_BOX_ROUNDEDRECTANGLE "\ued2f" +# define LINEA_SOFTWARE_CHARACTER "\ued30" +# define LINEA_SOFTWARE_CROP "\ued31" +# define LINEA_SOFTWARE_EYEDROPPER "\ued32" +# define LINEA_SOFTWARE_FONT_ALLCAPS "\ued33" +# define LINEA_SOFTWARE_FONT_BASELINE_SHIFT "\ued34" +# define LINEA_SOFTWARE_FONT_HORIZONTAL_SCALE "\ued35" +# define LINEA_SOFTWARE_FONT_KERNING "\ued36" +# define LINEA_SOFTWARE_FONT_LEADING "\ued37" +# define LINEA_SOFTWARE_FONT_SIZE "\ued38" +# define LINEA_SOFTWARE_FONT_SMALLCAPITAL "\ued39" +# define LINEA_SOFTWARE_FONT_SMALLCAPS "\ued3a" +# define LINEA_SOFTWARE_FONT_STRIKETHROUGH "\ued3b" +# define LINEA_SOFTWARE_FONT_TRACKING "\ued3c" +# define LINEA_SOFTWARE_FONT_UNDERLINE "\ued3d" +# define LINEA_SOFTWARE_FONT_VERTICAL_SCALE "\ued3e" +# define LINEA_SOFTWARE_HORIZONTAL_ALIGN_CENTER "\ued3f" +# define LINEA_SOFTWARE_HORIZONTAL_ALIGN_LEFT "\ued40" +# define LINEA_SOFTWARE_HORIZONTAL_ALIGN_RIGHT "\ued41" +# define LINEA_SOFTWARE_HORIZONTAL_DISTRIBUTE_CENTER "\ued42" +# define LINEA_SOFTWARE_HORIZONTAL_DISTRIBUTE_LEFT "\ued43" +# define LINEA_SOFTWARE_HORIZONTAL_DISTRIBUTE_RIGHT "\ued44" +# define LINEA_SOFTWARE_SLICE "\ued45" +# define LINEA_SOFTWARE_TRANSFORM_BEZIER "\ued46" +# define LINEA_SOFTWARE_VECTOR_BOX "\ued47" +# define LINEA_SOFTWARE_VECTOR_COMPOSITE "\ued48" +# define LINEA_SOFTWARE_VERTICAL_ALIGN_BOTTOM "\ued49" +# define LINEA_SOFTWARE_VERTICAL_ALIGN_CENTER "\ued4a" +# define LINEA_SOFTWARE_VERTICAL_ALIGN_TOP "\ued4b" +# define LINEA_SOFTWARE_VERTICAL_DISTRIBUTE_BOTTOM "\ued4c" +# define LINEA_SOFTWARE_VERTICAL_DISTRIBUTE_CENTER "\ued4d" +# define LINEA_SOFTWARE_VERTICAL_DISTRIBUTE_TOP "\ued4e" +# define LINEA_WEATHER_AQUARIUS "\ued4f" +# define LINEA_WEATHER_ARIES "\ued50" +# define LINEA_WEATHER_CANCER "\ued51" +# define LINEA_WEATHER_CAPRICORN "\ued52" +# define LINEA_WEATHER_CLOUD "\ued53" +# define LINEA_WEATHER_CLOUD_DROP "\ued54" +# define LINEA_WEATHER_CLOUD_LIGHTNING "\ued55" +# define LINEA_WEATHER_CLOUD_SNOWFLAKE "\ued56" +# define LINEA_WEATHER_DOWNPOUR_FULLMOON "\ued57" +# define LINEA_WEATHER_DOWNPOUR_HALFMOON "\ued58" +# define LINEA_WEATHER_DOWNPOUR_SUN "\ued59" +# define LINEA_WEATHER_DROP "\ued5a" +# define LINEA_WEATHER_FIRST_QUARTER "\ued5b" +# define LINEA_WEATHER_FOG "\ued5c" +# define LINEA_WEATHER_FOG_FULLMOON "\ued5d" +# define LINEA_WEATHER_FOG_HALFMOON "\ued5e" +# define LINEA_WEATHER_FOG_SUN "\ued5f" +# define LINEA_WEATHER_FULLMOON "\ued60" +# define LINEA_WEATHER_GEMINI "\ued61" +# define LINEA_WEATHER_HAIL "\ued62" +# define LINEA_WEATHER_HAIL_FULLMOON "\ued63" +# define LINEA_WEATHER_HAIL_HALFMOON "\ued64" +# define LINEA_WEATHER_HAIL_SUN "\ued65" +# define LINEA_WEATHER_LAST_QUARTER "\ued66" +# define LINEA_WEATHER_LEO "\ued67" +# define LINEA_WEATHER_LIBRA "\ued68" +# define LINEA_WEATHER_LIGHTNING "\ued69" +# define LINEA_WEATHER_MISTYRAIN "\ued6a" +# define LINEA_WEATHER_MISTYRAIN_FULLMOON "\ued6b" +# define LINEA_WEATHER_MISTYRAIN_HALFMOON "\ued6c" +# define LINEA_WEATHER_MISTYRAIN_SUN "\ued6d" +# define LINEA_WEATHER_MOON "\ued6e" +# define LINEA_WEATHER_MOONDOWN_FULL "\ued6f" +# define LINEA_WEATHER_MOONDOWN_HALF "\ued70" +# define LINEA_WEATHER_MOONSET_FULL "\ued71" +# define LINEA_WEATHER_MOONSET_HALF "\ued72" +# define LINEA_WEATHER_MOVE2 "\ued73" +# define LINEA_WEATHER_NEWMOON "\ued74" +# define LINEA_WEATHER_PISCES "\ued75" +# define LINEA_WEATHER_RAIN "\ued76" +# define LINEA_WEATHER_RAIN_FULLMOON "\ued77" +# define LINEA_WEATHER_RAIN_HALFMOON "\ued78" +# define LINEA_WEATHER_RAIN_SUN "\ued79" +# define LINEA_WEATHER_SAGITTARIUS "\ued7a" +# define LINEA_WEATHER_SCORPIO "\ued7b" +# define LINEA_WEATHER_SNOW "\ued7c" +# define LINEA_WEATHER_SNOW_FULLMOON "\ued7d" +# define LINEA_WEATHER_SNOW_HALFMOON "\ued7e" +# define LINEA_WEATHER_SNOW_SUN "\ued7f" +# define LINEA_WEATHER_SNOWFLAKE "\ued80" +# define LINEA_WEATHER_STAR "\ued81" +# define LINEA_WEATHER_STORM_11 "\ued82" +# define LINEA_WEATHER_STORM_32 "\ued83" +# define LINEA_WEATHER_STORM_FULLMOON "\ued84" +# define LINEA_WEATHER_STORM_HALFMOON "\ued85" +# define LINEA_WEATHER_STORM_SUN "\ued86" +# define LINEA_WEATHER_SUN "\ued87" +# define LINEA_WEATHER_SUNDOWN "\ued88" +# define LINEA_WEATHER_SUNSET "\ued89" +# define LINEA_WEATHER_TAURUS "\ued8a" +# define LINEA_WEATHER_TEMPEST "\ued8b" +# define LINEA_WEATHER_TEMPEST_FULLMOON "\ued8c" +# define LINEA_WEATHER_TEMPEST_HALFMOON "\ued8d" +# define LINEA_WEATHER_TEMPEST_SUN "\ued8e" +# define LINEA_WEATHER_VARIABLE_FULLMOON "\ued8f" +# define LINEA_WEATHER_VARIABLE_HALFMOON "\ued90" +# define LINEA_WEATHER_VARIABLE_SUN "\ued91" +# define LINEA_WEATHER_VIRGO "\ued92" +# define LINEA_WEATHER_WANING_CRESENT "\ued93" +# define LINEA_WEATHER_WANING_GIBBOUS "\ued94" +# define LINEA_WEATHER_WAXING_CRESENT "\ued95" +# define LINEA_WEATHER_WAXING_GIBBOUS "\ued96" +# define LINEA_WEATHER_WIND "\ued97" +# define LINEA_WEATHER_WIND_E "\ued98" +# define LINEA_WEATHER_WIND_FULLMOON "\ued99" +# define LINEA_WEATHER_WIND_HALFMOON "\ued9a" +# define LINEA_WEATHER_WIND_N "\ued9b" +# define LINEA_WEATHER_WIND_NE "\ued9c" +# define LINEA_WEATHER_WIND_NW "\ued9d" +# define LINEA_WEATHER_WIND_S "\ued9e" +# define LINEA_WEATHER_WIND_SE "\ued9f" +# define LINEA_WEATHER_WIND_SUN "\ueda0" +# define LINEA_WEATHER_WIND_SW "\ueda1" +# define LINEA_WEATHER_WIND_W "\ueda2" +# define LINEA_WEATHER_WINDGUST "\ueda3" +# define MFIZZ_3DPRINT "\ueda4" +# define MFIZZ_ALPINELINUX "\ueda5" +# define MFIZZ_ANGULAR "\ueda6" +# define MFIZZ_ANGULAR_ALT "\ueda7" +# define MFIZZ_ANTENNA "\ueda8" +# define MFIZZ_APACHE "\ueda9" +# define MFIZZ_ARCHLINUX "\uedaa" +# define MFIZZ_AWS "\uedab" +# define MFIZZ_AZURE "\uedac" +# define MFIZZ_BACKBONE "\uedad" +# define MFIZZ_BLACKBERRY "\uedae" +# define MFIZZ_BOMB "\uedaf" +# define MFIZZ_BOOTSTRAP "\uedb0" +# define MFIZZ_C "\uedb1" +# define MFIZZ_CASSANDRA "\uedb2" +# define MFIZZ_CENTOS "\uedb3" +# define MFIZZ_CLOJURE "\uedb4" +# define MFIZZ_CODEIGNITER "\uedb5" +# define MFIZZ_CODEPEN "\uedb6" +# define MFIZZ_COFFEE_BEAN "\uedb7" +# define MFIZZ_CPLUSPLUS "\uedb8" +# define MFIZZ_CSHARP "\uedb9" +# define MFIZZ_CSS "\uedba" +# define MFIZZ_CSS3 "\uedbb" +# define MFIZZ_CSS3_ALT "\uedbc" +# define MFIZZ_D3 "\uedbd" +# define MFIZZ_DATABASE "\uedbe" +# define MFIZZ_DATABASE_ALT "\uedbf" +# define MFIZZ_DATABASE_ALT2 "\uedc0" +# define MFIZZ_DEBIAN "\uedc1" +# define MFIZZ_DOCKER "\uedc2" +# define MFIZZ_DREAMHOST "\uedc3" +# define MFIZZ_ELIXIR "\uedc4" +# define MFIZZ_ELM "\uedc5" +# define MFIZZ_ERLANG "\uedc6" +# define MFIZZ_EXHERBO "\uedc7" +# define MFIZZ_FEDORA "\uedc8" +# define MFIZZ_FIRE_ALT "\uedc9" +# define MFIZZ_FREEBSD "\uedca" +# define MFIZZ_FREECODECAMP "\uedcb" +# define MFIZZ_GENTOO "\uedcc" +# define MFIZZ_GHOST "\uedcd" +# define MFIZZ_GIT "\uedce" +# define MFIZZ_GNOME "\uedcf" +# define MFIZZ_GO "\uedd0" +# define MFIZZ_GO_ALT "\uedd1" +# define MFIZZ_GOOGLE "\uedd2" +# define MFIZZ_GOOGLE_ALT "\uedd3" +# define MFIZZ_GOOGLE_CODE "\uedd4" +# define MFIZZ_GOOGLE_DEVELOPERS "\uedd5" +# define MFIZZ_GRADLE "\uedd6" +# define MFIZZ_GRAILS "\uedd7" +# define MFIZZ_GRAILS_ALT "\uedd8" +# define MFIZZ_GRUNT "\uedd9" +# define MFIZZ_GULP "\uedda" +# define MFIZZ_GULP_ALT "\ueddb" +# define MFIZZ_HADOOP "\ueddc" +# define MFIZZ_HASKELL "\ueddd" +# define MFIZZ_HEROKU "\uedde" +# define MFIZZ_HTML "\ueddf" +# define MFIZZ_HTML5 "\uede0" +# define MFIZZ_HTML5_ALT "\uede1" +# define MFIZZ_IPHONE "\uede2" +# define MFIZZ_JAVA "\uede3" +# define MFIZZ_JAVA_BOLD "\uede4" +# define MFIZZ_JAVA_DUKE "\uede5" +# define MFIZZ_JAVASCRIPT "\uede6" +# define MFIZZ_JAVASCRIPT_ALT "\uede7" +# define MFIZZ_JETTY "\uede8" +# define MFIZZ_JQUERY "\uede9" +# define MFIZZ_KDE "\uedea" +# define MFIZZ_LARAVEL "\uedeb" +# define MFIZZ_LINE_GRAPH "\uedec" +# define MFIZZ_LINUX_MINT "\ueded" +# define MFIZZ_LOOKING "\uedee" +# define MFIZZ_MAGENTO "\uedef" +# define MFIZZ_MARIADB "\uedf0" +# define MFIZZ_MAVEN "\uedf1" +# define MFIZZ_MICROSCOPE "\uedf2" +# define MFIZZ_MOBILE_DEVICE "\uedf3" +# define MFIZZ_MOBILE_PHONE_ALT "\uedf4" +# define MFIZZ_MOBILE_PHONE_BROADCAST "\uedf5" +# define MFIZZ_MONGODB "\uedf6" +# define MFIZZ_MSSQL "\uedf7" +# define MFIZZ_MYSQL "\uedf8" +# define MFIZZ_MYSQL_ALT "\uedf9" +# define MFIZZ_NETBSD "\uedfa" +# define MFIZZ_NGINX "\uedfb" +# define MFIZZ_NGINX_ALT "\uedfc" +# define MFIZZ_NGINX_ALT2 "\uedfd" +# define MFIZZ_NODEJS "\uedfe" +# define MFIZZ_NPM "\uedff" +# define MFIZZ_OBJC "\uee00" +# define MFIZZ_OPENSHIFT "\uee01" +# define MFIZZ_ORACLE "\uee02" +# define MFIZZ_ORACLE_ALT "\uee03" +# define MFIZZ_OSX "\uee04" +# define MFIZZ_PERL "\uee05" +# define MFIZZ_PHONE_ALT "\uee06" +# define MFIZZ_PHONE_GAP "\uee07" +# define MFIZZ_PHONE_RETRO "\uee08" +# define MFIZZ_PHP "\uee09" +# define MFIZZ_PHP_ALT "\uee0a" +# define MFIZZ_PLAYFRAMEWORK "\uee0b" +# define MFIZZ_PLAYFRAMEWORK_ALT "\uee0c" +# define MFIZZ_PLONE "\uee0d" +# define MFIZZ_POSTGRES "\uee0e" +# define MFIZZ_POSTGRES_ALT "\uee0f" +# define MFIZZ_PYTHON "\uee10" +# define MFIZZ_RASPBERRYPI "\uee11" +# define MFIZZ_REACTJS "\uee12" +# define MFIZZ_REDHAT "\uee13" +# define MFIZZ_REDIS "\uee14" +# define MFIZZ_RUBY "\uee15" +# define MFIZZ_RUBY_ON_RAILS "\uee16" +# define MFIZZ_RUBY_ON_RAILS_ALT "\uee17" +# define MFIZZ_RUST "\uee18" +# define MFIZZ_SASS "\uee19" +# define MFIZZ_SATELLITE "\uee1a" +# define MFIZZ_SCALA "\uee1b" +# define MFIZZ_SCALA_ALT "\uee1c" +# define MFIZZ_SCRIPT "\uee1d" +# define MFIZZ_SCRIPT_ALT "\uee1e" +# define MFIZZ_SHELL "\uee1f" +# define MFIZZ_SITEFINITY "\uee20" +# define MFIZZ_SOLARIS "\uee21" +# define MFIZZ_SPLATTER "\uee22" +# define MFIZZ_SPRING "\uee23" +# define MFIZZ_SUSE "\uee24" +# define MFIZZ_SVG "\uee25" +# define MFIZZ_SYMFONY "\uee26" +# define MFIZZ_TOMCAT "\uee27" +# define MFIZZ_UBUNTU "\uee28" +# define MFIZZ_UNITY "\uee29" +# define MFIZZ_WIRELESS "\uee2a" +# define MFIZZ_WORDPRESS "\uee2b" +# define MFIZZ_X11 "\uee2c" +# define FIRACODE_ASTERISK "\uee2d" +# define FIRACODE_PLUS "\uee2e" +# define FIRACODE_HYPHEN "\uee2f" +# define FIRACODE_SAD "\uee30" +# define FIRACODE_W_W_W "\uee31" +# define FIRACODE_ASTERISK_ASTERISK "\uee32" +# define FIRACODE_ASTERISK_ASTERISK_ASTERISK "\uee33" +# define FIRACODE_ASTERISK_ASTERISK_SLASH "\uee34" +# define FIRACODE_ASTERISK_GREATER "\uee35" +# define FIRACODE_ASTERISK_SLASH "\uee36" +# define FIRACODE_BACKSLASH_BACKSLASH "\uee37" +# define FIRACODE_BBB "\uee38" +# define FIRACODE_BRACELEFT_HYPHEN "\uee39" +# define FIRACODE_BRACKETLEFT_BRACKETRIGHT "\uee3a" +# define FIRACODE_COLON_COLON "\uee3b" +# define FIRACODE_COLON_COLON_COLON "\uee3c" +# define FIRACODE_COLON_EQUAL "\uee3d" +# define FIRACODE_EXCLAM_EXCLAM "\uee3e" +# define FIRACODE_EXCLAM_EQUAL "\uee3f" +# define FIRACODE_EXCLAM_EQUAL_EQUAL "\uee40" +# define FIRACODE_HYPHEN_BRACERIGHT "\uee41" +# define FIRACODE_HYPHEN_HYPHEN "\uee42" +# define FIRACODE_HYPHEN_HYPHEN_HYPHEN "\uee43" +# define FIRACODE_HYPHEN_HYPHEN_GREATER "\uee44" +# define FIRACODE_HYPHEN_GREATER "\uee45" +# define FIRACODE_HYPHEN_GREATER_GREATER "\uee46" +# define FIRACODE_HYPHEN_LESS "\uee47" +# define FIRACODE_HYPHEN_LESS_LESS "\uee48" +# define FIRACODE_HYPHEN_ASCIITILDE "\uee49" +# define FIRACODE_NUMBERSIGN_BRACELEFT "\uee4a" +# define FIRACODE_NUMBERSIGN_BRACKETLEFT "\uee4b" +# define FIRACODE_NUMBERSIGN_NUMBERSIGN "\uee4c" +# define FIRACODE_NNN "\uee4d" +# define FIRACODE_NNNN "\uee4e" +# define FIRACODE_NUMBERSIGN_PARENLEFT "\uee4f" +# define FIRACODE_NUMBERSIGN_QUESTION "\uee50" +# define FIRACODE_NUMBERSIGN_UNDERSCORE "\uee51" +# define FIRACODE_NUP "\uee52" +# define FIRACODE_PERIOD_HYPHEN "\uee53" +# define FIRACODE_PERIOD_EQUAL "\uee54" +# define FIRACODE_PERIOD_PERIOD "\uee55" +# define FIRACODE_PERIOD_PERIOD_LESS "\uee56" +# define FIRACODE_PERIOD_PERIOD_PERIOD "\uee57" +# define FIRACODE_QUESTION_EQUAL "\uee58" +# define FIRACODE_QUESTION_QUESTION "\uee59" +# define FIRACODE_SEMICOLON_SEMICOLON "\uee5a" +# define FIRACODE_SLASH_ASTERISK "\uee5b" +# define FIRACODE_SLASH_ASTERISK_ASTERISK "\uee5c" +# define FIRACODE_SLASH_EQUAL "\uee5d" +# define FIRACODE_SLASH_EQUAL_EQUAL "\uee5e" +# define FIRACODE_SLASH_GREATER "\uee5f" +# define FIRACODE_SLASH_SLASH "\uee60" +# define FIRACODE_SLASH_SLASH_SLASH "\uee61" +# define FIRACODE_AMPERSAND_AMPERSAND "\uee62" +# define FIRACODE_BAR_BAR "\uee63" +# define FIRACODE_BAR_BAR_EQUAL "\uee64" +# define FIRACODE_BAR_EQUAL "\uee65" +# define FIRACODE_BAR_GREATER "\uee66" +# define FIRACODE_ASCIICIRCUM_EQUAL "\uee67" +# define FIRACODE_DOLLAR_GREATER "\uee68" +# define FIRACODE_PLUS_PLUS "\uee69" +# define FIRACODE_PLUS_PLUS_PLUS "\uee6a" +# define FIRACODE_PLUS_GREATER "\uee6b" +# define FIRACODE_EQUAL_COLON_EQUAL "\uee6c" +# define FIRACODE_EQUAL_EQUAL "\uee6d" +# define FIRACODE_EQUAL_EQUAL_EQUAL "\uee6e" +# define FIRACODE_EQUAL_EQUAL_GREATER "\uee6f" +# define FIRACODE_EQUAL_GREATER "\uee70" +# define FIRACODE_EQUAL_GREATER_GREATER "\uee71" +# define FIRACODE_EQUAL_LESS "\uee72" +# define FIRACODE_EQUAL_LESS_LESS "\uee73" +# define FIRACODE_EQUAL_SLASH_EQUAL "\uee74" +# define FIRACODE_GREATER_HYPHEN "\uee75" +# define FIRACODE_GREATER_EQUAL "\uee76" +# define FIRACODE_GREATER_EQUAL_GREATER "\uee77" +# define FIRACODE_GREATER_GREATER "\uee78" +# define FIRACODE_GREATER_GREATER_HYPHEN "\uee79" +# define FIRACODE_GREATER_GREATER_EQUAL "\uee7a" +# define FIRACODE_GREATER_GREATER_GREATER "\uee7b" +# define FIRACODE_LESS_ASTERISK "\uee7c" +# define FIRACODE_LESS_ASTERISK_GREATER "\uee7d" +# define FIRACODE_LESS_BAR "\uee7e" +# define FIRACODE_LESS_BAR_GREATER "\uee7f" +# define FIRACODE_LESS_DOLLAR "\uee80" +# define FIRACODE_LESS_DOLLAR_GREATER "\uee81" +# define FIRACODE_LESS_EXCLAM_HYPHEN_HYPHEN "\uee82" +# define FIRACODE_LESS_HYPHEN "\uee83" +# define FIRACODE_LESS_HYPHEN_HYPHEN "\uee84" +# define FIRACODE_LESS_HYPHEN_GREATER "\uee85" +# define FIRACODE_LESS_PLUS "\uee86" +# define FIRACODE_LESS_PLUS_GREATER "\uee87" +# define FIRACODE_LESS_EQUAL "\uee88" +# define FIRACODE_LESS_EQUAL_EQUAL "\uee89" +# define FIRACODE_LESS_EQUAL_GREATER "\uee8a" +# define FIRACODE_LESS_EQUAL_LESS "\uee8b" +# define FIRACODE_LESS_GREATER "\uee8c" +# define FIRACODE_LESS_LESS "\uee8d" +# define FIRACODE_LESS_LESS_HYPHEN "\uee8e" +# define FIRACODE_LESS_LESS_EQUAL "\uee8f" +# define FIRACODE_LESS_LESS_LESS "\uee90" +# define FIRACODE_LESS_ASCIITILDE "\uee91" +# define FIRACODE_LESS_ASCIITILDE_ASCIITILDE "\uee92" +# define FIRACODE_LESS_SLASH "\uee93" +# define FIRACODE_LESS_SLASH_GREATER "\uee94" +# define FIRACODE_ASCIITILDE_AT "\uee95" +# define FIRACODE_ASCIITILDE_HYPHEN "\uee96" +# define FIRACODE_ASCIITILDE_EQUAL "\uee97" +# define FIRACODE_ASCIITILDE_GREATER "\uee98" +# define FIRACODE_ASCIITILDE_ASCIITILDE "\uee99" +# define FIRACODE_AAG "\uee9a" +# define FIRACODE_PERCENT_PERCENT "\uee9b" +# define FIRACODE_X_MULTIPLY "\uee9c" +# define FIRACODE_COLON_UC "\uee9d" +# define FIRACODE_PLUS_LC "\uee9e" +# define FIRACODE_PLUS_TOSF2 "\uee9f" +# define FIRACODE_NAMEME_1114119 "\ueea0" + +#endif // ICONS_IN_TERMINAL + diff --git a/nnn/src/icons-nerdfont.h b/nnn/src/icons-nerdfont.h @@ -0,0 +1,276 @@ +#ifndef ICONS_NERDFONT +#define ICONS_NERDFONT + +// You can find hex codes for nerd fonts here +// https://www.nerdfonts.com/cheat-sheet + +// Arrows +#define MD_ARROW_UPWARD "\uf55c" +#define MD_ARROW_FORWARD "\uf553" +#define MD_ARROW_DOWNWARD "\uf544" + +// Generics +#define ICON_DIRECTORY "\ue5ff" +#define ICON_FILE "\uf713" +#define ICON_EXEC "\uf144" +#define ICON_MANUAL "\uf5bd" + +// Top level and common icons +#define ICON_ARCHIVE "\uf53b" +#define ICON_BRIEFCASE "\uf5d5" +#define ICON_C "\ue61e" +#define ICON_CHANGELOG "\uf7d9" +#define ICON_CHESS "\uf639" +#define ICON_CLOJURE "\ue76a" +#define ICON_CONFIGURE "\uf423" +#define ICON_CPLUSPLUS "\ue61d" +#define ICON_DATABASE "\uf6b7" +#define ICON_DESKTOP "\ufcbe" +#define ICON_DOCUMENT "\uf718" +#define ICON_DOWNLOADS "\uf5d7" +#define ICON_ENCRYPT "\uf805" +#define ICON_FSHARP "\ue7a7" +#define ICON_GIT "\ue5fb" +#define ICON_HASKELL "\ue777" +#define ICON_HTML "\uf72d" +#define ICON_JAVA "\ue738" +#define ICON_JAVASCRIPT "\uf81d" +#define ICON_LICENSE "\uf718" +#define ICON_LINUX "\uf83c" +#define ICON_MAKEFILE "\uf68c" +#define ICON_MUSIC "\uf832" +#define ICON_MUSICFILE "\uf886" +#define ICON_OPTICALDISK "\ue271" +#define ICON_PICTUREFILE "\uf71e" +#define ICON_PICTURES "\uf753" +#define ICON_PLAYLIST "\uf910" +#define ICON_PUBLIC "\ue5ff" +#define ICON_PYTHON "\ue235" +#define ICON_REACT "\ue625" +#define ICON_RUBY "\ue23e" +#define ICON_SCRIPT "\ue795" +#define ICON_TEMPLATES "\ufac6" +#define ICON_TEX "\ufb68" +#define ICON_VIDEOFILE "\uf72a" +#define ICON_VIDEOS "\uf72f" +#define ICON_WORDDOC "\uf72b" + + +/* Numbers */ +#define ICON_EXT_1 ICON_MANUAL +#define ICON_EXT_7Z ICON_ARCHIVE + +/* A */ +#define ICON_EXT_A ICON_MANUAL +#define ICON_EXT_APK ICON_ARCHIVE +#define ICON_EXT_ASM ICON_FILE +#define ICON_EXT_AUP ICON_MUSICFILE +#define ICON_EXT_AVI ICON_VIDEOFILE + +/* B */ +#define ICON_EXT_BAT ICON_SCRIPT +#define ICON_EXT_BIB ICON_TEX +#define ICON_EXT_BIN "\uf471" +#define ICON_EXT_BMP ICON_PICTUREFILE +#define ICON_EXT_BZ2 ICON_ARCHIVE + +/* C */ +#define ICON_EXT_C ICON_C +#define ICON_EXT_CPLUSPLUS ICON_CPLUSPLUS +#define ICON_EXT_CAB ICON_ARCHIVE +#define ICON_EXT_CABAL ICON_HASKELL +#define ICON_EXT_CBR ICON_ARCHIVE +#define ICON_EXT_CBZ ICON_ARCHIVE +#define ICON_EXT_CC ICON_CPLUSPLUS +#define ICON_EXT_CLASS ICON_JAVA +#define ICON_EXT_CLJ ICON_CLOJURE +#define ICON_EXT_CLJC ICON_CLOJURE +#define ICON_EXT_CLJS ICON_CLOJURE +#define ICON_EXT_CLS ICON_TEX +#define ICON_EXT_CMAKE ICON_MAKEFILE +#define ICON_EXT_COFFEE "\ue751" +#define ICON_EXT_CONF ICON_CONFIGURE +#define ICON_EXT_CPIO ICON_ARCHIVE +#define ICON_EXT_CPP ICON_CPLUSPLUS +#define ICON_EXT_CSS "\ue749" +#define ICON_EXT_CUE ICON_PLAYLIST +#define ICON_EXT_CVS ICON_CONFIGURE +#define ICON_EXT_CXX ICON_CPLUSPLUS + +/* D */ +#define ICON_EXT_DB ICON_DATABASE +#define ICON_EXT_DEB "\ue77d" +#define ICON_EXT_DIFF "\uf440" +#define ICON_EXT_DLL ICON_SCRIPT +#define ICON_EXT_DOC ICON_WORDDOC +#define ICON_EXT_DOCX ICON_WORDDOC + +/* E */ +#define ICON_EXT_EJS ICON_JAVASCRIPT +#define ICON_EXT_ELF ICON_LINUX +#define ICON_EXT_EPUB ICON_MANUAL +#define ICON_EXT_EXE ICON_EXEC + +/* F */ +#define ICON_EXT_FEN ICON_CHESS +#define ICON_EXT_FSHARP ICON_FSHARP +#define ICON_EXT_FLAC ICON_MUSICFILE +#define ICON_EXT_FLV ICON_VIDEOFILE +#define ICON_EXT_FS ICON_FSHARP +#define ICON_EXT_FSI ICON_FSHARP +#define ICON_EXT_FSSCRIPT ICON_FSHARP +#define ICON_EXT_FSX ICON_FSHARP + +/* G */ +#define ICON_EXT_GEM ICON_RUBY +#define ICON_EXT_GIF ICON_PICTUREFILE +#define ICON_EXT_GO "\ufcd1" +#define ICON_EXT_GPG ICON_ENCRYPT +#define ICON_EXT_GZ ICON_ARCHIVE +#define ICON_EXT_GZIP ICON_ARCHIVE + +/* H */ +#define ICON_EXT_H ICON_C +#define ICON_EXT_HH ICON_CPLUSPLUS +#define ICON_EXT_HPP ICON_CPLUSPLUS +#define ICON_EXT_HS ICON_HASKELL +#define ICON_EXT_HTACCESS ICON_CONFIGURE +#define ICON_EXT_HTPASSWD ICON_CONFIGURE +#define ICON_EXT_HTM ICON_HTML +#define ICON_EXT_HTML ICON_HTML +#define ICON_EXT_HXX ICON_CPLUSPLUS + +/* I */ +#define ICON_EXT_ICO ICON_PICTUREFILE +#define ICON_EXT_IMG ICON_OPTICALDISK +#define ICON_EXT_INI ICON_CONFIGURE +#define ICON_EXT_ISO ICON_OPTICALDISK + +/* J */ +#define ICON_EXT_JAR ICON_JAVA +#define ICON_EXT_JAVA ICON_JAVA +#define ICON_EXT_JL ICON_CONFIGURE +#define ICON_EXT_JPEG ICON_PICTUREFILE +#define ICON_EXT_JPG ICON_PICTUREFILE +#define ICON_EXT_JS ICON_JAVASCRIPT +#define ICON_EXT_JSON "\ufb25" +#define ICON_EXT_JSX ICON_REACT + +/* K */ + +/* L */ +#define ICON_EXT_LHA ICON_ARCHIVE +#define ICON_EXT_LHS ICON_HASKELL +#define ICON_EXT_LOG ICON_DOCUMENT +#define ICON_EXT_LUA "\ue620" +#define ICON_EXT_LZH ICON_ARCHIVE +#define ICON_EXT_LZMA ICON_ARCHIVE + +/* M */ +#define ICON_EXT_M4A ICON_MUSICFILE +#define ICON_EXT_M4V ICON_VIDEOFILE +#define ICON_EXT_M "\ufd1c" +#define ICON_EXT_MAT "\uf0ce" +#define ICON_EXT_MD "\ue609" +#define ICON_EXT_MK ICON_MAKEFILE +#define ICON_EXT_MKV ICON_VIDEOFILE +#define ICON_EXT_MOV ICON_VIDEOFILE +#define ICON_EXT_MP3 ICON_MUSICFILE +#define ICON_EXT_MP4 ICON_VIDEOFILE +#define ICON_EXT_MPEG ICON_VIDEOFILE +#define ICON_EXT_MPG ICON_VIDEOFILE +#define ICON_EXT_MSI "\uf871" + +/* N */ +#define ICON_EXT_NIX "\uf313" + +/* O */ +#define ICON_EXT_O ICON_MANUAL +#define ICON_EXT_OGG ICON_MUSICFILE +#define ICON_EXT_OPUS ICON_MUSICFILE +#define ICON_EXT_ODOWNLOAD ICON_DOWNLOADS +#define ICON_EXT_OUT ICON_LINUX + +/* P */ +#define ICON_EXT_PART ICON_DOWNLOADS +#define ICON_EXT_PATCH "\uf440" +#define ICON_EXT_PDF "\uf724" +#define ICON_EXT_PGN ICON_CHESS +#define ICON_EXT_PHP "\ue73d" +#define ICON_EXT_PNG ICON_PICTUREFILE +#define ICON_EXT_PPT "\uf726" +#define ICON_EXT_PPTX "\uf726" +#define ICON_EXT_PSB "\ue7b8" +#define ICON_EXT_PSD "\ue7b8" +#define ICON_EXT_PY ICON_PYTHON +#define ICON_EXT_PYC ICON_PYTHON +#define ICON_EXT_PYD ICON_PYTHON +#define ICON_EXT_PYO ICON_PYTHON + +/* Q */ + +/* R */ +#define ICON_EXT_RAR ICON_ARCHIVE +#define ICON_EXT_RC ICON_CONFIGURE +#define ICON_EXT_ROM "\uf795" +#define ICON_EXT_RPM ICON_ARCHIVE +#define ICON_EXT_RSS "\uf96b" +#define ICON_EXT_RTF "\uf724" +#define ICON_EXT_RB ICON_RUBY + +/* S */ +#define ICON_EXT_SASS "\ue603" +#define ICON_EXT_SCSS "\ue603" +#define ICON_EXT_SO ICON_MANUAL +#define ICON_EXT_SCALA "\ue737" +#define ICON_EXT_SH ICON_SCRIPT +#define ICON_EXT_SLIM ICON_SCRIPT +#define ICON_EXT_SLN "\ue70c" +#define ICON_EXT_SQL ICON_DATABASE +#define ICON_EXT_SRT "\uf679" +#define ICON_EXT_STY ICON_TEX +#define ICON_EXT_SUB "\uf679" +#define ICON_EXT_SVG ICON_PICTUREFILE + +/* T */ +#define ICON_EXT_TAR ICON_ARCHIVE +#define ICON_EXT_TEX ICON_TEX +#define ICON_EXT_TGZ ICON_ARCHIVE +#define ICON_EXT_TS "\ue628" +#define ICON_EXT_TSX ICON_REACT +#define ICON_EXT_TXT ICON_DOCUMENT +#define ICON_EXT_TXZ ICON_ARCHIVE + +/* U */ + +/* V */ +#define ICON_EXT_VID ICON_VIDEOFILE +#define ICON_EXT_VIM "\ue62b" +#define ICON_EXT_VIMRC "\ue62b" + +/* W */ +#define ICON_EXT_WAV ICON_MUSICFILE +#define ICON_EXT_WEBM ICON_VIDEOFILE +#define ICON_EXT_WEBP ICON_PICTUREFILE +#define ICON_EXT_WMA ICON_VIDEOFILE +#define ICON_EXT_WMV ICON_VIDEOFILE + +/* X */ +#define ICON_EXT_XBPS ICON_ARCHIVE +#define ICON_EXT_XCF ICON_PICTUREFILE +#define ICON_EXT_XHTML ICON_HTML +#define ICON_EXT_XLS "\uf71a" +#define ICON_EXT_XLSX "\uf71a" +#define ICON_EXT_XML ICON_HTML +#define ICON_EXT_XZ ICON_ARCHIVE + +/* Y */ +#define ICON_EXT_YAML ICON_CONFIGURE +#define ICON_EXT_YML ICON_CONFIGURE + +/* Z */ +#define ICON_EXT_ZIP ICON_ARCHIVE +#define ICON_EXT_ZSH ICON_SCRIPT +#define ICON_EXT_ZST ICON_ARCHIVE + +#endif // ICONS_NERDFONT diff --git a/nnn/src/icons.h b/nnn/src/icons.h @@ -0,0 +1,530 @@ +#pragma once + +#if defined(ICONS) +#include "icons-in-terminal.h" +#elif defined(NERD) +#include "icons-nerdfont.h" +#endif + +struct icon_pair { + const char *match; + const char *icon; + /* + * Hex xterm 256 color code, 0 to follow file specific (if any) + * Codes: https://jonasjacek.github.io/colors/ + * Spectrum sorted: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg + */ + const unsigned char color; +}; + +/* + * Define a string to be printed before and after the icon + * Adjust if the icons are not printed properly + */ + +#define ICON_PADDING_LEFT "" +#define ICON_PADDING_RIGHT " " + +#define COLOR_VIDEO 93 /* Purple */ +#define COLOR_AUDIO 220 /* Gold1 */ +#define COLOR_IMAGE 82 /* Chartreuse2 */ +#define COLOR_DOCS 202 /* OrangeRed1 */ +#define COLOR_ARCHIVE 209 /* Salmon1 */ +#define COLOR_C 81 /* SteelBlue1 */ +#define COLOR_JAVA 32 /* DeepSkyBlue3 */ +#define COLOR_JAVASCRIPT 47 /* SpringGreen2 */ +#define COLOR_REACT 39 /* DeepSkyBlue1 */ +#define COLOR_CSS 199 /* DeepPink1 */ +#define COLOR_PYTHON 227 /* LightGoldenrod1 */ +#define COLOR_LUA 19 /* Blue3 */ +#define COLOR_DOCUMENT 15 /* White */ +#define COLOR_FSHARP 31 /* DeepSkyBlue3 */ +#define COLOR_RUBY 160 /* Red3 */ +#define COLOR_SCALA 196 /* Red1 */ +#define COLOR_SHELL 47 /* SpringGreen2 */ +#define COLOR_VIM 28 /* Green4 */ + +/* + * Using symbols defined in icons-in-terminal.h, or even using icons-in-terminal is not necessary. + * You can use whatever pathched font you like. You just have to put the desired icon as a string. + * If you are using icons-in-terminal the creator recommends that you do use the symbols in the generated header. + */ + +#if defined(ICONS) +static const struct icon_pair dir_icon = {"", FA_FOLDER, 0}; +static const struct icon_pair file_icon = {"", FA_FILE_O, 0}; +static const struct icon_pair exec_icon = {"", FA_COG, 0}; +#elif defined(NERD) +static const struct icon_pair dir_icon = {"", ICON_DIRECTORY, 0}; +static const struct icon_pair file_icon = {"", ICON_FILE, 0}; +static const struct icon_pair exec_icon = {"", ICON_EXEC, 0}; +#endif + +/* All entries are case-insensitive */ + +static const struct icon_pair icons_name[] = { +#if defined(ICONS) + {".git", FA_GIT, 0}, + {"Desktop", FA_DESKTOP, 0}, + {"Documents", FA_BRIEFCASE, 0}, + {"Downloads", FA_DOWNLOAD, 0}, + {"Music", FA_MUSIC, 0}, + {"Pictures", MD_CAMERA_ALT, 0}, + {"Public", FA_INBOX, 0}, + {"Templates", FA_PAPERCLIP, 0}, + {"Videos", FA_FILM, 0}, + {"CHANGELOG", FA_HISTORY, COLOR_DOCS}, + {"configure", FILE_CONFIG, 0}, + {"License", FA_COPYRIGHT, COLOR_DOCS}, + {"Makefile", FILE_CMAKE, 0}, +#elif defined(NERD) + {".git", ICON_GIT, 0}, + {"Desktop", ICON_DESKTOP, 0}, + {"Documents", ICON_BRIEFCASE, 0}, + {"Downloads", ICON_DOWNLOADS, 0}, + {"Music", ICON_MUSIC, 0}, + {"Pictures", ICON_PICTURES, 0}, + {"Public", ICON_PUBLIC, 0}, + {"Templates", ICON_TEMPLATES, 0}, + {"Videos", ICON_VIDEOS, 0}, + {"CHANGELOG", ICON_CHANGELOG, COLOR_DOCS}, + {"configure", ICON_CONFIGURE, 0}, + {"License", ICON_LICENSE, COLOR_DOCS}, + {"Makefile", ICON_MAKEFILE, 0}, +#endif +}; + +/* + * New entries should be added such that the first character of the extension is in the correct group . + * This is done for performance reason so that the correct icon can be found faster. + * All entries are case-insensitive + */ + +static const struct icon_pair icons_ext[] = { +#if defined(ICONS) + /* Numbers */ + {"1", FILE_MANPAGE, COLOR_DOCS}, + {"7z", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* A */ + {"a", FILE_MANPAGE, 0}, + {"apk", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"asm", FILE_NASM, 0}, + {"aup", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"avi", FA_FILE_MOVIE_O, COLOR_VIDEO}, + + /* B */ + {"bat", MFIZZ_SCRIPT, 0}, + {"bin", OCT_FILE_BINARY, 0}, + {"bmp", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"bz2", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* C */ + {"c", MFIZZ_C, 0}, + {"c++", MFIZZ_CPLUSPLUS, 0}, + {"cab", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"cbr", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"cbz", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"cc", MFIZZ_CPLUSPLUS, 0}, + {"class", MFIZZ_JAVA, 0}, + {"clj", MFIZZ_CLOJURE, 0}, + {"cljc", MFIZZ_CLOJURE, 0}, + {"cljs", MFIZZ_CLOJURE, 0}, + {"cmake", FILE_CMAKE, 0}, + {"coffee", MFIZZ_COFFEE_BEAN, 0}, + {"conf", FA_COGS, 0}, + {"cpio", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"cpp", MFIZZ_CPLUSPLUS, 0}, + {"css", MFIZZ_CSS3, 0}, + {"cue", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"cvs", FA_COGS, 0}, + {"cxx", MFIZZ_CPLUSPLUS, 0}, + + /* D */ + {"db", MFIZZ_DATABASE_ALT2, 0}, + {"deb", MFIZZ_DEBIAN, COLOR_ARCHIVE}, + {"diff", FILE_DIFF, 0}, + {"dll", FILE_MANPAGE, 0}, + {"doc", FILE_WORD, 0}, + {"docx", FILE_WORD, 0}, + + /* E */ + {"ejs", FA_FILE_CODE_O, 0}, + {"elf", FA_LINUX, 0}, + {"epub", FA_FILE_PDF_O, COLOR_DOCS}, + {"exe", FA_WINDOWS, 0}, + + /* F */ + {"f#", DEV_FSHARP, 0}, + {"flac", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"flv", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"fs", DEV_FSHARP, 0}, + {"fsi", DEV_FSHARP, 0}, + {"fsscript", DEV_FSHARP, 0}, + {"fsx", DEV_FSHARP, 0}, + + /* G */ + {"gem", FA_FILE_ARCHIVE_O, 0}, + {"gif", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"go", MFIZZ_GO, 0}, + {"gz", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"gzip", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* H */ + {"h", MFIZZ_C, 0}, + {"hh", MFIZZ_CPLUSPLUS, 0}, + {"htaccess", FA_COGS, 0}, + {"htpasswd", FA_COGS, 0}, + {"htm", FA_FILE_CODE_O, 0}, + {"html", FA_FILE_CODE_O, 0}, + {"hxx", MFIZZ_CPLUSPLUS, 0}, + + /* I */ + {"ico", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"img", LINEA_MUSIC_CD, COLOR_ARCHIVE}, + {"ini", FA_COGS, 0}, + {"iso", LINEA_MUSIC_CD, COLOR_ARCHIVE}, + + /* J */ + {"jar", MFIZZ_JAVA, 0}, + {"java", MFIZZ_JAVA, 0}, + {"jl", FA_COGS, 0}, + {"jpeg", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"jpg", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"js", DEV_JAVASCRIPT_BADGE, 0}, + {"json", MFIZZ_JAVASCRIPT, 0}, + {"jsx", FILE_JSX, 0}, + + /* K */ + + /* L */ + {"lha", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"log", FA_FILE_TEXT_O, 0}, + {"lua", FILE_LUA, COLOR_LUA}, + {"lzh", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"lzma", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* M */ + {"m4a", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"m4v", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"markdown", DEV_MARKDOWN, COLOR_DOCS}, + {"md", DEV_MARKDOWN, COLOR_DOCS}, + {"mk", FILE_CMAKE, 0}, + {"mkv", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"mov", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"mp3", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"mp4", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"mpeg", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"mpg", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"msi", FA_WINDOWS, 0}, + + /* N */ + + /* O */ + {"o", FILE_MANPAGE, 0}, + {"ogg", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"opus", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"opdownload", FA_DOWNLOAD, 0}, + {"out", FA_LINUX, 0}, + + /* P */ + {"part", FA_DOWNLOAD, 0}, + {"patch", FILE_PATCH, 0}, + {"pdf", FA_FILE_PDF_O, COLOR_DOCS}, + {"php", MFIZZ_PHP, 0}, + {"png", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"ppt", FILE_POWERPOINT, 0}, + {"pptx", FILE_POWERPOINT, 0}, + {"psb", DEV_PHOTOSHOP, 0}, + {"psd", DEV_PHOTOSHOP, 0}, + {"py", MFIZZ_PYTHON, 0}, + {"pyc", MFIZZ_PYTHON, 0}, + {"pyd", MFIZZ_PYTHON, 0}, + {"pyo", MFIZZ_PYTHON, 0}, + + /* Q */ + + /* R */ + {"rar", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"rb", MFIZZ_RUBY, COLOR_RUBY}, + {"rc", FA_COGS, 0}, + {"rom", FA_LOCK, 0}, + {"rpm", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"rss", FA_RSS_SQUARE, 0}, + {"rtf", FA_FILE_PDF_O, 0}, + + /* S */ + {"so", FILE_MANPAGE, 0}, + {"scala", MFIZZ_SCALA, 0}, + {"sh", MFIZZ_SCRIPT, COLOR_SHELL}, + {"slim", FA_FILE_CODE_O, 0}, + {"sln", DEV_VISUALSTUDIO, 0}, + {"sql", MFIZZ_MYSQL, 0}, + {"srt", FA_COMMENTS_O, 0}, + {"sub", FA_COMMENTS_O, 0}, + {"svg", FA_FILE_IMAGE_O, COLOR_IMAGE}, + + /* T */ + {"tar", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"tex", FILE_TEX, 0}, + {"tgz", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"ts", FILE_TS, 0}, + {"tsx", FILE_TSX, 0}, + {"txt", FA_FILE_TEXT_O, 0}, + {"txz", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* U */ + + /* V */ + {"vid", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"vim", DEV_VIM, 0}, + {"vimrc", DEV_VIM, 0}, + {"vtt", FA_COMMENTS_O, 0}, + + /* W */ + {"wav", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"webm", FA_FILE_MOVIE_O, COLOR_VIDEO}, + {"webp", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"wma", FA_FILE_AUDIO_O, COLOR_AUDIO}, + {"wmv", FA_FILE_MOVIE_O, COLOR_VIDEO}, + + /* X */ + {"xbps", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + {"xcf", FA_FILE_IMAGE_O, COLOR_IMAGE}, + {"xhtml", FA_FILE_CODE_O, 0}, + {"xls", FILE_EXCEL, 0}, + {"xlsx", FILE_EXCEL, 0}, + {"xml", FA_FILE_CODE_O, 0}, + {"xz", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* Y */ + {"yaml", FA_COGS, 0}, + {"yml", FA_COGS, 0}, + + /* Z */ + {"zip", FA_FILE_ARCHIVE_O, COLOR_ARCHIVE}, + + /* Other */ +#elif defined(NERD) + /* Numbers */ + {"1", ICON_EXT_1, COLOR_DOCS}, + {"7z", ICON_EXT_7Z, COLOR_ARCHIVE}, + + /* A */ + {"a", ICON_EXT_A, 0}, + {"apk", ICON_EXT_APK, COLOR_ARCHIVE}, + {"asm", ICON_EXT_ASM, 0}, + {"aup", ICON_EXT_AUP, COLOR_AUDIO}, + {"avi", ICON_EXT_AVI, COLOR_VIDEO}, + + /* B */ + {"bat", ICON_EXT_BAT, 0}, + {"bib", ICON_EXT_BIB, 0}, + {"bin", ICON_EXT_BIN, 0}, + {"bmp", ICON_EXT_BMP, COLOR_IMAGE}, + {"bz2", ICON_EXT_BZ2, COLOR_ARCHIVE}, + + /* C */ + {"c", ICON_EXT_C, COLOR_C}, + {"c++", ICON_EXT_CPLUSPLUS, COLOR_C}, + {"cabal", ICON_EXT_CABAL, COLOR_VIDEO}, + {"cab", ICON_EXT_CAB, COLOR_ARCHIVE}, + {"cbr", ICON_EXT_CBR, COLOR_ARCHIVE}, + {"cbz", ICON_EXT_CBZ, COLOR_ARCHIVE}, + {"cc", ICON_EXT_CC, COLOR_C}, + {"class", ICON_EXT_CLASS, COLOR_JAVA}, + {"clj", ICON_EXT_CLJ, 0}, + {"cljc", ICON_EXT_CLJC, 0}, + {"cljs", ICON_EXT_CLJS, 0}, + {"cls", ICON_EXT_CLS, 0}, + {"cmake", ICON_EXT_CMAKE, 0}, + {"coffee", ICON_EXT_COFFEE, 0}, + {"conf", ICON_EXT_CONF, 0}, + {"cpio", ICON_EXT_CPIO, COLOR_ARCHIVE}, + {"cpp", ICON_EXT_CPP, COLOR_C}, + {"css", ICON_EXT_CSS, COLOR_CSS}, + {"cue", ICON_EXT_CUE, COLOR_AUDIO}, + {"cvs", ICON_EXT_CVS, 0}, + {"cxx", ICON_EXT_CXX, COLOR_C}, + + /* D */ + {"db", ICON_EXT_DB, 0}, + {"deb", ICON_EXT_DEB, COLOR_ARCHIVE}, + {"diff", ICON_EXT_DIFF, 0}, + {"dll", ICON_EXT_DLL, 0}, + {"doc", ICON_EXT_DOC, COLOR_DOCUMENT}, + {"docx", ICON_EXT_DOCX, COLOR_DOCUMENT}, + + /* E */ + {"ejs", ICON_EXT_EJS, COLOR_JAVASCRIPT}, + {"elf", ICON_EXT_ELF, 0}, + {"epub", ICON_EXT_EPUB, COLOR_DOCS}, + {"exe", ICON_EXT_EXE, 0}, + + /* F */ + {"f#", ICON_EXT_FSHARP, COLOR_FSHARP}, + {"fen", ICON_EXT_FEN, 0}, + {"flac", ICON_EXT_FLAC, COLOR_AUDIO}, + {"flv", ICON_EXT_FLV, COLOR_VIDEO}, + {"fs", ICON_EXT_FS, COLOR_FSHARP}, + {"fsi", ICON_EXT_FSI, COLOR_FSHARP}, + {"fsscript", ICON_EXT_FSSCRIPT, COLOR_FSHARP}, + {"fsx", ICON_EXT_FSX, COLOR_FSHARP}, + + /* G */ + {"gem", ICON_EXT_GEM, COLOR_RUBY}, + {"gif", ICON_EXT_GIF, COLOR_IMAGE}, + {"go", ICON_EXT_GO, 0}, + {"gpg", ICON_EXT_GPG, COLOR_ARCHIVE}, + {"gz", ICON_EXT_GZ, COLOR_ARCHIVE}, + {"gzip", ICON_EXT_GZIP, COLOR_ARCHIVE}, + + /* H */ + {"h", ICON_EXT_H, COLOR_C}, + {"hh", ICON_EXT_HH, COLOR_C}, + {"hpp", ICON_EXT_HPP, COLOR_C}, + {"hs", ICON_EXT_HS, COLOR_VIM}, + {"htaccess", ICON_EXT_HTACCESS, 0}, + {"htpasswd", ICON_EXT_HTPASSWD, 0}, + {"htm", ICON_EXT_HTM, 0}, + {"html", ICON_EXT_HTML, 0}, + {"hxx", ICON_EXT_HXX, COLOR_C}, + + /* I */ + {"ico", ICON_EXT_ICO, COLOR_IMAGE}, + {"img", ICON_EXT_IMG, COLOR_ARCHIVE}, + {"ini", ICON_EXT_INI, 0}, + {"iso", ICON_EXT_ISO, COLOR_ARCHIVE}, + + /* J */ + {"jar", ICON_EXT_JAR, COLOR_JAVA}, + {"java", ICON_EXT_JAVA, COLOR_JAVA}, + {"jl", ICON_EXT_JL, 0}, + {"jpeg", ICON_EXT_JPEG, COLOR_IMAGE}, + {"jpg", ICON_EXT_JPG, COLOR_IMAGE}, + {"js", ICON_EXT_JS, COLOR_JAVASCRIPT}, + {"json", ICON_EXT_JSON, COLOR_JAVASCRIPT}, + {"jsx", ICON_EXT_JSX, COLOR_REACT}, + + /* K */ + + /* L */ + {"lha", ICON_EXT_LHA, COLOR_ARCHIVE}, + {"lhs", ICON_EXT_LHS, COLOR_VIM}, + {"log", ICON_EXT_LOG, 0}, + {"lua", ICON_EXT_LUA, COLOR_LUA}, + {"lzh", ICON_EXT_LZH, COLOR_ARCHIVE}, + {"lzma", ICON_EXT_LZMA, COLOR_ARCHIVE}, + + /* M */ + {"m", ICON_EXT_M, COLOR_C}, + {"m4a", ICON_EXT_M4A, COLOR_AUDIO}, + {"m4v", ICON_EXT_M4V, COLOR_VIDEO}, + {"markdown", ICON_EXT_MD, COLOR_DOCS}, + {"mat", ICON_EXT_MAT, COLOR_C}, + {"md", ICON_EXT_MD, COLOR_DOCS}, + {"mk", ICON_EXT_MK, 0}, + {"mkv", ICON_EXT_MKV, COLOR_VIDEO}, + {"mov", ICON_EXT_MOV, COLOR_VIDEO}, + {"mp3", ICON_EXT_MP3, COLOR_AUDIO}, + {"mp4", ICON_EXT_MP4, COLOR_VIDEO}, + {"mpeg", ICON_EXT_MPEG, COLOR_VIDEO}, + {"mpg", ICON_EXT_MPG, COLOR_VIDEO}, + {"msi", ICON_EXT_MSI, 0}, + + /* N */ + {"nix", ICON_EXT_NIX, COLOR_FSHARP}, + + /* O */ + {"o", ICON_EXT_O, 0}, + {"ogg", ICON_EXT_OGG, COLOR_AUDIO}, + {"opus", ICON_EXT_OPUS, COLOR_AUDIO}, + {"opdownload", ICON_EXT_ODOWNLOAD, 0}, + {"out", ICON_EXT_OUT, 0}, + + /* P */ + {"part", ICON_EXT_PART, 0}, + {"patch", ICON_EXT_PATCH, 0}, + {"pdf", ICON_EXT_PDF, COLOR_DOCS}, + {"pgn", ICON_EXT_PGN, 0}, + {"php", ICON_EXT_PHP, 0}, + {"png", ICON_EXT_PNG, COLOR_IMAGE}, + {"ppt", ICON_EXT_PPT, 0}, + {"pptx", ICON_EXT_PPTX, 0}, + {"psb", ICON_EXT_PSB, 0}, + {"psd", ICON_EXT_PSD, 0}, + {"py", ICON_EXT_PY, COLOR_PYTHON}, + {"pyc", ICON_EXT_PYC, COLOR_PYTHON}, + {"pyd", ICON_EXT_PYD, COLOR_PYTHON}, + {"pyo", ICON_EXT_PYO, COLOR_PYTHON}, + + /* Q */ + + /* R */ + {"rar", ICON_EXT_RAR, COLOR_ARCHIVE}, + {"rb", ICON_EXT_RB, COLOR_RUBY}, + {"rc", ICON_EXT_RC, 0}, + {"rom", ICON_EXT_ROM, 0}, + {"rpm", ICON_EXT_RPM, COLOR_ARCHIVE}, + {"rss", ICON_EXT_RSS, 0}, + {"rtf", ICON_EXT_RTF, 0}, + + /* S */ + {"sass", ICON_EXT_SASS, COLOR_CSS}, + {"scss", ICON_EXT_SCSS, COLOR_CSS}, + {"so", ICON_EXT_SO, 0}, + {"scala", ICON_EXT_SCALA, COLOR_SCALA}, + {"sh", ICON_EXT_SH, COLOR_SHELL}, + {"slim", ICON_EXT_SLIM, COLOR_DOCUMENT}, + {"sln", ICON_EXT_SLN, 0}, + {"sql", ICON_EXT_SQL, 0}, + {"srt", ICON_EXT_SRT, 0}, + {"sty", ICON_EXT_STY, 0}, + {"sub", ICON_EXT_SUB, 0}, + {"svg", ICON_EXT_SVG, COLOR_IMAGE}, + + /* T */ + {"tar", ICON_EXT_TAR, COLOR_ARCHIVE}, + {"tex", ICON_EXT_TEX, 0}, + {"tgz", ICON_EXT_TGZ, COLOR_ARCHIVE}, + {"ts", ICON_EXT_TS, COLOR_JAVASCRIPT}, + {"tsx", ICON_EXT_TSX, COLOR_REACT}, + {"txt", ICON_EXT_TXT, COLOR_DOCUMENT}, + {"txz", ICON_EXT_TXZ, COLOR_ARCHIVE}, + + /* U */ + + /* V */ + {"vid", ICON_EXT_VID, COLOR_VIDEO}, + {"vim", ICON_EXT_VIM, COLOR_VIM}, + {"vimrc", ICON_EXT_VIMRC, COLOR_VIM}, + {"vtt", ICON_EXT_SRT, 0}, + + /* W */ + {"wav", ICON_EXT_WAV, COLOR_AUDIO}, + {"webm", ICON_EXT_WEBM, COLOR_VIDEO}, + {"webp", ICON_EXT_WEBP, COLOR_IMAGE}, + {"wma", ICON_EXT_WMA, COLOR_AUDIO}, + {"wmv", ICON_EXT_WMV, COLOR_VIDEO}, + + /* X */ + {"xbps", ICON_EXT_XBPS, COLOR_ARCHIVE}, + {"xcf", ICON_EXT_XCF, COLOR_IMAGE}, + {"xhtml", ICON_EXT_XHTML, 0}, + {"xls", ICON_EXT_XLS, 0}, + {"xlsx", ICON_EXT_XLSX, 0}, + {"xml", ICON_EXT_XML, 0}, + {"xz", ICON_EXT_XZ, COLOR_ARCHIVE}, + + /* Y */ + {"yaml", ICON_EXT_YAML, COLOR_DOCUMENT}, + {"yml", ICON_EXT_YML, COLOR_DOCUMENT}, + + /* Z */ + {"zip", ICON_EXT_ZIP, COLOR_ARCHIVE}, + {"zsh", ICON_EXT_ZSH, COLOR_SHELL}, + {"zst", ICON_EXT_ZST, COLOR_ARCHIVE}, + + /* Other */ +#endif +}; diff --git a/nnn/src/nnn.c b/nnn/src/nnn.c @@ -0,0 +1,8798 @@ +/* + * BSD 2-Clause License + * + * Copyright (C) 2014-2016, Lazaros Koromilas <lostd@2f30.org> + * Copyright (C) 2014-2016, Dimitris Papastamos <sin@2f30.org> + * Copyright (C) 2016-2022, Arun Prakash Jana <engineerarun@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(__linux__) || defined(MINGW) || defined(__MINGW32__) \ + || defined(__MINGW64__) || defined(__CYGWIN__) +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#if defined(__arm__) || defined(__i386__) +#define _FILE_OFFSET_BITS 64 /* Support large files on 32-bit */ +#endif +#if defined(__linux__) +#include <sys/inotify.h> +#define LINUX_INOTIFY +#endif +#if !defined(__GLIBC__) +#include <sys/types.h> +#endif +#endif +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +#include <sys/types.h> +#include <sys/event.h> +#include <sys/time.h> +#define BSD_KQUEUE +#elif defined(__HAIKU__) +#include "../misc/haiku/haiku_interop.h" +#define HAIKU_NM +#else +#include <sys/sysmacros.h> +#endif +#include <sys/wait.h> + +#ifdef __linux__ /* Fix failure due to mvaddnwstr() */ +#ifndef NCURSES_WIDECHAR +#define NCURSES_WIDECHAR 1 +#endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__APPLE__) || defined(__sun) +#ifndef _XOPEN_SOURCE_EXTENDED +#define _XOPEN_SOURCE_EXTENDED +#endif +#endif +#ifndef __USE_XOPEN /* Fix wcswidth() failure, ncursesw/curses.h includes whcar.h on Ubuntu 14.04 */ +#define __USE_XOPEN +#endif +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <fts.h> +#include <libgen.h> +#include <limits.h> +#ifndef NOLC +#include <locale.h> +#endif +#include <pthread.h> +#include <stdio.h> +#ifndef NORL +#include <readline/history.h> +#include <readline/readline.h> +#endif +#ifdef PCRE +#include <pcre.h> +#else +#include <regex.h> +#endif +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> +#ifndef __USE_XOPEN_EXTENDED +#define __USE_XOPEN_EXTENDED 1 +#endif +#include <ftw.h> +#include <wchar.h> +#include <pwd.h> +#include <grp.h> + +#ifdef MACOS_BELOW_1012 +#include "../misc/macos-legacy/mach_gettime.h" +#endif + +#if !defined(alloca) && defined(__GNUC__) +/* + * GCC doesn't expand alloca() to __builtin_alloca() in standards mode + * (-std=...) and not all standard libraries do or supply it, e.g. + * NetBSD/arm64 so explicitly use the builtin. + */ +#define alloca(size) __builtin_alloca(size) +#endif + +#include "nnn.h" +#include "dbg.h" + +#if defined(ICONS) || defined(NERD) +#include "icons.h" +#define ICONS_ENABLED +#endif + +#ifdef TOURBIN_QSORT +#include "qsort.h" +#endif + +/* Macro definitions */ +#define VERSION "4.4" +#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn" + +#ifndef NOSSN +#define SESSIONS_VERSION 1 +#endif + +#ifndef S_BLKSIZE +#define S_BLKSIZE 512 /* S_BLKSIZE is missing on Android NDK (Termux) */ +#endif + +/* + * NAME_MAX and PATH_MAX may not exist, e.g. with dirent.c_name being a + * flexible array on Illumos. Use somewhat accommodating fallback values. + */ +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#define _ABSSUB(N, M) (((N) <= (M)) ? ((M) - (N)) : ((N) - (M))) +#define ELEMENTS(x) (sizeof(x) / sizeof(*(x))) +#undef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#undef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ISODD(x) ((x) & 1) +#define ISBLANK(x) ((x) == ' ' || (x) == '\t') +#define TOUPPER(ch) (((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch)) +#define TOLOWER(ch) (((ch) >= 'A' && (ch) <= 'Z') ? ((ch) - 'A' + 'a') : (ch)) +#define ISUPPER_(ch) ((ch) >= 'A' && (ch) <= 'Z') +#define ISLOWER_(ch) ((ch) >= 'a' && (ch) <= 'z') +#define CMD_LEN_MAX (PATH_MAX + ((NAME_MAX + 1) << 1)) +#define ALIGN_UP(x, A) ((((x) + (A) - 1) / (A)) * (A)) +#define READLINE_MAX 256 +#define FILTER '/' +#define RFILTER '\\' +#define CASE ':' +#define MSGWAIT '$' +#define SELECT ' ' +#define PROMPT ">>> " +#define REGEX_MAX 48 +#define ENTRY_INCR 64 /* Number of dir 'entry' structures to allocate per shot */ +#define NAMEBUF_INCR 0x800 /* 64 dir entries at once, avg. 32 chars per file name = 64*32B = 2KB */ +#define DESCRIPTOR_LEN 32 +#define _ALIGNMENT 0x10 /* 16-byte alignment */ +#define _ALIGNMENT_MASK 0xF +#define TMP_LEN_MAX 64 +#define DOT_FILTER_LEN 7 +#define ASCII_MAX 128 +#define EXEC_ARGS_MAX 10 +#define LIST_FILES_MAX (1 << 16) +#define SCROLLOFF 3 +#define COLOR_256 256 + +/* Time intervals */ +#define DBLCLK_INTERVAL_NS (400000000) +#define XDELAY_INTERVAL_MS (350000) /* 350 ms delay */ + +#ifndef CTX8 +#define CTX_MAX 4 +#else +#define CTX_MAX 8 +#endif + +#ifndef SED +/* BSDs or Solaris or SunOS */ +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(sun) || defined(__sun) +#define SED "gsed" +#else +#define SED "sed" +#endif +#endif + +/* Large selection threshold */ +#ifndef LARGESEL +#define LARGESEL 1000 +#endif + +#define MIN_DISPLAY_COL (CTX_MAX * 2) +#define ARCHIVE_CMD_LEN 16 +#define BLK_SHIFT_512 9 + +/* Detect hardlinks in du */ +#define HASH_BITS (0xFFFFFF) +#define HASH_OCTETS (HASH_BITS >> 6) /* 2^6 = 64 */ + +/* Entry flags */ +#define DIR_OR_DIRLNK 0x01 +#define HARD_LINK 0x02 +#define SYM_ORPHAN 0x04 +#define FILE_MISSING 0x08 +#define FILE_SELECTED 0x10 +#define FILE_SCANNED 0x20 + +/* Macros to define process spawn behaviour as flags */ +#define F_NONE 0x00 /* no flag set */ +#define F_MULTI 0x01 /* first arg can be combination of args; to be used with F_NORMAL */ +#define F_NOWAIT 0x02 /* don't wait for child process (e.g. file manager) */ +#define F_NOTRACE 0x04 /* suppress stdout and stderr (no traces) */ +#define F_NORMAL 0x08 /* spawn child process in non-curses regular CLI mode */ +#define F_CONFIRM 0x10 /* run command - show results before exit (must have F_NORMAL) */ +#define F_CHKRTN 0x20 /* wait for user prompt if cmd returns failure status */ +#define F_NOSTDIN 0x40 /* suppress stdin */ +#define F_PAGE 0x80 /* page output in run-cmd-as-plugin mode */ +#define F_TTY 0x100 /* Force stdout to go to tty if redirected to a non-tty */ +#define F_CLI (F_NORMAL | F_MULTI) +#define F_SILENT (F_CLI | F_NOTRACE) + +/* Version compare macros */ +/* + * states: S_N: normal, S_I: comparing integral part, S_F: comparing + * fractional parts, S_Z: idem but with leading Zeroes only + */ +#define S_N 0x0 +#define S_I 0x3 +#define S_F 0x6 +#define S_Z 0x9 + +/* result_type: VCMP: return diff; VLEN: compare using len_diff/diff */ +#define VCMP 2 +#define VLEN 3 + +/* Volume info */ +#define FREE 0 +#define CAPACITY 1 + +/* TYPE DEFINITIONS */ +typedef unsigned int uint_t; +typedef unsigned char uchar_t; +typedef unsigned short ushort_t; +typedef unsigned long long ullong_t; + +/* STRUCTURES */ + +/* Directory entry */ +typedef struct entry { + char *name; /* 8 bytes */ + time_t sec; /* 8 bytes */ + uint_t nsec; /* 4 bytes (enough to store nanosec) */ + mode_t mode; /* 4 bytes */ + off_t size; /* 8 bytes */ + struct { + ullong_t blocks : 40; /* 5 bytes (enough for 512 TiB in 512B blocks allocated) */ + ullong_t nlen : 16; /* 2 bytes (length of file name) */ + ullong_t flags : 8; /* 1 byte (flags specific to the file) */ + }; +#ifndef NOUG + uid_t uid; /* 4 bytes */ + gid_t gid; /* 4 bytes */ +#endif +} *pEntry; + +/* Selection marker */ +typedef struct { + char *startpos; + size_t len; +} selmark; + +/* Key-value pairs from env */ +typedef struct { + int key; + int off; +} kv; + +typedef struct { +#ifdef PCRE + const pcre *pcrex; +#else + const regex_t *regex; +#endif + const char *str; +} fltrexp_t; + +/* + * Settings + * NOTE: update default values if changing order + */ +typedef struct { + uint_t filtermode : 1; /* Set to enter filter mode */ + uint_t timeorder : 1; /* Set to sort by time */ + uint_t sizeorder : 1; /* Set to sort by file size */ + uint_t apparentsz : 1; /* Set to sort by apparent size (disk usage) */ + uint_t blkorder : 1; /* Set to sort by blocks used (disk usage) */ + uint_t extnorder : 1; /* Order by extension */ + uint_t showhidden : 1; /* Set to show hidden files */ + uint_t reserved0 : 1; + uint_t showdetail : 1; /* Clear to show lesser file info */ + uint_t ctxactive : 1; /* Context active or not */ + uint_t reverse : 1; /* Reverse sort */ + uint_t version : 1; /* Version sort */ + uint_t reserved1 : 1; + /* The following settings are global */ + uint_t curctx : 3; /* Current context number */ + uint_t prefersel : 1; /* Prefer selection over current, if exists */ + uint_t fileinfo : 1; /* Show file information on hover */ + uint_t nonavopen : 1; /* Open file on right arrow or `l` */ + uint_t autoenter : 1; /* auto-enter dir in type-to-nav mode */ + uint_t reserved2 : 1; + uint_t useeditor : 1; /* Use VISUAL to open text files */ + uint_t reserved3 : 3; + uint_t regex : 1; /* Use regex filters */ + uint_t x11 : 1; /* Copy to system clipboard, show notis, xterm title */ + uint_t timetype : 2; /* Time sort type (0: access, 1: change, 2: modification) */ + uint_t cliopener : 1; /* All-CLI app opener */ + uint_t waitedit : 1; /* For ops that can't be detached, used EDITOR */ + uint_t rollover : 1; /* Roll over at edges */ +} settings; + +/* Non-persistent program-internal states (alphabeical order) */ +typedef struct { + uint_t autofifo : 1; /* Auto-create NNN_FIFO */ + uint_t autonext : 1; /* Auto-proceed on open */ + uint_t dircolor : 1; /* Current status of dir color */ + uint_t dirctx : 1; /* Show dirs in context color */ + uint_t duinit : 1; /* Initialize disk usage */ + uint_t fifomode : 1; /* FIFO notify mode: 0: preview, 1: explore */ + uint_t forcequit : 1; /* Do not prompt on quit */ + uint_t initfile : 1; /* Positional arg is a file */ + uint_t interrupt : 1; /* Program received an interrupt */ + uint_t move : 1; /* Move operation */ + uint_t oldcolor : 1; /* Use older colorscheme */ + uint_t picked : 1; /* Plugin has picked files */ + uint_t picker : 1; /* Write selection to user-specified file */ + uint_t pluginit : 1; /* Plugin framework initialized */ + uint_t prstssn : 1; /* Persistent session */ + uint_t rangesel : 1; /* Range selection on */ + uint_t runctx : 3; /* The context in which plugin is to be run */ + uint_t runplugin : 1; /* Choose plugin mode */ + uint_t selmode : 1; /* Set when selecting files */ + uint_t stayonsel : 1; /* Disable auto-proceed on select */ + uint_t trash : 2; /* Trash method 0: rm -rf, 1: trash-cli, 2: gio trash */ + uint_t uidgid : 1; /* Show owner and group info */ + uint_t reserved : 7; /* Adjust when adding/removing a field */ +} runstate; + +/* Contexts or workspaces */ +typedef struct { + char c_path[PATH_MAX]; /* Current dir */ + char c_last[PATH_MAX]; /* Last visited dir */ + char c_name[NAME_MAX + 1]; /* Current file name */ + char c_fltr[REGEX_MAX]; /* Current filter */ + settings c_cfg; /* Current configuration */ + uint_t color; /* Color code for directories */ +} context; + +#ifndef NOSSN +typedef struct { + size_t ver; + size_t pathln[CTX_MAX]; + size_t lastln[CTX_MAX]; + size_t nameln[CTX_MAX]; + size_t fltrln[CTX_MAX]; +} session_header_t; +#endif + +/* GLOBALS */ + +/* Configuration, contexts */ +static settings cfg = { + 0, /* filtermode */ + 0, /* timeorder */ + 0, /* sizeorder */ + 0, /* apparentsz */ + 0, /* blkorder */ + 0, /* extnorder */ + 0, /* showhidden */ + 0, /* reserved0 */ + 0, /* showdetail */ + 1, /* ctxactive */ + 0, /* reverse */ + 0, /* version */ + 0, /* reserved1 */ + 0, /* curctx */ + 0, /* prefersel */ + 0, /* fileinfo */ + 0, /* nonavopen */ + 1, /* autoenter */ + 0, /* reserved2 */ + 0, /* useeditor */ + 0, /* reserved3 */ + 0, /* regex */ + 0, /* x11 */ + 2, /* timetype (T_MOD) */ + 0, /* cliopener */ + 0, /* waitedit */ + 1, /* rollover */ +}; + +static context g_ctx[CTX_MAX] __attribute__ ((aligned)); + +static int ndents, cur, last, curscroll, last_curscroll, total_dents = ENTRY_INCR, scroll_lines = 1; +static int nselected; +#ifndef NOFIFO +static int fifofd = -1; +#endif +static uint_t idletimeout, selbufpos, selbuflen; +static ushort_t xlines, xcols; +static ushort_t idle; +static uchar_t maxbm, maxplug, maxorder; +static uchar_t cfgsort[CTX_MAX + 1]; +static char *bmstr; +static char *pluginstr; +static char *orderstr; +static char *opener; +static char *editor; +static char *enveditor; +static char *pager; +static char *shell; +static char *home; +static char *initpath; +static char *cfgpath; +static char *selpath; +static char *listpath; +static char *listroot; +static char *plgpath; +static char *pnamebuf, *pselbuf, *findselpos; +static char *mark; +#ifndef NOX11 +static char *hostname; +#endif +#ifndef NOFIFO +static char *fifopath; +#endif +static char *lastcmd; +static ullong_t *ihashbmp; +static struct entry *pdents; +static blkcnt_t dir_blocks; +static kv *bookmark; +static kv *plug; +static kv *order; +static uchar_t tmpfplen, homelen; +static uchar_t blk_shift = BLK_SHIFT_512; +#ifndef NOMOUSE +static int middle_click_key; +#endif +#ifdef PCRE +static pcre *archive_pcre; +#else +static regex_t archive_re; +#endif + +/* pthread related */ +#define NUM_DU_THREADS (4) /* Can use sysconf(_SC_NPROCESSORS_ONLN) */ +#define DU_TEST (((node->fts_info & FTS_F) && \ + (sb->st_nlink <= 1 || test_set_bit((uint_t)sb->st_ino))) || node->fts_info & FTS_DP) + +static int threadbmp = -1; /* Has 1 in the bit position for idle threads */ +static volatile int active_threads; +static pthread_mutex_t running_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t hardlink_mutex = PTHREAD_MUTEX_INITIALIZER; +static ullong_t *core_files; +static blkcnt_t *core_blocks; +static ullong_t num_files; + +typedef struct { + char path[PATH_MAX]; + int entnum; + ushort_t core; + bool mntpoint; +} thread_data; + +static thread_data *core_data; + +/* Retain old signal handlers */ +static struct sigaction oldsighup; +static struct sigaction oldsigtstp; +static struct sigaction oldsigwinch; + +/* For use in functions which are isolated and don't return the buffer */ +static char g_buf[CMD_LEN_MAX] __attribute__ ((aligned)); + +/* For use as a scratch buffer in selection manipulation */ +static char g_sel[PATH_MAX] __attribute__ ((aligned)); + +/* Buffer to store tmp file path to show selection, file stats and help */ +static char g_tmpfpath[TMP_LEN_MAX] __attribute__ ((aligned)); + +/* Buffer to store plugins control pipe location */ +static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned)); + +/* Non-persistent runtime states */ +static runstate g_state; + +/* Options to identify file MIME */ +#if defined(__APPLE__) +#define FILE_MIME_OPTS "-bIL" +#elif !defined(__sun) /* no MIME option for 'file' */ +#define FILE_MIME_OPTS "-biL" +#endif + +/* Macros for utilities */ +#define UTIL_OPENER 0 +#define UTIL_ATOOL 1 +#define UTIL_BSDTAR 2 +#define UTIL_UNZIP 3 +#define UTIL_TAR 4 +#define UTIL_LOCKER 5 +#define UTIL_LAUNCH 6 +#define UTIL_SH_EXEC 7 +#define UTIL_BASH 8 +#define UTIL_SSHFS 9 +#define UTIL_RCLONE 10 +#define UTIL_VI 11 +#define UTIL_LESS 12 +#define UTIL_SH 13 +#define UTIL_FZF 14 +#define UTIL_NTFY 15 +#define UTIL_CBCP 16 +#define UTIL_NMV 17 +#define UTIL_TRASH_CLI 18 +#define UTIL_GIO_TRASH 19 +#define UTIL_RM_RF 20 + +/* Utilities to open files, run actions */ +static char * const utils[] = { +#ifdef __APPLE__ + "/usr/bin/open", +#elif defined __CYGWIN__ + "cygstart", +#elif defined __HAIKU__ + "open", +#else + "xdg-open", +#endif + "atool", + "bsdtar", + "unzip", + "tar", +#ifdef __APPLE__ + "bashlock", +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + "lock", +#elif defined __HAIKU__ + "peaclock", +#else + "vlock", +#endif + "launch", + "sh -c", + "bash", + "sshfs", + "rclone", + "vi", + "less", + "sh", + "fzf", + ".ntfy", + ".cbcp", + ".nmv", + "trash-put", + "gio trash", + "rm -rf", +}; + +/* Common strings */ +#define MSG_ZERO 0 /* Unused */ +#define MSG_0_ENTRIES 1 +#define STR_TMPFILE 2 +#define MSG_0_SELECTED 3 +#define MSG_CANCEL 4 +#define MSG_FAILED 5 +#define MSG_SSN_NAME 6 +#define MSG_CP_MV_AS 7 +#define MSG_CUR_SEL_OPTS 8 +#define MSG_FORCE_RM 9 +#define MSG_LIMIT 10 +#define MSG_NEW_OPTS 11 +#define MSG_CLI_MODE 12 +#define MSG_OVERWRITE 13 +#define MSG_SSN_OPTS 14 +#define MSG_QUIT_ALL 15 +#define MSG_HOSTNAME 16 +#define MSG_ARCHIVE_NAME 17 +#define MSG_OPEN_WITH 18 +#define MSG_NEW_PATH 19 +#define MSG_LINK_PREFIX 20 +#define MSG_COPY_NAME 21 +#define MSG_ENTER 22 +#define MSG_SEL_MISSING 23 +#define MSG_ACCESS 24 +#define MSG_EMPTY_FILE 25 +#define MSG_UNSUPPORTED 26 +#define MSG_NOT_SET 27 +#define MSG_EXISTS 28 +#define MSG_FEW_COLUMNS 29 +#define MSG_REMOTE_OPTS 30 +#define MSG_RCLONE_DELAY 31 +#define MSG_APP_NAME 32 +#define MSG_ARCHIVE_OPTS 33 +#define MSG_KEYS 34 +#define MSG_INVALID_REG 35 +#define MSG_ORDER 36 +#define MSG_LAZY 37 +#define MSG_FIRST 38 +#define MSG_RM_TMP 39 +#define MSG_INVALID_KEY 40 +#define MSG_NOCHANGE 41 +#define MSG_DIR_CHANGED 42 + +static const char * const messages[] = { + "", + "0 entries", + "/.nnnXXXXXX", + "0 selected", + "cancelled", + "failed!", + "session name: ", + "'c'p / 'm'v as?", + "'c'urrent / 's'el?", + "%s %s? [Esc cancels]", + "limit exceeded", + "'f'ile / 'd'ir / 's'ym / 'h'ard?", + "'c'li / 'g'ui?", + "overwrite?", + "'s'ave / 'l'oad / 'r'estore?", + "Quit all contexts?", + "remote name (- for hovered): ", + "archive [path/]name: ", + "open with: ", + "[path/]name: ", + "link prefix [@ for none]: ", + "copy [path/]name: ", + "\n'Enter' to continue", + "open failed", + "dir inaccessible", + "empty! edit/open with", + "?", + "not set", + "entry exists", + "too few cols!", + "'s'shfs / 'r'clone?", + "refresh if slow", + "app name: ", + "'o'pen / e'x'tract / 'l's / 'm'nt?", + "keys:", + "invalid regex", + "'a'u / 'd'u / 'e'xt / 'r'ev / 's'z / 't'm / 'v'er / 'c'lr / '^T'?", + "unmount failed! try lazy?", + "first file (\')/char?", + "remove tmp file?", + "invalid key", + "unchanged", + "dir changed, range sel off", +}; + +/* Supported configuration environment variables */ +#define NNN_OPTS 0 +#define NNN_BMS 1 +#define NNN_PLUG 2 +#define NNN_OPENER 3 +#define NNN_COLORS 4 +#define NNN_FCOLORS 5 +#define NNNLVL 6 +#define NNN_PIPE 7 +#define NNN_MCLICK 8 +#define NNN_SEL 9 +#define NNN_ARCHIVE 10 +#define NNN_ORDER 11 +#define NNN_HELP 12 /* strings end here */ +#define NNN_TRASH 13 /* flags begin here */ + +static const char * const env_cfg[] = { + "NNN_OPTS", + "NNN_BMS", + "NNN_PLUG", + "NNN_OPENER", + "NNN_COLORS", + "NNN_FCOLORS", + "NNNLVL", + "NNN_PIPE", + "NNN_MCLICK", + "NNN_SEL", + "NNN_ARCHIVE", + "NNN_ORDER", + "NNN_HELP", + "NNN_TRASH", +}; + +/* Required environment variables */ +#define ENV_SHELL 0 +#define ENV_VISUAL 1 +#define ENV_EDITOR 2 +#define ENV_PAGER 3 +#define ENV_NCUR 4 + +static const char * const envs[] = { + "SHELL", + "VISUAL", + "EDITOR", + "PAGER", + "nnn", +}; + +/* Time type used */ +#define T_ACCESS 0 +#define T_CHANGE 1 +#define T_MOD 2 + +#ifdef __linux__ +static char cp[] = "cp -iRp"; +static char mv[] = "mv -i"; +#else +static char cp[] = "cp -iRp"; +static char mv[] = "mv -i"; +#endif + +/* Archive commands */ +static const char * const archive_cmd[] = {"atool -a", "bsdtar -acvf", "zip -r", "tar -acvf"}; + +/* Tokens used for path creation */ +#define TOK_BM 0 +#define TOK_SSN 1 +#define TOK_MNT 2 +#define TOK_PLG 3 + +static const char * const toks[] = { + "bookmarks", + "sessions", + "mounts", + "plugins", /* must be the last entry */ +}; + +/* Patterns */ +#define P_CPMVFMT 0 +#define P_CPMVRNM 1 +#define P_ARCHIVE 2 +#define P_REPLACE 3 +#define P_ARCHIVE_CMD 4 + +static const char * const patterns[] = { + SED" -i 's|^\\(\\(.*/\\)\\(.*\\)$\\)|#\\1\\n\\3|' %s", + SED" 's|^\\([^#/][^/]\\?.*\\)$|%s/\\1|;s|^#\\(/.*\\)$|\\1|' " + "%s | tr '\\n' '\\0' | xargs -0 -n2 sh -c '%s \"$0\" \"$@\" < /dev/tty'", + "\\.(bz|bz2|gz|tar|taz|tbz|tbz2|tgz|z|zip)$", + SED" -i 's|^%s\\(.*\\)$|%s\\1|' %s", + SED" -ze 's|^%s/||' '%s' | xargs -0 %s %s", +}; + +/* Colors */ +#define C_BLK (CTX_MAX + 1) /* Block device: DarkSeaGreen1 */ +#define C_CHR (C_BLK + 1) /* Character device: Yellow1 */ +#define C_DIR (C_CHR + 1) /* Directory: DeepSkyBlue1 */ +#define C_EXE (C_DIR + 1) /* Executable file: Green1 */ +#define C_FIL (C_EXE + 1) /* Regular file: Normal */ +#define C_HRD (C_FIL + 1) /* Hard link: Plum4 */ +#define C_LNK (C_HRD + 1) /* Symbolic link: Cyan1 */ +#define C_MIS (C_LNK + 1) /* Missing file OR file details: Grey62 */ +#define C_ORP (C_MIS + 1) /* Orphaned symlink: DeepPink1 */ +#define C_PIP (C_ORP + 1) /* Named pipe (FIFO): Orange1 */ +#define C_SOC (C_PIP + 1) /* Socket: MediumOrchid1 */ +#define C_UND (C_SOC + 1) /* Unknown OR 0B regular/exe file: Red1 */ + +#ifdef ICONS_ENABLED +/* 0-9, A-Z, OTHER = 36. */ +static ushort_t icon_positions[37]; +#endif + +static char gcolors[] = "c1e2272e006033f7c6d6abc4"; +static uint_t fcolors[C_UND + 1] = {0}; + +/* Event handling */ +#ifdef LINUX_INOTIFY +#define NUM_EVENT_SLOTS 32 /* Make room for 32 events */ +#define EVENT_SIZE (sizeof(struct inotify_event)) +#define EVENT_BUF_LEN (EVENT_SIZE * NUM_EVENT_SLOTS) +static int inotify_fd, inotify_wd = -1; +static uint_t INOTIFY_MASK = /* IN_ATTRIB | */ IN_CREATE | IN_DELETE | IN_DELETE_SELF + | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO; +#elif defined(BSD_KQUEUE) +#define NUM_EVENT_SLOTS 1 +#define NUM_EVENT_FDS 1 +static int kq, event_fd = -1; +static struct kevent events_to_monitor[NUM_EVENT_FDS]; +static uint_t KQUEUE_FFLAGS = NOTE_DELETE | NOTE_EXTEND | NOTE_LINK + | NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE; +static struct timespec gtimeout; +#elif defined(HAIKU_NM) +static bool haiku_nm_active = FALSE; +static haiku_nm_h haiku_hnd; +#endif + +/* Function macros */ +#define tolastln() move(xlines - 1, 0) +#define tocursor() move(cur + 2 - curscroll, 0) +#define exitcurses() endwin() +#define printwarn(presel) printwait(strerror(errno), presel) +#define istopdir(path) ((path)[1] == '\0' && (path)[0] == '/') +#define copycurname() xstrsncpy(lastname, ndents ? pdents[cur].name : "\0", NAME_MAX + 1) +#define settimeout() timeout(1000) +#define cleartimeout() timeout(-1) +#define errexit() printerr(__LINE__) +#define setdirwatch() (cfg.filtermode ? (presel = FILTER) : (watch = TRUE)) +#define filterset() (g_ctx[cfg.curctx].c_fltr[1]) +/* We don't care about the return value from strcmp() */ +#define xstrcmp(a, b) (*(a) != *(b) ? -1 : strcmp((a), (b))) +/* A faster version of xisdigit */ +#define xisdigit(c) ((unsigned int) (c) - '0' <= 9) +#define xerror() perror(xitoa(__LINE__)) + +#ifdef TOURBIN_QSORT +#define ENTLESS(i, j) (entrycmpfn(pdents + (i), pdents + (j)) < 0) +#define ENTSWAP(i, j) (swap_ent((i), (j))) +#define ENTSORT(pdents, ndents, entrycmpfn) QSORT((ndents), ENTLESS, ENTSWAP) +#else +#define ENTSORT(pdents, ndents, entrycmpfn) qsort((pdents), (ndents), sizeof(*(pdents)), (entrycmpfn)) +#endif + +/* Forward declarations */ +static void redraw(char *path); +static int spawn(char *file, char *arg1, char *arg2, char *arg3, ushort_t flag); +static void move_cursor(int target, int ignore_scrolloff); +static char *load_input(int fd, const char *path); +static int set_sort_flags(int r); +static void statusbar(char *path); +static bool get_output(char *file, char *arg1, char *arg2, int fdout, bool multi, bool page); +#ifndef NOFIFO +static void notify_fifo(bool force); +#endif + +/* Functions */ + +static void sigint_handler(int sig) +{ + (void) sig; + g_state.interrupt = 1; +} + +static void clean_exit_sighandler(int sig) +{ + (void) sig; + exitcurses(); + /* This triggers cleanup() thanks to atexit() */ + exit(EXIT_SUCCESS); +} + +static char *xitoa(uint_t val) +{ + static char dst[32] = {'\0'}; + static const char digits[201] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + uint_t next = 30, quo, i; + + while (val >= 100) { + quo = val / 100; + i = (val - (quo * 100)) * 2; + val = quo; + dst[next] = digits[i + 1]; + dst[--next] = digits[i]; + --next; + } + + /* Handle last 1-2 digits */ + if (val < 10) + dst[next] = '0' + val; + else { + i = val * 2; + dst[next] = digits[i + 1]; + dst[--next] = digits[i]; + } + + return &dst[next]; +} + +/* Return the integer value of a char representing HEX */ +static uchar_t xchartohex(uchar_t c) +{ + if (xisdigit(c)) + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return c; +} + +/* + * Source: https://elixir.bootlin.com/linux/latest/source/arch/alpha/include/asm/bitops.h + */ +static bool test_set_bit(uint_t nr) +{ + nr &= HASH_BITS; + + pthread_mutex_lock(&hardlink_mutex); + ullong_t *m = ((ullong_t *)ihashbmp) + (nr >> 6); + + if (*m & (1 << (nr & 63))) { + pthread_mutex_unlock(&hardlink_mutex); + return FALSE; + } + + *m |= 1 << (nr & 63); + pthread_mutex_unlock(&hardlink_mutex); + + return TRUE; +} + +#ifndef __APPLE__ +/* Increase the limit on open file descriptors, if possible */ +static void max_openfds(void) +{ + struct rlimit rl; + + if (!getrlimit(RLIMIT_NOFILE, &rl)) + if (rl.rlim_cur < rl.rlim_max) { + rl.rlim_cur = rl.rlim_max; + setrlimit(RLIMIT_NOFILE, &rl); + } +} +#endif + +/* + * Wrapper to realloc() + * Frees current memory if realloc() fails and returns NULL. + * + * The *alloc() family returns aligned address: https://man7.org/linux/man-pages/man3/malloc.3.html + */ +static void *xrealloc(void *pcur, size_t len) +{ + void *pmem = realloc(pcur, len); + + if (!pmem) + free(pcur); + + return pmem; +} + +/* + * Just a safe strncpy(3) + * Always null ('\0') terminates if both src and dest are valid pointers. + * Returns the number of bytes copied including terminating null byte. + */ +static size_t xstrsncpy(char *restrict dst, const char *restrict src, size_t n) +{ + char *end = memccpy(dst, src, '\0', n); + + if (!end) { + dst[n - 1] = '\0'; // NOLINT + end = dst + n; /* If we return n here, binary size increases due to auto-inlining */ + } + + return end - dst; +} + +static inline size_t xstrlen(const char *restrict s) +{ +#if !defined(__GLIBC__) + return strlen(s); // NOLINT +#else + return (char *)rawmemchr(s, '\0') - s; // NOLINT +#endif +} + +static char *xstrdup(const char *restrict s) +{ + size_t len = xstrlen(s) + 1; + char *ptr = malloc(len); + + if (ptr) + xstrsncpy(ptr, s, len); + return ptr; +} + +static bool is_suffix(const char *restrict str, const char *restrict suffix) +{ + if (!str || !suffix) + return FALSE; + + size_t lenstr = xstrlen(str); + size_t lensuffix = xstrlen(suffix); + + if (lensuffix > lenstr) + return FALSE; + + return (xstrcmp(str + (lenstr - lensuffix), suffix) == 0); +} + +static inline bool is_prefix(const char *restrict str, const char *restrict prefix, size_t len) +{ + return !strncmp(str, prefix, len); +} + +/* + * The poor man's implementation of memrchr(3). + * We are only looking for '/' in this program. + * And we are NOT expecting a '/' at the end. + * Ideally 0 < n <= xstrlen(s). + */ +static void *xmemrchr(uchar_t *restrict s, uchar_t ch, size_t n) +{ +#if defined(__GLIBC__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + return memrchr(s, ch, n); +#else + + if (!s || !n) + return NULL; + + uchar_t *ptr = s + n; + + do { + if (*--ptr == ch) + return ptr; + } while (s != ptr); + + return NULL; +#endif +} + +/* A very simplified implementation, changes path */ +static char *xdirname(char *path) +{ + char *base = xmemrchr((uchar_t *)path, '/', xstrlen(path)); + + if (base == path) + path[1] = '\0'; + else + *base = '\0'; + + return path; +} + +static char *xbasename(char *path) +{ + char *base = xmemrchr((uchar_t *)path, '/', xstrlen(path)); // NOLINT + + return base ? base + 1 : path; +} + +static inline char *xextension(const char *fname, size_t len) +{ + return xmemrchr((uchar_t *)fname, '.', len); +} + +#ifndef NOUG +/* + * One-shot cache for getpwuid/getgrgid. Returns the cached name if the + * provided uid is the same as the previous uid. Returns xitoa(guid) if + * the guid is not found in the password database. + */ +static char *getpwname(uid_t uid) +{ + static uint_t uidcache = UINT_MAX; + static char *namecache; + + if (uidcache != uid) { + struct passwd *pw = getpwuid(uid); + + uidcache = uid; + namecache = pw ? pw->pw_name : NULL; + } + + return namecache ? namecache : xitoa(uid); +} + +static char *getgrname(gid_t gid) +{ + static uint_t gidcache = UINT_MAX; + static char *grpcache; + + if (gidcache != gid) { + struct group *gr = getgrgid(gid); + + gidcache = gid; + grpcache = gr ? gr->gr_name : NULL; + } + + return grpcache ? grpcache : xitoa(gid); +} +#endif + +static inline bool getutil(char *util) +{ + return spawn("which", util, NULL, NULL, F_NORMAL | F_NOTRACE) == 0; +} + +/* + * Updates out with "dir/name or "/name" + * Returns the number of bytes copied including the terminating NULL byte + * + * Note: dir and out must be PATH_MAX in length to avoid macOS fault + */ +static size_t mkpath(const char *dir, const char *name, char *out) +{ + size_t len = 0; + + if (name[0] != '/') { // NOLINT + /* Handle root case */ + if (istopdir(dir)) + len = 1; + else + len = xstrsncpy(out, dir, PATH_MAX); + + out[len - 1] = '/'; // NOLINT + } + return (xstrsncpy(out + len, name, PATH_MAX - len) + len); +} + +/* Assumes both the paths passed are directories */ +static char *common_prefix(const char *path, char *prefix) +{ + const char *x = path, *y = prefix; + char *sep; + + if (!path || !*path || !prefix) + return NULL; + + if (!*prefix) { + xstrsncpy(prefix, path, PATH_MAX); + return prefix; + } + + while (*x && *y && (*x == *y)) + ++x, ++y; + + /* Strings are same */ + if (!*x && !*y) + return prefix; + + /* Path is shorter */ + if (!*x && *y == '/') { + xstrsncpy(prefix, path, y - path); + return prefix; + } + + /* Prefix is shorter */ + if (!*y && *x == '/') + return prefix; + + /* Shorten prefix */ + prefix[y - prefix] = '\0'; + + sep = xmemrchr((uchar_t *)prefix, '/', y - prefix); + if (sep != prefix) + *sep = '\0'; + else /* Just '/' */ + prefix[1] = '\0'; + + return prefix; +} + +/* + * The library function realpath() resolves symlinks. + * If there's a symlink in file list we want to show the symlink not what it's points to. + * Resolves ./../~ in path + */ +static char *abspath(const char *path, const char *cwd, char *buf) +{ + if (!path) + return NULL; + + if (path[0] == '~') + cwd = home; + else if ((path[0] != '/') && !cwd) + cwd = getcwd(NULL, 0); + + size_t dst_size = 0, src_size = xstrlen(path), cwd_size = cwd ? xstrlen(cwd) : 0; + size_t len = src_size; + const char *src; + char *dst; + /* + * We need to add 2 chars at the end as relative paths may start with: + * ./ (find .) + * no separator (fd .): this needs an additional char for '/' + */ + char *resolved_path = buf ? buf : malloc(src_size + cwd_size + 2); + + if (!resolved_path) + return NULL; + + /* Turn relative paths into absolute */ + if (path[0] != '/') { + if (!cwd) { + if (!buf) + free(resolved_path); + return NULL; + } + dst_size = xstrsncpy(resolved_path, cwd, cwd_size + 1) - 1; + } else + resolved_path[0] = '\0'; + + src = path; + dst = resolved_path + dst_size; + for (const char *next = NULL; next != path + src_size;) { + next = memchr(src, '/', len); + if (!next) + next = path + src_size; + + if (next - src == 2 && src[0] == '.' && src[1] == '.') { + if (dst - resolved_path) { + dst = xmemrchr((uchar_t *)resolved_path, '/', dst - resolved_path); + *dst = '\0'; + } + } else if (next - src == 1 && src[0] == '.') { + /* NOP */ + } else if (next - src) { + *(dst++) = '/'; + xstrsncpy(dst, src, next - src + 1); + dst += next - src; + } + + src = next + 1; + len = src_size - (src - path); + } + + if (*resolved_path == '\0') { + resolved_path[0] = '/'; + resolved_path[1] = '\0'; + } + + return resolved_path; +} + +static bool set_tilde_in_path(char *path) +{ + if (is_prefix(path, home, homelen)) { + home[homelen] = path[homelen - 1]; + path[homelen - 1] = '~'; + return TRUE; + } + + return FALSE; +} + +static void reset_tilde_in_path(char *path) +{ + path[homelen - 1] = home[homelen]; + home[homelen] = '\0'; +} + +#ifndef NOX11 +static void xterm_cfg(char *path) +{ + if (cfg.x11 && !g_state.picker) { + /* Signal CWD change to terminal */ + printf("\033]7;file://%s%s\033\\", hostname, path); + + /* Set terminal window title */ + bool r = set_tilde_in_path(path); + + printf("\033]2;%s\007", r ? &path[homelen - 1] : path); + fflush(stdout); + + if (r) + reset_tilde_in_path(path); + } +} +#endif + +static void convert_tilde(const char *path, char *buf) +{ + if (path[0] == '~') { + ssize_t len = xstrlen(home); + ssize_t loclen = xstrlen(path); + + xstrsncpy(buf, home, len + 1); + xstrsncpy(buf + len, path + 1, loclen); + } +} + +static int create_tmp_file(void) +{ + xstrsncpy(g_tmpfpath + tmpfplen - 1, messages[STR_TMPFILE], TMP_LEN_MAX - tmpfplen); + + int fd = mkstemp(g_tmpfpath); + + if (fd == -1) { + DPRINTF_S(strerror(errno)); + } + + return fd; +} + +static void msg(const char *message) +{ + dprintf(STDERR_FILENO, "%s\n", message); +} + +#ifdef KEY_RESIZE +static void handle_key_resize(void) +{ + endwin(); + refresh(); +} + +/* Clear the old prompt */ +static void clearoldprompt(void) +{ + // clear info line + move(xlines - 2, 0); + clrtoeol(); + + tolastln(); + clrtoeol(); + handle_key_resize(); +} +#endif + +/* Messages show up at the bottom */ +static inline void printmsg_nc(const char *msg) +{ + tolastln(); + addstr(msg); + clrtoeol(); +} + +static void printmsg(const char *msg) +{ + attron(COLOR_PAIR(cfg.curctx + 1)); + printmsg_nc(msg); + attroff(COLOR_PAIR(cfg.curctx + 1)); +} + +static void printwait(const char *msg, int *presel) +{ + printmsg(msg); + if (presel) { + *presel = MSGWAIT; + if (ndents) + xstrsncpy(g_ctx[cfg.curctx].c_name, pdents[cur].name, NAME_MAX + 1); + } +} + +/* Kill curses and display error before exiting */ +static void printerr(int linenum) +{ + exitcurses(); + perror(xitoa(linenum)); + if (!g_state.picker && selpath) + unlink(selpath); + free(pselbuf); + exit(1); +} + +static inline bool xconfirm(int c) +{ + return (c == 'y' || c == 'Y'); +} + +static int get_input(const char *prompt) +{ + if (prompt) + printmsg(prompt); + cleartimeout(); + + int r = getch(); + +#ifdef KEY_RESIZE + while (r == KEY_RESIZE) { + if (prompt) { + clearoldprompt(); + xlines = LINES; + printmsg(prompt); + } + + r = getch(); + } +#endif + settimeout(); + return r; +} + +static bool isselfileempty(void) +{ + struct stat sb; + + return (stat(selpath, &sb) == -1) || (!sb.st_size); +} + +static int get_cur_or_sel(void) +{ + bool sel = (selbufpos || !isselfileempty()); + + /* Check both local buffer and selection file for external selection */ + if (sel && ndents) { + /* If selection is preferred and we have a local selection, return selection. + * Always show the prompt in case of an external selection. + */ + if (cfg.prefersel && selbufpos) + return 's'; + + int choice = get_input(messages[MSG_CUR_SEL_OPTS]); + + return ((choice == 'c' || choice == 's') ? choice : 0); + } + + if (sel) + return 's'; + + if (ndents) + return 'c'; + + return 0; +} + +static void xdelay(useconds_t delay) +{ + refresh(); + usleep(delay); +} + +static char confirm_force(bool selection) +{ + char str[64]; + + snprintf(str, 64, messages[MSG_FORCE_RM], + g_state.trash ? utils[UTIL_GIO_TRASH] + 4 : utils[UTIL_RM_RF], + (selection ? "selection" : "hovered")); + + int r = get_input(str); + + if (r == ESC) + return '\0'; /* cancel */ + if (r == 'y' || r == 'Y') + return 'f'; /* forceful for rm */ + return (g_state.trash ? '\0' : 'i'); /* interactive for rm */ +} + +/* Writes buflen char(s) from buf to a file */ +static void writesel(const char *buf, const size_t buflen) +{ + if (!selpath) + return; + + int fd = open(selpath, O_CREAT | O_WRONLY | O_TRUNC, 0666); + + if (fd != -1) { + if (write(fd, buf, buflen) != (ssize_t)buflen) + printwarn(NULL); + close(fd); + } else + printwarn(NULL); +} + +static void appendfpath(const char *path, const size_t len) +{ + if ((selbufpos >= selbuflen) || ((len + 3) > (selbuflen - selbufpos))) { + selbuflen += PATH_MAX; + pselbuf = xrealloc(pselbuf, selbuflen); + if (!pselbuf) + errexit(); + } + + selbufpos += xstrsncpy(pselbuf + selbufpos, path, len); +} + +static void selbufrealloc(const size_t alloclen) +{ + if ((selbufpos + alloclen) > selbuflen) { + selbuflen = ALIGN_UP(selbufpos + alloclen, PATH_MAX); + pselbuf = xrealloc(pselbuf, selbuflen); + if (!pselbuf) + errexit(); + } +} + +/* Write selected file paths to fd, linefeed separated */ +static size_t seltofile(int fd, uint_t *pcount) +{ + uint_t lastpos, count = 0; + char *pbuf = pselbuf; + size_t pos = 0; + ssize_t len, prefixlen = 0, initlen = 0; + + if (pcount) + *pcount = 0; + + if (!selbufpos) + return 0; + + lastpos = selbufpos - 1; + + if (listpath) { + prefixlen = (ssize_t)xstrlen(listroot); + initlen = (ssize_t)xstrlen(listpath); + } + + while (pos <= lastpos) { + DPRINTF_S(pbuf); + len = (ssize_t)xstrlen(pbuf); + + if (!listpath || !is_prefix(pbuf, listpath, initlen)) { + if (write(fd, pbuf, len) != len) + return pos; + } else { + if (write(fd, listroot, prefixlen) != prefixlen) + return pos; + if (write(fd, pbuf + initlen, len - initlen) != (len - initlen)) + return pos; + } + + pos += len; + if (pos <= lastpos) { + if (write(fd, "\n", 1) != 1) + return pos; + pbuf += len + 1; + } + ++pos; + ++count; + } + + if (pcount) + *pcount = count; + + return pos; +} + +/* List selection from selection file (another instance) */ +static bool listselfile(void) +{ + if (isselfileempty()) + return FALSE; + + snprintf(g_buf, CMD_LEN_MAX, "tr \'\\0\' \'\\n\' < %s", selpath); + spawn(utils[UTIL_SH_EXEC], g_buf, NULL, NULL, F_CLI | F_CONFIRM); + + return TRUE; +} + +/* Reset selection indicators */ +static void resetselind(void) +{ + for (int r = 0; r < ndents; ++r) + if (pdents[r].flags & FILE_SELECTED) + pdents[r].flags &= ~FILE_SELECTED; +} + +static void startselection(void) +{ + if (!g_state.selmode) { + g_state.selmode = 1; + nselected = 0; + + if (selbufpos) { + resetselind(); + writesel(NULL, 0); + selbufpos = 0; + } + } +} + +static void clearselection(void) +{ + nselected = 0; + selbufpos = 0; + g_state.selmode = 0; + writesel(NULL, 0); +} + +static char *findinsel(char *startpos, int len) +{ + if (!selbufpos) + return FALSE; + + if (!startpos) + startpos = pselbuf; + + char *found = startpos; + size_t buflen = selbufpos - (startpos - pselbuf); + + while (1) { + /* memmem(3): not specified in POSIX.1, but present on a number of other systems. */ + found = memmem(found, buflen - (found - startpos), g_sel, len); + if (!found) + return NULL; + if (found == startpos || *(found - 1) == '\0') + return found; + found += len; /* We found g_sel as a substring of a path, move forward */ + if (found >= startpos + buflen) + return NULL; + } +} + +static int markcmp(const void *va, const void *vb) +{ + const selmark *ma = (selmark*)va; + const selmark *mb = (selmark*)vb; + + return ma->startpos - mb->startpos; +} + +/* scanselforpath() must be called before calling this */ +static inline void findmarkentry(size_t len, struct entry *dentp) +{ + if (!(dentp->flags & FILE_SCANNED)) { + if (findinsel(findselpos, len + xstrsncpy(g_sel + len, dentp->name, dentp->nlen))) + dentp->flags |= FILE_SELECTED; + dentp->flags |= FILE_SCANNED; + } +} + +/* + * scanselforpath() must be called before calling this + * pathlen = length of path + 1 (+1 for trailing slash) + */ +static void invertselbuf(const int pathlen) +{ + size_t len, endpos, shrinklen = 0, alloclen = 0; + char * const pbuf = g_sel + pathlen; + char *found; + int i, nmarked = 0, prev = 0; + struct entry *dentp; + selmark *marked = malloc(nselected * sizeof(selmark)); + bool scan = FALSE; + + /* First pass: inversion */ + for (i = 0; i < ndents; ++i) { + dentp = &pdents[i]; + + if (dentp->flags & FILE_SCANNED) { + if (dentp->flags & FILE_SELECTED) { + dentp->flags ^= FILE_SELECTED; /* Clear selection status */ + scan = TRUE; + } else { + dentp->flags |= FILE_SELECTED; + alloclen += pathlen + dentp->nlen; + } + } else { + dentp->flags |= FILE_SCANNED; + scan = TRUE; + } + + if (scan) { + len = pathlen + xstrsncpy(pbuf, dentp->name, NAME_MAX); + found = findinsel(findselpos, len); + if (found) { + if (findselpos == found) + findselpos += len; + + if (nmarked && (found + == (marked[nmarked - 1].startpos + marked[nmarked - 1].len))) + marked[nmarked - 1].len += len; + else { + marked[nmarked].startpos = found; + marked[nmarked].len = len; + ++nmarked; + } + + --nselected; + shrinklen += len; /* buffer size adjustment */ + } else { + dentp->flags |= FILE_SELECTED; + alloclen += pathlen + dentp->nlen; + } + scan = FALSE; + } + } + + /* + * Files marked for deselection could be found in arbitrary order. + * Sort by appearance in selection buffer. + * With entries sorted we can merge adjacent ones allowing us to + * move them in a single go. + */ + qsort(marked, nmarked, sizeof(selmark), &markcmp); + + /* Some files might be adjacent. Merge them into a single entry */ + for (i = 1; i < nmarked; ++i) { + if (marked[i].startpos == marked[prev].startpos + marked[prev].len) + marked[prev].len += marked[i].len; + else { + ++prev; + marked[prev].startpos = marked[i].startpos; + marked[prev].len = marked[i].len; + } + } + + /* + * Number of entries is increased by encountering a non-adjacent entry + * After we finish the loop we should increment it once more. + */ + + if (nmarked) /* Make sure there is something to deselect */ + nmarked = prev + 1; + + /* Using merged entries remove unselected chunks from selection buffer */ + for (i = 0; i < nmarked; ++i) { + /* + * found: points to where the current block starts + * variable is recycled from previous for readability + * endpos: points to where the the next block starts + * area between the end of current block (found + len) + * and endpos is selected entries. This is what we are + * moving back. + */ + found = marked[i].startpos; + endpos = (i + 1 == nmarked ? selbufpos : marked[i + 1].startpos - pselbuf); + len = marked[i].len; + + /* Move back only selected entries. No selected memory is moved twice */ + memmove(found, found + len, endpos - (found + len - pselbuf)); + } + + free(marked); + + /* Buffer size adjustment */ + selbufpos -= shrinklen; + + selbufrealloc(alloclen); + + /* Second pass: append newly selected to buffer */ + for (i = 0; i < ndents; ++i) { + if (pdents[i].flags & FILE_SELECTED) { + len = pathlen + xstrsncpy(pbuf, pdents[i].name, NAME_MAX); + appendfpath(g_sel, len); + ++nselected; + } + } + + nselected ? writesel(pselbuf, selbufpos - 1) : clearselection(); +} + +/* + * scanselforpath() must be called before calling this + * pathlen = length of path + 1 (+1 for trailing slash) + */ +static void addtoselbuf(const int pathlen, int startid, int endid) +{ + int i; + size_t len, alloclen = 0; + struct entry *dentp; + char *found; + char * const pbuf = g_sel + pathlen; + + /* Remember current selection buffer position */ + for (i = startid; i <= endid; ++i) { + dentp = &pdents[i]; + + if (findselpos) { + len = pathlen + xstrsncpy(pbuf, dentp->name, NAME_MAX); + found = findinsel(findselpos, len); + if (found) { + dentp->flags |= (FILE_SCANNED | FILE_SELECTED); + if (found == findselpos) { + findselpos += len; + if (findselpos == (pselbuf + selbufpos)) + findselpos = NULL; + } + } else + alloclen += pathlen + dentp->nlen; + } else + alloclen += pathlen + dentp->nlen; + } + + selbufrealloc(alloclen); + + for (i = startid; i <= endid; ++i) { + if (!(pdents[i].flags & FILE_SELECTED)) { + len = pathlen + xstrsncpy(pbuf, pdents[i].name, NAME_MAX); + appendfpath(g_sel, len); + ++nselected; + pdents[i].flags |= (FILE_SCANNED | FILE_SELECTED); + } + } + + writesel(pselbuf, selbufpos - 1); /* Truncate NULL from end */ +} + +/* Removes g_sel from selbuf */ +static void rmfromselbuf(size_t len) +{ + char *found = findinsel(findselpos, len); + if (!found) + return; + + memmove(found, found + len, selbufpos - (found + len - pselbuf)); + selbufpos -= len; + + nselected ? writesel(pselbuf, selbufpos - 1) : clearselection(); +} + +static int scanselforpath(const char *path, bool getsize) +{ + if (!path[1]) { /* path should always be at least two bytes (including NULL) */ + g_sel[0] = '/'; + findselpos = pselbuf; + return 1; /* Length of '/' is 1 */ + } + + size_t off = xstrsncpy(g_sel, path, PATH_MAX); + + g_sel[off - 1] = '/'; + /* + * We set findselpos only here. Directories can be listed in arbitrary order. + * This is the best best we can do for remembering position. + */ + findselpos = findinsel(NULL, off); + + if (getsize) + return off; + return (findselpos ? off : 0); +} + +/* Finish selection procedure before an operation */ +static void endselection(bool endselmode) +{ + int fd; + ssize_t count; + char buf[sizeof(patterns[P_REPLACE]) + PATH_MAX + (TMP_LEN_MAX << 1)]; + + if (endselmode && g_state.selmode) + g_state.selmode = 0; + + /* The code below is only for listing mode */ + if (!listpath || !selbufpos) + return; + + fd = create_tmp_file(); + if (fd == -1) { + DPRINTF_S("couldn't create tmp file"); + return; + } + + seltofile(fd, NULL); + if (close(fd)) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + return; + } + + snprintf(buf, sizeof(buf), patterns[P_REPLACE], listpath, listroot, g_tmpfpath); + spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI); + + fd = open(g_tmpfpath, O_RDONLY); + if (fd == -1) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + if (unlink(g_tmpfpath)) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + } + return; + } + + count = read(fd, pselbuf, selbuflen); + if (count < 0) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + if (close(fd) || unlink(g_tmpfpath)) { + DPRINTF_S(strerror(errno)); + } + return; + } + + if (close(fd) || unlink(g_tmpfpath)) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + return; + } + + selbufpos = count; + pselbuf[--count] = '\0'; + for (--count; count > 0; --count) + if (pselbuf[count] == '\n' && pselbuf[count+1] == '/') + pselbuf[count] = '\0'; + + writesel(pselbuf, selbufpos - 1); +} + +/* Returns: 1 - success, 0 - none selected, -1 - other failure */ +static int editselection(void) +{ + int ret = -1; + int fd, lines = 0; + ssize_t count; + struct stat sb; + time_t mtime; + + if (!selbufpos) /* External selection is only editable at source */ + return listselfile(); + + fd = create_tmp_file(); + if (fd == -1) { + DPRINTF_S("couldn't create tmp file"); + return -1; + } + + seltofile(fd, NULL); + if (close(fd)) { + DPRINTF_S(strerror(errno)); + return -1; + } + + /* Save the last modification time */ + if (stat(g_tmpfpath, &sb)) { + DPRINTF_S(strerror(errno)); + unlink(g_tmpfpath); + return -1; + } + mtime = sb.st_mtime; + + spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, NULL, F_CLI); + + fd = open(g_tmpfpath, O_RDONLY); + if (fd == -1) { + DPRINTF_S(strerror(errno)); + unlink(g_tmpfpath); + return -1; + } + + fstat(fd, &sb); + + if (mtime == sb.st_mtime) { + DPRINTF_S("selection is not modified"); + unlink(g_tmpfpath); + return 1; + } + + if (sb.st_size > selbufpos) { + DPRINTF_S("edited buffer larger than previous"); + unlink(g_tmpfpath); + goto emptyedit; + } + + count = read(fd, pselbuf, selbuflen); + if (count < 0) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + if (close(fd) || unlink(g_tmpfpath)) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + } + goto emptyedit; + } + + if (close(fd) || unlink(g_tmpfpath)) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + goto emptyedit; + } + + if (!count) { + ret = 1; + goto emptyedit; + } + + resetselind(); + selbufpos = count; + /* The last character should be '\n' */ + pselbuf[--count] = '\0'; + for (--count; count > 0; --count) { + /* Replace every '\n' that separates two paths */ + if (pselbuf[count] == '\n' && pselbuf[count + 1] == '/') { + ++lines; + pselbuf[count] = '\0'; + } + } + + /* Add a line for the last file */ + ++lines; + + if (lines > nselected) { + DPRINTF_S("files added to selection"); + goto emptyedit; + } + + nselected = lines; + writesel(pselbuf, selbufpos - 1); + + return 1; + +emptyedit: + resetselind(); + clearselection(); + return ret; +} + +static bool selsafe(void) +{ + /* Fail if selection file path not generated */ + if (!selpath) { + printmsg(messages[MSG_SEL_MISSING]); + return FALSE; + } + + /* Fail if selection file path isn't accessible */ + if (access(selpath, R_OK | W_OK) == -1) { + errno == ENOENT ? printmsg(messages[MSG_0_SELECTED]) : printwarn(NULL); + return FALSE; + } + + return TRUE; +} + +static void export_file_list(void) +{ + if (!ndents) + return; + + struct entry *pdent = pdents; + int fd = create_tmp_file(); + + if (fd == -1) { + DPRINTF_S(strerror(errno)); + return; + } + + for (int r = 0; r < ndents; ++pdent, ++r) { + if (write(fd, pdent->name, pdent->nlen - 1) != (pdent->nlen - 1)) + break; + + if ((r != ndents - 1) && (write(fd, "\n", 1) != 1)) + break; + } + + if (close(fd)) { + DPRINTF_S(strerror(errno)); + } + + spawn(editor, g_tmpfpath, NULL, NULL, F_CLI); + + if (xconfirm(get_input(messages[MSG_RM_TMP]))) + unlink(g_tmpfpath); +} + +static bool init_fcolors(void) +{ + char *f_colors = getenv(env_cfg[NNN_FCOLORS]); + + if (!f_colors || !*f_colors) + f_colors = gcolors; + + for (uchar_t id = C_BLK; *f_colors && id <= C_UND; ++id) { + fcolors[id] = xchartohex(*f_colors) << 4; + if (*++f_colors) { + fcolors[id] += xchartohex(*f_colors); + if (fcolors[id]) + init_pair(id, fcolors[id], -1); + } else + return FALSE; + ++f_colors; + } + + return TRUE; +} + +/* Initialize curses mode */ +static bool initcurses(void *oldmask) +{ +#ifdef NOMOUSE + (void) oldmask; +#endif + + if (g_state.picker) { + if (!newterm(NULL, stderr, stdin)) { + msg("newterm!"); + return FALSE; + } + } else if (!initscr()) { + msg("initscr!"); + DPRINTF_S(getenv("TERM")); + return FALSE; + } + + cbreak(); + noecho(); + nonl(); + //intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); +#ifndef NOMOUSE +#if NCURSES_MOUSE_VERSION <= 1 + mousemask(BUTTON1_PRESSED | BUTTON1_DOUBLE_CLICKED | BUTTON2_PRESSED | BUTTON3_PRESSED, + (mmask_t *)oldmask); +#else + mousemask(BUTTON1_PRESSED | BUTTON2_PRESSED | BUTTON3_PRESSED | BUTTON4_PRESSED + | BUTTON5_PRESSED, (mmask_t *)oldmask); +#endif + mouseinterval(0); +#endif + curs_set(FALSE); /* Hide cursor */ + + char *colors = getenv(env_cfg[NNN_COLORS]); + + if (colors || !getenv("NO_COLOR")) { + uint_t *pcode; + bool ext = FALSE; + + start_color(); + use_default_colors(); + + /* Initialize file colors */ + if (COLORS >= COLOR_256) { + if (!(g_state.oldcolor || init_fcolors())) { + exitcurses(); + msg(env_cfg[NNN_FCOLORS]); + return FALSE; + } + } else + g_state.oldcolor = 1; + + DPRINTF_D(COLORS); + DPRINTF_D(COLOR_PAIRS); + + if (colors && *colors == '#') { + char *sep = strchr(colors, ';'); + + if (!g_state.oldcolor && COLORS >= COLOR_256) { + ++colors; + ext = TRUE; + + /* + * If fallback colors are specified, set the separator + * to NULL so we don't interpret separator and fallback + * if fewer than CTX_MAX xterm 256 colors are specified. + */ + if (sep) + *sep = '\0'; + } else { + colors = sep; /* Detect if 8 colors fallback is appended */ + if (colors) + ++colors; + } + } + + /* Get and set the context colors */ + for (uchar_t i = 0; i < CTX_MAX; ++i) { + pcode = &g_ctx[i].color; + + if (colors && *colors) { + if (ext) { + *pcode = xchartohex(*colors) << 4; + if (*++colors) + fcolors[i + 1] = *pcode += xchartohex(*colors); + else { /* Each color code must be 2 hex symbols */ + exitcurses(); + msg(env_cfg[NNN_COLORS]); + return FALSE; + } + } else + *pcode = (*colors < '0' || *colors > '7') ? 4 : *colors - '0'; + ++colors; + } else + *pcode = 4; + + init_pair(i + 1, *pcode, -1); + } + } + +#ifdef ICONS_ENABLED + if (!g_state.oldcolor) { + uchar_t icolors[COLOR_256] = {0}; + char c; + + memset(icon_positions, 0x7f, sizeof(icon_positions)); + + for (uint_t i = 0; i < sizeof(icons_ext)/sizeof(struct icon_pair); ++i) { + c = TOUPPER(icons_ext[i].match[0]); + if (c >= 'A' && c <= 'Z') { + if (icon_positions[c - 'A' + 10] == 0x7f7f) + icon_positions[c - 'A' + 10] = i; + } else if (c >= '0' && c <= '9') { + if (icon_positions[c - '0'] == 0x7f7f) + icon_positions[c - '0'] = i; + } else if (icon_positions[36] == 0x7f7f) + icon_positions[36] = i; + + if (icons_ext[i].color && !icolors[icons_ext[i].color]) { + init_pair(C_UND + 1 + icons_ext[i].color, icons_ext[i].color, -1); + icolors[icons_ext[i].color] = 1; + } + } + } +#endif + + settimeout(); /* One second */ + set_escdelay(25); + return TRUE; +} + +/* No NULL check here as spawn() guards against it */ +static char *parseargs(char *cmd, char **argv, int *pindex) +{ + int count = 0; + size_t len = xstrlen(cmd) + 1; + char *line = (char *)malloc(len); + + if (!line) { + DPRINTF_S("malloc()!"); + return NULL; + } + + xstrsncpy(line, cmd, len); + argv[count++] = line; + cmd = line; + + while (*line) { // NOLINT + if (ISBLANK(*line)) { + *line++ = '\0'; + + if (!*line) // NOLINT + break; + + argv[count++] = line; + if (count == EXEC_ARGS_MAX) { + count = -1; + break; + } + } + + ++line; + } + + if (count == -1 || count > (EXEC_ARGS_MAX - 4)) { /* 3 args and last NULL */ + free(cmd); + cmd = NULL; + DPRINTF_S("NULL or too many args"); + } + + *pindex = count; + return cmd; +} + +static void enable_signals(void) +{ + struct sigaction dfl_act = {.sa_handler = SIG_DFL}; + + sigaction(SIGHUP, &dfl_act, NULL); + sigaction(SIGINT, &dfl_act, NULL); + sigaction(SIGQUIT, &dfl_act, NULL); + sigaction(SIGTSTP, &dfl_act, NULL); + sigaction(SIGWINCH, &dfl_act, NULL); +} + +static pid_t xfork(uchar_t flag) +{ + pid_t p = fork(); + + if (p > 0) { + /* the parent ignores the interrupt, quit and hangup signals */ + sigaction(SIGHUP, &(struct sigaction){.sa_handler = SIG_IGN}, &oldsighup); + sigaction(SIGTSTP, &(struct sigaction){.sa_handler = SIG_DFL}, &oldsigtstp); + sigaction(SIGWINCH, &(struct sigaction){.sa_handler = SIG_IGN}, &oldsigwinch); + } else if (p == 0) { + /* We create a grandchild to detach */ + if (flag & F_NOWAIT) { + p = fork(); + + if (p > 0) + _exit(EXIT_SUCCESS); + else if (p == 0) { + enable_signals(); + setsid(); + return p; + } + + perror("fork"); + _exit(EXIT_FAILURE); + } + + /* So they can be used to stop the child */ + enable_signals(); + } + + /* This is the parent waiting for the child to create grandchild */ + if (flag & F_NOWAIT) + waitpid(p, NULL, 0); + + if (p == -1) + perror("fork"); + return p; +} + +static int join(pid_t p, uchar_t flag) +{ + int status = 0xFFFF; + + if (!(flag & F_NOWAIT)) { + /* wait for the child to exit */ + do { + } while (waitpid(p, &status, 0) == -1); + + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + DPRINTF_D(status); + } + } + + /* restore parent's signal handling */ + sigaction(SIGHUP, &oldsighup, NULL); + sigaction(SIGTSTP, &oldsigtstp, NULL); + sigaction(SIGWINCH, &oldsigwinch, NULL); + + return status; +} + +/* + * Spawns a child process. Behaviour can be controlled using flag. + * Limited to 3 arguments to a program, flag works on bit set. + */ +static int spawn(char *file, char *arg1, char *arg2, char *arg3, ushort_t flag) +{ + pid_t pid; + int status = 0, retstatus = 0xFFFF; + char *argv[EXEC_ARGS_MAX] = {0}; + char *cmd = NULL; + + if (!file || !*file) + return retstatus; + + /* Swap args if the first arg is NULL and the other 2 aren't */ + if (!arg1 && arg2) { + arg1 = arg2; + if (arg3) { + arg2 = arg3; + arg3 = NULL; + } else + arg2 = NULL; + } + + if (flag & F_MULTI) { + cmd = parseargs(file, argv, &status); + if (!cmd) + return -1; + } else + argv[status++] = file; + + argv[status] = arg1; + argv[++status] = arg2; + argv[++status] = arg3; + + if (flag & F_NORMAL) + exitcurses(); + + pid = xfork(flag); + if (pid == 0) { + /* Suppress stdout and stderr */ + if (flag & F_NOTRACE) { + int fd = open("/dev/null", O_WRONLY, 0200); + + if (flag & F_NOSTDIN) + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } else if (flag & F_TTY) { + /* If stdout has been redirected to a non-tty, force output to tty */ + if (!isatty(STDOUT_FILENO)) { + int fd = open(ctermid(NULL), O_WRONLY, 0200); + dup2(fd, STDOUT_FILENO); + close(fd); + } + } + + execvp(*argv, argv); + _exit(EXIT_SUCCESS); + } else { + retstatus = join(pid, flag); + DPRINTF_D(pid); + + if ((flag & F_CONFIRM) || ((flag & F_CHKRTN) && retstatus)) { + status = write(STDOUT_FILENO, messages[MSG_ENTER], xstrlen(messages[MSG_ENTER])); + (void)status; + while ((read(STDIN_FILENO, &status, 1) > 0) && (status != '\n')); + } + + if (flag & F_NORMAL) + refresh(); + + free(cmd); + } + + return retstatus; +} + +/* Get program name from env var, else return fallback program */ +static char *xgetenv(const char * const name, char *fallback) +{ + char *value = getenv(name); + + return value && value[0] ? value : fallback; +} + +/* Checks if an env variable is set to 1 */ +static inline uint_t xgetenv_val(const char *name) +{ + char *str = getenv(name); + + if (str && str[0]) + return atoi(str); + + return 0; +} + +/* Check if a dir exists, IS a dir, and is readable */ +static bool xdiraccess(const char *path) +{ + DIR *dirp = opendir(path); + + if (!dirp) { + printwarn(NULL); + return FALSE; + } + + closedir(dirp); + return TRUE; +} + +static bool plugscript(const char *plugin, uchar_t flags) +{ + mkpath(plgpath, plugin, g_buf); + if (!access(g_buf, X_OK)) { + spawn(g_buf, NULL, NULL, NULL, flags); + return TRUE; + } + + return FALSE; +} + +static void opstr(char *buf, char *op) +{ + snprintf(buf, CMD_LEN_MAX, "xargs -0 sh -c '%s \"$0\" \"$@\" . < /dev/tty' < %s", + op, selpath); +} + +static bool rmmulstr(char *buf) +{ + char r = confirm_force(TRUE); + if (!r) + return FALSE; + + if (!g_state.trash) + snprintf(buf, CMD_LEN_MAX, "xargs -0 sh -c 'rm -%cr \"$0\" \"$@\" < /dev/tty' < %s", + r, selpath); + else + snprintf(buf, CMD_LEN_MAX, "xargs -0 %s < %s", + utils[(g_state.trash == 1) ? UTIL_TRASH_CLI : UTIL_GIO_TRASH], selpath); + + return TRUE; +} + +/* Returns TRUE if file is removed, else FALSE */ +static bool xrm(char * const fpath) +{ + char r = confirm_force(FALSE); + if (!r) + return FALSE; + + if (!g_state.trash) { + char rm_opts[] = "-ir"; + + rm_opts[1] = r; + spawn("rm", rm_opts, fpath, NULL, F_NORMAL | F_CHKRTN); + } else + spawn(utils[(g_state.trash == 1) ? UTIL_TRASH_CLI : UTIL_GIO_TRASH], + fpath, NULL, NULL, F_NORMAL | F_MULTI); + + return (access(fpath, F_OK) == -1); /* File is removed */ +} + +static void xrmfromsel(char *path, char *fpath) +{ +#ifndef NOX11 + bool selected = TRUE; +#endif + + if ((pdents[cur].flags & DIR_OR_DIRLNK) && scanselforpath(fpath, FALSE)) + clearselection(); + else if (pdents[cur].flags & FILE_SELECTED) { + --nselected; + rmfromselbuf(mkpath(path, pdents[cur].name, g_sel)); + } +#ifndef NOX11 + else + selected = FALSE; + + if (selected && cfg.x11) + plugscript(utils[UTIL_CBCP], F_NOWAIT | F_NOTRACE); +#endif +} + +static uint_t lines_in_file(int fd, char *buf, size_t buflen) +{ + ssize_t len; + uint_t count = 0; + + while ((len = read(fd, buf, buflen)) > 0) + while (len) + count += (buf[--len] == '\n'); + + /* For all use cases 0 linecount is considered as error */ + return ((len < 0) ? 0 : count); +} + +static bool cpmv_rename(int choice, const char *path) +{ + int fd; + uint_t count = 0, lines = 0; + bool ret = FALSE; + char *cmd = (choice == 'c' ? cp : mv); + char buf[sizeof(patterns[P_CPMVRNM]) + sizeof(cmd) + (PATH_MAX << 1)]; + + fd = create_tmp_file(); + if (fd == -1) + return ret; + + /* selsafe() returned TRUE for this to be called */ + if (!selbufpos) { + snprintf(buf, sizeof(buf), "tr '\\0' '\\n' < %s > %s", selpath, g_tmpfpath); + spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI); + + count = lines_in_file(fd, buf, sizeof(buf)); + if (!count) + goto finish; + } else + seltofile(fd, &count); + + close(fd); + + snprintf(buf, sizeof(buf), patterns[P_CPMVFMT], g_tmpfpath); + spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI); + + spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, NULL, F_CLI); + + fd = open(g_tmpfpath, O_RDONLY); + if (fd == -1) + goto finish; + + lines = lines_in_file(fd, buf, sizeof(buf)); + DPRINTF_U(count); + DPRINTF_U(lines); + if (!lines || (2 * count != lines)) { + DPRINTF_S("num mismatch"); + goto finish; + } + + snprintf(buf, sizeof(buf), patterns[P_CPMVRNM], path, g_tmpfpath, cmd); + if (!spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI | F_CHKRTN)) + ret = TRUE; +finish: + if (fd >= 0) + close(fd); + + return ret; +} + +static bool cpmvrm_selection(enum action sel, char *path) +{ + int r; + + if (isselfileempty()) { + if (nselected) + clearselection(); + printmsg(messages[MSG_0_SELECTED]); + return FALSE; + } + + if (!selsafe()) + return FALSE; + + switch (sel) { + case SEL_CP: + opstr(g_buf, cp); + break; + case SEL_MV: + opstr(g_buf, mv); + break; + case SEL_CPMVAS: + r = get_input(messages[MSG_CP_MV_AS]); + if (r != 'c' && r != 'm') { + printmsg(messages[MSG_INVALID_KEY]); + return FALSE; + } + + if (!cpmv_rename(r, path)) { + printmsg(messages[MSG_FAILED]); + return FALSE; + } + break; + default: /* SEL_RM */ + if (!rmmulstr(g_buf)) { + printmsg(messages[MSG_CANCEL]); + return FALSE; + } + } + + if (sel != SEL_CPMVAS && spawn(utils[UTIL_SH_EXEC], g_buf, NULL, NULL, F_CLI | F_CHKRTN)) { + printmsg(messages[MSG_FAILED]); + return FALSE; + } + + /* Clear selection */ + clearselection(); + + return TRUE; +} + +#ifndef NOBATCH +static bool batch_rename(void) +{ + int fd1, fd2; + uint_t count = 0, lines = 0; + bool dir = FALSE, ret = FALSE; + char foriginal[TMP_LEN_MAX] = {0}; + static const char batchrenamecmd[] = "paste -d'\n' %s %s | "SED" 'N; /^\\(.*\\)\\n\\1$/!p;d' | " + "tr '\n' '\\0' | xargs -0 -n2 sh -c 'mv -i \"$0\" \"$@\" <" + " /dev/tty'"; + char buf[sizeof(batchrenamecmd) + (PATH_MAX << 1)]; + int i = get_cur_or_sel(); + + if (!i) + return ret; + + if (i == 'c') { /* Rename entries in current dir */ + selbufpos = 0; + dir = TRUE; + } + + fd1 = create_tmp_file(); + if (fd1 == -1) + return ret; + + xstrsncpy(foriginal, g_tmpfpath, xstrlen(g_tmpfpath) + 1); + + fd2 = create_tmp_file(); + if (fd2 == -1) { + unlink(foriginal); + close(fd1); + return ret; + } + + if (dir) + for (i = 0; i < ndents; ++i) + appendfpath(pdents[i].name, NAME_MAX); + + seltofile(fd1, &count); + seltofile(fd2, NULL); + close(fd2); + + if (dir) /* Don't retain dir entries in selection */ + selbufpos = 0; + + spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, NULL, F_CLI); + + /* Reopen file descriptor to get updated contents */ + fd2 = open(g_tmpfpath, O_RDONLY); + if (fd2 == -1) + goto finish; + + lines = lines_in_file(fd2, buf, sizeof(buf)); + DPRINTF_U(count); + DPRINTF_U(lines); + if (!lines || (count != lines)) { + DPRINTF_S("cannot delete files"); + goto finish; + } + + snprintf(buf, sizeof(buf), batchrenamecmd, foriginal, g_tmpfpath); + spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI); + ret = TRUE; + +finish: + if (fd1 >= 0) + close(fd1); + unlink(foriginal); + + if (fd2 >= 0) + close(fd2); + unlink(g_tmpfpath); + + return ret; +} +#endif + +static void get_archive_cmd(char *cmd, const char *archive) +{ + uchar_t i = 3; + + if (getutil(utils[UTIL_ATOOL])) + i = 0; + else if (getutil(utils[UTIL_BSDTAR])) + i = 1; + else if (is_suffix(archive, ".zip")) + i = 2; + // else tar + + xstrsncpy(cmd, archive_cmd[i], ARCHIVE_CMD_LEN); +} + +static void archive_selection(const char *cmd, const char *archive, const char *curpath) +{ + char *buf = malloc((xstrlen(patterns[P_ARCHIVE_CMD]) + xstrlen(cmd) + xstrlen(archive) + + xstrlen(curpath) + xstrlen(selpath)) * sizeof(char)); + if (!buf) { + DPRINTF_S(strerror(errno)); + printwarn(NULL); + return; + } + + snprintf(buf, CMD_LEN_MAX, patterns[P_ARCHIVE_CMD], curpath, selpath, cmd, archive); + spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI | F_CONFIRM); + free(buf); +} + +static void write_lastdir(const char *curpath, const char *outfile) +{ + if (!outfile) + xstrsncpy(cfgpath + xstrlen(cfgpath), "/.lastd", 8); + else + convert_tilde(outfile, g_buf); + + int fd = open(outfile + ? (outfile[0] == '~' ? g_buf : outfile) + : cfgpath, O_CREAT | O_WRONLY | O_TRUNC, 0666); + + if (fd != -1) { + dprintf(fd, "cd \"%s\"", curpath); + close(fd); + } +} + +/* + * We assume none of the strings are NULL. + * + * Let's have the logic to sort numeric names in numeric order. + * E.g., the order '1, 10, 2' doesn't make sense to human eyes. + * + * If the absolute numeric values are same, we fallback to alphasort. + */ +static int xstricmp(const char * const s1, const char * const s2) +{ + char *p1, *p2; + + long long v1 = strtoll(s1, &p1, 10); + long long v2 = strtoll(s2, &p2, 10); + + /* Check if at least 1 string is numeric */ + if (s1 != p1 || s2 != p2) { + /* Handle both pure numeric */ + if (s1 != p1 && s2 != p2) { + if (v2 > v1) + return -1; + + if (v1 > v2) + return 1; + } + + /* Only first string non-numeric */ + if (s1 == p1) + return 1; + + /* Only second string non-numeric */ + if (s2 == p2) + return -1; + } + + /* Handle 1. all non-numeric and 2. both same numeric value cases */ +#ifndef NOLC + return strcoll(s1, s2); +#else + return strcasecmp(s1, s2); +#endif +} + +/* + * Version comparison + * + * The code for version compare is a modified version of the GLIBC + * and uClibc implementation of strverscmp(). The source is here: + * https://elixir.bootlin.com/uclibc-ng/latest/source/libc/string/strverscmp.c + */ + +/* + * Compare S1 and S2 as strings holding indices/version numbers, + * returning less than, equal to or greater than zero if S1 is less than, + * equal to or greater than S2 (for more info, see the texinfo doc). + * + * Ignores case. + */ +static int xstrverscasecmp(const char * const s1, const char * const s2) +{ + const uchar_t *p1 = (const uchar_t *)s1; + const uchar_t *p2 = (const uchar_t *)s2; + int state, diff; + uchar_t c1, c2; + + /* + * Symbol(s) 0 [1-9] others + * Transition (10) 0 (01) d (00) x + */ + static const uint8_t next_state[] = { + /* state x d 0 */ + /* S_N */ S_N, S_I, S_Z, + /* S_I */ S_N, S_I, S_I, + /* S_F */ S_N, S_F, S_F, + /* S_Z */ S_N, S_F, S_Z + }; + + static const int8_t result_type[] __attribute__ ((aligned)) = { + /* state x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */ + + /* S_N */ VCMP, VCMP, VCMP, VCMP, VLEN, VCMP, VCMP, VCMP, VCMP, + /* S_I */ VCMP, -1, -1, 1, VLEN, VLEN, 1, VLEN, VLEN, + /* S_F */ VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, VCMP, + /* S_Z */ VCMP, 1, 1, -1, VCMP, VCMP, -1, VCMP, VCMP + }; + + if (p1 == p2) + return 0; + + c1 = TOUPPER(*p1); + ++p1; + c2 = TOUPPER(*p2); + ++p2; + + /* Hint: '0' is a digit too. */ + state = S_N + ((c1 == '0') + (xisdigit(c1) != 0)); + + while ((diff = c1 - c2) == 0) { + if (c1 == '\0') + return diff; + + state = next_state[state]; + c1 = TOUPPER(*p1); + ++p1; + c2 = TOUPPER(*p2); + ++p2; + state += (c1 == '0') + (xisdigit(c1) != 0); + } + + state = result_type[state * 3 + (((c2 == '0') + (xisdigit(c2) != 0)))]; // NOLINT + + switch (state) { + case VCMP: + return diff; + case VLEN: + while (xisdigit(*p1++)) + if (!xisdigit(*p2++)) + return 1; + return xisdigit(*p2) ? -1 : diff; + default: + return state; + } +} + +static int (*namecmpfn)(const char * const s1, const char * const s2) = &xstricmp; + +static char * (*fnstrstr)(const char *haystack, const char *needle) = &strcasestr; +#ifdef PCRE +static const unsigned char *tables; +static int pcreflags = PCRE_NO_AUTO_CAPTURE | PCRE_EXTENDED | PCRE_CASELESS | PCRE_UTF8; +#else +static int regflags = REG_NOSUB | REG_EXTENDED | REG_ICASE; +#endif + +#ifdef PCRE +static int setfilter(pcre **pcrex, const char *filter) +{ + const char *errstr = NULL; + int erroffset = 0; + + *pcrex = pcre_compile(filter, pcreflags, &errstr, &erroffset, tables); + + return errstr ? -1 : 0; +} +#else +static int setfilter(regex_t *regex, const char *filter) +{ + return regcomp(regex, filter, regflags); +} +#endif + +static int visible_re(const fltrexp_t *fltrexp, const char *fname) +{ +#ifdef PCRE + return pcre_exec(fltrexp->pcrex, NULL, fname, xstrlen(fname), 0, 0, NULL, 0) == 0; +#else + return regexec(fltrexp->regex, fname, 0, NULL, 0) == 0; +#endif +} + +static int visible_str(const fltrexp_t *fltrexp, const char *fname) +{ + return fnstrstr(fname, fltrexp->str) != NULL; +} + +static int (*filterfn)(const fltrexp_t *fltr, const char *fname) = &visible_str; + +static void clearfilter(void) +{ + char *fltr = g_ctx[cfg.curctx].c_fltr; + + if (fltr[1]) { + fltr[REGEX_MAX - 1] = fltr[1]; + fltr[1] = '\0'; + } +} + +static int entrycmp(const void *va, const void *vb) +{ + const struct entry *pa = (pEntry)va; + const struct entry *pb = (pEntry)vb; + + if ((pb->flags & DIR_OR_DIRLNK) != (pa->flags & DIR_OR_DIRLNK)) { + if (pb->flags & DIR_OR_DIRLNK) + return 1; + return -1; + } + + /* Sort based on specified order */ + if (cfg.timeorder) { + if (pb->sec > pa->sec) + return 1; + if (pb->sec < pa->sec) + return -1; + /* If sec matches, comare nsec */ + if (pb->nsec > pa->nsec) + return 1; + if (pb->nsec < pa->nsec) + return -1; + } else if (cfg.sizeorder) { + if (pb->size > pa->size) + return 1; + if (pb->size < pa->size) + return -1; + } else if (cfg.blkorder) { + if (pb->blocks > pa->blocks) + return 1; + if (pb->blocks < pa->blocks) + return -1; + } else if (cfg.extnorder && !(pb->flags & DIR_OR_DIRLNK)) { + char *extna = xextension(pa->name, pa->nlen - 1); + char *extnb = xextension(pb->name, pb->nlen - 1); + + if (extna || extnb) { + if (!extna) + return -1; + + if (!extnb) + return 1; + + int ret = strcasecmp(extna, extnb); + + if (ret) + return ret; + } + } + + return namecmpfn(pa->name, pb->name); +} + +static int reventrycmp(const void *va, const void *vb) +{ + if ((((pEntry)vb)->flags & DIR_OR_DIRLNK) + != (((pEntry)va)->flags & DIR_OR_DIRLNK)) { + if (((pEntry)vb)->flags & DIR_OR_DIRLNK) + return 1; + return -1; + } + + return -entrycmp(va, vb); +} + +static int (*entrycmpfn)(const void *va, const void *vb) = &entrycmp; + +/* In case of an error, resets *wch to Esc */ +static int handle_alt_key(wint_t *wch) +{ + timeout(0); + + int r = get_wch(wch); + + if (r == ERR) + *wch = ESC; + cleartimeout(); + + return r; +} + +static inline int handle_event(void) +{ + if (nselected && isselfileempty()) + clearselection(); + return CONTROL('L'); +} + +/* + * Returns SEL_* if key is bound and 0 otherwise. + * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}). + * The next keyboard input can be simulated by presel. + */ +static int nextsel(int presel) +{ +#ifdef BENCH + return SEL_QUIT; +#endif + int c = presel; + uint_t i; + bool escaped = FALSE; + + if (c == 0 || c == MSGWAIT) { +try_quit: + c = getch(); + //DPRINTF_D(c); + //DPRINTF_S(keyname(c)); + +#ifdef KEY_RESIZE + if (c == KEY_RESIZE) + handle_key_resize(); +#endif + + /* Handle Alt+key */ + if (c == ESC) { + timeout(0); + c = getch(); + if (c != ERR) { + if (c == ESC) + c = CONTROL('L'); + else { + ungetch(c); + c = ';'; + } + settimeout(); + } else if (escaped) { + settimeout(); + c = CONTROL('Q'); + } else { +#ifndef NOFIFO + if (!g_state.fifomode) + notify_fifo(TRUE); /* Send hovered path to NNN_FIFO */ +#endif + escaped = TRUE; + settimeout(); + goto try_quit; + } + } + + if (c == ERR && presel == MSGWAIT) + c = (cfg.filtermode || filterset()) ? FILTER : CONTROL('L'); + else if (c == FILTER || c == CONTROL('L')) + /* Clear previous filter when manually starting */ + clearfilter(); + } + + if (c == -1) { + ++idle; + + /* + * Do not check for directory changes in du mode. + * A redraw forces du calculation. + * Check for changes every odd second. + */ +#ifdef LINUX_INOTIFY + if (!cfg.blkorder && inotify_wd >= 0 && (idle & 1)) { + struct inotify_event *event; + char inotify_buf[EVENT_BUF_LEN]; + + memset((void *)inotify_buf, 0x0, EVENT_BUF_LEN); + i = read(inotify_fd, inotify_buf, EVENT_BUF_LEN); + if (i > 0) { + for (char *ptr = inotify_buf; + ptr + ((struct inotify_event *)ptr)->len < inotify_buf + i; + ptr += sizeof(struct inotify_event) + event->len) { + event = (struct inotify_event *)ptr; + DPRINTF_D(event->wd); + DPRINTF_D(event->mask); + if (!event->wd) + break; + + if (event->mask & INOTIFY_MASK) { + c = handle_event(); + break; + } + } + DPRINTF_S("inotify read done"); + } + } +#elif defined(BSD_KQUEUE) + if (!cfg.blkorder && event_fd >= 0 && (idle & 1)) { + struct kevent event_data[NUM_EVENT_SLOTS]; + + memset((void *)event_data, 0x0, sizeof(struct kevent) * NUM_EVENT_SLOTS); + if (kevent(kq, events_to_monitor, NUM_EVENT_SLOTS, + event_data, NUM_EVENT_FDS, &gtimeout) > 0) + c = handle_event(); + } +#elif defined(HAIKU_NM) + if (!cfg.blkorder && haiku_nm_active && (idle & 1) && haiku_is_update_needed(haiku_hnd)) + c = handle_event(); +#endif + } else + idle = 0; + + for (i = 0; i < (int)ELEMENTS(bindings); ++i) + if (c == bindings[i].sym) + return bindings[i].act; + + return 0; +} + +static int getorderstr(char *sort) +{ + int i = 0; + + if (cfg.showhidden) + sort[i++] = 'H'; + + if (cfg.timeorder) + sort[i++] = (cfg.timetype == T_MOD) ? 'M' : ((cfg.timetype == T_ACCESS) ? 'A' : 'C'); + else if (cfg.sizeorder) + sort[i++] = 'S'; + else if (cfg.extnorder) + sort[i++] = 'E'; + + if (entrycmpfn == &reventrycmp) + sort[i++] = 'R'; + + if (namecmpfn == &xstrverscasecmp) + sort[i++] = 'V'; + + if (i) + sort[i] = ' '; + + return i; +} + +static void showfilterinfo(void) +{ + int i = 0; + char info[REGEX_MAX] = "\0\0\0\0\0"; + + i = getorderstr(info); + + if (cfg.fileinfo && ndents && get_output("file", "-b", pdents[cur].name, -1, FALSE, FALSE)) + mvaddstr(xlines - 2, 2, g_buf); + else { + snprintf(info + i, REGEX_MAX - i - 1, " %s [/], %s [:]", + (cfg.regex ? "reg" : "str"), + ((fnstrstr == &strcasestr) ? "ic" : "noic")); + } + + mvaddstr(xlines - 2, xcols - xstrlen(info), info); +} + +static void showfilter(char *str) +{ + attron(COLOR_PAIR(cfg.curctx + 1)); + showfilterinfo(); + printmsg(str); + // printmsg calls attroff() +} + +static inline void swap_ent(int id1, int id2) +{ + struct entry _dent, *pdent1 = &pdents[id1], *pdent2 = &pdents[id2]; + + *(&_dent) = *pdent1; + *pdent1 = *pdent2; + *pdent2 = *(&_dent); +} + +#ifdef PCRE +static int fill(const char *fltr, pcre *pcrex) +#else +static int fill(const char *fltr, regex_t *re) +#endif +{ +#ifdef PCRE + fltrexp_t fltrexp = { .pcrex = pcrex, .str = fltr }; +#else + fltrexp_t fltrexp = { .regex = re, .str = fltr }; +#endif + + for (int count = 0; count < ndents; ++count) { + if (filterfn(&fltrexp, pdents[count].name) == 0) { + if (count != --ndents) { + swap_ent(count, ndents); + --count; + } + + continue; + } + } + + return ndents; +} + +static int matches(const char *fltr) +{ +#ifdef PCRE + pcre *pcrex = NULL; + + /* Search filter */ + if (cfg.regex && setfilter(&pcrex, fltr)) + return -1; + + ndents = fill(fltr, pcrex); + + if (cfg.regex) + pcre_free(pcrex); +#else + regex_t re; + + /* Search filter */ + if (cfg.regex && setfilter(&re, fltr)) + return -1; + + ndents = fill(fltr, &re); + + if (cfg.regex) + regfree(&re); +#endif + + ENTSORT(pdents, ndents, entrycmpfn); + + return ndents; +} + +/* + * Return the position of the matching entry or 0 otherwise + * Note there's no NULL check for fname + */ +static int dentfind(const char *fname, int n) +{ + for (int i = 0; i < n; ++i) + if (xstrcmp(fname, pdents[i].name) == 0) + return i; + + return 0; +} + +static int filterentries(char *path, char *lastname) +{ + wchar_t *wln = (wchar_t *)alloca(sizeof(wchar_t) * REGEX_MAX); + char *ln = g_ctx[cfg.curctx].c_fltr; + wint_t ch[2] = {0}; + int r, total = ndents, len; + char *pln = g_ctx[cfg.curctx].c_fltr + 1; + + DPRINTF_S(__func__); + + if (ndents && (ln[0] == FILTER || ln[0] == RFILTER) && *pln) { + if (matches(pln) != -1) { + move_cursor(dentfind(lastname, ndents), 0); + redraw(path); + } + + if (!cfg.filtermode) { + statusbar(path); + return 0; + } + + len = mbstowcs(wln, ln, REGEX_MAX); + } else { + ln[0] = wln[0] = cfg.regex ? RFILTER : FILTER; + ln[1] = wln[1] = '\0'; + len = 1; + } + + cleartimeout(); + curs_set(TRUE); + showfilter(ln); + + while ((r = get_wch(ch)) != ERR) { + //DPRINTF_D(*ch); + //DPRINTF_S(keyname(*ch)); + + switch (*ch) { +#ifdef KEY_RESIZE + case 0: // fallthrough + case KEY_RESIZE: + clearoldprompt(); + redraw(path); + showfilter(ln); + continue; +#endif + case KEY_DC: // fallthrough + case KEY_BACKSPACE: // fallthrough + case '\b': // fallthrough + case DEL: /* handle DEL */ + if (len != 1) { + wln[--len] = '\0'; + wcstombs(ln, wln, REGEX_MAX); + ndents = total; + } else { + *ch = FILTER; + goto end; + } + // fallthrough + case CONTROL('L'): + if (*ch == CONTROL('L')) { + if (wln[1]) { + ln[REGEX_MAX - 1] = ln[1]; + ln[1] = wln[1] = '\0'; + len = 1; + ndents = total; + } else if (ln[REGEX_MAX - 1]) { /* Show the previous filter */ + ln[1] = ln[REGEX_MAX - 1]; + ln[REGEX_MAX - 1] = '\0'; + len = mbstowcs(wln, ln, REGEX_MAX); + } else + goto end; + } + + /* Go to the top, we don't know if the hovered file will match the filter */ + cur = 0; + + if (matches(pln) != -1) + redraw(path); + + showfilter(ln); + continue; +#ifndef NOMOUSE + case KEY_MOUSE: + goto end; +#endif + case ESC: + if (handle_alt_key(ch) != ERR) { + if (*ch == ESC) /* Handle Alt+Esc */ + *ch = 'q'; /* Quit context */ + else { + unget_wch(*ch); + *ch = ';'; /* Run plugin */ + } + } + goto end; + } + + if (r != OK) /* Handle Fn keys in main loop */ + break; + + /* Handle all control chars in main loop */ + if (*ch < ASCII_MAX && keyname(*ch)[0] == '^' && *ch != '^') + goto end; + + if (len == 1) { + if (*ch == '?') /* Help and config key, '?' is an invalid regex */ + goto end; + + if (cfg.filtermode) { + switch (*ch) { + case '\'': // fallthrough /* Go to first non-dir file */ + case '+': // fallthrough /* Toggle auto-proceed on open */ + case ',': // fallthrough /* Mark CWD */ + case '-': // fallthrough /* Visit last visited dir */ + case '.': // fallthrough /* Show hidden files */ + case ';': // fallthrough /* Run plugin key */ + case '=': // fallthrough /* Launch app */ + case '>': // fallthrough /* Export file list */ + case '@': // fallthrough /* Visit start dir */ + case ']': // fallthorugh /* Prompt key */ + case '`': // fallthrough /* Visit / */ + case '~': /* Go HOME */ + goto end; + } + } + + /* Toggle case-sensitivity */ + if (*ch == CASE) { + fnstrstr = (fnstrstr == &strcasestr) ? &strstr : &strcasestr; +#ifdef PCRE + pcreflags ^= PCRE_CASELESS; +#else + regflags ^= REG_ICASE; +#endif + showfilter(ln); + continue; + } + + /* Toggle string or regex filter */ + if (*ch == FILTER) { + ln[0] = (ln[0] == FILTER) ? RFILTER : FILTER; + wln[0] = (uchar_t)ln[0]; + cfg.regex ^= 1; + filterfn = cfg.regex ? &visible_re : &visible_str; + showfilter(ln); + continue; + } + + /* Reset cur in case it's a repeat search */ + cur = 0; + } else if (len == REGEX_MAX - 1) + continue; + + wln[len] = (wchar_t)*ch; + wln[++len] = '\0'; + wcstombs(ln, wln, REGEX_MAX); + + /* Forward-filtering optimization: + * - new matches can only be a subset of current matches. + */ + /* ndents = total; */ +#ifdef MATCHFLTR + r = matches(pln); + if (r <= 0) { + !r ? unget_wch(KEY_BACKSPACE) : showfilter(ln); +#else + if (matches(pln) == -1) { + showfilter(ln); +#endif + continue; + } + + /* If the only match is a dir, auto-enter and cd into it */ + if (ndents == 1 && cfg.filtermode + && cfg.autoenter && (pdents[0].flags & DIR_OR_DIRLNK)) { + *ch = KEY_ENTER; + cur = 0; + goto end; + } + + /* + * redraw() should be above the auto-enter optimization, for + * the case where there's an issue with dir auto-enter, say, + * due to a permission problem. The transition is _jumpy_ in + * case of such an error. However, we optimize for successful + * cases where the dir has permissions. This skips a redraw(). + */ + redraw(path); + showfilter(ln); + } +end: + + /* Save last working filter in-filter */ + if (ln[1]) + ln[REGEX_MAX - 1] = ln[1]; + + /* Save current */ + copycurname(); + + curs_set(FALSE); + settimeout(); + + /* Return keys for navigation etc. */ + return *ch; +} + +/* Show a prompt with input string and return the changes */ +static char *xreadline(const char *prefill, const char *prompt) +{ + size_t len, pos; + int x, r; + const int WCHAR_T_WIDTH = sizeof(wchar_t); + wint_t ch[2] = {0}; + wchar_t * const buf = malloc(sizeof(wchar_t) * READLINE_MAX); + + if (!buf) + errexit(); + + cleartimeout(); + printmsg(prompt); + + if (prefill) { + DPRINTF_S(prefill); + len = pos = mbstowcs(buf, prefill, READLINE_MAX); + } else + len = (size_t)-1; + + if (len == (size_t)-1) { + buf[0] = '\0'; + len = pos = 0; + } + + x = getcurx(stdscr); + curs_set(TRUE); + + while (1) { + buf[len] = ' '; + attron(COLOR_PAIR(cfg.curctx + 1)); + if (pos > (size_t)(xcols - x)) { + mvaddnwstr(xlines - 1, x, buf + (pos - (xcols - x) + 1), xcols - x); + move(xlines - 1, xcols - 1); + } else { + mvaddnwstr(xlines - 1, x, buf, len + 1); + move(xlines - 1, x + wcswidth(buf, pos)); + } + attroff(COLOR_PAIR(cfg.curctx + 1)); + + r = get_wch(ch); + if (r == ERR) + continue; + + if (r == OK) { + switch (*ch) { + case KEY_ENTER: // fallthrough + case '\n': // fallthrough + case '\r': + goto END; + case CONTROL('D'): + if (pos < len) + ++pos; + else if (!(pos || len)) { /* Exit on ^D at empty prompt */ + len = 0; + goto END; + } else + continue; + // fallthrough + case DEL: // fallthrough + case '\b': /* rhel25 sends '\b' for backspace */ + if (pos > 0) { + memmove(buf + pos - 1, buf + pos, + (len - pos) * WCHAR_T_WIDTH); + --len, --pos; + } + continue; + case '\t': + if (!(len || pos) && ndents) + len = pos = mbstowcs(buf, pdents[cur].name, READLINE_MAX); + continue; + case CONTROL('F'): + if (pos < len) + ++pos; + continue; + case CONTROL('B'): + if (pos > 0) + --pos; + continue; + case CONTROL('W'): + printmsg(prompt); + do { + if (pos == 0) + break; + memmove(buf + pos - 1, buf + pos, + (len - pos) * WCHAR_T_WIDTH); + --pos, --len; + } while (buf[pos - 1] != ' ' && buf[pos - 1] != '/'); // NOLINT + continue; + case CONTROL('K'): + printmsg(prompt); + len = pos; + continue; + case CONTROL('L'): + printmsg(prompt); + len = pos = 0; + continue; + case CONTROL('A'): + pos = 0; + continue; + case CONTROL('E'): + pos = len; + continue; + case CONTROL('U'): + printmsg(prompt); + memmove(buf, buf + pos, (len - pos) * WCHAR_T_WIDTH); + len -= pos; + pos = 0; + continue; + case ESC: /* Exit prompt on Esc, but just filter out Alt+key */ + if (handle_alt_key(ch) != ERR) + continue; + + len = 0; + goto END; + } + + /* Filter out all other control chars */ + if (*ch < ASCII_MAX && keyname(*ch)[0] == '^') + continue; + + if (pos < READLINE_MAX - 1) { + memmove(buf + pos + 1, buf + pos, + (len - pos) * WCHAR_T_WIDTH); + buf[pos] = *ch; + ++len, ++pos; + continue; + } + } else { + switch (*ch) { +#ifdef KEY_RESIZE + case KEY_RESIZE: + clearoldprompt(); + xlines = LINES; + printmsg(prompt); + break; +#endif + case KEY_LEFT: + if (pos > 0) + --pos; + break; + case KEY_RIGHT: + if (pos < len) + ++pos; + break; + case KEY_BACKSPACE: + if (pos > 0) { + memmove(buf + pos - 1, buf + pos, + (len - pos) * WCHAR_T_WIDTH); + --len, --pos; + } + break; + case KEY_DC: + if (pos < len) { + memmove(buf + pos, buf + pos + 1, + (len - pos - 1) * WCHAR_T_WIDTH); + --len; + } + break; + case KEY_END: + pos = len; + break; + case KEY_HOME: + pos = 0; + break; + case KEY_UP: // fallthrough + case KEY_DOWN: + if (prompt && lastcmd && (xstrcmp(prompt, PROMPT) == 0)) { + printmsg(prompt); + len = pos = mbstowcs(buf, lastcmd, READLINE_MAX); // fallthrough + } + default: + break; + } + } + } + +END: + curs_set(FALSE); + settimeout(); + printmsg(""); + + buf[len] = '\0'; + + pos = wcstombs(g_buf, buf, READLINE_MAX - 1); + if (pos >= READLINE_MAX - 1) + g_buf[READLINE_MAX - 1] = '\0'; + + free(buf); + return g_buf; +} + +#ifndef NORL +/* + * Caller should check the value of presel to confirm if it needs to wait to show warning + */ +static char *getreadline(const char *prompt) +{ + exitcurses(); + + char *input = readline(prompt); + + refresh(); + + if (input && input[0]) { + add_history(input); + xstrsncpy(g_buf, input, CMD_LEN_MAX); + free(input); + return g_buf; + } + + free(input); + return NULL; +} +#endif + +/* + * Create symbolic/hard link(s) to file(s) in selection list + * Returns the number of links created, -1 on error + */ +static int xlink(char *prefix, char *path, char *curfname, char *buf, int *presel, int type) +{ + int count = 0, choice; + char *psel = pselbuf, *fname; + size_t pos = 0, len, r; + int (*link_fn)(const char *, const char *) = NULL; + char lnpath[PATH_MAX]; + + choice = get_cur_or_sel(); + if (!choice) + return -1; + + if (type == 's') /* symbolic link */ + link_fn = &symlink; + else /* hard link */ + link_fn = &link; + + if (choice == 'c' || (nselected == 1)) { + mkpath(path, prefix, lnpath); /* Generate link path */ + mkpath(path, (choice == 'c') ? curfname : pselbuf, buf); /* Generate target file path */ + + if (!link_fn(buf, lnpath)) { + if (choice == 's') + clearselection(); + return 1; /* One link created */ + } + + printwarn(presel); + return -1; + } + + while (pos < selbufpos) { + len = xstrlen(psel); + fname = xbasename(psel); + + r = xstrsncpy(buf, prefix, NAME_MAX + 1); /* Copy prefix */ + xstrsncpy(buf + r - 1, fname, NAME_MAX - r); /* Suffix target file name */ + mkpath(path, buf, lnpath); /* Generate link path */ + + if (!link_fn(psel, lnpath)) + ++count; + + pos += len + 1; + psel += len + 1; + } + + clearselection(); + return count; +} + +static bool parsekvpair(kv **arr, char **envcpy, const uchar_t id, uchar_t *items) +{ + bool new = TRUE; + const uchar_t INCR = 8; + uint_t i = 0; + kv *kvarr = NULL; + char *ptr = getenv(env_cfg[id]); + + if (!ptr || !*ptr) + return TRUE; + + *envcpy = xstrdup(ptr); + if (!*envcpy) { + xerror(); + return FALSE; + } + + ptr = *envcpy; + + while (*ptr && i < 100) { + if (new) { + if (!(i & (INCR - 1))) { + kvarr = xrealloc(kvarr, sizeof(kv) * (i + INCR)); + *arr = kvarr; + if (!kvarr) { + xerror(); + return FALSE; + } + memset(kvarr + i, 0, sizeof(kv) * INCR); + } + kvarr[i].key = (uchar_t)*ptr; + if (*++ptr != ':' || *++ptr == '\0' || *ptr == ';') + return FALSE; + kvarr[i].off = ptr - *envcpy; + ++i; + + new = FALSE; + } + + if (*ptr == ';') { + *ptr = '\0'; + new = TRUE; + } + + ++ptr; + } + + *items = i; + return (i != 0); +} + +/* + * Get the value corresponding to a key + * + * NULL is returned in case of no match, path resolution failure etc. + * buf would be modified, so check return value before access + */ +static char *get_kv_val(kv *kvarr, char *buf, int key, uchar_t max, uchar_t id) +{ + char *val; + + if (!kvarr) + return NULL; + + for (int r = 0; r < max && kvarr[r].key; ++r) { + if (kvarr[r].key == key) { + /* Do not allocate new memory for plugin */ + if (id == NNN_PLUG) + return pluginstr + kvarr[r].off; + + val = bmstr + kvarr[r].off; + convert_tilde(val, g_buf); + return abspath(((val[0] == '~') ? g_buf : val), NULL, buf); + } + } + + DPRINTF_S("Invalid key"); + return NULL; +} + +static int get_kv_key(kv *kvarr, char *val, uchar_t max, uchar_t id) +{ + if (!kvarr) + return -1; + + if (id != NNN_ORDER) /* For now this function supports only order string */ + return -1; + + for (int r = 0; r < max && kvarr[r].key; ++r) { + if (xstrcmp((orderstr + kvarr[r].off), val) == 0) + return kvarr[r].key; + } + + return -1; +} + +static void resetdircolor(int flags) +{ + /* Directories are always shown on top, clear the color when moving to first file */ + if (g_state.dircolor && !(flags & DIR_OR_DIRLNK)) { + attroff(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + g_state.dircolor = 0; + } +} + +/* + * Replace escape characters in a string with '?' + * Adjust string length to maxcols if > 0; + * Max supported str length: NAME_MAX; + */ +#ifdef NOLC +static char *unescape(const char *str, uint_t maxcols) +{ + char * const wbuf = g_buf; + char *buf = wbuf; + + xstrsncpy(wbuf, str, maxcols); +#else +static wchar_t *unescape(const char *str, uint_t maxcols) +{ + wchar_t * const wbuf = (wchar_t *)g_buf; + wchar_t *buf = wbuf; + size_t len = mbstowcs(wbuf, str, maxcols); /* Convert multi-byte to wide char */ + + len = wcswidth(wbuf, len); + + if (len >= maxcols) { + size_t lencount = maxcols; + + while (len > maxcols) /* Reduce wide chars one by one till it fits */ + len = wcswidth(wbuf, --lencount); + + wbuf[lencount] = L'\0'; + } +#endif + + while (*buf) { + if (*buf <= '\x1f' || *buf == '\x7f') + *buf = '\?'; + + ++buf; + } + + return wbuf; +} + +static off_t get_size(off_t size, off_t *pval, int comp) +{ + off_t rem = *pval; + off_t quo = rem / 10; + + if ((rem - (quo * 10)) >= 5) { + rem = quo + 1; + if (rem == comp) { + ++size; + rem = 0; + } + } else + rem = quo; + + *pval = rem; + return size; +} + +static char *coolsize(off_t size) +{ + const char * const U = "BKMGTPEZY"; + static char size_buf[12]; /* Buffer to hold human readable size */ + off_t rem = 0; + size_t ret; + int i = 0; + + while (size >= 1024) { + rem = size & (0x3FF); /* 1024 - 1 = 0x3FF */ + size >>= 10; + ++i; + } + + if (i == 1) { + rem = (rem * 1000) >> 10; + rem /= 10; + size = get_size(size, &rem, 10); + } else if (i == 2) { + rem = (rem * 1000) >> 10; + size = get_size(size, &rem, 100); + } else if (i > 2) { + rem = (rem * 10000) >> 10; + size = get_size(size, &rem, 1000); + } + + if (i > 0 && i < 6 && rem) { + ret = xstrsncpy(size_buf, xitoa(size), 12); + size_buf[ret - 1] = '.'; + + char *frac = xitoa(rem); + size_t toprint = i > 3 ? 3 : i; + size_t len = xstrlen(frac); + + if (len < toprint) { + size_buf[ret] = size_buf[ret + 1] = size_buf[ret + 2] = '0'; + xstrsncpy(size_buf + ret + (toprint - len), frac, len + 1); + } else + xstrsncpy(size_buf + ret, frac, toprint + 1); + + ret += toprint; + } else { + ret = xstrsncpy(size_buf, size ? xitoa(size) : "0", 12); + --ret; + } + + size_buf[ret] = U[i]; + size_buf[ret + 1] = '\0'; + + return size_buf; +} + +/* Convert a mode field into "ls -l" type perms field. */ +static char *get_lsperms(mode_t mode) +{ + static const char * const rwx[] = {"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"}; + static char bits[11] = {'\0'}; + + switch (mode & S_IFMT) { + case S_IFREG: + bits[0] = '-'; + break; + case S_IFDIR: + bits[0] = 'd'; + break; + case S_IFLNK: + bits[0] = 'l'; + break; + case S_IFSOCK: + bits[0] = 's'; + break; + case S_IFIFO: + bits[0] = 'p'; + break; + case S_IFBLK: + bits[0] = 'b'; + break; + case S_IFCHR: + bits[0] = 'c'; + break; + default: + bits[0] = '?'; + break; + } + + xstrsncpy(&bits[1], rwx[(mode >> 6) & 7], 4); + xstrsncpy(&bits[4], rwx[(mode >> 3) & 7], 4); + xstrsncpy(&bits[7], rwx[(mode & 7)], 4); + + if (mode & S_ISUID) + bits[3] = (mode & 0100) ? 's' : 'S'; /* user executable */ + if (mode & S_ISGID) + bits[6] = (mode & 0010) ? 's' : 'l'; /* group executable */ + if (mode & S_ISVTX) + bits[9] = (mode & 0001) ? 't' : 'T'; /* others executable */ + + return bits; +} + +#ifdef ICONS_ENABLED +static const struct icon_pair *get_icon(const struct entry *ent) +{ + ushort_t i = 0; + + for (; i < sizeof(icons_name)/sizeof(struct icon_pair); ++i) + if (strcasecmp(ent->name, icons_name[i].match) == 0) + return &icons_name[i]; + + if (ent->flags & DIR_OR_DIRLNK) + return &dir_icon; + + char *tmp = xextension(ent->name, ent->nlen); + + if (!tmp) { + if (ent->mode & 0100) + return &exec_icon; + + return &file_icon; + } + + /* Skip the . */ + ++tmp; + + if (*tmp >= '0' && *tmp <= '9') + i = *tmp - '0'; /* NUMBER 0-9 */ + else if (TOUPPER(*tmp) >= 'A' && TOUPPER(*tmp) <= 'Z') + i = TOUPPER(*tmp) - 'A' + 10; /* LETTER A-Z */ + else + i = 36; /* OTHER */ + + for (ushort_t j = icon_positions[i]; j < sizeof(icons_ext)/sizeof(struct icon_pair) && + icons_ext[j].match[0] == icons_ext[icon_positions[i]].match[0]; ++j) + if (strcasecmp(tmp, icons_ext[j].match) == 0) + return &icons_ext[j]; + + /* If there's no match and the file is executable, icon that */ + if (ent->mode & 0100) + return &exec_icon; + + return &file_icon; +} + +static void print_icon(const struct entry *ent, const int attrs) +{ + const struct icon_pair *picon = get_icon(ent); + + addstr(ICON_PADDING_LEFT); + if (picon->color) + attron(COLOR_PAIR(C_UND + 1 + picon->color)); + else if (attrs) + attron(attrs); + addstr(picon->icon); + if (picon->color) + attroff(COLOR_PAIR(C_UND + 1 + picon->color)); + else if (attrs) + attroff(attrs); + addstr(ICON_PADDING_RIGHT); +} +#endif + +static void print_time(const time_t *timep) +{ + struct tm t; + + localtime_r(timep, &t); + printw("%s-%02d-%02d %02d:%02d", + xitoa(t.tm_year + 1900), t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min); +} + +static char get_detail_ind(const mode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFDIR: // fallthrough + case S_IFREG: return ' '; + case S_IFLNK: return '@'; + case S_IFSOCK: return '='; + case S_IFIFO: return '|'; + case S_IFBLK: return 'b'; + case S_IFCHR: return 'c'; + } + return '?'; +} + +/* Note: attribute and indicator values must be initialized to 0 */ +static uchar_t get_color_pair_name_ind(const struct entry *ent, char *pind, int *pattr) +{ + switch (ent->mode & S_IFMT) { + case S_IFREG: + if (!ent->size) { + if (ent->mode & 0100) + *pind = '*'; + return C_UND; + } + if (ent->flags & HARD_LINK) { + if (ent->mode & 0100) + *pind = '*'; + return C_HRD; + } + if (ent->mode & 0100) { + *pind = '*'; + return C_EXE; + } + return C_FIL; + case S_IFDIR: + *pind = '/'; + if (g_state.oldcolor) + return C_DIR; + *pattr |= A_BOLD; + return g_state.dirctx ? cfg.curctx + 1 : C_DIR; + case S_IFLNK: + if (ent->flags & DIR_OR_DIRLNK) { + *pind = '/'; + *pattr |= g_state.oldcolor ? A_DIM : A_BOLD; + } else { + *pind = '@'; + if (g_state.oldcolor) + *pattr |= A_DIM; + } + if (!g_state.oldcolor || cfg.showdetail) + return (ent->flags & SYM_ORPHAN) ? C_ORP : C_LNK; + return 0; + case S_IFSOCK: + *pind = '='; + return C_SOC; + case S_IFIFO: + *pind = '|'; + return C_PIP; + case S_IFBLK: + return C_BLK; + case S_IFCHR: + return C_CHR; + } + + *pind = '?'; + return C_UND; +} + +static void printent(const struct entry *ent, uint_t namecols, bool sel) +{ + char ind = '\0'; + int attrs; + + if (cfg.showdetail) { + int type = ent->mode & S_IFMT; + char perms[6] = {' ', ' ', (char)('0' + ((ent->mode >> 6) & 7)), + (char)('0' + ((ent->mode >> 3) & 7)), + (char)('0' + (ent->mode & 7)), '\0'}; + + addch(' '); + attrs = g_state.oldcolor ? (resetdircolor(ent->flags), A_DIM) + : (fcolors[C_MIS] ? COLOR_PAIR(C_MIS) : 0); + if (attrs) + attron(attrs); + + /* Print details */ + print_time(&ent->sec); + + printw("%s%9s ", perms, (type == S_IFREG || type == S_IFDIR) + ? coolsize(cfg.blkorder ? (blkcnt_t)ent->blocks << blk_shift : ent->size) + : (type = (uchar_t)get_detail_ind(ent->mode), (char *)&type)); + + if (attrs) + attroff(attrs); + } + + attrs = 0; + + uchar_t color_pair = get_color_pair_name_ind(ent, &ind, &attrs); + + addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' '); + + if (g_state.oldcolor) + resetdircolor(ent->flags); + else { + if (ent->flags & FILE_MISSING) + color_pair = C_MIS; + if (color_pair && fcolors[color_pair]) + attrs |= COLOR_PAIR(color_pair); +#ifdef ICONS_ENABLED + print_icon(ent, attrs); +#endif + } + + if (sel) + attrs |= A_REVERSE; + if (attrs) + attron(attrs); + if (!ind) + ++namecols; + +#ifndef NOLC + addwstr(unescape(ent->name, namecols)); +#else + addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1)); +#endif + + if (attrs) + attroff(attrs); + if (ind) + addch(ind); +} + +static void savecurctx(char *path, char *curname, int nextctx) +{ + settings tmpcfg = cfg; + context *ctxr = &g_ctx[nextctx]; + + /* Save current context */ + if (curname) + xstrsncpy(g_ctx[tmpcfg.curctx].c_name, curname, NAME_MAX + 1); + else + g_ctx[tmpcfg.curctx].c_name[0] = '\0'; + + g_ctx[tmpcfg.curctx].c_cfg = tmpcfg; + + if (ctxr->c_cfg.ctxactive) { /* Switch to saved context */ + tmpcfg = ctxr->c_cfg; + /* Skip ordering an open context */ + if (order) { + cfgsort[CTX_MAX] = cfgsort[nextctx]; + cfgsort[nextctx] = '0'; + } + } else { /* Set up a new context from current context */ + ctxr->c_cfg.ctxactive = 1; + xstrsncpy(ctxr->c_path, path, PATH_MAX); + ctxr->c_last[0] = ctxr->c_name[0] = ctxr->c_fltr[0] = ctxr->c_fltr[1] = '\0'; + ctxr->c_cfg = tmpcfg; + /* If already in an ordered dir, clear ordering for the new context and let it order */ + if (cfgsort[cfg.curctx] == 'z') + cfgsort[nextctx] = 'z'; + } + + tmpcfg.curctx = nextctx; + cfg = tmpcfg; +} + +#ifndef NOSSN +static void save_session(const char *sname, int *presel) +{ + int fd, i; + session_header_t header; + bool status = FALSE; + char ssnpath[PATH_MAX]; + char spath[PATH_MAX]; + + memset(&header, 0, sizeof(session_header_t)); + + header.ver = SESSIONS_VERSION; + + for (i = 0; i < CTX_MAX; ++i) { + if (g_ctx[i].c_cfg.ctxactive) { + if (cfg.curctx == i && ndents) + /* Update current file name, arrows don't update it */ + xstrsncpy(g_ctx[i].c_name, pdents[cur].name, NAME_MAX + 1); + header.pathln[i] = strnlen(g_ctx[i].c_path, PATH_MAX) + 1; + header.lastln[i] = strnlen(g_ctx[i].c_last, PATH_MAX) + 1; + header.nameln[i] = strnlen(g_ctx[i].c_name, NAME_MAX) + 1; + header.fltrln[i] = REGEX_MAX; + } + } + + mkpath(cfgpath, toks[TOK_SSN], ssnpath); + mkpath(ssnpath, sname, spath); + + fd = open(spath, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (fd == -1) { + printwait(messages[MSG_SEL_MISSING], presel); + return; + } + + if ((write(fd, &header, sizeof(header)) != (ssize_t)sizeof(header)) + || (write(fd, &cfg, sizeof(cfg)) != (ssize_t)sizeof(cfg))) + goto END; + + for (i = 0; i < CTX_MAX; ++i) + if ((write(fd, &g_ctx[i].c_cfg, sizeof(settings)) != (ssize_t)sizeof(settings)) + || (write(fd, &g_ctx[i].color, sizeof(uint_t)) != (ssize_t)sizeof(uint_t)) + || (header.nameln[i] > 0 + && write(fd, g_ctx[i].c_name, header.nameln[i]) != (ssize_t)header.nameln[i]) + || (header.lastln[i] > 0 + && write(fd, g_ctx[i].c_last, header.lastln[i]) != (ssize_t)header.lastln[i]) + || (header.fltrln[i] > 0 + && write(fd, g_ctx[i].c_fltr, header.fltrln[i]) != (ssize_t)header.fltrln[i]) + || (header.pathln[i] > 0 + && write(fd, g_ctx[i].c_path, header.pathln[i]) != (ssize_t)header.pathln[i])) + goto END; + + status = TRUE; + +END: + close(fd); + + if (!status) + printwait(messages[MSG_FAILED], presel); +} + +static bool load_session(const char *sname, char **path, char **lastdir, char **lastname, bool restore) +{ + int fd, i = 0; + session_header_t header; + bool has_loaded_dynamically = !(sname || restore); + bool status = (sname && g_state.picker); /* Picker mode with session program option */ + char ssnpath[PATH_MAX]; + char spath[PATH_MAX]; + + mkpath(cfgpath, toks[TOK_SSN], ssnpath); + + if (!restore) { + sname = sname ? sname : xreadline(NULL, messages[MSG_SSN_NAME]); + if (!sname[0]) + return FALSE; + + mkpath(ssnpath, sname, spath); + + /* If user is explicitly loading the "last session", skip auto-save */ + if ((sname[0] == '@') && !sname[1]) + has_loaded_dynamically = FALSE; + } else + mkpath(ssnpath, "@", spath); + + if (has_loaded_dynamically) + save_session("@", NULL); + + fd = open(spath, O_RDONLY, 0666); + if (fd == -1) { + if (!status) { + printmsg(messages[MSG_SEL_MISSING]); + xdelay(XDELAY_INTERVAL_MS); + } + return FALSE; + } + + status = FALSE; + + if ((read(fd, &header, sizeof(header)) != (ssize_t)sizeof(header)) + || (header.ver != SESSIONS_VERSION) + || (read(fd, &cfg, sizeof(cfg)) != (ssize_t)sizeof(cfg))) + goto END; + + g_ctx[cfg.curctx].c_name[0] = g_ctx[cfg.curctx].c_last[0] + = g_ctx[cfg.curctx].c_fltr[0] = g_ctx[cfg.curctx].c_fltr[1] = '\0'; + + for (; i < CTX_MAX; ++i) + if ((read(fd, &g_ctx[i].c_cfg, sizeof(settings)) != (ssize_t)sizeof(settings)) + || (read(fd, &g_ctx[i].color, sizeof(uint_t)) != (ssize_t)sizeof(uint_t)) + || (header.nameln[i] > 0 + && read(fd, g_ctx[i].c_name, header.nameln[i]) != (ssize_t)header.nameln[i]) + || (header.lastln[i] > 0 + && read(fd, g_ctx[i].c_last, header.lastln[i]) != (ssize_t)header.lastln[i]) + || (header.fltrln[i] > 0 + && read(fd, g_ctx[i].c_fltr, header.fltrln[i]) != (ssize_t)header.fltrln[i]) + || (header.pathln[i] > 0 + && read(fd, g_ctx[i].c_path, header.pathln[i]) != (ssize_t)header.pathln[i])) + goto END; + + *path = g_ctx[cfg.curctx].c_path; + *lastdir = g_ctx[cfg.curctx].c_last; + *lastname = g_ctx[cfg.curctx].c_name; + set_sort_flags('\0'); /* Set correct sort options */ + status = TRUE; + +END: + close(fd); + + if (!status) { + printmsg(messages[MSG_FAILED]); + xdelay(XDELAY_INTERVAL_MS); + } else if (restore) + unlink(spath); + + return status; +} +#endif + +static uchar_t get_free_ctx(void) +{ + uchar_t r = cfg.curctx; + + do + r = (r + 1) & ~CTX_MAX; + while (g_ctx[r].c_cfg.ctxactive && (r != cfg.curctx)); + + return r; +} + +/* ctx is absolute: 1 to 4, + for smart context */ +static void set_smart_ctx(int ctx, char *nextpath, char **path, char *file, char **lastname, char **lastdir) +{ + if (ctx == '+') /* Get smart context */ + ctx = (int)(get_free_ctx() + 1); + + if (ctx == 0 || ctx == cfg.curctx + 1) { /* Same context */ + xstrsncpy(*lastdir, *path, PATH_MAX); + xstrsncpy(*path, nextpath, PATH_MAX); + } else { /* New context */ + --ctx; + /* Deactivate the new context and build from scratch */ + g_ctx[ctx].c_cfg.ctxactive = 0; + DPRINTF_S(nextpath); + savecurctx(nextpath, file, ctx); + *path = g_ctx[ctx].c_path; + *lastdir = g_ctx[ctx].c_last; + *lastname = g_ctx[ctx].c_name; + } +} + +/* + * Gets only a single line (that's what we need for now) or shows full command output in pager. + * Uses g_buf internally. + */ +static bool get_output(char *file, char *arg1, char *arg2, int fdout, bool multi, bool page) +{ + pid_t pid; + int pipefd[2]; + int index = 0, flags; + bool ret = FALSE; + bool tmpfile = ((fdout == -1) && page); + char *argv[EXEC_ARGS_MAX] = {0}; + char *cmd = NULL; + int fd = -1; + ssize_t len; + + if (tmpfile) { + fdout = create_tmp_file(); + if (fdout == -1) + return FALSE; + } + + if (multi) { + cmd = parseargs(file, argv, &index); + if (!cmd) + return FALSE; + } else + argv[index++] = file; + + argv[index] = arg1; + argv[++index] = arg2; + + if (pipe(pipefd) == -1) { + free(cmd); + errexit(); + } + + for (index = 0; index < 2; ++index) { + /* Get previous flags */ + flags = fcntl(pipefd[index], F_GETFL, 0); + + /* Set bit for non-blocking flag */ + flags |= O_NONBLOCK; + + /* Change flags on fd */ + fcntl(pipefd[index], F_SETFL, flags); + } + + pid = fork(); + if (pid == 0) { + /* In child */ + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[1]); + execvp(*argv, argv); + _exit(EXIT_SUCCESS); + } + + /* In parent */ + waitpid(pid, NULL, 0); + close(pipefd[1]); + free(cmd); + + while ((len = read(pipefd[0], g_buf, CMD_LEN_MAX - 1)) > 0) { + ret = TRUE; + if (fdout == -1) /* Read only the first line of output to buffer */ + break; + if (write(fdout, g_buf, len) != len) + break; + } + + close(pipefd[0]); + if (!page) + return ret; + + if (tmpfile) { + close(fdout); + close(fd); + } + + spawn(pager, g_tmpfpath, NULL, NULL, F_CLI | F_TTY); + + if (tmpfile) + unlink(g_tmpfpath); + + return TRUE; +} + +/* + * Follows the stat(1) output closely + */ +static bool show_stats(char *fpath) +{ + static char * const cmds[] = { +#ifdef FILE_MIME_OPTS + ("file " FILE_MIME_OPTS), +#endif + "file -b", + "stat", + }; + + size_t r = ELEMENTS(cmds); + int fd = create_tmp_file(); + if (fd == -1) + return FALSE; + + while (r) + get_output(cmds[--r], fpath, NULL, fd, TRUE, FALSE); + + close(fd); + + spawn(pager, g_tmpfpath, NULL, NULL, F_CLI | F_TTY); + unlink(g_tmpfpath); + return TRUE; +} + +static bool xchmod(const char *fpath, mode_t mode) +{ + /* (Un)set (S_IXUSR | S_IXGRP | S_IXOTH) */ + (0100 & mode) ? (mode &= ~0111) : (mode |= 0111); + + return (chmod(fpath, mode) == 0); +} + +static size_t get_fs_info(const char *path, bool type) +{ + struct statvfs svb; + + if (statvfs(path, &svb) == -1) + return 0; + + if (type == CAPACITY) + return (size_t)svb.f_blocks << ffs((int)(svb.f_frsize >> 1)); + + return (size_t)svb.f_bavail << ffs((int)(svb.f_frsize >> 1)); +} + +/* Create non-existent parents and a file or dir */ +static bool xmktree(char *path, bool dir) +{ + char *p = path; + char *slash = path; + + if (!p || !*p) + return FALSE; + + /* Skip the first '/' */ + ++p; + + while (*p != '\0') { + if (*p == '/') { + slash = p; + *p = '\0'; + } else { + ++p; + continue; + } + + /* Create folder from path to '\0' inserted at p */ + if (mkdir(path, 0777) == -1 && errno != EEXIST) { +#ifdef __HAIKU__ + // XDG_CONFIG_HOME contains a directory + // that is read-only, but the full path + // is writeable. + // Try to continue and see what happens. + // TODO: Find a more robust solution. + if (errno == B_READ_ONLY_DEVICE) + goto next; +#endif + DPRINTF_S("mkdir1!"); + DPRINTF_S(strerror(errno)); + *slash = '/'; + return FALSE; + } + +#ifdef __HAIKU__ +next: +#endif + /* Restore path */ + *slash = '/'; + ++p; + } + + if (dir) { + if (mkdir(path, 0777) == -1 && errno != EEXIST) { + DPRINTF_S("mkdir2!"); + DPRINTF_S(strerror(errno)); + return FALSE; + } + } else { + int fd = open(path, O_CREAT, 0666); + + if (fd == -1 && errno != EEXIST) { + DPRINTF_S("open!"); + DPRINTF_S(strerror(errno)); + return FALSE; + } + + close(fd); + } + + return TRUE; +} + +/* List or extract archive */ +static bool handle_archive(char *fpath /* in-out param */, char op) +{ + char arg[] = "-tvf"; /* options for tar/bsdtar to list files */ + char *util, *outdir = NULL; + bool x_to = FALSE; + bool is_atool = getutil(utils[UTIL_ATOOL]); + + if (op == 'x') { + outdir = xreadline(is_atool ? "." : xbasename(fpath), messages[MSG_NEW_PATH]); + if (!outdir || !*outdir) { /* Cancelled */ + printwait(messages[MSG_CANCEL], NULL); + return FALSE; + } + /* Do not create smart context for current dir */ + if (!(*outdir == '.' && outdir[1] == '\0')) { + if (!xmktree(outdir, TRUE) || (chdir(outdir) == -1)) { + printwarn(NULL); + return FALSE; + } + /* Copy the new dir path to open it in smart context */ + outdir = getcwd(NULL, 0); + x_to = TRUE; + } + } + + if (is_atool) { + util = utils[UTIL_ATOOL]; + arg[1] = op; + arg[2] = '\0'; + } else if (getutil(utils[UTIL_BSDTAR])) { + util = utils[UTIL_BSDTAR]; + if (op == 'x') + arg[1] = op; + } else if (is_suffix(fpath, ".zip")) { + util = utils[UTIL_UNZIP]; + arg[1] = (op == 'l') ? 'v' /* verbose listing */ : '\0'; + arg[2] = '\0'; + } else { + util = utils[UTIL_TAR]; + if (op == 'x') + arg[1] = op; + } + + if (op == 'x') /* extract */ + spawn(util, arg, fpath, NULL, F_NORMAL | F_MULTI); + else /* list */ + get_output(util, arg, fpath, -1, TRUE, TRUE); + + if (x_to) { + if (chdir(xdirname(fpath)) == -1) { + printwarn(NULL); + free(outdir); + return FALSE; + } + xstrsncpy(fpath, outdir, PATH_MAX); + free(outdir); + } else if (op == 'x') + fpath[0] = '\0'; + + return TRUE; +} + +static char *visit_parent(char *path, char *newpath, int *presel) +{ + char *dir; + + /* There is no going back */ + if (istopdir(path)) { + /* Continue in type-to-nav mode, if enabled */ + if (cfg.filtermode && presel) + *presel = FILTER; + return NULL; + } + + /* Use a copy as xdirname() may change the string passed */ + if (newpath) + xstrsncpy(newpath, path, PATH_MAX); + else + newpath = path; + + dir = xdirname(newpath); + if (chdir(dir) == -1) { + printwarn(presel); + return NULL; + } + + return dir; +} + +static void valid_parent(char *path, char *lastname) +{ + /* Save history */ + xstrsncpy(lastname, xbasename(path), NAME_MAX + 1); + + while (!istopdir(path)) + if (visit_parent(path, NULL, NULL)) + break; + + printwarn(NULL); + xdelay(XDELAY_INTERVAL_MS); +} + +static bool archive_mount(char *newpath) +{ + char *str = "install archivemount"; + char *dir, *cmd = str + 8; /* Start of "archivemount" */ + char *name = pdents[cur].name; + size_t len = pdents[cur].nlen; + char mntpath[PATH_MAX]; + + if (!getutil(cmd)) { + printmsg(str); + return FALSE; + } + + dir = xstrdup(name); + if (!dir) { + printmsg(messages[MSG_FAILED]); + return FALSE; + } + + while (len > 1) + if (dir[--len] == '.') { + dir[len] = '\0'; + break; + } + + DPRINTF_S(dir); + + /* Create the mount point */ + mkpath(cfgpath, toks[TOK_MNT], mntpath); + mkpath(mntpath, dir, newpath); + free(dir); + + if (!xmktree(newpath, TRUE)) { + printwarn(NULL); + return FALSE; + } + + /* Mount archive */ + DPRINTF_S(name); + DPRINTF_S(newpath); + if (spawn(cmd, name, newpath, NULL, F_NORMAL)) { + printmsg(messages[MSG_FAILED]); + return FALSE; + } + + return TRUE; +} + +static bool remote_mount(char *newpath) +{ + uchar_t flag = F_CLI; + int opt; + char *tmp, *env; + bool r = getutil(utils[UTIL_RCLONE]), s = getutil(utils[UTIL_SSHFS]); + char mntpath[PATH_MAX]; + + if (!(r || s)) { + printmsg("install sshfs/rclone"); + return FALSE; + } + + if (r && s) + opt = get_input(messages[MSG_REMOTE_OPTS]); + else + opt = (!s) ? 'r' : 's'; + + if (opt == 's') + env = xgetenv("NNN_SSHFS", utils[UTIL_SSHFS]); + else if (opt == 'r') { + flag |= F_NOWAIT | F_NOTRACE; + env = xgetenv("NNN_RCLONE", "rclone mount"); + } else { + printmsg(messages[MSG_INVALID_KEY]); + return FALSE; + } + + tmp = xreadline(NULL, "host[:dir] > "); + if (!tmp[0]) { + printmsg(messages[MSG_CANCEL]); + return FALSE; + } + + char *div = strchr(tmp, ':'); + + if (div) + *div = '\0'; + + /* Create the mount point */ + mkpath(cfgpath, toks[TOK_MNT], mntpath); + mkpath(mntpath, tmp, newpath); + if (!xmktree(newpath, TRUE)) { + printwarn(NULL); + return FALSE; + } + + if (!div) { /* Convert "host" to "host:" */ + size_t len = xstrlen(tmp); + + tmp[len] = ':'; + tmp[len + 1] = '\0'; + } else + *div = ':'; + + /* Connect to remote */ + if (opt == 's') { + if (spawn(env, tmp, newpath, NULL, flag)) { + printmsg(messages[MSG_FAILED]); + return FALSE; + } + } else { + spawn(env, tmp, newpath, NULL, flag); + printmsg(messages[MSG_RCLONE_DELAY]); + xdelay(XDELAY_INTERVAL_MS << 2); /* Set 4 times the usual delay */ + } + + return TRUE; +} + +/* + * Unmounts if the directory represented by name is a mount point. + * Otherwise, asks for hostname + * Returns TRUE if directory needs to be refreshed *. + */ +static bool unmount(char *name, char *newpath, int *presel, char *currentpath) +{ +#if defined(__APPLE__) || defined(__FreeBSD__) + static char cmd[] = "umount"; +#else + static char cmd[] = "fusermount3"; /* Arch Linux utility */ + static bool found = FALSE; +#endif + char *tmp = name; + struct stat sb, psb; + bool child = FALSE; + bool parent = FALSE; + bool hovered = FALSE; + char mntpath[PATH_MAX]; + +#if !defined(__APPLE__) && !defined(__FreeBSD__) + /* On Ubuntu it's fusermount */ + if (!found && !getutil(cmd)) { + cmd[10] = '\0'; + found = TRUE; + } +#endif + + mkpath(cfgpath, toks[TOK_MNT], mntpath); + + if (tmp && strcmp(mntpath, currentpath) == 0) { + mkpath(mntpath, tmp, newpath); + child = lstat(newpath, &sb) != -1; + parent = lstat(xdirname(newpath), &psb) != -1; + if (!child && !parent) { + *presel = MSGWAIT; + return FALSE; + } + } + + if (!tmp || !child || !S_ISDIR(sb.st_mode) || (child && parent && sb.st_dev == psb.st_dev)) { + tmp = xreadline(NULL, messages[MSG_HOSTNAME]); + if (!tmp[0]) + return FALSE; + if (name && (tmp[0] == '-') && (tmp[1] == '\0')) { + mkpath(currentpath, name, newpath); + hovered = TRUE; + } + } + + if (!hovered) + mkpath(mntpath, tmp, newpath); + + if (!xdiraccess(newpath)) { + *presel = MSGWAIT; + return FALSE; + } + +#if defined(__APPLE__) || defined(__FreeBSD__) + if (spawn(cmd, newpath, NULL, NULL, F_NORMAL)) { +#else + if (spawn(cmd, "-qu", newpath, NULL, F_NORMAL)) { +#endif + if (!xconfirm(get_input(messages[MSG_LAZY]))) + return FALSE; + +#ifdef __APPLE__ + if (spawn(cmd, "-l", newpath, NULL, F_NORMAL)) { +#elif defined(__FreeBSD__) + if (spawn(cmd, "-f", newpath, NULL, F_NORMAL)) { +#else + if (spawn(cmd, "-quz", newpath, NULL, F_NORMAL)) { +#endif + printwait(messages[MSG_FAILED], presel); + return FALSE; + } + } + + if (rmdir(newpath) == -1) { + printwarn(presel); + return FALSE; + } + + return TRUE; +} + +static void lock_terminal(void) +{ + spawn(xgetenv("NNN_LOCKER", utils[UTIL_LOCKER]), NULL, NULL, NULL, F_CLI); +} + +static void printkv(kv *kvarr, int fd, uchar_t max, uchar_t id) +{ + char *val = (id == NNN_BMS) ? bmstr : pluginstr; + + for (uchar_t i = 0; i < max && kvarr[i].key; ++i) + dprintf(fd, " %c: %s\n", (char)kvarr[i].key, val + kvarr[i].off); +} + +static void printkeys(kv *kvarr, char *buf, uchar_t max) +{ + uchar_t i = 0; + + for (; i < max && kvarr[i].key; ++i) { + buf[i << 1] = ' '; + buf[(i << 1) + 1] = kvarr[i].key; + } + + buf[i << 1] = '\0'; +} + +static size_t handle_bookmark(const char *bmark, char *newpath) +{ + int fd = '\r'; + size_t r; + + if (maxbm || bmark) { + r = xstrsncpy(g_buf, messages[MSG_KEYS], CMD_LEN_MAX); + + if (bmark) { /* There is a marked directory */ + g_buf[--r] = ' '; + g_buf[++r] = ','; + g_buf[++r] = '\0'; + ++r; + } + printkeys(bookmark, g_buf + r - 1, maxbm); + printmsg(g_buf); + fd = get_input(NULL); + } + + r = FALSE; + if (fd == ',') /* Visit marked directory */ + bmark ? xstrsncpy(newpath, bmark, PATH_MAX) : (r = MSG_NOT_SET); + else if (fd == '\r') /* Visit bookmarks directory */ + mkpath(cfgpath, toks[TOK_BM], newpath); + else if (!get_kv_val(bookmark, newpath, fd, maxbm, NNN_BMS)) + r = MSG_INVALID_KEY; + + if (!r && chdir(newpath) == -1) + r = MSG_ACCESS; + + return r; +} + +static void add_bookmark(char *path, char *newpath, int *presel) +{ + char *dir = xbasename(path); + + dir = xreadline(dir[0] ? dir : NULL, "name: "); + if (dir && *dir) { + size_t r = mkpath(cfgpath, toks[TOK_BM], newpath); + + newpath[r - 1] = '/'; + xstrsncpy(newpath + r, dir, PATH_MAX - r); + printwait((symlink(path, newpath) == -1) ? strerror(errno) : newpath, presel); + } else + printwait(messages[MSG_CANCEL], presel); +} + +/* + * The help string tokens (each line) start with a HEX value + * which indicates the number of spaces to print before the + * particular token. This method was chosen instead of a flat + * string because the number of bytes in help was increasing + * the binary size by around a hundred bytes. This would only + * have increased as we keep adding new options. + */ +static void show_help(const char *path) +{ + const char *start, *end; + const char helpstr[] = { + "0\n" + "1NAVIGATION\n" + "9Up k Up%-16cPgUp ^U Page up\n" + "9Dn j Down%-14cPgDn ^D Page down\n" + "9Lt h Parent%-12c~ ` @ - ~, /, start, prev\n" + "5Ret Rt l Open%-20c' First file/match\n" + "9g ^A Top%-21c. Toggle hidden\n" + "9G ^E End%-21c+ Toggle auto-proceed on open\n" + "8B (,) Book(mark)%-11cb ^/ Select bookmark\n" + "a1-4 Context%-11c(Sh)Tab Cycle/new context\n" + "62Esc ^Q Quit%-20cq Quit context\n" + "b^G QuitCD%-18cQ Pick/err, quit\n" + "0\n" + "1FILTER & PROMPT\n" + "c/ Filter%-17c^N Toggle type-to-nav\n" + "aEsc Exit prompt%-12c^L Toggle last filter\n" + "d%-20cAlt+Esc Unfilter, quit context\n" + "0\n" + "1FILES\n" + "9o ^O Open with%-15cn Create new/link\n" + "9f ^F File stats%-14cd Detail mode toggle\n" + "b^R Rename/dup%-14cr Batch rename\n" + "cz Archive%-17ce Edit file\n" + "c* Toggle exe%-14c> Export list\n" + "5Space ^J (Un)select%-12cm-m Select range/clear\n" + "ca Select all%-14cA Invert sel\n" + "9p ^P Copy here%-12cw ^W Cp/mv sel as\n" + "9v ^V Move here%-15cE Edit sel list\n" + "9x ^X Delete%-16cEsc Send to FIFO\n" + "0\n" + "1MISC\n" + "8Alt ; Select plugin%-11c= Launch app\n" + "9! ^] Shell%-19c] Cmd prompt\n" + "cc Connect remote%-10cu Unmount remote/archive\n" + "9t ^T Sort toggles%-12cs Manage session\n" + "cT Set time type%-11c0 Lock\n" + "b^L Redraw%-18c? Help, conf\n" + }; + + int fd = create_tmp_file(); + if (fd == -1) + return; + + dprintf(fd, " |V\\_\n" + " /. \\\\\n" + " (;^; ||\n" + " /___3\n" + " (___n))\n"); + + char *prog = xgetenv(env_cfg[NNN_HELP], NULL); + if (prog) + get_output(prog, NULL, NULL, fd, TRUE, FALSE); + + start = end = helpstr; + while (*end) { + if (*end == '\n') { + snprintf(g_buf, CMD_LEN_MAX, "%*c%.*s", + xchartohex(*start), ' ', (int)(end - start), start + 1); + dprintf(fd, g_buf, ' '); + start = end + 1; + } + + ++end; + } + + dprintf(fd, "\nLOCATIONS:\n"); + for (uchar_t i = 0; i < CTX_MAX; ++i) + if (g_ctx[i].c_cfg.ctxactive) + dprintf(fd, " %u: %s\n", i + 1, g_ctx[i].c_path); + + dprintf(fd, "\nVOLUME: %s of ", coolsize(get_fs_info(path, FREE))); + dprintf(fd, "%s free\n\n", coolsize(get_fs_info(path, CAPACITY))); + + if (bookmark) { + dprintf(fd, "BOOKMARKS\n"); + printkv(bookmark, fd, maxbm, NNN_BMS); + dprintf(fd, "\n"); + } + + if (plug) { + dprintf(fd, "PLUGIN KEYS\n"); + printkv(plug, fd, maxplug, NNN_PLUG); + dprintf(fd, "\n"); + } + + for (uchar_t i = NNN_OPENER; i <= NNN_TRASH; ++i) { + start = getenv(env_cfg[i]); + if (start) + dprintf(fd, "%s: %s\n", env_cfg[i], start); + } + + if (selpath) + dprintf(fd, "SELECTION FILE: %s\n", selpath); + + dprintf(fd, "\nv%s\n%s\n", VERSION, GENERAL_INFO); + close(fd); + + spawn(pager, g_tmpfpath, NULL, NULL, F_CLI | F_TTY); + unlink(g_tmpfpath); +} + +static void setexports(void) +{ + char dvar[] = "d0"; + char fvar[] = "f0"; + + if (ndents) { + setenv(envs[ENV_NCUR], pdents[cur].name, 1); + xstrsncpy(g_ctx[cfg.curctx].c_name, pdents[cur].name, NAME_MAX + 1); + } else if (g_ctx[cfg.curctx].c_name[0]) + g_ctx[cfg.curctx].c_name[0] = '\0'; + + for (uchar_t i = 0; i < CTX_MAX; ++i) { + if (g_ctx[i].c_cfg.ctxactive) { + dvar[1] = fvar[1] = '1' + i; + setenv(dvar, g_ctx[i].c_path, 1); + + if (g_ctx[i].c_name[0]) { + mkpath(g_ctx[i].c_path, g_ctx[i].c_name, g_buf); + setenv(fvar, g_buf, 1); + } + } + } + setenv("NNN_INCLUDE_HIDDEN", xitoa(cfg.showhidden), 1); +} + +static bool run_cmd_as_plugin(const char *file, char *runfile, uchar_t flags) +{ + size_t len; + + xstrsncpy(g_buf, file, PATH_MAX); + + len = xstrlen(g_buf); + if (len > 1 && g_buf[len - 1] == '*') { + flags &= ~F_CONFIRM; /* Skip user confirmation */ + g_buf[len - 1] = '\0'; /* Get rid of trailing no confirmation symbol */ + --len; + } + + if (flags & F_PAGE) + get_output(g_buf, NULL, NULL, -1, TRUE, TRUE); + else if (flags & F_NOTRACE) { + if (is_suffix(g_buf, " $nnn")) + g_buf[len - 5] = '\0'; + else + runfile = NULL; + spawn(g_buf, runfile, NULL, NULL, flags); + } else + spawn(utils[UTIL_SH_EXEC], g_buf, NULL, NULL, flags); + + return TRUE; +} + +static bool plctrl_init(void) +{ + size_t len; + + /* g_tmpfpath is used to generate tmp file names */ + g_tmpfpath[tmpfplen - 1] = '\0'; + len = xstrsncpy(g_pipepath, g_tmpfpath, TMP_LEN_MAX); + g_pipepath[len - 1] = '/'; + len = xstrsncpy(g_pipepath + len, "nnn-pipe.", TMP_LEN_MAX - len) + len; + xstrsncpy(g_pipepath + len - 1, xitoa(getpid()), TMP_LEN_MAX - len); + setenv(env_cfg[NNN_PIPE], g_pipepath, TRUE); + + return EXIT_SUCCESS; +} + +static void rmlistpath(void) +{ + if (listpath) { + DPRINTF_S(__func__); + DPRINTF_S(listpath); + spawn(utils[UTIL_RM_RF], listpath, NULL, NULL, F_NOTRACE | F_MULTI); + /* Do not free if program was started in list mode */ + if (listpath != initpath) + free(listpath); + listpath = NULL; + } +} + +static ssize_t read_nointr(int fd, void *buf, size_t count) +{ + ssize_t len; + + do + len = read(fd, buf, count); + while (len == -1 && errno == EINTR); + + return len; +} + +static char *readpipe(int fd, char *ctxnum, char **path) +{ + char ctx, *nextpath = NULL; + + if (read_nointr(fd, g_buf, 1) != 1) + return NULL; + + if (g_buf[0] == '-') { /* Clear selection on '-' */ + clearselection(); + if (read_nointr(fd, g_buf, 1) != 1) + return NULL; + } + + if (g_buf[0] == '+') + ctx = (char)(get_free_ctx() + 1); + else if (g_buf[0] < '0') + return NULL; + else { + ctx = g_buf[0] - '0'; + if (ctx > CTX_MAX) + return NULL; + } + + if (read_nointr(fd, g_buf, 1) != 1) + return NULL; + + char op = g_buf[0]; + + if (op == 'c') { + ssize_t len = read_nointr(fd, g_buf, PATH_MAX); + + if (len <= 0) + return NULL; + + g_buf[len] = '\0'; /* Terminate the path read */ + if (g_buf[0] == '/') { + nextpath = g_buf; + len = xstrlen(g_buf); + while (--len && (g_buf[len] == '/')) /* Trim all trailing '/' */ + g_buf[len] = '\0'; + } + } else if (op == 'l') { + rmlistpath(); /* Remove last list mode path, if any */ + nextpath = load_input(fd, *path); + } else if (op == 'p') { + free(selpath); + selpath = NULL; + clearselection(); + g_state.picker = 0; + g_state.picked = 1; + } + + *ctxnum = ctx; + + return nextpath; +} + +static bool run_plugin(char **path, const char *file, char *runfile, char **lastname, char **lastdir) +{ + pid_t p; + char ctx = 0; + uchar_t flags = 0; + bool cmd_as_plugin = FALSE; + char *nextpath; + + if (!g_state.pluginit) { + plctrl_init(); + g_state.pluginit = 1; + } + + setexports(); + + /* Check for run-cmd-as-plugin mode */ + if (*file == '!') { + flags = F_MULTI | F_CONFIRM; + ++file; + + if (*file == '|') { /* Check if output should be paged */ + flags |= F_PAGE; + ++file; + } else if (*file == '&') { /* Check if GUI flags are to be used */ + flags = F_NOTRACE | F_NOWAIT; + ++file; + } + + if (!*file) + return FALSE; + + if ((flags & F_NOTRACE) || (flags & F_PAGE)) + return run_cmd_as_plugin(file, runfile, flags); + + cmd_as_plugin = TRUE; + } + + if (mkfifo(g_pipepath, 0600) != 0) + return FALSE; + + exitcurses(); + + p = fork(); + + if (!p) { // In child + int wfd = open(g_pipepath, O_WRONLY | O_CLOEXEC); + + if (wfd == -1) + _exit(EXIT_FAILURE); + + if (!cmd_as_plugin) { + char *sel = NULL; + char std[2] = "-"; + + /* Generate absolute path to plugin */ + mkpath(plgpath, file, g_buf); + + if (g_state.picker) + sel = selpath ? selpath : std; + + if (runfile && runfile[0]) { + xstrsncpy(*lastname, runfile, NAME_MAX); + spawn(g_buf, *lastname, *path, sel, 0); + } else + spawn(g_buf, NULL, *path, sel, 0); + } else + run_cmd_as_plugin(file, NULL, flags); + + close(wfd); + _exit(EXIT_SUCCESS); + } + + int rfd; + + do + rfd = open(g_pipepath, O_RDONLY); + while (rfd == -1 && errno == EINTR); + + nextpath = readpipe(rfd, &ctx, path); + if (nextpath) + set_smart_ctx(ctx, nextpath, path, runfile, lastname, lastdir); + + close(rfd); + + /* wait for the child to finish. no zombies allowed */ + waitpid(p, NULL, 0); + + refresh(); + + unlink(g_pipepath); + + return TRUE; +} + +static bool launch_app(char *newpath) +{ + int r = F_NORMAL; + char *tmp = newpath; + + mkpath(plgpath, utils[UTIL_LAUNCH], newpath); + + if (!getutil(utils[UTIL_FZF]) || access(newpath, X_OK) < 0) { + tmp = xreadline(NULL, messages[MSG_APP_NAME]); + r = F_NOWAIT | F_NOTRACE | F_MULTI; + } + + if (tmp && *tmp) // NOLINT + spawn(tmp, (r == F_NORMAL) ? "0" : NULL, NULL, NULL, r); + + return FALSE; +} + +/* Returns TRUE if at least one command was run */ +static bool prompt_run(void) +{ + bool ret = FALSE; + char *cmdline, *next; + int cnt_j, cnt_J; + size_t len; + + const char *xargs_j = "xargs -0 -I{} %s < %s"; + const char *xargs_J = "xargs -0 %s < %s"; + char cmd[CMD_LEN_MAX + 32]; // 32 for xargs format strings + + while (1) { +#ifndef NORL + if (g_state.picker) { +#endif + cmdline = xreadline(NULL, PROMPT); +#ifndef NORL + } else + cmdline = getreadline("\n"PROMPT); +#endif + // Check for an empty command + if (!cmdline || !cmdline[0]) + break; + + free(lastcmd); + lastcmd = xstrdup(cmdline); + ret = TRUE; + + len = xstrlen(cmdline); + + cnt_j = 0; + next = cmdline; + while ((next = strstr(next, "%j"))) { + ++cnt_j; + + // replace %j with {} for xargs later + next[0] = '{'; + next[1] = '}'; + + ++next; + } + + cnt_J = 0; + next = cmdline; + while ((next = strstr(next, "%J"))) { + ++cnt_J; + + // %J should be the last thing in the command + if (next == cmdline + len - 2) { + cmdline[len - 2] = '\0'; + } + + ++next; + } + + // We can't handle both %j and %J in a single command + if (cnt_j && cnt_J) + break; + + if (cnt_j) + snprintf(cmd, CMD_LEN_MAX + 32, xargs_j, cmdline, selpath); + else if (cnt_J) + snprintf(cmd, CMD_LEN_MAX + 32, xargs_J, cmdline, selpath); + + spawn(shell, "-c", (cnt_j || cnt_J) ? cmd : cmdline, NULL, F_CLI | F_CONFIRM); + } + + return ret; +} + +static bool handle_cmd(enum action sel, char *newpath) +{ + endselection(FALSE); + + if (sel == SEL_LAUNCH) + return launch_app(newpath); + + setexports(); + + if (sel == SEL_PROMPT) + return prompt_run(); + + /* Set nnn nesting level */ + char *tmp = getenv(env_cfg[NNNLVL]); + int r = tmp ? atoi(tmp) : 0; + + setenv(env_cfg[NNNLVL], xitoa(r + 1), 1); + spawn(shell, NULL, NULL, NULL, F_CLI); + setenv(env_cfg[NNNLVL], xitoa(r), 1); + return TRUE; +} + +static void dentfree(void) +{ + free(pnamebuf); + free(pdents); + free(mark); + + /* Thread data cleanup */ + free(core_blocks); + free(core_data); + free(core_files); +} + +static void *du_thread(void *p_data) +{ + thread_data *pdata = (thread_data *)p_data; + char *path[2] = {pdata->path, NULL}; + ullong_t tfiles = 0; + blkcnt_t tblocks = 0; + struct stat *sb; + FTS *tree = fts_open(path, FTS_PHYSICAL | FTS_XDEV | FTS_NOCHDIR, 0); + FTSENT *node; + + while ((node = fts_read(tree))) { + if (node->fts_info & FTS_D) { + if (g_state.interrupt) + break; + continue; + } + + sb = node->fts_statp; + + if (cfg.apparentsz) { + if (sb->st_size && DU_TEST) + tblocks += sb->st_size; + } else if (sb->st_blocks && DU_TEST) + tblocks += sb->st_blocks; + + ++tfiles; + } + + fts_close(tree); + + if (pdata->entnum >= 0) + pdents[pdata->entnum].blocks = tblocks; + + if (!pdata->mntpoint) { + core_blocks[pdata->core] += tblocks; + core_files[pdata->core] += tfiles; + } else + core_files[pdata->core] += 1; + + pthread_mutex_lock(&running_mutex); + threadbmp |= (1 << pdata->core); + --active_threads; + pthread_mutex_unlock(&running_mutex); + + return NULL; +} + +static void dirwalk(char *path, int entnum, bool mountpoint) +{ + /* Loop till any core is free */ + while (active_threads == NUM_DU_THREADS); + + if (g_state.interrupt) + return; + + pthread_mutex_lock(&running_mutex); + int core = ffs(threadbmp) - 1; + + threadbmp &= ~(1 << core); + ++active_threads; + pthread_mutex_unlock(&running_mutex); + + xstrsncpy(core_data[core].path, path, PATH_MAX); + core_data[core].entnum = entnum; + core_data[core].core = (ushort_t)core; + core_data[core].mntpoint = mountpoint; + + pthread_t tid = 0; + + pthread_create(&tid, NULL, du_thread, (void *)&(core_data[core])); + + tolastln(); + addstr(xbasename(path)); + addstr(" [^C aborts]\n"); + refresh(); +} + +static void prep_threads(void) +{ + if (!g_state.duinit) { + /* drop MSB 1s */ + threadbmp >>= (32 - NUM_DU_THREADS); + + core_blocks = calloc(NUM_DU_THREADS, sizeof(blkcnt_t)); + core_data = calloc(NUM_DU_THREADS, sizeof(thread_data)); + core_files = calloc(NUM_DU_THREADS, sizeof(ullong_t)); + +#ifndef __APPLE__ + /* Increase current open file descriptor limit */ + max_openfds(); +#endif + g_state.duinit = TRUE; + } else { + memset(core_blocks, 0, NUM_DU_THREADS * sizeof(blkcnt_t)); + memset(core_data, 0, NUM_DU_THREADS * sizeof(thread_data)); + memset(core_files, 0, NUM_DU_THREADS * sizeof(ullong_t)); + } +} + +/* Skip self and parent */ +static bool selforparent(const char *path) +{ + return path[0] == '.' && (path[1] == '\0' || (path[1] == '.' && path[2] == '\0')); +} + +static int dentfill(char *path, struct entry **ppdents) +{ + uchar_t entflags = 0; + int flags = 0; + struct dirent *dp; + char *namep, *pnb, *buf; + struct entry *dentp; + size_t off = 0, namebuflen = NAMEBUF_INCR; + struct stat sb_path, sb; + DIR *dirp = opendir(path); + + ndents = 0; + + DPRINTF_S(__func__); + + if (!dirp) + return 0; + + int fd = dirfd(dirp); + + if (cfg.blkorder) { + num_files = 0; + dir_blocks = 0; + buf = g_buf; + + if (fstatat(fd, path, &sb_path, 0) == -1) + goto exit; + + if (!ihashbmp) { + ihashbmp = calloc(1, HASH_OCTETS << 3); + if (!ihashbmp) + goto exit; + } else + memset(ihashbmp, 0, HASH_OCTETS << 3); + + prep_threads(); + + attron(COLOR_PAIR(cfg.curctx + 1)); + } + +#if _POSIX_C_SOURCE >= 200112L + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); +#endif + + dp = readdir(dirp); + if (!dp) + goto exit; + +#if defined(__sun) || defined(__HAIKU__) + flags = AT_SYMLINK_NOFOLLOW; /* no d_type */ +#else + if (cfg.blkorder || dp->d_type == DT_UNKNOWN) { + /* + * Optimization added for filesystems which support dirent.d_type + * see readdir(3) + * Known drawbacks: + * - the symlink size is set to 0 + * - the modification time of the symlink is set to that of the target file + */ + flags = AT_SYMLINK_NOFOLLOW; + } +#endif + + do { + namep = dp->d_name; + + if (selforparent(namep)) + continue; + + if (!cfg.showhidden && namep[0] == '.') { + if (!cfg.blkorder) + continue; + + if (fstatat(fd, namep, &sb, AT_SYMLINK_NOFOLLOW) == -1) + continue; + + if (S_ISDIR(sb.st_mode)) { + if (sb_path.st_dev == sb.st_dev) { // NOLINT + mkpath(path, namep, buf); // NOLINT + dirwalk(buf, -1, FALSE); + + if (g_state.interrupt) + goto exit; + } + } else { + /* Do not recount hard links */ + if (sb.st_nlink <= 1 || test_set_bit((uint_t)sb.st_ino)) + dir_blocks += (cfg.apparentsz ? sb.st_size : sb.st_blocks); + ++num_files; + } + + continue; + } + + if (fstatat(fd, namep, &sb, flags) == -1) { + if (flags || (fstatat(fd, namep, &sb, AT_SYMLINK_NOFOLLOW) == -1)) { + /* Missing file */ + DPRINTF_U(flags); + if (!flags) { + DPRINTF_S(namep); + DPRINTF_S(strerror(errno)); + } + + entflags = FILE_MISSING; + memset(&sb, 0, sizeof(struct stat)); + } else /* Orphaned symlink */ + entflags = SYM_ORPHAN; + } + + if (ndents == total_dents) { + if (cfg.blkorder) + while (active_threads); + + total_dents += ENTRY_INCR; + *ppdents = xrealloc(*ppdents, total_dents * sizeof(**ppdents)); + if (!*ppdents) { + free(pnamebuf); + closedir(dirp); + errexit(); + } + DPRINTF_P(*ppdents); + } + + /* If not enough bytes left to copy a file name of length NAME_MAX, re-allocate */ + if (namebuflen - off < NAME_MAX + 1) { + namebuflen += NAMEBUF_INCR; + + pnb = pnamebuf; + pnamebuf = (char *)xrealloc(pnamebuf, namebuflen); + if (!pnamebuf) { + free(*ppdents); + closedir(dirp); + errexit(); + } + DPRINTF_P(pnamebuf); + + /* realloc() may result in memory move, we must re-adjust if that happens */ + if (pnb != pnamebuf) { + dentp = *ppdents; + dentp->name = pnamebuf; + + for (int count = 1; count < ndents; ++dentp, ++count) + /* Current file name starts at last file name start + length */ + (dentp + 1)->name = (char *)((size_t)dentp->name + dentp->nlen); + } + } + + dentp = *ppdents + ndents; + + /* Selection file name */ + dentp->name = (char *)((size_t)pnamebuf + off); + dentp->nlen = xstrsncpy(dentp->name, namep, NAME_MAX + 1); + off += dentp->nlen; + + /* Copy other fields */ + if (cfg.timetype == T_MOD) { + dentp->sec = sb.st_mtime; +#ifdef __APPLE__ + dentp->nsec = (uint_t)sb.st_mtimespec.tv_nsec; +#else + dentp->nsec = (uint_t)sb.st_mtim.tv_nsec; +#endif + } else if (cfg.timetype == T_ACCESS) { + dentp->sec = sb.st_atime; +#ifdef __APPLE__ + dentp->nsec = (uint_t)sb.st_atimespec.tv_nsec; +#else + dentp->nsec = (uint_t)sb.st_atim.tv_nsec; +#endif + } else { + dentp->sec = sb.st_ctime; +#ifdef __APPLE__ + dentp->nsec = (uint_t)sb.st_ctimespec.tv_nsec; +#else + dentp->nsec = (uint_t)sb.st_ctim.tv_nsec; +#endif + } + +#if !(defined(__sun) || defined(__HAIKU__)) + if (!flags && dp->d_type == DT_LNK) { + /* Do not add sizes for links */ + dentp->mode = (sb.st_mode & ~S_IFMT) | S_IFLNK; + dentp->size = listpath ? sb.st_size : 0; + } else { + dentp->mode = sb.st_mode; + dentp->size = sb.st_size; + } +#else + dentp->mode = sb.st_mode; + dentp->size = sb.st_size; +#endif + +#ifndef NOUG + dentp->uid = sb.st_uid; + dentp->gid = sb.st_gid; +#endif + + dentp->flags = S_ISDIR(sb.st_mode) ? 0 : ((sb.st_nlink > 1) ? HARD_LINK : 0); + if (entflags) { + dentp->flags |= entflags; + entflags = 0; + } + + if (cfg.blkorder) { + if (S_ISDIR(sb.st_mode)) { + mkpath(path, namep, buf); // NOLINT + + /* Need to show the disk usage of this dir */ + dirwalk(buf, ndents, (sb_path.st_dev != sb.st_dev)); // NOLINT + + if (g_state.interrupt) + goto exit; + } else { + dentp->blocks = (cfg.apparentsz ? sb.st_size : sb.st_blocks); + /* Do not recount hard links */ + if (sb.st_nlink <= 1 || test_set_bit((uint_t)sb.st_ino)) + dir_blocks += dentp->blocks; + ++num_files; + } + } + + if (flags) { + /* Flag if this is a dir or symlink to a dir */ + if (S_ISLNK(sb.st_mode)) { + sb.st_mode = 0; + fstatat(fd, namep, &sb, 0); + } + + if (S_ISDIR(sb.st_mode)) + dentp->flags |= DIR_OR_DIRLNK; +#if !(defined(__sun) || defined(__HAIKU__)) /* no d_type */ + } else if (dp->d_type == DT_DIR || ((dp->d_type == DT_LNK + || dp->d_type == DT_UNKNOWN) && S_ISDIR(sb.st_mode))) { + dentp->flags |= DIR_OR_DIRLNK; +#endif + } + + ++ndents; + } while ((dp = readdir(dirp))); + +exit: + if (cfg.blkorder) { + while (active_threads); + + attroff(COLOR_PAIR(cfg.curctx + 1)); + for (int i = 0; i < NUM_DU_THREADS; ++i) { + num_files += core_files[i]; + dir_blocks += core_blocks[i]; + } + } + + /* Should never be null */ + if (closedir(dirp) == -1) + errexit(); + + return ndents; +} + +static void populate(char *path, char *lastname) +{ +#ifdef DEBUG + struct timespec ts1, ts2; + + clock_gettime(CLOCK_REALTIME, &ts1); /* Use CLOCK_MONOTONIC on FreeBSD */ +#endif + + ndents = dentfill(path, &pdents); + if (!ndents) + return; + +#ifndef NOSORT + ENTSORT(pdents, ndents, entrycmpfn); +#endif + +#ifdef DEBUG + clock_gettime(CLOCK_REALTIME, &ts2); + DPRINTF_U(ts2.tv_nsec - ts1.tv_nsec); +#endif + + /* Find cur from history */ + /* No NULL check for lastname, always points to an array */ + move_cursor(*lastname ? dentfind(lastname, ndents) : 0, 0); + + // Force full redraw + last_curscroll = -1; +} + +#ifndef NOFIFO +static void notify_fifo(bool force) +{ + if (!fifopath) + return; + + if (fifofd == -1) { + fifofd = open(fifopath, O_WRONLY|O_NONBLOCK|O_CLOEXEC); + if (fifofd == -1) { + if (errno != ENXIO) + /* Unexpected error, the FIFO file might have been removed */ + /* We give up FIFO notification */ + fifopath = NULL; + return; + } + } + + static struct entry lastentry; + + if (!force && !memcmp(&lastentry, &pdents[cur], sizeof(struct entry))) + return; + + lastentry = pdents[cur]; + + char path[PATH_MAX]; + size_t len = mkpath(g_ctx[cfg.curctx].c_path, ndents ? pdents[cur].name : "", path); + + path[len - 1] = '\n'; + + ssize_t ret = write(fifofd, path, len); + + if (ret != (ssize_t)len && !(ret == -1 && (errno == EAGAIN || errno == EPIPE))) { + DPRINTF_S(strerror(errno)); + } +} + +static void send_to_explorer(int *presel) +{ + if (nselected) { + int fd = open(fifopath, O_WRONLY|O_NONBLOCK|O_CLOEXEC, 0600); + if ((fd == -1) || (seltofile(fd, NULL) != (size_t)(selbufpos))) + printwarn(presel); + else { + resetselind(); + clearselection(); + } + if (fd > 1) + close(fd); + } else + notify_fifo(TRUE); /* Send opened path to NNN_FIFO */ +} +#endif + +static void move_cursor(int target, int ignore_scrolloff) +{ + int onscreen = xlines - 4; /* Leave top 2 and bottom 2 lines */ + + target = MAX(0, MIN(ndents - 1, target)); + last_curscroll = curscroll; + last = cur; + cur = target; + + if (!ignore_scrolloff) { + int delta = target - last; + int scrolloff = MIN(SCROLLOFF, onscreen >> 1); + + /* + * When ignore_scrolloff is 1, the cursor can jump into the scrolloff + * margin area, but when ignore_scrolloff is 0, act like a boa + * constrictor and squeeze the cursor towards the middle region of the + * screen by allowing it to move inward and disallowing it to move + * outward (deeper into the scrolloff margin area). + */ + if (((cur < (curscroll + scrolloff)) && delta < 0) + || ((cur > (curscroll + onscreen - scrolloff - 1)) && delta > 0)) + curscroll += delta; + } + curscroll = MIN(curscroll, MIN(cur, ndents - onscreen)); + curscroll = MAX(curscroll, MAX(cur - (onscreen - 1), 0)); + +#ifndef NOFIFO + if (!g_state.fifomode) + notify_fifo(FALSE); /* Send hovered path to NNN_FIFO */ +#endif +} + +static void handle_screen_move(enum action sel) +{ + int onscreen; + + switch (sel) { + case SEL_NEXT: + if (ndents && (cfg.rollover || (cur != ndents - 1))) + move_cursor((cur + 1) % ndents, 0); + break; + case SEL_PREV: + if (ndents && (cfg.rollover || cur)) + move_cursor((cur + ndents - 1) % ndents, 0); + break; + case SEL_PGDN: + onscreen = xlines - 4; + move_cursor(curscroll + (onscreen - 1), 1); + curscroll += onscreen - 1; + break; + case SEL_CTRL_D: + onscreen = xlines - 4; + move_cursor(curscroll + (onscreen - 1), 1); + curscroll += onscreen >> 1; + break; + case SEL_PGUP: // fallthrough + onscreen = xlines - 4; + move_cursor(curscroll, 1); + curscroll -= onscreen - 1; + break; + case SEL_CTRL_U: + onscreen = xlines - 4; + move_cursor(curscroll, 1); + curscroll -= onscreen >> 1; + break; + case SEL_HOME: + move_cursor(0, 1); + break; + case SEL_END: + move_cursor(ndents - 1, 1); + break; + default: /* case SEL_FIRST */ + { + int c = get_input(messages[MSG_FIRST]); + + if (!c) + break; + + c = TOUPPER(c); + + int r = (c == TOUPPER(*pdents[cur].name)) ? (cur + 1) : 0; + + for (; r < ndents; ++r) { + if (((c == '\'') && !(pdents[r].flags & DIR_OR_DIRLNK)) + || (c == TOUPPER(*pdents[r].name))) { + move_cursor((r) % ndents, 0); + break; + } + } + break; + } + } +} + +static void handle_openwith(const char *path, const char *name, char *newpath, char *tmp) +{ + /* Confirm if app is CLI or GUI */ + int r = get_input(messages[MSG_CLI_MODE]); + + r = (r == 'c' ? F_CLI : + (r == 'g' ? F_NOWAIT | F_NOTRACE | F_MULTI : 0)); + if (r) { + mkpath(path, name, newpath); + spawn(tmp, newpath, NULL, NULL, r); + } +} + +static void copynextname(char *lastname) +{ + if (cur) { + cur += (cur != (ndents - 1)) ? 1 : -1; + copycurname(); + } else + lastname[0] = '\0'; +} + +static int handle_context_switch(enum action sel) +{ + int r = -1; + + switch (sel) { + case SEL_CYCLE: // fallthrough + case SEL_CYCLER: + /* visit next and previous contexts */ + r = cfg.curctx; + if (sel == SEL_CYCLE) + do + r = (r + 1) & ~CTX_MAX; + while (!g_ctx[r].c_cfg.ctxactive); + else { + do /* Attempt to create a new context */ + r = (r + 1) & ~CTX_MAX; + while (g_ctx[r].c_cfg.ctxactive && (r != cfg.curctx)); + + if (r == cfg.curctx) /* If all contexts are active, reverse cycle */ + do + r = (r + (CTX_MAX - 1)) & (CTX_MAX - 1); + while (!g_ctx[r].c_cfg.ctxactive); + } // fallthrough + default: /* SEL_CTXN */ + if (sel >= SEL_CTX1) /* CYCLE keys are lesser in value */ + r = sel - SEL_CTX1; /* Save the next context id */ + + if (cfg.curctx == r) { + if (sel == SEL_CYCLE) + (r == CTX_MAX - 1) ? (r = 0) : ++r; + else if (sel == SEL_CYCLER) + (r == 0) ? (r = CTX_MAX - 1) : --r; + else + return -1; + } + } + + return r; +} + +static int set_sort_flags(int r) +{ + bool session = (r == '\0'); + bool reverse = FALSE; + + if (ISUPPER_(r) && (r != 'R') && (r != 'C')) { + reverse = TRUE; + r = TOLOWER(r); + } + + /* Set the correct input in case of a session load */ + if (session) { + if (cfg.apparentsz) { + cfg.apparentsz = 0; + r = 'a'; + } else if (cfg.blkorder) { + cfg.blkorder = 0; + r = 'd'; + } + + if (cfg.version) + namecmpfn = &xstrverscasecmp; + + if (cfg.reverse) + entrycmpfn = &reventrycmp; + } else if (r == CONTROL('T')) { + /* Cycling order: clear -> size -> time -> clear */ + if (cfg.timeorder) + r = 's'; + else if (cfg.sizeorder) + r = 'c'; + else + r = 't'; + } + + switch (r) { + case 'a': /* Apparent du */ + cfg.apparentsz ^= 1; + if (cfg.apparentsz) { + cfg.blkorder = 1; + blk_shift = 0; + } else + cfg.blkorder = 0; + // fallthrough + case 'd': /* Disk usage */ + if (r == 'd') { + if (!cfg.apparentsz) + cfg.blkorder ^= 1; + cfg.apparentsz = 0; + blk_shift = ffs(S_BLKSIZE) - 1; + } + + if (cfg.blkorder) + cfg.showdetail = 1; + cfg.timeorder = 0; + cfg.sizeorder = 0; + cfg.extnorder = 0; + if (!session) { + cfg.reverse = 0; + entrycmpfn = &entrycmp; + } + endselection(TRUE); /* We are going to reload dir */ + break; + case 'c': + cfg.timeorder = 0; + cfg.sizeorder = 0; + cfg.apparentsz = 0; + cfg.blkorder = 0; + cfg.extnorder = 0; + cfg.reverse = 0; + cfg.version = 0; + entrycmpfn = &entrycmp; + namecmpfn = &xstricmp; + break; + case 'e': /* File extension */ + cfg.extnorder ^= 1; + cfg.sizeorder = 0; + cfg.timeorder = 0; + cfg.apparentsz = 0; + cfg.blkorder = 0; + cfg.reverse = 0; + entrycmpfn = &entrycmp; + break; + case 'r': /* Reverse sort */ + cfg.reverse ^= 1; + entrycmpfn = cfg.reverse ? &reventrycmp : &entrycmp; + break; + case 's': /* File size */ + cfg.sizeorder ^= 1; + cfg.timeorder = 0; + cfg.apparentsz = 0; + cfg.blkorder = 0; + cfg.extnorder = 0; + cfg.reverse = 0; + entrycmpfn = &entrycmp; + break; + case 't': /* Time */ + cfg.timeorder ^= 1; + cfg.sizeorder = 0; + cfg.apparentsz = 0; + cfg.blkorder = 0; + cfg.extnorder = 0; + cfg.reverse = 0; + entrycmpfn = &entrycmp; + break; + case 'v': /* Version */ + cfg.version ^= 1; + namecmpfn = cfg.version ? &xstrverscasecmp : &xstricmp; + cfg.timeorder = 0; + cfg.sizeorder = 0; + cfg.apparentsz = 0; + cfg.blkorder = 0; + cfg.extnorder = 0; + break; + default: + return 0; + } + + if (reverse) { + cfg.reverse = 1; + entrycmpfn = &reventrycmp; + } + + cfgsort[cfg.curctx] = (uchar_t)r; + + return r; +} + +static bool set_time_type(int *presel) +{ + bool ret = FALSE; + char buf[] = "'a'ccess / 'c'hange / 'm'od [ ]"; + + buf[sizeof(buf) - 3] = cfg.timetype == T_MOD ? 'm' : (cfg.timetype == T_ACCESS ? 'a' : 'c'); + + int r = get_input(buf); + + if (r == 'a' || r == 'c' || r == 'm') { + r = (r == 'm') ? T_MOD : ((r == 'a') ? T_ACCESS : T_CHANGE); + if (cfg.timetype != r) { + cfg.timetype = r; + + if (cfg.filtermode || g_ctx[cfg.curctx].c_fltr[1]) + *presel = FILTER; + + ret = TRUE; + } else + r = MSG_NOCHANGE; + } else + r = MSG_INVALID_KEY; + + if (!ret) + printwait(messages[r], presel); + + return ret; +} + +static void statusbar(char *path) +{ + int i = 0, len = 0; + char *ptr; + pEntry pent = &pdents[cur]; + + if (!ndents) { + printmsg("0/0"); + return; + } + + /* Get the file extension for regular files */ + if (S_ISREG(pent->mode)) { + i = (int)(pent->nlen - 1); + ptr = xextension(pent->name, i); + if (ptr) + len = i - (ptr - pent->name); + if (!ptr || len > 5 || len < 2) + ptr = "\b"; + } else + ptr = "\b"; + + attron(COLOR_PAIR(cfg.curctx + 1)); + + if (cfg.fileinfo && get_output("file", "-b", pdents[cur].name, -1, FALSE, FALSE)) + mvaddstr(xlines - 2, 2, g_buf); + + tolastln(); + + printw("%d/%s ", cur + 1, xitoa(ndents)); + + if (g_state.selmode || nselected) { + attron(A_REVERSE); + addch(' '); + if (g_state.rangesel) + addch('*'); + else if (g_state.selmode) + addch('+'); + if (nselected) + addstr(xitoa(nselected)); + addch(' '); + attroff(A_REVERSE); + addch(' '); + } + + if (cfg.blkorder) { /* du mode */ + char buf[24]; + + xstrsncpy(buf, coolsize(dir_blocks << blk_shift), 12); + + printw("%cu:%s free:%s files:%llu %lluB %s\n", + (cfg.apparentsz ? 'a' : 'd'), buf, coolsize(get_fs_info(path, FREE)), + num_files, (ullong_t)pent->blocks << blk_shift, ptr); + } else { /* light or detail mode */ + char sort[] = "\0\0\0\0\0"; + + if (getorderstr(sort)) + addstr(sort); + + /* Timestamp */ + print_time(&pent->sec); + + addch(' '); + addstr(get_lsperms(pent->mode)); + addch(' '); +#ifndef NOUG + if (g_state.uidgid) { + addstr(getpwname(pent->uid)); + addch(':'); + addstr(getgrname(pent->gid)); + addch(' '); + } +#endif + if (S_ISLNK(pent->mode)) { + if (!cfg.fileinfo) { + i = readlink(pent->name, g_buf, PATH_MAX); + addstr(coolsize(i >= 0 ? i : pent->size)); /* Show symlink size */ + if (i > 1) { /* Show symlink target */ + int y; + + addstr(" ->"); + getyx(stdscr, len, y); + i = MIN(i, xcols - y); + g_buf[i] = '\0'; + addstr(g_buf); + } + } + } else { + addstr(coolsize(pent->size)); + addch(' '); + addstr(ptr); + if (pent->flags & HARD_LINK) { + struct stat sb; + + if (stat(pent->name, &sb) != -1) { + addch(' '); + addstr(xitoa((int)sb.st_nlink)); /* Show number of links */ + addch('-'); + addstr(xitoa((int)sb.st_ino)); /* Show inode number */ + } + } + } + clrtoeol(); + } + + attroff(COLOR_PAIR(cfg.curctx + 1)); + /* Plase HW cursor on current for Braille systems */ + tocursor(); +} + +static inline void markhovered(void) +{ + if (cfg.showdetail && ndents) { /* Reversed block for hovered entry */ + tocursor(); +#ifdef ICONS_ENABLED + addstr(MD_ARROW_FORWARD); +#else + addch(' ' | A_REVERSE); +#endif + } +} + +static int adjust_cols(int n) +{ + /* Calculate the number of cols available to print entry name */ +#ifdef ICONS_ENABLED + n -= (g_state.oldcolor ? 0 : 1 + xstrlen(ICON_PADDING_LEFT) + xstrlen(ICON_PADDING_RIGHT)); +#endif + if (cfg.showdetail) { + /* Fallback to light mode if less than 35 columns */ + if (n < 36) + cfg.showdetail ^= 1; + else /* 2 more accounted for below */ + n -= 32; + } + + /* 2 columns for preceding space and indicator */ + return (n - 2); +} + +static void draw_line(int ncols) +{ + bool dir = FALSE; + + ncols = adjust_cols(ncols); + + if (g_state.oldcolor && (pdents[last].flags & DIR_OR_DIRLNK)) { + attron(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + dir = TRUE; + } + + move(2 + last - curscroll, 0); + printent(&pdents[last], ncols, FALSE); + + if (g_state.oldcolor && (pdents[cur].flags & DIR_OR_DIRLNK)) { + if (!dir) {/* First file is not a directory */ + attron(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + dir = TRUE; + } + } else if (dir) { /* Second file is not a directory */ + attroff(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + dir = FALSE; + } + + move(2 + cur - curscroll, 0); + printent(&pdents[cur], ncols, TRUE); + + /* Must reset e.g. no files in dir */ + if (dir) + attroff(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + + markhovered(); +} + +static void redraw(char *path) +{ + getmaxyx(stdscr, xlines, xcols); + + int ncols = (xcols <= PATH_MAX) ? xcols : PATH_MAX; + int onscreen = xlines - 4; + int i, j = 1; + + // Fast redraw + if (g_state.move) { + g_state.move = 0; + + if (ndents && (last_curscroll == curscroll)) + return draw_line(ncols); + } + + DPRINTF_S(__func__); + + /* Clear screen */ + erase(); + + /* Enforce scroll/cursor invariants */ + move_cursor(cur, 1); + + /* Fail redraw if < than 10 columns, context info prints 10 chars */ + if (ncols <= MIN_DISPLAY_COL) { + printmsg(messages[MSG_FEW_COLUMNS]); + return; + } + + //DPRINTF_D(cur); + DPRINTF_S(path); + + for (i = 0; i < CTX_MAX; ++i) { /* 8 chars printed for contexts - "1 2 3 4 " */ + if (!g_ctx[i].c_cfg.ctxactive) + addch(i + '1'); + else + addch((i + '1') | (COLOR_PAIR(i + 1) | A_BOLD + /* active: underline, current: reverse */ + | ((cfg.curctx != i) ? A_UNDERLINE : A_REVERSE))); + + addch(' '); + } + + attron(A_UNDERLINE | COLOR_PAIR(cfg.curctx + 1)); + + /* Print path */ + bool in_home = set_tilde_in_path(path); + char *ptr = in_home ? &path[homelen - 1] : path; + + i = (int)xstrlen(ptr); + if ((i + MIN_DISPLAY_COL) <= ncols) + addnstr(ptr, ncols - MIN_DISPLAY_COL); + else { + char *base = xmemrchr((uchar_t *)ptr, '/', i); + + if (in_home) { + addch(*ptr); + ++ptr; + i = 1; + } else + i = 0; + + if (ptr && (base != ptr)) { + while (ptr < base) { + if (*ptr == '/') { + i += 2; /* 2 characters added */ + if (ncols < i + MIN_DISPLAY_COL) { + base = NULL; /* Can't print more characters */ + break; + } + + addch(*ptr); + addch(*(++ptr)); + } + ++ptr; + } + } + + if (base) + addnstr(base, ncols - (MIN_DISPLAY_COL + i)); + } + + if (in_home) + reset_tilde_in_path(path); + + attroff(A_UNDERLINE | COLOR_PAIR(cfg.curctx + 1)); + + /* Go to first entry */ + if (curscroll > 0) { + move(1, 0); +#ifdef ICONS_ENABLED + addstr(MD_ARROW_UPWARD); +#else + addch('^'); +#endif + } + + if (g_state.oldcolor) { + attron(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + g_state.dircolor = 1; + } + + onscreen = MIN(onscreen + curscroll, ndents); + + ncols = adjust_cols(ncols); + + int len = scanselforpath(path, FALSE); + + /* Print listing */ + for (i = curscroll; i < onscreen; ++i) { + move(++j, 0); + + if (len) + findmarkentry(len, &pdents[i]); + + printent(&pdents[i], ncols, i == cur); + } + + /* Must reset e.g. no files in dir */ + if (g_state.dircolor) { + attroff(COLOR_PAIR(cfg.curctx + 1) | A_BOLD); + g_state.dircolor = 0; + } + + /* Go to last entry */ + if (onscreen < ndents) { + move(xlines - 2, 0); +#ifdef ICONS_ENABLED + addstr(MD_ARROW_DOWNWARD); +#else + addch('v'); +#endif + } + + markhovered(); +} + +static bool cdprep(char *lastdir, char *lastname, char *path, char *newpath) +{ + if (lastname) + lastname[0] = '\0'; + + /* Save last working directory */ + xstrsncpy(lastdir, path, PATH_MAX); + + /* Save the newly opted dir in path */ + xstrsncpy(path, newpath, PATH_MAX); + DPRINTF_S(path); + + clearfilter(); + return cfg.filtermode; +} + +static bool browse(char *ipath, const char *session, int pkey) +{ + char newpath[PATH_MAX] __attribute__ ((aligned)), + rundir[PATH_MAX] __attribute__ ((aligned)), + runfile[NAME_MAX + 1] __attribute__ ((aligned)); + char *path, *lastdir, *lastname, *dir, *tmp; + pEntry pent; + enum action sel; + struct stat sb; + int r = -1, presel, selstartid = 0, selendid = 0; + const uchar_t opener_flags = (cfg.cliopener ? F_CLI : (F_NOTRACE | F_NOSTDIN | F_NOWAIT)); + bool watch = FALSE, cd = TRUE; + ino_t inode = 0; + +#ifndef NOMOUSE + MEVENT event = {0}; + struct timespec mousetimings[2] = {{.tv_sec = 0, .tv_nsec = 0}, {.tv_sec = 0, .tv_nsec = 0} }; + int mousedent[2] = {-1, -1}; + bool currentmouse = 1, rightclicksel = 0; +#endif + + atexit(dentfree); + + getmaxyx(stdscr, xlines, xcols); + +#ifndef NOSSN + /* set-up first context */ + if (!session || !load_session(session, &path, &lastdir, &lastname, FALSE)) { +#else + (void)session; +#endif + g_ctx[0].c_last[0] = '\0'; + lastdir = g_ctx[0].c_last; /* last visited directory */ + + if (g_state.initfile) { + xstrsncpy(g_ctx[0].c_name, xbasename(ipath), sizeof(g_ctx[0].c_name)); + xdirname(ipath); + } else + g_ctx[0].c_name[0] = '\0'; + + lastname = g_ctx[0].c_name; /* last visited file name */ + + xstrsncpy(g_ctx[0].c_path, ipath, PATH_MAX); + /* If the initial path is a file, retain a way to return to start dir */ + if (g_state.initfile) { + free(initpath); + initpath = ipath = getcwd(NULL, 0); + } + path = g_ctx[0].c_path; /* current directory */ + + g_ctx[0].c_fltr[0] = g_ctx[0].c_fltr[1] = '\0'; + g_ctx[0].c_cfg = cfg; /* current configuration */ +#ifndef NOSSN + } +#endif + + newpath[0] = rundir[0] = runfile[0] = '\0'; + + presel = pkey ? ';' : ((cfg.filtermode + || (session && (g_ctx[cfg.curctx].c_fltr[0] == FILTER + || g_ctx[cfg.curctx].c_fltr[0] == RFILTER) + && g_ctx[cfg.curctx].c_fltr[1])) ? FILTER : 0); + + pdents = xrealloc(pdents, total_dents * sizeof(struct entry)); + if (!pdents) + errexit(); + + /* Allocate buffer to hold names */ + pnamebuf = (char *)xrealloc(pnamebuf, NAMEBUF_INCR); + if (!pnamebuf) + errexit(); + +begin: + /* + * Can fail when permissions change while browsing. + * It's assumed that path IS a directory when we are here. + */ + if (chdir(path) == -1) { + DPRINTF_S("directory inaccessible"); + valid_parent(path, lastname); + setdirwatch(); + } + +#ifndef NOX11 + xterm_cfg(path); +#endif + +#ifdef LINUX_INOTIFY + if ((presel == FILTER || watch) && inotify_wd >= 0) { + inotify_rm_watch(inotify_fd, inotify_wd); + inotify_wd = -1; + watch = FALSE; + } +#elif defined(BSD_KQUEUE) + if ((presel == FILTER || watch) && event_fd >= 0) { + close(event_fd); + event_fd = -1; + watch = FALSE; + } +#elif defined(HAIKU_NM) + if ((presel == FILTER || watch) && haiku_hnd != NULL) { + haiku_stop_watch(haiku_hnd); + haiku_nm_active = FALSE; + watch = FALSE; + } +#endif + + if (order && cd) { + if (cfgsort[cfg.curctx] != '0') { + if (cfgsort[cfg.curctx] == 'z') + set_sort_flags('c'); + if ((!cfgsort[cfg.curctx] || (cfgsort[cfg.curctx] == 'c')) + && ((r = get_kv_key(order, path, maxorder, NNN_ORDER)) > 0)) { + set_sort_flags(r); + cfgsort[cfg.curctx] = 'z'; + } + } else + cfgsort[cfg.curctx] = cfgsort[CTX_MAX]; + } + cd = TRUE; + + populate(path, lastname); + if (g_state.interrupt) { + g_state.interrupt = cfg.apparentsz = cfg.blkorder = 0; + blk_shift = BLK_SHIFT_512; + presel = CONTROL('L'); + } + +#ifdef LINUX_INOTIFY + if (presel != FILTER && inotify_wd == -1) + inotify_wd = inotify_add_watch(inotify_fd, path, INOTIFY_MASK); +#elif defined(BSD_KQUEUE) + if (presel != FILTER && event_fd == -1) { +#if defined(O_EVTONLY) + event_fd = open(path, O_EVTONLY); +#else + event_fd = open(path, O_RDONLY); +#endif + if (event_fd >= 0) + EV_SET(&events_to_monitor[0], event_fd, EVFILT_VNODE, + EV_ADD | EV_CLEAR, KQUEUE_FFLAGS, 0, path); + } +#elif defined(HAIKU_NM) + haiku_nm_active = haiku_watch_dir(haiku_hnd, path) == EXIT_SUCCESS; +#endif + + while (1) { + /* Do not do a double redraw in filterentries */ + if ((presel != FILTER) || !filterset()) { + redraw(path); + statusbar(path); + } + +nochange: + /* Exit if parent has exited */ + if (getppid() == 1) + _exit(EXIT_FAILURE); + + /* If CWD is deleted or moved or perms changed, find an accessible parent */ + if (chdir(path) == -1) + goto begin; + + /* If STDIN is no longer a tty (closed) we should exit */ + if (!isatty(STDIN_FILENO) && !g_state.picker) + return EXIT_FAILURE; + + sel = nextsel(presel); + if (presel) + presel = 0; + + switch (sel) { +#ifndef NOMOUSE + case SEL_CLICK: + if (getmouse(&event) != OK) + goto nochange; + + /* Handle clicking on a context at the top */ + if (event.bstate == BUTTON1_PRESSED && event.y == 0) { + /* Get context from: "[1 2 3 4]..." */ + r = event.x >> 1; + + /* If clicked after contexts, go to parent */ + if (r >= CTX_MAX) + sel = SEL_BACK; + else if (r >= 0 && r != cfg.curctx) { + savecurctx(path, ndents ? pdents[cur].name : NULL, r); + + /* Reset the pointers */ + path = g_ctx[r].c_path; + lastdir = g_ctx[r].c_last; + lastname = g_ctx[r].c_name; + + setdirwatch(); + goto begin; + } + } +#endif + // fallthrough + case SEL_BACK: +#ifndef NOMOUSE + if (sel == SEL_BACK) { +#endif + dir = visit_parent(path, newpath, &presel); + if (!dir) + goto nochange; + + /* Save history */ + xstrsncpy(lastname, xbasename(path), NAME_MAX + 1); + + cdprep(lastdir, NULL, path, dir) ? (presel = FILTER) : (watch = TRUE); + goto begin; +#ifndef NOMOUSE + } +#endif + +#ifndef NOMOUSE + /* Middle click action */ + if (event.bstate == BUTTON2_PRESSED) { + presel = middle_click_key; + goto nochange; + } +#if NCURSES_MOUSE_VERSION > 1 + /* Scroll up */ + if (event.bstate == BUTTON4_PRESSED && ndents && (cfg.rollover || cur)) { + move_cursor((!cfg.rollover && cur < scroll_lines + ? 0 : (cur + ndents - scroll_lines) % ndents), 0); + break; + } + + /* Scroll down */ + if (event.bstate == BUTTON5_PRESSED && ndents + && (cfg.rollover || (cur != ndents - 1))) { + move_cursor((!cfg.rollover && cur >= ndents - scroll_lines) + ? (ndents - 1) : ((cur + scroll_lines) % ndents), 0); + break; + } +#endif + + /* Toggle filter mode on left click on last 2 lines */ + if (event.y >= xlines - 2 && event.bstate == BUTTON1_PRESSED) { + clearfilter(); + cfg.filtermode ^= 1; + if (cfg.filtermode) { + presel = FILTER; + goto nochange; + } + + /* Start watching the directory */ + watch = TRUE; + copycurname(); + cd = FALSE; + goto begin; + } + + /* Handle clicking on a file */ + if (event.y >= 2 && event.y <= ndents + 1 && + (event.bstate == BUTTON1_PRESSED || + event.bstate == BUTTON3_PRESSED)) { + r = curscroll + (event.y - 2); + if (r != cur) + move_cursor(r, 1); +#ifndef NOFIFO + else if ((event.bstate == BUTTON1_PRESSED) && !g_state.fifomode) + notify_fifo(TRUE); /* Send clicked path to NNN_FIFO */ +#endif + /* Handle right click selection */ + if (event.bstate == BUTTON3_PRESSED) { + rightclicksel = 1; + presel = SELECT; + goto nochange; + } + + currentmouse ^= 1; + clock_gettime( +#if defined(CLOCK_MONOTONIC_RAW) + CLOCK_MONOTONIC_RAW, +#elif defined(CLOCK_MONOTONIC) + CLOCK_MONOTONIC, +#else + CLOCK_REALTIME, +#endif + &mousetimings[currentmouse]); + mousedent[currentmouse] = cur; + + /* Single click just selects, double click falls through to SEL_OPEN */ + if ((mousedent[0] != mousedent[1]) || + (((_ABSSUB(mousetimings[0].tv_sec, mousetimings[1].tv_sec) << 30) + + (_ABSSUB(mousetimings[0].tv_nsec, mousetimings[1].tv_nsec))) + > DBLCLK_INTERVAL_NS)) + break; + /* Double click */ + mousetimings[currentmouse].tv_sec = 0; + mousedent[currentmouse] = -1; + sel = SEL_OPEN; + } else { + if (cfg.filtermode || filterset()) + presel = FILTER; + copycurname(); + goto nochange; + } +#endif + // fallthrough + case SEL_NAV_IN: // fallthrough + case SEL_OPEN: + /* Cannot descend in empty directories */ + if (!ndents) { + cd = FALSE; + goto begin; + } + + pent = &pdents[cur]; + mkpath(path, pent->name, newpath); + DPRINTF_S(newpath); + + /* Visit directory */ + if (pent->flags & DIR_OR_DIRLNK) { + if (chdir(newpath) == -1) { + printwarn(&presel); + goto nochange; + } + + cdprep(lastdir, lastname, path, newpath) + ? (presel = FILTER) : (watch = TRUE); + goto begin; + } + + /* Cannot use stale data in entry, file may be missing by now */ + if (stat(newpath, &sb) == -1) { + printwarn(&presel); + goto nochange; + } + DPRINTF_U(sb.st_mode); + + /* Do not open non-regular files */ + if (!S_ISREG(sb.st_mode)) { + printwait(messages[MSG_UNSUPPORTED], &presel); + goto nochange; + } + + /* Handle plugin selection mode */ + if (g_state.runplugin) { + g_state.runplugin = 0; + /* Must be in plugin dir and same context to select plugin */ + if ((g_state.runctx == cfg.curctx) && !strcmp(path, plgpath)) { + endselection(FALSE); + /* Copy path so we can return back to earlier dir */ + xstrsncpy(path, rundir, PATH_MAX); + rundir[0] = '\0'; + clearfilter(); + + if (chdir(path) == -1 + || !run_plugin(&path, pent->name, + runfile, &lastname, &lastdir)) { + DPRINTF_S("plugin failed!"); + } + + if (g_state.picked) + return EXIT_SUCCESS; + + if (runfile[0]) { + xstrsncpy(lastname, runfile, NAME_MAX + 1); + runfile[0] = '\0'; + } + setdirwatch(); + goto begin; + } + } + +#ifndef NOFIFO + if (g_state.fifomode && (sel == SEL_OPEN)) { + send_to_explorer(&presel); /* Write selection to explorer fifo */ + break; + } +#endif + /* If opened as vim plugin and Enter/^M pressed, pick */ + if (g_state.picker && (sel == SEL_OPEN)) { + if (nselected == 0) /* Pick if none selected */ + appendfpath(newpath, mkpath(path, pent->name, newpath)); + return EXIT_SUCCESS; + } + + if (sel == SEL_NAV_IN) { + /* If in listing dir, go to target on `l` or Right on symlink */ + if (listpath && S_ISLNK(pent->mode) + && is_prefix(path, listpath, xstrlen(listpath))) { + if (!realpath(pent->name, newpath)) { + printwarn(&presel); + goto nochange; + } + + xdirname(newpath); + + if (chdir(newpath) == -1) { + printwarn(&presel); + goto nochange; + } + + cdprep(lastdir, NULL, path, newpath) + ? (presel = FILTER) : (watch = TRUE); + xstrsncpy(lastname, pent->name, NAME_MAX + 1); + goto begin; + } + + /* Open file disabled on right arrow or `l` */ + if (cfg.nonavopen) + goto nochange; + } + + if (!sb.st_size) { + printwait(messages[MSG_EMPTY_FILE], &presel); + goto nochange; + } + + if (cfg.useeditor +#ifdef FILE_MIME_OPTS + && get_output("file", FILE_MIME_OPTS, newpath, -1, FALSE, FALSE) + && is_prefix(g_buf, "text/", 5) +#else + /* no MIME option; guess from description instead */ + && get_output("file", "-bL", newpath, -1, FALSE, FALSE) + && strstr(g_buf, "text") +#endif + ) { + spawn(editor, newpath, NULL, NULL, F_CLI); + if (cfg.filtermode) { + presel = FILTER; + clearfilter(); + } + continue; + } + + /* Get the extension for regex match */ + tmp = xextension(pent->name, pent->nlen - 1); +#ifdef PCRE + if (tmp && !pcre_exec(archive_pcre, NULL, tmp, + pent->nlen - (tmp - pent->name) - 1, 0, 0, NULL, 0)) { +#else + if (tmp && !regexec(&archive_re, tmp, 0, NULL, 0)) { +#endif + r = get_input(messages[MSG_ARCHIVE_OPTS]); + if (r == 'l' || r == 'x') { + mkpath(path, pent->name, newpath); + if (!handle_archive(newpath, r)) { + presel = MSGWAIT; + goto nochange; + } + if (r == 'l') { + statusbar(path); + goto nochange; + } + } + + if ((r == 'm') && !archive_mount(newpath)) { + presel = MSGWAIT; + goto nochange; + } + + if (r == 'x' || r == 'm') { + if (newpath[0]) + set_smart_ctx('+', newpath, &path, + ndents ? pdents[cur].name : NULL, + &lastname, &lastdir); + else + copycurname(); + clearfilter(); + goto begin; + } + + if (r != 'o') { + printwait(messages[MSG_INVALID_KEY], &presel); + goto nochange; + } + } + + /* Invoke desktop opener as last resort */ + spawn(opener, newpath, NULL, NULL, opener_flags); + + /* Move cursor to the next entry if not the last entry */ + if (g_state.autonext && cur != ndents - 1) + move_cursor((cur + 1) % ndents, 0); + if (cfg.filtermode) { + presel = FILTER; + clearfilter(); + } + continue; + case SEL_NEXT: // fallthrough + case SEL_PREV: // fallthrough + case SEL_PGDN: // fallthrough + case SEL_CTRL_D: // fallthrough + case SEL_PGUP: // fallthrough + case SEL_CTRL_U: // fallthrough + case SEL_HOME: // fallthrough + case SEL_END: // fallthrough + case SEL_FIRST: + if (ndents) { + g_state.move = 1; + handle_screen_move(sel); + } + break; + case SEL_CDHOME: // fallthrough + case SEL_CDBEGIN: // fallthrough + case SEL_CDLAST: // fallthrough + case SEL_CDROOT: + dir = (sel == SEL_CDHOME) ? home + : ((sel == SEL_CDBEGIN) ? ipath + : ((sel == SEL_CDLAST) ? lastdir + : "/" /* SEL_CDROOT */)); + + if (!dir || !*dir) { + printwait(messages[MSG_NOT_SET], &presel); + goto nochange; + } + + if (strcmp(path, dir) == 0) { + if (dir == ipath) { + if (cfg.filtermode) + presel = FILTER; + goto nochange; + } + dir = lastdir; /* Go to last dir on home/root key repeat */ + } + + if (chdir(dir) == -1) { + presel = MSGWAIT; + goto nochange; + } + + /* SEL_CDLAST: dir pointing to lastdir */ + xstrsncpy(newpath, dir, PATH_MAX); // fallthrough + case SEL_BMOPEN: + if (sel == SEL_BMOPEN) { + r = (int)handle_bookmark(mark, newpath); + if (r) { + printwait(messages[r], &presel); + goto nochange; + } + + if (strcmp(path, newpath) == 0) + break; + } + + /* In list mode, retain the last file name to highlight it, if possible */ + cdprep(lastdir, listpath && sel == SEL_CDLAST ? NULL : lastname, path, newpath) + ? (presel = FILTER) : (watch = TRUE); + goto begin; + case SEL_REMOTE: + if ((sel == SEL_REMOTE) && !remote_mount(newpath)) { + presel = MSGWAIT; + goto nochange; + } + + set_smart_ctx('+', newpath, &path, + ndents ? pdents[cur].name : NULL, &lastname, &lastdir); + clearfilter(); + goto begin; + case SEL_CYCLE: // fallthrough + case SEL_CYCLER: // fallthrough + case SEL_CTX1: // fallthrough + case SEL_CTX2: // fallthrough + case SEL_CTX3: // fallthrough + case SEL_CTX4: +#ifdef CTX8 + case SEL_CTX5: + case SEL_CTX6: + case SEL_CTX7: + case SEL_CTX8: +#endif + r = handle_context_switch(sel); + if (r < 0) + continue; + savecurctx(path, ndents ? pdents[cur].name : NULL, r); + + /* Reset the pointers */ + path = g_ctx[r].c_path; + lastdir = g_ctx[r].c_last; + lastname = g_ctx[r].c_name; + tmp = g_ctx[r].c_fltr; + + if (cfg.filtermode || ((tmp[0] == FILTER || tmp[0] == RFILTER) && tmp[1])) + presel = FILTER; + else + watch = TRUE; + + goto begin; + case SEL_MARK: + free(mark); + mark = xstrdup(path); + printwait(mark, &presel); + goto nochange; + case SEL_BMARK: + add_bookmark(path, newpath, &presel); + goto nochange; + case SEL_FLTR: + /* Unwatch dir if we are still in a filtered view */ +#ifdef LINUX_INOTIFY + if (inotify_wd >= 0) { + inotify_rm_watch(inotify_fd, inotify_wd); + inotify_wd = -1; + } +#elif defined(BSD_KQUEUE) + if (event_fd >= 0) { + close(event_fd); + event_fd = -1; + } +#elif defined(HAIKU_NM) + if (haiku_nm_active) { + haiku_stop_watch(haiku_hnd); + haiku_nm_active = FALSE; + } +#endif + presel = filterentries(path, lastname); + if (presel == ESC) { + presel = 0; + break; + } + if (presel == FILTER) { /* Refresh dir and filter again */ + cd = FALSE; + goto begin; + } + goto nochange; + case SEL_MFLTR: // fallthrough + case SEL_HIDDEN: // fallthrough + case SEL_DETAIL: // fallthrough + case SEL_SORT: + switch (sel) { + case SEL_MFLTR: + cfg.filtermode ^= 1; + if (cfg.filtermode) { + presel = FILTER; + clearfilter(); + goto nochange; + } + + watch = TRUE; // fallthrough + case SEL_HIDDEN: + if (sel == SEL_HIDDEN) { + cfg.showhidden ^= 1; + if (cfg.filtermode) + presel = FILTER; + clearfilter(); + } + copycurname(); + cd = FALSE; + goto begin; + case SEL_DETAIL: + cfg.showdetail ^= 1; + cfg.blkorder = 0; + continue; + default: /* SEL_SORT */ + r = set_sort_flags(get_input(messages[MSG_ORDER])); + if (!r) { + printwait(messages[MSG_INVALID_KEY], &presel); + goto nochange; + } + } + + if (cfg.filtermode || filterset()) + presel = FILTER; + + if (ndents) { + copycurname(); + + if (r == 'd' || r == 'a') { + presel = 0; + goto begin; + } + + ENTSORT(pdents, ndents, entrycmpfn); + move_cursor(ndents ? dentfind(lastname, ndents) : 0, 0); + } + continue; + case SEL_STATS: // fallthrough + case SEL_CHMODX: + if (ndents) { + tmp = (listpath && xstrcmp(path, listpath) == 0) ? listroot : path; + mkpath(tmp, pdents[cur].name, newpath); + + if ((sel == SEL_STATS && !show_stats(newpath)) + || (lstat(newpath, &sb) == -1) + || (sel == SEL_CHMODX && !xchmod(newpath, sb.st_mode))) { + printwarn(&presel); + goto nochange; + } + + if (sel == SEL_CHMODX) + pdents[cur].mode ^= 0111; + } + break; + case SEL_REDRAW: // fallthrough + case SEL_RENAMEMUL: // fallthrough + case SEL_HELP: // fallthrough + case SEL_AUTONEXT: // fallthrough + case SEL_EDIT: // fallthrough + case SEL_LOCK: + { + bool refresh = FALSE; + + if (ndents) + mkpath(path, pdents[cur].name, newpath); + else if (sel == SEL_EDIT) /* Avoid trying to edit a non-existing file */ + goto nochange; + + switch (sel) { + case SEL_REDRAW: + refresh = TRUE; + break; + case SEL_RENAMEMUL: + endselection(TRUE); + setenv("NNN_INCLUDE_HIDDEN", xitoa(cfg.showhidden), 1); + setenv("NNN_LIST", listpath ? listroot : "", 1); + + if (!(getutil(utils[UTIL_BASH]) + && plugscript(utils[UTIL_NMV], F_CLI)) +#ifndef NOBATCH + && !batch_rename() +#endif + ) { + printwait(messages[MSG_FAILED], &presel); + goto nochange; + } + clearselection(); + refresh = TRUE; + break; + case SEL_HELP: + show_help(path); // fallthrough + case SEL_AUTONEXT: + if (sel == SEL_AUTONEXT) + g_state.autonext ^= 1; + if (cfg.filtermode) + presel = FILTER; + copycurname(); + goto nochange; + case SEL_EDIT: + if (!g_state.picker) + spawn(editor, newpath, NULL, NULL, F_CLI); + continue; + default: /* SEL_LOCK */ + lock_terminal(); + break; + } + + /* In case of successful operation, reload contents */ + + /* Continue in type-to-nav mode, if enabled */ + if ((cfg.filtermode || filterset()) && !refresh) { + presel = FILTER; + goto nochange; + } + + /* Save current */ + copycurname(); + /* Repopulate as directory content may have changed */ + cd = FALSE; + goto begin; + } + case SEL_SEL: + if (!ndents) + goto nochange; + + startselection(); + if (g_state.rangesel) + g_state.rangesel = 0; + + /* Toggle selection status */ + pdents[cur].flags ^= FILE_SELECTED; + + if (pdents[cur].flags & FILE_SELECTED) { + ++nselected; + appendfpath(newpath, mkpath(path, pdents[cur].name, newpath)); + writesel(pselbuf, selbufpos - 1); /* Truncate NULL from end */ + } else { + --nselected; + rmfromselbuf(mkpath(path, pdents[cur].name, g_sel)); + } + +#ifndef NOX11 + if (cfg.x11) + plugscript(utils[UTIL_CBCP], F_NOWAIT | F_NOTRACE); +#endif +#ifndef NOMOUSE + if (rightclicksel) + rightclicksel = 0; + else +#endif + /* move cursor to the next entry if this is not the last entry */ + if (!g_state.stayonsel && (cur != ndents - 1)) + move_cursor((cur + 1) % ndents, 0); + break; + case SEL_SELMUL: + if (!ndents) + goto nochange; + + startselection(); + g_state.rangesel ^= 1; + + if (stat(path, &sb) == -1) { + printwarn(&presel); + goto nochange; + } + + if (g_state.rangesel) { /* Range selection started */ + inode = sb.st_ino; + selstartid = cur; + continue; + } + + if (inode != sb.st_ino) { + printwait(messages[MSG_DIR_CHANGED], &presel); + goto nochange; + } + + if (cur < selstartid) { + selendid = selstartid; + selstartid = cur; + } else + selendid = cur; + + /* Clear selection on repeat on same file */ + if (selstartid == selendid) { + resetselind(); + clearselection(); + break; + } // fallthrough + case SEL_SELALL: // fallthrough + case SEL_SELINV: + if (sel == SEL_SELALL || sel == SEL_SELINV) { + if (!ndents) + goto nochange; + + startselection(); + if (g_state.rangesel) + g_state.rangesel = 0; + + selstartid = 0; + selendid = ndents - 1; + } + + if ((nselected > LARGESEL) || (nselected && (ndents > LARGESEL))) { + printmsg("processing..."); + refresh(); + } + + r = scanselforpath(path, TRUE); /* Get path length suffixed by '/' */ + ((sel == SEL_SELINV) && findselpos) + ? invertselbuf(r) : addtoselbuf(r, selstartid, selendid); + +#ifndef NOX11 + if (cfg.x11) + plugscript(utils[UTIL_CBCP], F_NOWAIT | F_NOTRACE); +#endif + continue; + case SEL_SELEDIT: + r = editselection(); + if (r <= 0) { + r = !r ? MSG_0_SELECTED : MSG_FAILED; + printwait(messages[r], &presel); + } else { +#ifndef NOX11 + if (cfg.x11) + plugscript(utils[UTIL_CBCP], F_NOWAIT | F_NOTRACE); +#endif + cfg.filtermode ? presel = FILTER : statusbar(path); + } + goto nochange; + case SEL_CP: // fallthrough + case SEL_MV: // fallthrough + case SEL_CPMVAS: // fallthrough + case SEL_RM: + { + if (sel == SEL_RM) { + r = get_cur_or_sel(); + if (!r) { + statusbar(path); + goto nochange; + } + + if (r == 'c') { + tmp = (listpath && xstrcmp(path, listpath) == 0) + ? listroot : path; + mkpath(tmp, pdents[cur].name, newpath); + if (!xrm(newpath)) + continue; + + xrmfromsel(tmp, newpath); + + copynextname(lastname); + + if (cfg.filtermode || filterset()) + presel = FILTER; + cd = FALSE; + goto begin; + } + } + + (nselected == 1 && (sel == SEL_CP || sel == SEL_MV)) + ? mkpath(path, xbasename(pselbuf), newpath) + : (newpath[0] = '\0'); + + endselection(TRUE); + + if (!cpmvrm_selection(sel, path)) { + presel = MSGWAIT; + goto nochange; + } + + if (cfg.filtermode) + presel = FILTER; + clearfilter(); + +#ifndef NOX11 + /* Show notification on operation complete */ + if (cfg.x11) + plugscript(utils[UTIL_NTFY], F_NOWAIT | F_NOTRACE); +#endif + + if (newpath[0] && !access(newpath, F_OK)) + xstrsncpy(lastname, xbasename(newpath), NAME_MAX+1); + else + copycurname(); + cd = FALSE; + goto begin; + } + case SEL_ARCHIVE: // fallthrough + case SEL_OPENWITH: // fallthrough + case SEL_NEW: // fallthrough + case SEL_RENAME: + { + int fd, ret = 'n'; + + if (!ndents && (sel == SEL_OPENWITH || sel == SEL_RENAME)) + break; + + if (sel != SEL_OPENWITH) + endselection(TRUE); + + switch (sel) { + case SEL_ARCHIVE: + r = get_cur_or_sel(); + if (!r) { + statusbar(path); + goto nochange; + } + + if (r == 's') { + if (!selsafe()) { + presel = MSGWAIT; + goto nochange; + } + + tmp = NULL; + } else + tmp = pdents[cur].name; + + tmp = xreadline(tmp, messages[MSG_ARCHIVE_NAME]); + break; + case SEL_OPENWITH: +#ifndef NORL + if (g_state.picker) { +#endif + tmp = xreadline(NULL, messages[MSG_OPEN_WITH]); +#ifndef NORL + } else + tmp = getreadline(messages[MSG_OPEN_WITH]); +#endif + break; + case SEL_NEW: + r = get_input(messages[MSG_NEW_OPTS]); + if (r == 'f' || r == 'd') + tmp = xreadline(NULL, messages[MSG_NEW_PATH]); + else if (r == 's' || r == 'h') + tmp = xreadline(NULL, + messages[nselected <= 1?MSG_NEW_PATH:MSG_LINK_PREFIX]); + else + tmp = NULL; + break; + default: /* SEL_RENAME */ + tmp = xreadline(pdents[cur].name, ""); + break; + } + + if (!tmp || !*tmp) + break; + + switch (sel) { + case SEL_ARCHIVE: + if (r == 'c' && strcmp(tmp, pdents[cur].name) == 0) + goto nochange; + + mkpath(path, tmp, newpath); + if (access(newpath, F_OK) == 0) { + if (!xconfirm(get_input(messages[MSG_OVERWRITE]))) { + statusbar(path); + goto nochange; + } + } + get_archive_cmd(newpath, tmp); + (r == 's') ? archive_selection(newpath, tmp, path) + : spawn(newpath, tmp, pdents[cur].name, + NULL, F_CLI | F_CONFIRM); + + mkpath(path, tmp, newpath); + if (access(newpath, F_OK) == 0) { /* File created */ + xstrsncpy(lastname, tmp, NAME_MAX + 1); + clearfilter(); /* Archive name may not match */ + clearselection(); /* Archive operation complete */ + cd = FALSE; + goto begin; + } + continue; + case SEL_OPENWITH: + handle_openwith(path, pdents[cur].name, newpath, tmp); + + cfg.filtermode ? presel = FILTER : statusbar(path); + copycurname(); + goto nochange; + case SEL_RENAME: + /* Skip renaming to same name */ + if (strcmp(tmp, pdents[cur].name) == 0) { + tmp = xreadline(pdents[cur].name, messages[MSG_COPY_NAME]); + if (!tmp || !tmp[0] || !strcmp(tmp, pdents[cur].name)) { + cfg.filtermode ? presel = FILTER : statusbar(path); + copycurname(); + goto nochange; + } + ret = 'd'; + } + break; + default: /* SEL_NEW */ + break; + } + + /* Open the descriptor to currently open directory */ +#ifdef O_DIRECTORY + fd = open(path, O_RDONLY | O_DIRECTORY); +#else + fd = open(path, O_RDONLY); +#endif + if (fd == -1) { + printwarn(&presel); + goto nochange; + } + + /* Check if another file with same name exists */ + if (fstatat(fd, tmp, &sb, AT_SYMLINK_NOFOLLOW) == 0) { + if (sel == SEL_RENAME) { + /* Overwrite file with same name? */ + if (!xconfirm(get_input(messages[MSG_OVERWRITE]))) { + close(fd); + break; + } + } else { + /* Do nothing in case of NEW */ + close(fd); + printwait(messages[MSG_EXISTS], &presel); + goto nochange; + } + } + + if (sel == SEL_RENAME) { + /* Rename the file */ + if (ret == 'd') + spawn("cp -rp", pdents[cur].name, tmp, NULL, F_SILENT); + else if (renameat(fd, pdents[cur].name, fd, tmp) != 0) { + close(fd); + printwarn(&presel); + goto nochange; + } + close(fd); + xstrsncpy(lastname, tmp, NAME_MAX + 1); + } else { /* SEL_NEW */ + close(fd); + presel = 0; + + /* Check if it's a dir or file */ + if (r == 'f' || r == 'd') { + mkpath(path, tmp, newpath); + ret = xmktree(newpath, r == 'f' ? FALSE : TRUE); + } else if (r == 's' || r == 'h') { + if (tmp[0] == '@' && tmp[1] == '\0') + tmp[0] = '\0'; + ret = xlink(tmp, path, (ndents ? pdents[cur].name : NULL), + newpath, &presel, r); + } + + if (!ret) + printwait(messages[MSG_FAILED], &presel); + + if (ret <= 0) + goto nochange; + + if (r == 'f' || r == 'd') + xstrsncpy(lastname, tmp, NAME_MAX + 1); + else if (ndents) { + if (cfg.filtermode) + presel = FILTER; + copycurname(); + } + clearfilter(); + } + + cd = FALSE; + goto begin; + } + case SEL_PLUGIN: + /* Check if directory is accessible */ + if (!xdiraccess(plgpath)) { + printwarn(&presel); + goto nochange; + } + + if (!pkey) { + r = xstrsncpy(g_buf, messages[MSG_KEYS], CMD_LEN_MAX); + printkeys(plug, g_buf + r - 1, maxplug); + printmsg(g_buf); + r = get_input(NULL); + } else { + r = pkey; + pkey = '\0'; + } + + if (r != '\r') { + endselection(FALSE); + tmp = get_kv_val(plug, NULL, r, maxplug, NNN_PLUG); + if (!tmp) { + printwait(messages[MSG_INVALID_KEY], &presel); + goto nochange; + } + + if (tmp[0] == '-' && tmp[1]) { + ++tmp; + r = FALSE; /* Do not refresh dir after completion */ + } else + r = TRUE; + + if (!run_plugin(&path, tmp, (ndents ? pdents[cur].name : NULL), + &lastname, &lastdir)) { + printwait(messages[MSG_FAILED], &presel); + goto nochange; + } + + if (g_state.picked) + return EXIT_SUCCESS; + + copycurname(); + + if (!r) { + cfg.filtermode ? presel = FILTER : statusbar(path); + goto nochange; + } + } else { /* 'Return/Enter' enters the plugin directory */ + g_state.runplugin ^= 1; + if (!g_state.runplugin && rundir[0]) { + /* + * If toggled, and still in the plugin dir, + * switch to original directory + */ + if (strcmp(path, plgpath) == 0) { + xstrsncpy(path, rundir, PATH_MAX); + xstrsncpy(lastname, runfile, NAME_MAX + 1); + rundir[0] = runfile[0] = '\0'; + setdirwatch(); + goto begin; + } + + /* Otherwise, initiate choosing plugin again */ + g_state.runplugin = 1; + } + + xstrsncpy(lastdir, path, PATH_MAX); + xstrsncpy(rundir, path, PATH_MAX); + xstrsncpy(path, plgpath, PATH_MAX); + if (ndents) + xstrsncpy(runfile, pdents[cur].name, NAME_MAX); + g_state.runctx = cfg.curctx; + lastname[0] = '\0'; + } + setdirwatch(); + clearfilter(); + goto begin; + case SEL_SHELL: // fallthrough + case SEL_LAUNCH: // fallthrough + case SEL_PROMPT: + r = handle_cmd(sel, newpath); + + /* Continue in type-to-nav mode, if enabled */ + if (cfg.filtermode) + presel = FILTER; + + /* Save current */ + copycurname(); + + if (!r) + goto nochange; + + /* Repopulate as directory content may have changed */ + cd = FALSE; + goto begin; + case SEL_UMOUNT: + presel = MSG_ZERO; + if (!unmount((ndents ? pdents[cur].name : NULL), newpath, &presel, path)) { + if (presel == MSG_ZERO) + statusbar(path); + goto nochange; + } + + /* Dir removed, go to next entry */ + copynextname(lastname); + cd = FALSE; + goto begin; +#ifndef NOSSN + case SEL_SESSIONS: + r = get_input(messages[MSG_SSN_OPTS]); + + if (r == 's') { + tmp = xreadline(NULL, messages[MSG_SSN_NAME]); + if (tmp && *tmp) + save_session(tmp, &presel); + } else if (r == 'l' || r == 'r') { + if (load_session(NULL, &path, &lastdir, &lastname, r == 'r')) { + setdirwatch(); + goto begin; + } + } + + statusbar(path); + goto nochange; +#endif + case SEL_EXPORT: + export_file_list(); + cfg.filtermode ? presel = FILTER : statusbar(path); + goto nochange; + case SEL_TIMETYPE: + if (!set_time_type(&presel)) + goto nochange; + cd = FALSE; + goto begin; + case SEL_QUITCTX: // fallthrough + case SEL_QUITCD: // fallthrough + case SEL_QUIT: + case SEL_QUITERR: + if (sel == SEL_QUITCTX) { + int ctx = cfg.curctx; + + for (r = (ctx - 1) & (CTX_MAX - 1); + (r != ctx) && !g_ctx[r].c_cfg.ctxactive; + r = ((r - 1) & (CTX_MAX - 1))) { + }; + + if (r != ctx) { + g_ctx[ctx].c_cfg.ctxactive = 0; + + /* Switch to next active context */ + path = g_ctx[r].c_path; + lastdir = g_ctx[r].c_last; + lastname = g_ctx[r].c_name; + + cfg = g_ctx[r].c_cfg; + + cfg.curctx = r; + setdirwatch(); + goto begin; + } + } else if (!g_state.forcequit) { + for (r = 0; r < CTX_MAX; ++r) + if (r != cfg.curctx && g_ctx[r].c_cfg.ctxactive) { + r = get_input(messages[MSG_QUIT_ALL]); + break; + } + + if (!(r == CTX_MAX || xconfirm(r))) + break; // fallthrough + } + + /* CD on Quit */ + tmp = getenv("NNN_TMPFILE"); + if ((sel == SEL_QUITCD) || tmp) { + write_lastdir(path, tmp); + /* ^G is a way to quit picker mode without picking anything */ + if ((sel == SEL_QUITCD) && g_state.picker) + selbufpos = 0; + } + + if (sel != SEL_QUITERR) + return EXIT_SUCCESS; + + if (selbufpos && !g_state.picker) { + /* Pick files to stdout and exit */ + g_state.picker = 1; + free(selpath); + selpath = NULL; + return EXIT_SUCCESS; + } + + return EXIT_FAILURE; + default: + if (xlines != LINES || xcols != COLS) + continue; + + if (idletimeout && idle == idletimeout) { + lock_terminal(); /* Locker */ + idle = 0; + } + + copycurname(); + goto nochange; + } /* switch (sel) */ + } +} + +static char *make_tmp_tree(char **paths, ssize_t entries, const char *prefix) +{ + /* tmpdir holds the full path */ + /* tmp holds the path without the tmp dir prefix */ + int err; + struct stat sb; + char *slash, *tmp; + ssize_t len = xstrlen(prefix); + char *tmpdir = malloc(PATH_MAX); + + if (!tmpdir) { + DPRINTF_S(strerror(errno)); + return NULL; + } + + tmp = tmpdir + tmpfplen - 1; + xstrsncpy(tmpdir, g_tmpfpath, tmpfplen); + xstrsncpy(tmp, "/nnnXXXXXX", 11); + + /* Points right after the base tmp dir */ + tmp += 10; + + /* handle the case where files are directly under / */ + if (!prefix[1] && (prefix[0] == '/')) + len = 0; + + if (!mkdtemp(tmpdir)) { + free(tmpdir); + + DPRINTF_S(strerror(errno)); + return NULL; + } + + listpath = tmpdir; + + for (ssize_t i = 0; i < entries; ++i) { + if (!paths[i]) + continue; + + err = stat(paths[i], &sb); + if (err && errno == ENOENT) + continue; + + /* Don't copy the common prefix */ + xstrsncpy(tmp, paths[i] + len, xstrlen(paths[i]) - len + 1); + + /* Get the dir containing the path */ + slash = xmemrchr((uchar_t *)tmp, '/', xstrlen(paths[i]) - len); + if (slash) + *slash = '\0'; + + xmktree(tmpdir, TRUE); + + if (slash) + *slash = '/'; + + if (symlink(paths[i], tmpdir)) { + DPRINTF_S(paths[i]); + DPRINTF_S(strerror(errno)); + } + } + + /* Get the dir in which to start */ + *tmp = '\0'; + return tmpdir; +} + +static char *load_input(int fd, const char *path) +{ + ssize_t i, chunk_count = 1, chunk = (ssize_t)(512 * 1024) /* 512 KiB chunk size */, entries = 0; + char *input = malloc(sizeof(char) * chunk), *tmpdir = NULL; + char cwd[PATH_MAX], *next; + size_t offsets[LIST_FILES_MAX]; + char **paths = NULL; + ssize_t input_read, total_read = 0, off = 0; + int msgnum = 0; + + if (!input) { + DPRINTF_S(strerror(errno)); + return NULL; + } + + if (!path) { + if (!getcwd(cwd, PATH_MAX)) { + free(input); + return NULL; + } + } else + xstrsncpy(cwd, path, PATH_MAX); + + while (chunk_count < 512) { + input_read = read(fd, input + total_read, chunk); + if (input_read < 0) { + DPRINTF_S(strerror(errno)); + goto malloc_1; + } + + if (input_read == 0) + break; + + total_read += input_read; + ++chunk_count; + + while (off < total_read) { + if ((next = memchr(input + off, '\0', total_read - off)) == NULL) + break; + ++next; + + if (next - input == off + 1) { + off = next - input; + continue; + } + + if (entries == LIST_FILES_MAX) { + msgnum = MSG_LIMIT; + goto malloc_1; + } + + offsets[entries++] = off; + off = next - input; + } + + if (chunk_count == 512) { + msgnum = MSG_LIMIT; + goto malloc_1; + } + + /* We don't need to allocate another chunk */ + if (chunk_count == (total_read - input_read) / chunk) + continue; + + chunk_count = total_read / chunk; + if (total_read % chunk) + ++chunk_count; + + input = xrealloc(input, (chunk_count + 1) * chunk); + if (!input) + return NULL; + } + + if (off != total_read) { + if (entries == LIST_FILES_MAX) { + msgnum = MSG_LIMIT; + goto malloc_1; + } + + offsets[entries++] = off; + } + + DPRINTF_D(entries); + DPRINTF_D(total_read); + DPRINTF_D(chunk_count); + + if (!entries) { + msgnum = MSG_0_ENTRIES; + goto malloc_1; + } + + input[total_read] = '\0'; + + paths = malloc(entries * sizeof(char *)); + if (!paths) + goto malloc_1; + + for (i = 0; i < entries; ++i) + paths[i] = input + offsets[i]; + + listroot = malloc(sizeof(char) * PATH_MAX); + if (!listroot) + goto malloc_1; + listroot[0] = '\0'; + + DPRINTF_S(paths[0]); + + for (i = 0; i < entries; ++i) { + if (paths[i][0] == '\n' || selforparent(paths[i])) { + paths[i] = NULL; + continue; + } + + paths[i] = abspath(paths[i], cwd, NULL); + if (!paths[i]) { + entries = i; // free from the previous entry + goto malloc_2; + + } + + DPRINTF_S(paths[i]); + + xstrsncpy(g_buf, paths[i], PATH_MAX); + if (!common_prefix(xdirname(g_buf), listroot)) { + entries = i + 1; // free from the current entry + goto malloc_2; + } + + DPRINTF_S(listroot); + } + + DPRINTF_S(listroot); + + if (listroot[0]) + tmpdir = make_tmp_tree(paths, entries, listroot); + +malloc_2: + for (i = entries - 1; i >= 0; --i) + free(paths[i]); +malloc_1: + if (msgnum) { + if (home) { /* We are past init stage */ + printmsg(messages[msgnum]); + xdelay(XDELAY_INTERVAL_MS); + } else + msg(messages[msgnum]); + } + free(input); + free(paths); + return tmpdir; +} + +static void check_key_collision(void) +{ + int key; + bool bitmap[KEY_MAX] = {FALSE}; + + for (ullong_t i = 0; i < sizeof(bindings) / sizeof(struct key); ++i) { + key = bindings[i].sym; + + if (bitmap[key]) + dprintf(STDERR_FILENO, "key collision! [%s]\n", keyname(key)); + else + bitmap[key] = TRUE; + } +} + +static void usage(void) +{ + dprintf(STDERR_FILENO, + "%s: nnn [OPTIONS] [PATH]\n\n" + "The unorthodox terminal file manager.\n\n" + "positional args:\n" + " PATH start dir/file [default: .]\n\n" + "optional args:\n" +#ifndef NOFIFO + " -a auto NNN_FIFO\n" +#endif + " -A no dir auto-enter in type-to-nav\n" + " -b key open bookmark key (trumps -s/S)\n" + " -c cli-only NNN_OPENER (trumps -e)\n" + " -C 8-color scheme\n" + " -d detail mode\n" + " -D dirs in context color\n" + " -e text in $VISUAL/$EDITOR/vi\n" + " -E internal edits in EDITOR\n" +#ifndef NORL + " -f use readline history file\n" +#endif +#ifndef NOFIFO + " -F val fifo mode [0:preview 1:explore]\n" +#endif + " -g regex filters\n" + " -H show hidden files\n" + " -i show current file info\n" + " -J no auto-proceed on selection\n" + " -K detect key collision\n" + " -l val set scroll lines\n" + " -n type-to-nav mode\n" + " -o open files only on Enter\n" + " -p file selection file [-:stdout]\n" + " -P key run plugin key\n" + " -Q no quit confirmation\n" + " -r use advcpmv patched cp, mv\n" + " -R no rollover at edges\n" +#ifndef NOSSN + " -s name load session by name\n" + " -S persistent session\n" +#endif + " -t secs timeout to lock\n" + " -T key sort order [a/d/e/r/s/t/v]\n" + " -u use selection (no prompt)\n" +#ifndef NOUG + " -U show user and group\n" +#endif + " -V show version\n" +#ifndef NOX11 + " -x notis, selection sync, xterm title\n" +#endif + " -h show help\n\n" + "v%s\n%s\n", __func__, VERSION, GENERAL_INFO); +} + +static bool setup_config(void) +{ + size_t r, len; + char *xdgcfg = getenv("XDG_CONFIG_HOME"); + bool xdg = FALSE; + + /* Set up configuration file paths */ + if (xdgcfg && xdgcfg[0]) { + DPRINTF_S(xdgcfg); + if (xdgcfg[0] == '~') { + r = xstrsncpy(g_buf, home, PATH_MAX); + xstrsncpy(g_buf + r - 1, xdgcfg + 1, PATH_MAX); + xdgcfg = g_buf; + DPRINTF_S(xdgcfg); + } + + if (!xdiraccess(xdgcfg)) { + xerror(); + return FALSE; + } + + len = xstrlen(xdgcfg) + xstrlen("/nnn/bookmarks") + 1; + xdg = TRUE; + } + + if (!xdg) + len = xstrlen(home) + xstrlen("/.config/nnn/bookmarks") + 1; + + cfgpath = (char *)malloc(len); + plgpath = (char *)malloc(len); + if (!cfgpath || !plgpath) { + xerror(); + return FALSE; + } + + if (xdg) { + xstrsncpy(cfgpath, xdgcfg, len); + r = len - xstrlen("/nnn/bookmarks"); + } else { + r = xstrsncpy(cfgpath, home, len); + + /* Create ~/.config */ + xstrsncpy(cfgpath + r - 1, "/.config", len - r); + DPRINTF_S(cfgpath); + r += 8; /* length of "/.config" */ + } + + /* Create ~/.config/nnn */ + xstrsncpy(cfgpath + r - 1, "/nnn", len - r); + DPRINTF_S(cfgpath); + + /* Create bookmarks, sessions, mounts and plugins directories */ + for (r = 0; r < ELEMENTS(toks); ++r) { + mkpath(cfgpath, toks[r], plgpath); + if (!xmktree(plgpath, TRUE)) { + DPRINTF_S(toks[r]); + xerror(); + return FALSE; + } + } + + /* Set selection file path */ + if (!g_state.picker) { + char *env_sel = xgetenv(env_cfg[NNN_SEL], NULL); + + selpath = env_sel ? xstrdup(env_sel) + : (char *)malloc(len + 3); /* Length of "/.config/nnn/.selection" */ + + if (!selpath) { + xerror(); + return FALSE; + } + + if (!env_sel) { + r = xstrsncpy(selpath, cfgpath, len + 3); + xstrsncpy(selpath + r - 1, "/.selection", 12); + DPRINTF_S(selpath); + } + } + + return TRUE; +} + +static bool set_tmp_path(void) +{ + char *tmp = "/tmp"; + char *path = xdiraccess(tmp) ? tmp : getenv("TMPDIR"); + + if (!path) { + msg("set TMPDIR"); + return FALSE; + } + + tmpfplen = (uchar_t)xstrsncpy(g_tmpfpath, path, TMP_LEN_MAX); + DPRINTF_S(g_tmpfpath); + DPRINTF_U(tmpfplen); + + return TRUE; +} + +static void cleanup(void) +{ +#ifndef NOX11 + if (cfg.x11 && !g_state.picker) { + printf("\033[23;0t"); /* reset terminal window title */ + fflush(stdout); + + free(hostname); + } +#endif + free(selpath); + free(plgpath); + free(cfgpath); + free(initpath); + free(bmstr); + free(pluginstr); + free(listroot); + free(ihashbmp); + free(bookmark); + free(plug); + free(lastcmd); +#ifndef NOFIFO + if (g_state.autofifo) + unlink(fifopath); +#endif + if (g_state.pluginit) + unlink(g_pipepath); +#ifdef DEBUG + disabledbg(); +#endif +} + +int main(int argc, char *argv[]) +{ + char *arg = NULL; + char *session = NULL; + int fd, opt, sort = 0, pkey = '\0'; /* Plugin key */ +#ifndef NOMOUSE + mmask_t mask; + char *middle_click_env = xgetenv(env_cfg[NNN_MCLICK], "\0"); + + middle_click_key = (middle_click_env[0] == '^' && middle_click_env[1]) + ? CONTROL(middle_click_env[1]) + : (uchar_t)middle_click_env[0]; +#endif + + const char * const env_opts = xgetenv(env_cfg[NNN_OPTS], NULL); + int env_opts_id = env_opts ? (int)xstrlen(env_opts) : -1; +#ifndef NORL + bool rlhist = FALSE; +#endif + + while ((opt = (env_opts_id > 0 + ? env_opts[--env_opts_id] + : getopt(argc, argv, "aAb:cCdDeEfF:gHiJKl:nop:P:QrRs:St:T:uUVxh"))) != -1) { + switch (opt) { +#ifndef NOFIFO + case 'a': + g_state.autofifo = 1; + break; +#endif + case 'A': + cfg.autoenter = 0; + break; + case 'b': + if (env_opts_id < 0) + arg = optarg; + break; + case 'c': + cfg.cliopener = 1; + break; + case 'C': + g_state.oldcolor = 1; + break; + case 'd': + cfg.showdetail = 1; + break; + case 'D': + g_state.dirctx = 1; + break; + case 'e': + cfg.useeditor = 1; + break; + case 'E': + cfg.waitedit = 1; + break; + case 'f': +#ifndef NORL + rlhist = TRUE; +#endif + break; +#ifndef NOFIFO + case 'F': + if (env_opts_id < 0) { + fd = atoi(optarg); + if ((fd < 0) || (fd > 1)) + return EXIT_FAILURE; + g_state.fifomode = fd; + } + break; +#endif + case 'g': + cfg.regex = 1; + filterfn = &visible_re; + break; + case 'H': + cfg.showhidden = 1; + break; + case 'i': + cfg.fileinfo = 1; + break; + case 'J': + g_state.stayonsel = 1; + break; + case 'K': + check_key_collision(); + return EXIT_SUCCESS; + case 'l': + if (env_opts_id < 0) + scroll_lines = atoi(optarg); + break; + case 'n': + cfg.filtermode = 1; + break; + case 'o': + cfg.nonavopen = 1; + break; + case 'p': + if (env_opts_id >= 0) + break; + + g_state.picker = 1; + if (!(optarg[0] == '-' && optarg[1] == '\0')) { + fd = open(optarg, O_WRONLY | O_CREAT, 0600); + if (fd == -1) { + xerror(); + return EXIT_FAILURE; + } + + close(fd); + selpath = abspath(optarg, NULL, NULL); + unlink(selpath); + } + break; + case 'P': + if (env_opts_id < 0 && !optarg[1]) + pkey = (uchar_t)optarg[0]; + break; + case 'Q': + g_state.forcequit = 1; + break; + case 'r': +#ifdef __linux__ + cp[2] = cp[5] = mv[2] = mv[5] = 'g'; /* cp -iRp -> cpg -giRp */ + cp[4] = mv[4] = '-'; +#endif + break; + case 'R': + cfg.rollover = 0; + break; +#ifndef NOSSN + case 's': + if (env_opts_id < 0) + session = optarg; + break; + case 'S': + g_state.prstssn = 1; + if (!session) /* Support named persistent sessions */ + session = "@"; + break; +#endif + case 't': + if (env_opts_id < 0) + idletimeout = atoi(optarg); + break; + case 'T': + if (env_opts_id < 0) + sort = (uchar_t)optarg[0]; + break; + case 'u': + cfg.prefersel = 1; + break; + case 'U': + g_state.uidgid = 1; + break; + case 'V': + dprintf(STDOUT_FILENO, "%s\n", VERSION); + return EXIT_SUCCESS; + case 'x': + cfg.x11 = 1; + break; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + usage(); + return EXIT_FAILURE; + } + if (env_opts_id == 0) + env_opts_id = -1; + } + +#ifdef DEBUG + enabledbg(); + DPRINTF_S(VERSION); +#endif + + /* Prefix for temporary files */ + if (!set_tmp_path()) + return EXIT_FAILURE; + + atexit(cleanup); + + /* Check if we are in path list mode */ + if (!isatty(STDIN_FILENO)) { + /* This is the same as listpath */ + initpath = load_input(STDIN_FILENO, NULL); + if (!initpath) + return EXIT_FAILURE; + + /* We return to tty */ + if (!isatty(STDOUT_FILENO)) { + fd = open(ctermid(NULL), O_RDONLY, 0400); + dup2(fd, STDIN_FILENO); + close(fd); + } else + dup2(STDOUT_FILENO, STDIN_FILENO); + + if (session) + session = NULL; + } + + home = getenv("HOME"); + if (!home) { + msg("set HOME"); + return EXIT_FAILURE; + } + DPRINTF_S(home); + homelen = (uchar_t)xstrlen(home); + + if (!setup_config()) + return EXIT_FAILURE; + + /* Get custom opener, if set */ + opener = xgetenv(env_cfg[NNN_OPENER], utils[UTIL_OPENER]); + DPRINTF_S(opener); + + /* Parse bookmarks string */ + if (!parsekvpair(&bookmark, &bmstr, NNN_BMS, &maxbm)) { + msg(env_cfg[NNN_BMS]); + return EXIT_FAILURE; + } + + /* Parse plugins string */ + if (!parsekvpair(&plug, &pluginstr, NNN_PLUG, &maxplug)) { + msg(env_cfg[NNN_PLUG]); + return EXIT_FAILURE; + } + + /* Parse order string */ + if (!parsekvpair(&order, &orderstr, NNN_ORDER, &maxorder)) { + msg(env_cfg[NNN_ORDER]); + return EXIT_FAILURE; + } + + if (!initpath) { + if (arg) { /* Open a bookmark directly */ + if (!arg[1]) /* Bookmarks keys are single char */ + initpath = get_kv_val(bookmark, NULL, *arg, maxbm, NNN_BMS); + + if (!initpath) { + msg(messages[MSG_INVALID_KEY]); + return EXIT_FAILURE; + } + + if (session) + session = NULL; + } else if (argc == optind) { + /* Start in the current directory */ + char *startpath = getenv("PWD"); + + initpath = startpath ? xstrdup(startpath) : getcwd(NULL, 0); + if (!initpath) + initpath = "/"; + } else { + arg = argv[optind]; + DPRINTF_S(arg); + if (xstrlen(arg) > 7 && is_prefix(arg, "file://", 7)) + arg = arg + 7; + initpath = abspath(arg, NULL, NULL); + DPRINTF_S(initpath); + if (!initpath) { + xerror(); + return EXIT_FAILURE; + } + + /* + * If nnn is set as the file manager, applications may try to open + * files by invoking nnn. In that case pass the file path to the + * desktop opener and exit. + */ + struct stat sb; + + if (stat(initpath, &sb) == -1) { + xerror(); + return EXIT_FAILURE; + } + + if (!S_ISDIR(sb.st_mode)) + g_state.initfile = 1; + + if (session) + session = NULL; + } + } + + /* Set archive handling (enveditor used as tmp var) */ + enveditor = getenv(env_cfg[NNN_ARCHIVE]); +#ifdef PCRE + if (setfilter(&archive_pcre, (enveditor ? enveditor : patterns[P_ARCHIVE]))) { +#else + if (setfilter(&archive_re, (enveditor ? enveditor : patterns[P_ARCHIVE]))) { +#endif + msg(messages[MSG_INVALID_REG]); + return EXIT_FAILURE; + } + + /* An all-CLI opener overrides option -e) */ + if (cfg.cliopener) + cfg.useeditor = 0; + + /* Get VISUAL/EDITOR */ + enveditor = xgetenv(envs[ENV_EDITOR], utils[UTIL_VI]); + editor = xgetenv(envs[ENV_VISUAL], enveditor); + DPRINTF_S(getenv(envs[ENV_VISUAL])); + DPRINTF_S(getenv(envs[ENV_EDITOR])); + DPRINTF_S(editor); + + /* Get PAGER */ + pager = xgetenv(envs[ENV_PAGER], utils[UTIL_LESS]); + DPRINTF_S(pager); + + /* Get SHELL */ + shell = xgetenv(envs[ENV_SHELL], utils[UTIL_SH]); + DPRINTF_S(shell); + + DPRINTF_S(getenv("PWD")); + +#ifndef NOFIFO + /* Create fifo */ + if (g_state.autofifo) { + g_tmpfpath[tmpfplen - 1] = '\0'; + + size_t r = mkpath(g_tmpfpath, "nnn-fifo.", g_buf); + + xstrsncpy(g_buf + r - 1, xitoa(getpid()), PATH_MAX - r); + setenv("NNN_FIFO", g_buf, TRUE); + } + + fifopath = xgetenv("NNN_FIFO", NULL); + if (fifopath) { + if (mkfifo(fifopath, 0600) != 0 && !(errno == EEXIST && access(fifopath, W_OK) == 0)) { + xerror(); + return EXIT_FAILURE; + } + + sigaction(SIGPIPE, &(struct sigaction){.sa_handler = SIG_IGN}, NULL); + } +#endif + +#ifdef LINUX_INOTIFY + /* Initialize inotify */ + inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (inotify_fd < 0) { + xerror(); + return EXIT_FAILURE; + } +#elif defined(BSD_KQUEUE) + kq = kqueue(); + if (kq < 0) { + xerror(); + return EXIT_FAILURE; + } +#elif defined(HAIKU_NM) + haiku_hnd = haiku_init_nm(); + if (!haiku_hnd) { + xerror(); + return EXIT_FAILURE; + } +#endif + + /* Configure trash preference */ + opt = xgetenv_val(env_cfg[NNN_TRASH]); + if (opt && opt <= 2) + g_state.trash = opt; + + /* Ignore/handle certain signals */ + struct sigaction act = {.sa_handler = sigint_handler}; + + if (sigaction(SIGINT, &act, NULL) < 0) { + xerror(); + return EXIT_FAILURE; + } + + act.sa_handler = clean_exit_sighandler; + + if (sigaction(SIGTERM, &act, NULL) < 0 || sigaction(SIGHUP, &act, NULL) < 0) { + xerror(); + return EXIT_FAILURE; + } + + act.sa_handler = SIG_IGN; + + if (sigaction(SIGQUIT, &act, NULL) < 0) { + xerror(); + return EXIT_FAILURE; + } + +#ifndef NOLC + /* Set locale */ + setlocale(LC_ALL, ""); +#ifdef PCRE + tables = pcre_maketables(); +#endif +#endif + +#ifndef NORL +#if RL_READLINE_VERSION >= 0x0603 + /* readline would overwrite the WINCH signal hook */ + rl_change_environment = 0; +#endif + /* Bind TAB to cycling */ + rl_variable_bind("completion-ignore-case", "on"); +#ifdef __linux__ + rl_bind_key('\t', rl_menu_complete); +#else + rl_bind_key('\t', rl_complete); +#endif + if (rlhist) { + mkpath(cfgpath, ".history", g_buf); + read_history(g_buf); + } +#endif + +#ifndef NOX11 + if (cfg.x11 && !g_state.picker) { + /* Save terminal window title */ + printf("\033[22;0t"); + fflush(stdout); + + hostname = malloc(_POSIX_HOST_NAME_MAX + 1); + if (!hostname) { + xerror(); + return EXIT_FAILURE; + } + gethostname(hostname, _POSIX_HOST_NAME_MAX); + hostname[_POSIX_HOST_NAME_MAX] = '\0'; + } +#endif + +#ifndef NOMOUSE + if (!initcurses(&mask)) +#else + if (!initcurses(NULL)) +#endif + return EXIT_FAILURE; + + if (sort) + set_sort_flags(sort); + + opt = browse(initpath, session, pkey); + +#ifndef NOSSN + if (session && g_state.prstssn) + save_session(session, NULL); +#endif + +#ifndef NOMOUSE + mousemask(mask, NULL); +#endif + + exitcurses(); + +#ifndef NORL + if (rlhist) { + mkpath(cfgpath, ".history", g_buf); + write_history(g_buf); + } +#endif + + if (g_state.picker) { + if (selbufpos) { + fd = selpath ? open(selpath, O_WRONLY | O_CREAT | O_TRUNC, 0600) : STDOUT_FILENO; + if ((fd == -1) || (seltofile(fd, NULL) != (size_t)(selbufpos))) + xerror(); + + if (fd > 1) + close(fd); + } + } else if (selpath) + unlink(selpath); + + /* Remove tmp dir in list mode */ + rmlistpath(); + + /* Free the regex */ +#ifdef PCRE + pcre_free(archive_pcre); +#else + regfree(&archive_re); +#endif + + /* Free the selection buffer */ + free(pselbuf); + +#ifdef LINUX_INOTIFY + /* Shutdown inotify */ + if (inotify_wd >= 0) + inotify_rm_watch(inotify_fd, inotify_wd); + close(inotify_fd); +#elif defined(BSD_KQUEUE) + if (event_fd >= 0) + close(event_fd); + close(kq); +#elif defined(HAIKU_NM) + haiku_close_nm(haiku_hnd); +#endif + +#ifndef NOFIFO + if (!g_state.fifomode) + notify_fifo(FALSE); + if (fifofd != -1) + close(fifofd); +#endif + + return opt; +} diff --git a/nnn/src/nnn.h b/nnn/src/nnn.h @@ -0,0 +1,282 @@ +/* + * BSD 2-Clause License + * + * Copyright (C) 2014-2016, Lazaros Koromilas <lostd@2f30.org> + * Copyright (C) 2014-2016, Dimitris Papastamos <sin@2f30.org> + * Copyright (C) 2016-2022, Arun Prakash Jana <engineerarun@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <curses.h> + +#define CONTROL(c) ((c) & 0x1f) + +#ifndef ESC +#define ESC (27) +#endif + +#ifndef DEL +#define DEL (127) +#endif + +/* Supported actions */ +enum action { + SEL_BACK = 1, + SEL_OPEN, + SEL_NAV_IN, + SEL_NEXT, + SEL_PREV, + SEL_PGDN, + SEL_PGUP, + SEL_CTRL_D, + SEL_CTRL_U, + SEL_HOME, + SEL_END, + SEL_FIRST, + SEL_CDHOME, + SEL_CDBEGIN, + SEL_CDLAST, + SEL_CDROOT, + SEL_BMOPEN, + SEL_REMOTE, + SEL_CYCLE, + SEL_CYCLER, + SEL_CTX1, + SEL_CTX2, + SEL_CTX3, + SEL_CTX4, +#ifdef CTX8 + SEL_CTX5, + SEL_CTX6, + SEL_CTX7, + SEL_CTX8, +#endif + SEL_MARK, + SEL_BMARK, + SEL_FLTR, + SEL_MFLTR, + SEL_HIDDEN, + SEL_DETAIL, + SEL_STATS, + SEL_CHMODX, + SEL_ARCHIVE, + SEL_SORT, + SEL_REDRAW, + SEL_SEL, + SEL_SELMUL, + SEL_SELALL, + SEL_SELINV, + SEL_SELEDIT, + SEL_CP, + SEL_MV, + SEL_CPMVAS, + SEL_RM, + SEL_OPENWITH, + SEL_NEW, + SEL_RENAME, + SEL_RENAMEMUL, + SEL_UMOUNT, + SEL_HELP, + SEL_AUTONEXT, + SEL_EDIT, + SEL_PLUGIN, + SEL_SHELL, + SEL_LAUNCH, + SEL_PROMPT, + SEL_LOCK, + SEL_SESSIONS, + SEL_EXPORT, + SEL_TIMETYPE, + SEL_QUITCTX, + SEL_QUITCD, + SEL_QUIT, + SEL_QUITERR, +#ifndef NOMOUSE + SEL_CLICK, +#endif +}; + +/* Associate a pressed key to an action */ +struct key { + int sym; /* Key pressed */ + enum action act; /* Action */ +}; + +static struct key bindings[] = { + /* Back */ + { KEY_LEFT, SEL_BACK }, + { 'h', SEL_BACK }, + /* Inside or select */ + { KEY_ENTER, SEL_OPEN }, + { '\r', SEL_OPEN }, + /* Pure navigate inside */ + { KEY_RIGHT, SEL_NAV_IN }, + { 'l', SEL_NAV_IN }, + /* Next */ + { 'j', SEL_NEXT }, + { KEY_DOWN, SEL_NEXT }, + /* Previous */ + { 'k', SEL_PREV }, + { KEY_UP, SEL_PREV }, + /* Page down */ + { KEY_NPAGE, SEL_PGDN }, + /* Page up */ + { KEY_PPAGE, SEL_PGUP }, + /* Ctrl+D */ + { CONTROL('D'), SEL_CTRL_D }, + /* Ctrl+U */ + { CONTROL('U'), SEL_CTRL_U }, + /* First entry */ + { KEY_HOME, SEL_HOME }, + { 'g', SEL_HOME }, + { CONTROL('A'), SEL_HOME }, + /* Last entry */ + { KEY_END, SEL_END }, + { 'G', SEL_END }, + { CONTROL('E'), SEL_END }, + /* Go to first file */ + { '\'', SEL_FIRST }, + /* HOME */ + { '~', SEL_CDHOME }, + /* Initial directory */ + { '@', SEL_CDBEGIN }, + /* Last visited dir */ + { '-', SEL_CDLAST }, + /* Go to / */ + { '`', SEL_CDROOT }, + /* Leader key */ + { 'b', SEL_BMOPEN }, + { CONTROL('_'), SEL_BMOPEN }, + /* Connect to server over SSHFS */ + { 'c', SEL_REMOTE }, + /* Cycle contexts in forward direction */ + { '\t', SEL_CYCLE }, + /* Cycle contexts in reverse direction */ + { KEY_BTAB, SEL_CYCLER }, + /* Go to/create context N */ + { '1', SEL_CTX1 }, + { '2', SEL_CTX2 }, + { '3', SEL_CTX3 }, + { '4', SEL_CTX4 }, +#ifdef CTX8 + { '5', SEL_CTX5 }, + { '6', SEL_CTX6 }, + { '7', SEL_CTX7 }, + { '8', SEL_CTX8 }, +#endif + /* Mark a path to visit later */ + { ',', SEL_MARK }, + /* Create a bookmark */ + { 'B', SEL_BMARK }, + /* Filter */ + { '/', SEL_FLTR }, + /* Toggle filter mode */ + { CONTROL('N'), SEL_MFLTR }, + /* Toggle hide .dot files */ + { '.', SEL_HIDDEN }, + /* Detailed listing */ + { 'd', SEL_DETAIL }, + /* File details */ + { 'f', SEL_STATS }, + { CONTROL('F'), SEL_STATS }, + /* Toggle executable status */ + { '*', SEL_CHMODX }, + /* Create archive */ + { 'z', SEL_ARCHIVE }, + /* Sort toggles */ + { 't', SEL_SORT }, + { CONTROL('T'), SEL_SORT }, + /* Redraw window */ + { CONTROL('L'), SEL_REDRAW }, + /* Select current file path */ + { CONTROL('J'), SEL_SEL }, + { ' ', SEL_SEL }, + /* Toggle select multiple files */ + { 'm', SEL_SELMUL }, + /* Select all files in current dir */ + { 'a', SEL_SELALL }, + /* Invert selection in current dir */ + { 'A', SEL_SELINV }, + /* List, edit selection */ + { 'E', SEL_SELEDIT }, + /* Copy from selection buffer */ + { 'p', SEL_CP }, + { CONTROL('P'), SEL_CP }, + /* Move from selection buffer */ + { 'v', SEL_MV }, + { CONTROL('V'), SEL_MV }, + /* Copy/move from selection buffer and rename */ + { 'w', SEL_CPMVAS }, + { CONTROL('W'), SEL_CPMVAS }, + /* Delete from selection buffer */ + { 'x', SEL_RM }, + { CONTROL('X'), SEL_RM }, + /* Open in a custom application */ + { 'o', SEL_OPENWITH }, + { CONTROL('O'), SEL_OPENWITH }, + /* Create a new file */ + { 'n', SEL_NEW }, + /* Show rename prompt */ + { CONTROL('R'), SEL_RENAME }, + /* Rename contents of current dir */ + { 'r', SEL_RENAMEMUL }, + /* Disconnect a SSHFS mount point */ + { 'u', SEL_UMOUNT }, + /* Show help */ + { '?', SEL_HELP }, + /* Quit a context */ + { '+', SEL_AUTONEXT }, + /* Edit in EDITOR */ + { 'e', SEL_EDIT }, + /* Run a plugin */ + { ';', SEL_PLUGIN }, + /* Run command */ + { '!', SEL_SHELL }, + { CONTROL(']'), SEL_SHELL }, + /* Launcher */ + { '=', SEL_LAUNCH }, + /* Show command prompt */ + { ']', SEL_PROMPT }, + /* Lock screen */ + { '0', SEL_LOCK }, + /* Manage sessions */ + { 's', SEL_SESSIONS }, + /* Export list */ + { '>', SEL_EXPORT }, + /* Set time type */ + { 'T', SEL_TIMETYPE }, + /* Quit a context */ + { 'q', SEL_QUITCTX }, + /* Change dir on quit */ + { CONTROL('G'), SEL_QUITCD }, + /* Quit */ + { CONTROL('Q'), SEL_QUIT }, + /* Quit with an error code */ + { 'Q', SEL_QUITERR }, +#ifndef NOMOUSE + { KEY_MOUSE, SEL_CLICK }, +#endif +}; diff --git a/nnn/src/qsort.h b/nnn/src/qsort.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013, 2017 Alexey Tourbin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * This is a traditional Quicksort implementation which mostly follows + * [Sedgewick 1978]. Sorting is performed entirely on array indices, + * while actual access to the array elements is abstracted out with the + * user-defined `LESS` and `SWAP` primitives. + * + * Synopsis: + * QSORT(N, LESS, SWAP); + * where + * N - the number of elements in A[]; + * LESS(i, j) - compares A[i] to A[j]; + * SWAP(i, j) - exchanges A[i] with A[j]. + */ + +#ifndef QSORT_H +#define QSORT_H + +/* Sort 3 elements. */ +#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ +do { \ + if (Q_LESS(q_a2, q_a1)) { \ + if (Q_LESS(q_a3, q_a2)) \ + Q_SWAP(q_a1, q_a3); \ + else { \ + Q_SWAP(q_a1, q_a2); \ + if (Q_LESS(q_a3, q_a2)) \ + Q_SWAP(q_a2, q_a3); \ + } \ + } \ + else if (Q_LESS(q_a3, q_a2)) { \ + Q_SWAP(q_a2, q_a3); \ + if (Q_LESS(q_a2, q_a1)) \ + Q_SWAP(q_a1, q_a2); \ + } \ +} while (0) + +/* Partition [q_l,q_r] around a pivot. After partitioning, + * [q_l,q_j] are the elements that are less than or equal to the pivot, + * while [q_i,q_r] are the elements greater than or equal to the pivot. */ +#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ +do { \ + /* The middle element, not to be confused with the median. */ \ + Q_UINT q_m = (q_l) + (((q_r) - (q_l)) >> 1); \ + /* Reorder the second, the middle, and the last items. \ + * As [Edelkamp Weiss 2016] explain, using the second element \ + * instead of the first one helps avoid bad behaviour for \ + * decreasingly sorted arrays. This method is used in recent \ + * versions of gcc's std::sort, see gcc bug 58437#c13, although \ + * the details are somewhat different (cf. #c14). */ \ + Q_SORT3((q_l) + 1, q_m, q_r, Q_LESS, Q_SWAP); \ + /* Place the median at the beginning. */ \ + Q_SWAP(q_l, q_m); \ + /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ + * q_i and q_j are initially off by one, they get decremented \ + * in the do-while loops. */ \ + (q_i) = (q_l) + 1; (q_j) = q_r; \ + while (1) { \ + do (q_i)++; while (Q_LESS(q_i, q_l)); \ + do (q_j)--; while (Q_LESS(q_l, q_j)); \ + if ((q_i) >= (q_j)) break; /* Sedgewick says "until j < i" */ \ + Q_SWAP(q_i, q_j); \ + } \ + /* Compensate for the i==j case. */ \ + (q_i) = (q_j) + 1; \ + /* Put the median to its final place. */ \ + Q_SWAP(q_l, q_j); \ + /* The median is not part of the left subfile. */ \ + (q_j)--; \ +} while (0) + +/* Insertion sort is applied to small subfiles - this is contrary to + * Sedgewick's suggestion to run a separate insertion sort pass after + * the partitioning is done. The reason I don't like a separate pass + * is that it triggers extra comparisons, because it can't see that the + * medians are already in their final positions and need not be rechecked. + * Since I do not assume that comparisons are cheap, I also do not try + * to eliminate the (q_j > q_l) boundary check. */ +#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ +do { \ + Q_UINT q_i, q_j; \ + /* For each item starting with the second... */ \ + for (q_i = (q_l) + 1; q_i <= (q_r); q_i++) \ + /* move it down the array so that the first part is sorted. */ \ + for (q_j = q_i; q_j > (q_l) && (Q_LESS(q_j, q_j - 1)); q_j--) \ + Q_SWAP(q_j, q_j - 1); \ +} while (0) + +/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to + * Q_THRESH, the algorithm performs recursive partitioning. When the size + * drops below Q_THRESH, the algorithm switches to insertion sort. + * The minimum valid value is probably 5 (with 5 items, the second and + * the middle items, the middle itself being rounded down, are distinct). */ +#define Q_THRESH 16 + +/* The main loop. */ +#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ +do { \ + Q_UINT q_l = 0; \ + Q_UINT q_r = (Q_N) - 1; \ + Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ + struct { Q_UINT q_l, q_r; } \ + /* On 32-bit platforms, to sort a "char[3GB+]" array, \ + * it may take full 32 stack frames. On 64-bit CPUs, \ + * though, the address space is limited to 48 bits. \ + * The usage is further reduced if Q_N has a 32-bit type. */ \ + q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ + while (1) { \ + if (q_r - q_l + 1 >= Q_THRESH) { \ + Q_UINT q_i, q_j; \ + Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ + /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ + * Dealing with them depends on which one is bigger. */ \ + if (q_j - q_l >= q_r - q_i) \ + Q_SUBFILES(q_l, q_j, q_i, q_r); \ + else \ + Q_SUBFILES(q_i, q_r, q_l, q_j); \ + } \ + else { \ + Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ + /* Pop subfiles from the stack, until it gets empty. */ \ + if (q_sp == 0) break; \ + q_sp--; \ + q_l = q_st[q_sp].q_l; \ + q_r = q_st[q_sp].q_r; \ + } \ + } \ +} while (0) + +/* The missing part: dealing with subfiles. + * Assumes that the first subfile is not smaller than the second. */ +#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ +do { \ + /* If the second subfile is only a single element, it needs \ + * no further processing. The first subfile will be processed \ + * on the next iteration (both subfiles cannot be only a single \ + * element, due to Q_THRESH). */ \ + if ((q_l2) == (q_r2)) { \ + q_l = q_l1; \ + q_r = q_r1; \ + } \ + else { \ + /* Otherwise, both subfiles need processing. \ + * Push the larger subfile onto the stack. */ \ + q_st[q_sp].q_l = q_l1; \ + q_st[q_sp].q_r = q_r1; \ + q_sp++; \ + /* Process the smaller subfile on the next iteration. */ \ + q_l = q_l2; \ + q_r = q_r2; \ + } \ +} while (0) + +/* And now, ladies and gentlemen, may I proudly present to you... */ +#define QSORT(Q_N, Q_LESS, Q_SWAP) \ +do { \ + if ((Q_N) > 1) \ + /* We could check sizeof(Q_N) and use "unsigned", but at least \ + * on x86_64, this has the performance penalty of up to 5%. */ \ + Q_LOOP(unsigned long, Q_N, Q_LESS, Q_SWAP); \ +} while (0) + +#endif + +/* ex:set ts=8 sts=4 sw=4 noet: */ diff --git a/portatil/dwm/LICENSE b/portatil/dwm/LICENSE @@ -0,0 +1,37 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe <anselm@garbe.ca> +© 2006-2009 Jukka Salmi <jukka at salmi dot ch> +© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com> +© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com> +© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com> +© 2007-2009 Christof Musik <christof at sendfax dot de> +© 2007-2009 Premysl Hruby <dfenze at gmail dot com> +© 2007-2008 Enno Gottox Boland <gottox at s01 dot de> +© 2008 Martin Hurton <martin dot hurton at gmail dot com> +© 2008 Neale Pickett <neale dot woozle dot org> +© 2009 Mate Nagy <mnagy at port70 dot net> +© 2010-2016 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2010-2012 Connor Lane Smith <cls@lubutu.com> +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau <quinq@fifth.space> +© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com> +© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/portatil/dwm/Makefile b/portatil/dwm/Makefile @@ -0,0 +1,51 @@ +# dwm - dynamic window manager +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dwm.c util.c +OBJ = ${SRC:.c=.o} + +all: options dwm + +options: + @echo dwm 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 $@ + +dwm: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz + +dist: clean + mkdir -p dwm-${VERSION} + cp -R LICENSE Makefile README config.def.h config.mk\ + dwm.1 drw.h util.h ${SRC} dwm.png transient.c dwm-${VERSION} + tar -cf dwm-${VERSION}.tar dwm-${VERSION} + gzip dwm-${VERSION}.tar + rm -rf dwm-${VERSION} + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f dwm ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwm\ + ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +.PHONY: all options clean dist install uninstall diff --git a/portatil/dwm/README b/portatil/dwm/README @@ -0,0 +1,48 @@ +dwm - dynamic window manager +============================ +dwm is an extremely fast, small, and dynamic window manager for X. + + +Requirements +------------ +In order to build dwm you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dwm is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dwm (if +necessary as root): + + make clean install + + +Running dwm +----------- +Add the following line to your .xinitrc to start dwm using startx: + + exec dwm + +In order to connect dwm to a specific display, make sure that +the DISPLAY environment variable is set correctly, e.g.: + + DISPLAY=foo.bar:1 exec dwm + +(This will start dwm on display :1 of the host foo.bar.) + +In order to display status info in the bar, you can do something +like this in your .xinitrc: + + while xsetroot -name "`date` `uptime | sed 's/.*,//'`" + do + sleep 1 + done & + exec dwm + + +Configuration +------------- +The configuration of dwm is done by creating a custom config.h +and (re)compiling the source code. diff --git a/portatil/dwm/config.def.h.bak b/portatil/dwm/config.def.h.bak @@ -0,0 +1,116 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "monospace:size=10" }; +static const char dmenufont[] = "monospace:size=10"; +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_cyan[] = "#005577"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod1Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/portatil/dwm/config.def.h.orig b/portatil/dwm/config.def.h.orig @@ -0,0 +1,116 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "monospace:size=10" }; +static const char dmenufont[] = "monospace:size=10"; +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_cyan[] = "#005577"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod1Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/portatil/dwm/config.h b/portatil/dwm/config.h @@ -0,0 +1,158 @@ +/* See LICENSE file for copyright and license details. */ +#include <gcolors.h> + +/* appearance */ +static const unsigned int borderpx = 3; /* border pixel of windows */ +static const unsigned int gappx = 12; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ +static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: pin systray to monitor X*/ +static const unsigned int systrayspacing = 2; /* systray spacing */ +static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/ +static const int showsystray = 1; +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "mononoki Nerd Font Mono:size=10" }; // Previamente en monospace:size=10 +static const char dmenufont[] = "mononoki Nerd Font Mono:size=10"; +static const char col_gray1[] = "#222222";//COL_BG_0; //"#222222"; +static const char col_gray2[] = "#444444";//COL_BG_1; //"#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_fosc[] = COL_FOSC; //"#590c0a"; +static const char col_clar[] = COL_CLAR; //"#005577"; col_cyan +//static const char col_granate[] = "#590c0a"; +static const char col_black[] = COL_NEGRE; +static const char col_red[] = COL_VERMELL; +static const char col_yellow[] = COL_GROC; +static const char col_white[] = COL_BLANC; +static const char col_granate[] = "#663338"; + +static const char *colors[][3] = { + /* fg bg border */ + [SchemeStatus] = { col_gray3, col_gray1, col_gray2 }, + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_granate, col_granate }, + [SchemeTagsSel] = { col_clar, col_gray2, "#000000" }, // Tagbar selected + [SchemeTagsNorm] = { col_gray3, col_gray1, "#000000" }, // Tagbar selected + [SchemeInfoSel] = { col_gray4, col_fosc, "#000000" }, // Tagbar selected + [SchemeInfoNorm] = { col_gray3, col_gray1, "#000000" }, // Tagbar selected +}; + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "1", "2", "3", "4", "5" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_fosc, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_q, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_s, togglesticky, {0} }, + { MODKEY, XK_Down, moveresize, {.v = "0x 25y 0w 0h"} }, + { MODKEY, XK_Up, moveresize, {.v = "0x -25y 0w 0h"} }, + { MODKEY, XK_Right, moveresize, {.v = "25x 0y 0w 0h"} }, + { MODKEY, XK_Left, moveresize, {.v = "-25x 0y 0w 0h"} }, + { MODKEY|ShiftMask, XK_Down, moveresize, {.v = "0x 0y 0w 25h"} }, + { MODKEY|ShiftMask, XK_Up, moveresize, {.v = "0x 0y 0w -25h"} }, + { MODKEY|ShiftMask, XK_Right, moveresize, {.v = "0x 0y 25w 0h"} }, + { MODKEY|ShiftMask, XK_Left, moveresize, {.v = "0x 0y -25w 0h"} }, + { MODKEY|ControlMask, XK_Up, moveresizeedge, {.v = "t"} }, + { MODKEY|ControlMask, XK_Down, moveresizeedge, {.v = "b"} }, + { MODKEY|ControlMask, XK_Left, moveresizeedge, {.v = "l"} }, + { MODKEY|ControlMask, XK_Right, moveresizeedge, {.v = "r"} }, + { MODKEY|ControlMask|ShiftMask, XK_Up, moveresizeedge, {.v = "T"} }, + { MODKEY|ControlMask|ShiftMask, XK_Down, moveresizeedge, {.v = "B"} }, + { MODKEY|ControlMask|ShiftMask, XK_Left, moveresizeedge, {.v = "L"} }, + { MODKEY|ControlMask|ShiftMask, XK_Right, moveresizeedge, {.v = "R"} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + { MODKEY, XK_minus, setgaps, {.i = -1 } }, + { MODKEY, XK_equal, setgaps, {.i = +1 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static Button buttons[] = { + /* click event mask button function argument */ + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button2, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/portatil/dwm/config.h.orig b/portatil/dwm/config.h.orig @@ -0,0 +1,142 @@ +/* See LICENSE file for copyright and license details. */ +#include <gcolors.h> + +/* appearance */ +static const unsigned int borderpx = 3; /* border pixel of windows */ +static const unsigned int gappx = 12; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ +static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: pin systray to monitor X*/ +static const unsigned int systrayspacing = 2; /* systray spacing */ +static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/ +static const int showsystray = 1; +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "mononoki Nerd Font Mono:size=10" }; // Previamente en monospace:size=10 +static const char dmenufont[] = "mononoki Nerd Font Mono:size=10"; +static const char col_gray1[] = "#222222";//COL_BG_0; //"#222222"; +static const char col_gray2[] = "#444444";//COL_BG_1; //"#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_fosc[] = COL_FOSC; //"#590c0a"; +static const char col_clar[] = COL_CLAR; //"#005577"; col_cyan +//static const char col_granate[] = "#590c0a"; +static const char col_black[] = COL_NEGRE; +static const char col_red[] = COL_VERMELL; +static const char col_yellow[] = COL_GROC; +static const char col_white[] = COL_BLANC; +static const char col_granate[] = "#663338"; + +static const char *colors[][3] = { + /* fg bg border */ + [SchemeStatus] = { col_gray3, col_gray1, col_gray2 }, + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_granate, col_granate }, + [SchemeTagsSel] = { col_clar, col_gray2, "#000000" }, // Tagbar selected + [SchemeTagsNorm] = { col_gray3, col_gray1, "#000000" }, // Tagbar selected + [SchemeInfoSel] = { col_gray4, col_fosc, "#000000" }, // Tagbar selected + [SchemeInfoNorm] = { col_gray3, col_gray1, "#000000" }, // Tagbar selected +}; + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "1", "2", "3", "4", "5" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod1Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_fosc, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_q, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_s, togglesticky, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + { MODKEY, XK_minus, setgaps, {.i = -1 } }, + { MODKEY, XK_equal, setgaps, {.i = +1 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static Button buttons[] = { + /* click event mask button function argument */ + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button2, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/portatil/dwm/config.h.rej b/portatil/dwm/config.h.rej @@ -0,0 +1,25 @@ +--- config.h ++++ config.h +@@ -79,6 +79,22 @@ static Key keys[] = { + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, ++ { MODKEY, XK_Down, moveresize, {.v = "0x 25y 0w 0h" } }, ++ { MODKEY, XK_Up, moveresize, {.v = "0x -25y 0w 0h" } }, ++ { MODKEY, XK_Right, moveresize, {.v = "25x 0y 0w 0h" } }, ++ { MODKEY, XK_Left, moveresize, {.v = "-25x 0y 0w 0h" } }, ++ { MODKEY|ShiftMask, XK_Down, moveresize, {.v = "0x 0y 0w 25h" } }, ++ { MODKEY|ShiftMask, XK_Up, moveresize, {.v = "0x 0y 0w -25h" } }, ++ { MODKEY|ShiftMask, XK_Right, moveresize, {.v = "0x 0y 25w 0h" } }, ++ { MODKEY|ShiftMask, XK_Left, moveresize, {.v = "0x 0y -25w 0h" } }, ++ { MODKEY|ControlMask, XK_Up, moveresizeedge, {.v = "t"} }, ++ { MODKEY|ControlMask, XK_Down, moveresizeedge, {.v = "b"} }, ++ { MODKEY|ControlMask, XK_Left, moveresizeedge, {.v = "l"} }, ++ { MODKEY|ControlMask, XK_Right, moveresizeedge, {.v = "r"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Up, moveresizeedge, {.v = "T"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Down, moveresizeedge, {.v = "B"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Left, moveresizeedge, {.v = "L"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Right, moveresizeedge, {.v = "R"} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, diff --git a/portatil/dwm/config.mk b/portatil/dwm/config.mk @@ -0,0 +1,38 @@ +# dwm version +VERSION = 6.3 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = ${X11INC}/freetype2 + +# includes and libs +INCS = -I${X11INC} -I${FREETYPEINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/portatil/dwm/drw.c b/portatil/dwm/drw.c @@ -0,0 +1,436 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <X11/Xlib.h> +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/portatil/dwm/drw.h b/portatil/dwm/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/portatil/dwm/drw.o b/portatil/dwm/drw.o Binary files differ. diff --git a/portatil/dwm/dwm b/portatil/dwm/dwm Binary files differ. diff --git a/portatil/dwm/dwm-attachaside-6.3.diff b/portatil/dwm/dwm-attachaside-6.3.diff @@ -0,0 +1,92 @@ +diff --git a/dwm.c b/dwm.c +index 0362114..be7e7a6 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -49,7 +49,8 @@ + #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) + #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +-#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) ++#define ISVISIBLEONTAG(C, T) ((C->tags & T)) ++#define ISVISIBLE(C) ISVISIBLEONTAG(C, C->mon->tagset[C->mon->seltags]) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define MOUSEMASK (BUTTONMASK|PointerMotionMask) + #define WIDTH(X) ((X)->w + 2 * (X)->bw) +@@ -147,6 +148,7 @@ static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interac + static void arrange(Monitor *m); + static void arrangemon(Monitor *m); + static void attach(Client *c); ++static void attachaside(Client *c); + static void attachstack(Client *c); + static void buttonpress(XEvent *e); + static void checkotherwm(void); +@@ -184,6 +186,7 @@ static void maprequest(XEvent *e); + static void monocle(Monitor *m); + static void motionnotify(XEvent *e); + static void movemouse(const Arg *arg); ++static Client *nexttagged(Client *c); + static Client *nexttiled(Client *c); + static void pop(Client *); + static void propertynotify(XEvent *e); +@@ -406,6 +409,17 @@ attach(Client *c) + } + + void ++attachaside(Client *c) { ++ Client *at = nexttagged(c); ++ if(!at) { ++ attach(c); ++ return; ++ } ++ c->next = at->next; ++ at->next = c; ++} ++ ++void + attachstack(Client *c) + { + c->snext = c->mon->stack; +@@ -1076,7 +1090,7 @@ manage(Window w, XWindowAttributes *wa) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); +- attach(c); ++ attachaside(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +@@ -1210,6 +1224,16 @@ movemouse(const Arg *arg) + } + + Client * ++nexttagged(Client *c) { ++ Client *walked = c->mon->clients; ++ for(; ++ walked && (walked->isfloating || !ISVISIBLEONTAG(walked, c->tags)); ++ walked = walked->next ++ ); ++ return walked; ++} ++ ++Client * + nexttiled(Client *c) + { + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); +@@ -1434,7 +1458,7 @@ sendmon(Client *c, Monitor *m) + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ +- attach(c); ++ attachaside(c); + attachstack(c); + focus(NULL); + arrange(NULL); +@@ -1891,7 +1915,7 @@ updategeom(void) + m->clients = c->next; + detachstack(c); + c->mon = mons; +- attach(c); ++ attachaside(c); + attachstack(c); + } + if (m == selmon) diff --git a/portatil/dwm/dwm-status2d-systray-6.3.diff b/portatil/dwm/dwm-status2d-systray-6.3.diff @@ -0,0 +1,888 @@ +diff --git a/config.h b/config.h +index a2ac963..86fcc84 100644 +--- a/config.h ++++ b/config.h +@@ -3,8 +3,13 @@ + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int snap = 32; /* snap pixel */ +-static const int showbar = 1; /* 0 means no bar */ +-static const int topbar = 1; /* 0 means bottom bar */ ++static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ ++static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: systray on left of status text */ ++static const unsigned int systrayspacing = 2; /* systray spacing */ ++static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/ ++static const int showsystray = 1; /* 0 means no systray */ ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = { "monospace:size=10" }; + static const char dmenufont[] = "monospace:size=10"; + static const char col_gray1[] = "#222222"; +@@ -101,8 +106,8 @@ static Key keys[] = { + /* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ + static Button buttons[] = { + /* click event mask button function argument */ +- { ClkLtSymbol, 0, Button1, setlayout, {0} }, +- { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, ++ { ClkTagBar, MODKEY, Button1, tag, {0} }, ++ { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, +diff --git a/dwm.c b/dwm.c +index a96f33c..8153bfe 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -57,12 +57,27 @@ + #define TAGMASK ((1 << LENGTH(tags)) - 1) + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + ++#define SYSTEM_TRAY_REQUEST_DOCK 0 ++/* XEMBED messages */ ++#define XEMBED_EMBEDDED_NOTIFY 0 ++#define XEMBED_WINDOW_ACTIVATE 1 ++#define XEMBED_FOCUS_IN 4 ++#define XEMBED_MODALITY_ON 10 ++#define XEMBED_MAPPED (1 << 0) ++#define XEMBED_WINDOW_ACTIVATE 1 ++#define XEMBED_WINDOW_DEACTIVATE 2 ++#define VERSION_MAJOR 0 ++#define VERSION_MINOR 0 ++#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR ++ + /* enums */ + enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ + enum { SchemeNorm, SchemeSel }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, ++ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ ++enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */ + enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ + enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ +@@ -141,6 +156,12 @@ typedef struct { + int monitor; + } Rule; + ++typedef struct Systray Systray; ++struct Systray { ++ Window win; ++ Client *icons; ++}; ++ + /* function declarations */ + static void applyrules(Client *c); + static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +@@ -163,6 +184,7 @@ static void detachstack(Client *c); + static Monitor *dirtomon(int dir); + static void drawbar(Monitor *m); + static void drawbars(void); ++static int drawstatusbar(Monitor *m, int bh, char* text); + static void enternotify(XEvent *e); + static void expose(XEvent *e); + static void focus(Client *c); +@@ -172,6 +194,7 @@ static void focusstack(const Arg *arg); + static Atom getatomprop(Client *c, Atom prop); + static int getrootptr(int *x, int *y); + static long getstate(Window w); ++static unsigned int getsystraywidth(); + static int gettextprop(Window w, Atom atom, char *text, unsigned int size); + static void grabbuttons(Client *c, int focused); + static void grabkeys(void); +@@ -189,13 +212,16 @@ static void pop(Client *); + static void propertynotify(XEvent *e); + static void quit(const Arg *arg); + static Monitor *recttomon(int x, int y, int w, int h); ++static void removesystrayicon(Client *i); + static void resize(Client *c, int x, int y, int w, int h, int interact); ++static void resizebarwin(Monitor *m); + static void resizeclient(Client *c, int x, int y, int w, int h); + static void resizemouse(const Arg *arg); ++static void resizerequest(XEvent *e); + static void restack(Monitor *m); + static void run(void); + static void scan(void); +-static int sendevent(Client *c, Atom proto); ++static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4); + static void sendmon(Client *c, Monitor *m); + static void setclientstate(Client *c, long state); + static void setfocus(Client *c); +@@ -207,6 +233,7 @@ static void seturgent(Client *c, int urg); + static void showhide(Client *c); + static void sigchld(int unused); + static void spawn(const Arg *arg); ++static Monitor *systraytomon(Monitor *m); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *); +@@ -224,20 +251,25 @@ static int updategeom(void); + static void updatenumlockmask(void); + static void updatesizehints(Client *c); + static void updatestatus(void); ++static void updatesystray(void); ++static void updatesystrayicongeom(Client *i, int w, int h); ++static void updatesystrayiconstate(Client *i, XPropertyEvent *ev); + static void updatetitle(Client *c); + static void updatewindowtype(Client *c); + static void updatewmhints(Client *c); + static void view(const Arg *arg); + static Client *wintoclient(Window w); + static Monitor *wintomon(Window w); ++static Client *wintosystrayicon(Window w); + static int xerror(Display *dpy, XErrorEvent *ee); + static int xerrordummy(Display *dpy, XErrorEvent *ee); + static int xerrorstart(Display *dpy, XErrorEvent *ee); + static void zoom(const Arg *arg); + + /* variables */ ++static Systray *systray = NULL; + static const char broken[] = "broken"; +-static char stext[256]; ++static char stext[1024]; + static int screen; + static int sw, sh; /* X display screen geometry width, height */ + static int bh, blw = 0; /* bar geometry */ +@@ -258,9 +290,10 @@ static void (*handler[LASTEvent]) (XEvent *) = { + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, ++ [ResizeRequest] = resizerequest, + [UnmapNotify] = unmapnotify + }; +-static Atom wmatom[WMLast], netatom[NetLast]; ++static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast]; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; +@@ -440,7 +473,7 @@ buttonpress(XEvent *e) + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; +- else if (ev->x > selmon->ww - (int)TEXTW(stext)) ++ else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth()) + click = ClkStatusText; + else + click = ClkWinTitle; +@@ -483,9 +516,16 @@ cleanup(void) + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); +- for (i = 0; i < CurLast; i++) ++ ++ if (showsystray) { ++ XUnmapWindow(dpy, systray->win); ++ XDestroyWindow(dpy, systray->win); ++ free(systray); ++ } ++ ++ for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); +- for (i = 0; i < LENGTH(colors); i++) ++ for (i = 0; i < LENGTH(colors) + 1; i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); +@@ -513,9 +553,58 @@ cleanupmon(Monitor *mon) + void + clientmessage(XEvent *e) + { ++ XWindowAttributes wa; ++ XSetWindowAttributes swa; + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + ++ if (showsystray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) { ++ /* add systray icons */ ++ if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { ++ if (!(c = (Client *)calloc(1, sizeof(Client)))) ++ die("fatal: could not malloc() %u bytes\n", sizeof(Client)); ++ if (!(c->win = cme->data.l[2])) { ++ free(c); ++ return; ++ } ++ c->mon = selmon; ++ c->next = systray->icons; ++ systray->icons = c; ++ if (!XGetWindowAttributes(dpy, c->win, &wa)) { ++ /* use sane defaults */ ++ wa.width = bh; ++ wa.height = bh; ++ wa.border_width = 0; ++ } ++ c->x = c->oldx = c->y = c->oldy = 0; ++ c->w = c->oldw = wa.width; ++ c->h = c->oldh = wa.height; ++ c->oldbw = wa.border_width; ++ c->bw = 0; ++ c->isfloating = True; ++ /* reuse tags field as mapped status */ ++ c->tags = 1; ++ updatesizehints(c); ++ updatesystrayicongeom(c, wa.width, wa.height); ++ XAddToSaveSet(dpy, c->win); ++ XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask); ++ XReparentWindow(dpy, c->win, systray->win, 0, 0); ++ /* use parents background color */ ++ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa); ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ /* FIXME not sure if I have to send these events, too */ ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_FOCUS_IN, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_MODALITY_ON, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ XSync(dpy, False); ++ resizebarwin(selmon); ++ updatesystray(); ++ setclientstate(c, NormalState); ++ } ++ return; ++ } ++ + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { +@@ -568,7 +657,7 @@ configurenotify(XEvent *e) + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); +- XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); ++ resizebarwin(m); + } + focus(NULL); + arrange(NULL); +@@ -653,6 +742,11 @@ destroynotify(XEvent *e) + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); ++ else if ((c = wintosystrayicon(ev->window))) { ++ removesystrayicon(c); ++ resizebarwin(selmon); ++ updatesystray(); ++ } + } + + void +@@ -693,10 +787,119 @@ dirtomon(int dir) + return m; + } + ++int ++drawstatusbar(Monitor *m, int bh, char* stext) { ++ int ret, i, w, x, len; ++ short isCode = 0; ++ char *text; ++ char *p; ++ ++ len = strlen(stext) + 1 ; ++ if (!(text = (char*) malloc(sizeof(char)*len))) ++ die("malloc"); ++ p = text; ++ memcpy(text, stext, len); ++ ++ /* compute width of the status text */ ++ w = 0; ++ i = -1; ++ while (text[++i]) { ++ if (text[i] == '^') { ++ if (!isCode) { ++ isCode = 1; ++ text[i] = '\0'; ++ w += TEXTW(text) - lrpad; ++ text[i] = '^'; ++ if (text[++i] == 'f') ++ w += atoi(text + ++i); ++ } else { ++ isCode = 0; ++ text = text + i + 1; ++ i = -1; ++ } ++ } ++ } ++ if (!isCode) ++ w += TEXTW(text) - lrpad; ++ else ++ isCode = 0; ++ text = p; ++ ++ w += 2; /* 1px padding on both sides */ ++ ret = m->ww - w; ++ x = m->ww - w - getsystraywidth(); ++ ++ drw_setscheme(drw, scheme[LENGTH(colors)]); ++ drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; ++ drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; ++ drw_rect(drw, x, 0, w, bh, 1, 1); ++ x++; ++ ++ /* process status text */ ++ i = -1; ++ while (text[++i]) { ++ if (text[i] == '^' && !isCode) { ++ isCode = 1; ++ ++ text[i] = '\0'; ++ w = TEXTW(text) - lrpad; ++ drw_text(drw, x, 0, w, bh, 0, text, 0); ++ ++ x += w; ++ ++ /* process code */ ++ while (text[++i] != '^') { ++ if (text[i] == 'c') { ++ char buf[8]; ++ memcpy(buf, (char*)text+i+1, 7); ++ buf[7] = '\0'; ++ drw_clr_create(drw, &drw->scheme[ColFg], buf); ++ i += 7; ++ } else if (text[i] == 'b') { ++ char buf[8]; ++ memcpy(buf, (char*)text+i+1, 7); ++ buf[7] = '\0'; ++ drw_clr_create(drw, &drw->scheme[ColBg], buf); ++ i += 7; ++ } else if (text[i] == 'd') { ++ drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; ++ drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; ++ } else if (text[i] == 'r') { ++ int rx = atoi(text + ++i); ++ while (text[++i] != ','); ++ int ry = atoi(text + ++i); ++ while (text[++i] != ','); ++ int rw = atoi(text + ++i); ++ while (text[++i] != ','); ++ int rh = atoi(text + ++i); ++ ++ drw_rect(drw, rx + x, ry, rw, rh, 1, 0); ++ } else if (text[i] == 'f') { ++ x += atoi(text + ++i); ++ } ++ } ++ ++ text = text + i + 1; ++ i=-1; ++ isCode = 0; ++ } ++ } ++ ++ if (!isCode) { ++ w = TEXTW(text) - lrpad; ++ drw_text(drw, x, 0, w, bh, 0, text, 0); ++ } ++ ++ drw_setscheme(drw, scheme[SchemeNorm]); ++ free(p); ++ ++ return ret; ++} ++ + void + drawbar(Monitor *m) + { +- int x, w, tw = 0; ++ int x, w, tw = 0, stw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; +@@ -705,13 +908,15 @@ drawbar(Monitor *m) + if (!m->showbar) + return; + ++ if(showsystray && m == systraytomon(m) && !systrayonleft) ++ stw = getsystraywidth(); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ +- drw_setscheme(drw, scheme[SchemeNorm]); +- tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ +- drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); ++ tw = m->ww - drawstatusbar(m, bh, stext); + } + ++ resizebarwin(m); + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) +@@ -732,7 +937,7 @@ drawbar(Monitor *m) + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->ww - tw - x) > bh) { ++ if ((w = m->ww - tw - stw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); +@@ -743,7 +948,7 @@ drawbar(Monitor *m) + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } +- drw_map(drw, m->barwin, 0, 0, m->ww, bh); ++ drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh); + } + + void +@@ -780,8 +985,11 @@ expose(XEvent *e) + Monitor *m; + XExposeEvent *ev = &e->xexpose; + +- if (ev->count == 0 && (m = wintomon(ev->window))) ++ if (ev->count == 0 && (m = wintomon(ev->window))) { + drawbar(m); ++ if (m == selmon) ++ updatesystray(); ++ } + } + + void +@@ -867,9 +1075,17 @@ getatomprop(Client *c, Atom prop) + unsigned char *p = NULL; + Atom da, atom = None; + +- if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, ++ /* FIXME getatomprop should return the number of items and a pointer to ++ * the stored data instead of this workaround */ ++ Atom req = XA_ATOM; ++ if (prop == xatom[XembedInfo]) ++ req = xatom[XembedInfo]; ++ ++ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; ++ if (da == xatom[XembedInfo] && dl == 2) ++ atom = ((Atom *)p)[1]; + XFree(p); + } + return atom; +@@ -903,6 +1119,16 @@ getstate(Window w) + return result; + } + ++unsigned int ++getsystraywidth() ++{ ++ unsigned int w = 0; ++ Client *i; ++ if(showsystray) ++ for(i = systray->icons; i; w += i->w + systrayspacing, i = i->next) ; ++ return w ? w + systrayspacing : 1; ++} ++ + int + gettextprop(Window w, Atom atom, char *text, unsigned int size) + { +@@ -1007,7 +1233,8 @@ killclient(const Arg *arg) + { + if (!selmon->sel) + return; +- if (!sendevent(selmon->sel, wmatom[WMDelete])) { ++ ++ if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); +@@ -1096,6 +1323,13 @@ maprequest(XEvent *e) + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + ++ Client *i; ++ if ((i = wintosystrayicon(ev->window))) { ++ sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION); ++ resizebarwin(selmon); ++ updatesystray(); ++ } ++ + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) +@@ -1219,7 +1453,18 @@ propertynotify(XEvent *e) + Window trans; + XPropertyEvent *ev = &e->xproperty; + +- if ((ev->window == root) && (ev->atom == XA_WM_NAME)) ++ if ((c = wintosystrayicon(ev->window))) { ++ if (ev->atom == XA_WM_NORMAL_HINTS) { ++ updatesizehints(c); ++ updatesystrayicongeom(c, c->w, c->h); ++ } ++ else ++ updatesystrayiconstate(c, ev); ++ resizebarwin(selmon); ++ updatesystray(); ++ } ++ ++ if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ +@@ -1269,6 +1514,19 @@ recttomon(int x, int y, int w, int h) + return r; + } + ++void ++removesystrayicon(Client *i) ++{ ++ Client **ii; ++ ++ if (!showsystray || !i) ++ return; ++ for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next); ++ if (ii) ++ *ii = i->next; ++ free(i); ++} ++ + void + resize(Client *c, int x, int y, int w, int h, int interact) + { +@@ -1276,6 +1534,14 @@ resize(Client *c, int x, int y, int w, int h, int interact) + resizeclient(c, x, y, w, h); + } + ++void ++resizebarwin(Monitor *m) { ++ unsigned int w = m->ww; ++ if (showsystray && m == systraytomon(m) && !systrayonleft) ++ w -= getsystraywidth(); ++ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh); ++} ++ + void + resizeclient(Client *c, int x, int y, int w, int h) + { +@@ -1348,6 +1614,19 @@ resizemouse(const Arg *arg) + } + } + ++void ++resizerequest(XEvent *e) ++{ ++ XResizeRequestEvent *ev = &e->xresizerequest; ++ Client *i; ++ ++ if ((i = wintosystrayicon(ev->window))) { ++ updatesystrayicongeom(i, ev->width, ev->height); ++ resizebarwin(selmon); ++ updatesystray(); ++ } ++} ++ + void + restack(Monitor *m) + { +@@ -1437,26 +1716,37 @@ setclientstate(Client *c, long state) + } + + int +-sendevent(Client *c, Atom proto) ++sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4) + { + int n; +- Atom *protocols; ++ Atom *protocols, mt; + int exists = 0; + XEvent ev; + +- if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { +- while (!exists && n--) +- exists = protocols[n] == proto; +- XFree(protocols); ++ if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) { ++ mt = wmatom[WMProtocols]; ++ if (XGetWMProtocols(dpy, w, &protocols, &n)) { ++ while (!exists && n--) ++ exists = protocols[n] == proto; ++ XFree(protocols); ++ } + } ++ else { ++ exists = True; ++ mt = proto; ++ } ++ + if (exists) { + ev.type = ClientMessage; +- ev.xclient.window = c->win; +- ev.xclient.message_type = wmatom[WMProtocols]; ++ ev.xclient.window = w; ++ ev.xclient.message_type = mt; + ev.xclient.format = 32; +- ev.xclient.data.l[0] = proto; +- ev.xclient.data.l[1] = CurrentTime; +- XSendEvent(dpy, c->win, False, NoEventMask, &ev); ++ ev.xclient.data.l[0] = d0; ++ ev.xclient.data.l[1] = d1; ++ ev.xclient.data.l[2] = d2; ++ ev.xclient.data.l[3] = d3; ++ ev.xclient.data.l[4] = d4; ++ XSendEvent(dpy, w, False, mask, &ev); + } + return exists; + } +@@ -1470,7 +1760,7 @@ setfocus(Client *c) + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } +- sendevent(c, wmatom[WMTakeFocus]); ++ sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0); + } + + void +@@ -1558,22 +1848,32 @@ setup(void) + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); +- netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); +- netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); ++ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); ++ netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False); ++ netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); ++ netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False); ++ netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False); ++ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); +- /* init cursors */ ++ xatom[Manager] = XInternAtom(dpy, "MANAGER", False); ++ xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False); ++ xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False); ++ /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ +- scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); ++ scheme = ecalloc(LENGTH(colors) + 1, sizeof(Clr *)); ++ scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], 3); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); ++ /* init system tray */ ++ updatesystray(); + /* init bars */ + updatebars(); + updatestatus(); +@@ -1707,7 +2007,18 @@ togglebar(const Arg *arg) + { + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); +- XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); ++ resizebarwin(selmon); ++ if (showsystray) { ++ XWindowChanges wc; ++ if (!selmon->showbar) ++ wc.y = -bh; ++ else if (selmon->showbar) { ++ wc.y = 0; ++ if (!selmon->topbar) ++ wc.y = selmon->mh - bh; ++ } ++ XConfigureWindow(dpy, systray->win, CWY, &wc); ++ } + arrange(selmon); + } + +@@ -1802,11 +2113,18 @@ unmapnotify(XEvent *e) + else + unmanage(c, 0); + } ++ else if ((c = wintosystrayicon(ev->window))) { ++ /* KLUDGE! sometimes icons occasionally unmap their windows, but do ++ * _not_ destroy them. We map those windows back */ ++ XMapRaised(dpy, c->win); ++ updatesystray(); ++ } + } + + void + updatebars(void) + { ++ unsigned int w; + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, +@@ -1817,10 +2135,15 @@ updatebars(void) + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; +- m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), ++ w = m->ww; ++ if (showsystray && m == systraytomon(m)) ++ w -= getsystraywidth(); ++ m->barwin = XCreateWindow(dpy, root, m->wx, m->by, w, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); ++ if (showsystray && m == systraytomon(m)) ++ XMapRaised(dpy, systray->win); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +@@ -1996,6 +2319,125 @@ updatestatus(void) + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); ++ updatesystray(); ++} ++ ++ ++void ++updatesystrayicongeom(Client *i, int w, int h) ++{ ++ if (i) { ++ i->h = bh; ++ if (w == h) ++ i->w = bh; ++ else if (h == bh) ++ i->w = w; ++ else ++ i->w = (int) ((float)bh * ((float)w / (float)h)); ++ applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False); ++ /* force icons into the systray dimensions if they don't want to */ ++ if (i->h > bh) { ++ if (i->w == i->h) ++ i->w = bh; ++ else ++ i->w = (int) ((float)bh * ((float)i->w / (float)i->h)); ++ i->h = bh; ++ } ++ } ++} ++ ++void ++updatesystrayiconstate(Client *i, XPropertyEvent *ev) ++{ ++ long flags; ++ int code = 0; ++ ++ if (!showsystray || !i || ev->atom != xatom[XembedInfo] || ++ !(flags = getatomprop(i, xatom[XembedInfo]))) ++ return; ++ ++ if (flags & XEMBED_MAPPED && !i->tags) { ++ i->tags = 1; ++ code = XEMBED_WINDOW_ACTIVATE; ++ XMapRaised(dpy, i->win); ++ setclientstate(i, NormalState); ++ } ++ else if (!(flags & XEMBED_MAPPED) && i->tags) { ++ i->tags = 0; ++ code = XEMBED_WINDOW_DEACTIVATE; ++ XUnmapWindow(dpy, i->win); ++ setclientstate(i, WithdrawnState); ++ } ++ else ++ return; ++ sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0, ++ systray->win, XEMBED_EMBEDDED_VERSION); ++} ++ ++void ++updatesystray(void) ++{ ++ XSetWindowAttributes wa; ++ XWindowChanges wc; ++ Client *i; ++ Monitor *m = systraytomon(NULL); ++ unsigned int x = m->mx + m->mw; ++ unsigned int sw = TEXTW(stext) - lrpad + systrayspacing; ++ unsigned int w = 1; ++ ++ if (!showsystray) ++ return; ++ if (systrayonleft) ++ x -= sw + lrpad / 2; ++ if (!systray) { ++ /* init systray */ ++ if (!(systray = (Systray *)calloc(1, sizeof(Systray)))) ++ die("fatal: could not malloc() %u bytes\n", sizeof(Systray)); ++ systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh, 0, 0, scheme[SchemeSel][ColBg].pixel); ++ wa.event_mask = ButtonPressMask | ExposureMask; ++ wa.override_redirect = True; ++ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ XSelectInput(dpy, systray->win, SubstructureNotifyMask); ++ XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation], XA_CARDINAL, 32, ++ PropModeReplace, (unsigned char *)&netatom[NetSystemTrayOrientationHorz], 1); ++ XChangeWindowAttributes(dpy, systray->win, CWEventMask|CWOverrideRedirect|CWBackPixel, &wa); ++ XMapRaised(dpy, systray->win); ++ XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime); ++ if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) { ++ sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime, netatom[NetSystemTray], systray->win, 0, 0); ++ XSync(dpy, False); ++ } ++ else { ++ fprintf(stderr, "dwm: unable to obtain system tray.\n"); ++ free(systray); ++ systray = NULL; ++ return; ++ } ++ } ++ for (w = 0, i = systray->icons; i; i = i->next) { ++ /* make sure the background color stays the same */ ++ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa); ++ XMapRaised(dpy, i->win); ++ w += systrayspacing; ++ i->x = w; ++ XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h); ++ w += i->w; ++ if (i->mon != m) ++ i->mon = m; ++ } ++ w = w ? w + systrayspacing : 1; ++ x -= w; ++ XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh); ++ wc.x = x; wc.y = m->by; wc.width = w; wc.height = bh; ++ wc.stack_mode = Above; wc.sibling = m->barwin; ++ XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSibling|CWStackMode, &wc); ++ XMapWindow(dpy, systray->win); ++ XMapSubwindows(dpy, systray->win); ++ /* redraw background */ ++ XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel); ++ XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh); ++ XSync(dpy, False); + } + + void +@@ -2063,6 +2505,16 @@ wintoclient(Window w) + return NULL; + } + ++Client * ++wintosystrayicon(Window w) { ++ Client *i = NULL; ++ ++ if (!showsystray || !w) ++ return i; ++ for (i = systray->icons; i && i->win != w; i = i->next) ; ++ return i; ++} ++ + Monitor * + wintomon(Window w) + { +@@ -2116,6 +2568,22 @@ xerrorstart(Display *dpy, XErrorEvent *ee) + return -1; + } + ++Monitor * ++systraytomon(Monitor *m) { ++ Monitor *t; ++ int i, n; ++ if(!systraypinning) { ++ if(!m) ++ return selmon; ++ return m == selmon ? m : NULL; ++ } ++ for(n = 1, t = mons; t && t->next; n++, t = t->next) ; ++ for(i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next) ; ++ if(systraypinningfailfirst && n < systraypinning) ++ return mons; ++ return t; ++} ++ + void + zoom(const Arg *arg) + { diff --git a/portatil/dwm/dwm-sticky-6.1.diff b/portatil/dwm/dwm-sticky-6.1.diff @@ -0,0 +1,58 @@ +diff --git a/config.h b/config.h +index 7054c06..9b5d5b8 100644 +--- a/config.h ++++ b/config.h +@@ -76,6 +76,7 @@ static Key keys[] = { + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, ++ { MODKEY, XK_s, togglesticky, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, +diff --git a/dwm.c b/dwm.c +index 0362114..0ef5c7f 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -49,7 +49,7 @@ + #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) + #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +-#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) ++#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]) || C->issticky) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define MOUSEMASK (BUTTONMASK|PointerMotionMask) + #define WIDTH(X) ((X)->w + 2 * (X)->bw) +@@ -92,7 +92,7 @@ struct Client { + int basew, baseh, incw, inch, maxw, maxh, minw, minh; + int bw, oldbw; + unsigned int tags; +- int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; ++ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, issticky; + Client *next; + Client *snext; + Monitor *mon; +@@ -211,6 +211,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *); + static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); ++static void togglesticky(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unfocus(Client *c, int setfocus); +@@ -1713,6 +1714,15 @@ togglefloating(const Arg *arg) + } + + void ++togglesticky(const Arg *arg) ++{ ++ if (!selmon->sel) ++ return; ++ selmon->sel->issticky = !selmon->sel->issticky; ++ arrange(selmon); ++} ++ ++void + toggletag(const Arg *arg) + { + unsigned int newtags; diff --git a/portatil/dwm/dwm.1 b/portatil/dwm/dwm.1 @@ -0,0 +1,176 @@ +.TH DWM 1 dwm\-VERSION +.SH NAME +dwm \- dynamic window manager +.SH SYNOPSIS +.B dwm +.RB [ \-v ] +.SH DESCRIPTION +dwm is a dynamic window manager for X. It manages windows in tiled, monocle +and floating layouts. Either layout can be applied dynamically, optimising the +environment for the application in use and the task performed. +.P +In tiled layouts windows are managed in a master and stacking area. The master +area on the left contains one window by default, and the stacking area on the +right contains all other windows. The number of master area windows can be +adjusted from zero to an arbitrary number. In monocle layout all windows are +maximised to the screen size. In floating layout windows can be resized and +moved freely. Dialog windows are always managed floating, regardless of the +layout applied. +.P +Windows are grouped by tags. Each window can be tagged with one or multiple +tags. Selecting certain tags displays all windows with these tags. +.P +Each screen contains a small status bar which displays all available tags, the +layout, the title of the focused window, and the text read from the root window +name property, if the screen is focused. A floating window is indicated with an +empty square and a maximised floating window is indicated with a filled square +before the windows title. The selected tags are indicated with a different +color. The tags of the focused window are indicated with a filled square in the +top left corner. The tags which are applied to one or more windows are +indicated with an empty square in the top left corner. +.P +dwm draws a small border around windows to indicate the focus state. +.SH OPTIONS +.TP +.B \-v +prints version information to stderr, then exits. +.SH USAGE +.SS Status bar +.TP +.B X root window name +is read and displayed in the status text area. It can be set with the +.BR xsetroot (1) +command. +.TP +.B Button1 +click on a tag label to display all windows with that tag, click on the layout +label toggles between tiled and floating layout. +.TP +.B Button3 +click on a tag label adds/removes all windows with that tag to/from the view. +.TP +.B Mod1\-Button1 +click on a tag label applies that tag to the focused window. +.TP +.B Mod1\-Button3 +click on a tag label adds/removes that tag to/from the focused window. +.SS Keyboard commands +.TP +.B Mod1\-Shift\-Return +Start +.BR st(1). +.TP +.B Mod1\-p +Spawn +.BR dmenu(1) +for launching other programs. +.TP +.B Mod1\-, +Focus previous screen, if any. +.TP +.B Mod1\-. +Focus next screen, if any. +.TP +.B Mod1\-Shift\-, +Send focused window to previous screen, if any. +.TP +.B Mod1\-Shift\-. +Send focused window to next screen, if any. +.TP +.B Mod1\-b +Toggles bar on and off. +.TP +.B Mod1\-t +Sets tiled layout. +.TP +.B Mod1\-f +Sets floating layout. +.TP +.B Mod1\-m +Sets monocle layout. +.TP +.B Mod1\-space +Toggles between current and previous layout. +.TP +.B Mod1\-j +Focus next window. +.TP +.B Mod1\-k +Focus previous window. +.TP +.B Mod1\-i +Increase number of windows in master area. +.TP +.B Mod1\-d +Decrease number of windows in master area. +.TP +.B Mod1\-l +Increase master area size. +.TP +.B Mod1\-h +Decrease master area size. +.TP +.B Mod1\-Return +Zooms/cycles focused window to/from master area (tiled layouts only). +.TP +.B Mod1\-Shift\-c +Close focused window. +.TP +.B Mod1\-Shift\-space +Toggle focused window between tiled and floating state. +.TP +.B Mod1\-Tab +Toggles to the previously selected tags. +.TP +.B Mod1\-Shift\-[1..n] +Apply nth tag to focused window. +.TP +.B Mod1\-Shift\-0 +Apply all tags to focused window. +.TP +.B Mod1\-Control\-Shift\-[1..n] +Add/remove nth tag to/from focused window. +.TP +.B Mod1\-[1..n] +View all windows with nth tag. +.TP +.B Mod1\-0 +View all windows with any tag. +.TP +.B Mod1\-Control\-[1..n] +Add/remove all windows with nth tag to/from the view. +.TP +.B Mod1\-Shift\-q +Quit dwm. +.SS Mouse commands +.TP +.B Mod1\-Button1 +Move focused window while dragging. Tiled windows will be toggled to the floating state. +.TP +.B Mod1\-Button2 +Toggles focused window between floating and tiled state. +.TP +.B Mod1\-Button3 +Resize focused window while dragging. Tiled windows will be toggled to the floating state. +.SH CUSTOMIZATION +dwm is customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH SEE ALSO +.BR dmenu (1), +.BR st (1) +.SH ISSUES +Java applications which use the XToolkit/XAWT backend may draw grey windows +only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early +JDK 1.6 versions, because it assumes a reparenting window manager. Possible workarounds +are using JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or setting the +environment variable +.BR AWT_TOOLKIT=MToolkit +(to use the older Motif backend instead) or running +.B xprop -root -f _NET_WM_NAME 32a -set _NET_WM_NAME LG3D +or +.B wmname LG3D +(to pretend that a non-reparenting window manager is running that the +XToolkit/XAWT backend can recognize) or when using OpenJDK setting the environment variable +.BR _JAVA_AWT_WM_NONREPARENTING=1 . +.SH BUGS +Send all bug reports with a patch to hackers@suckless.org. diff --git a/portatil/dwm/dwm.c b/portatil/dwm/dwm.c @@ -0,0 +1,2930 @@ +/* See LICENSE file for copyright and license details. + * + * dynamic window manager is designed like any other X client as well. It is + * driven through handling X events. In contrast to other X clients, a window + * manager selects for SubstructureRedirectMask on the root window, to receive + * events about window (dis-)appearance. Only one X connection at a time is + * allowed to select for this event mask. + * + * The event handlers of dwm are organized in an array which is accessed + * whenever a new event has been fetched. This allows event dispatching + * in O(1) time. + * + * Each child of the root window is called a client, except windows which have + * set the override_redirect flag. Clients are organized in a linked client + * list on each monitor, the focus history is remembered through a stack list + * on each monitor. Each client contains a bit array to indicate the tags of a + * client. + * + * Keys and tagging rules are organized as arrays and defined in config.h. + * + * To understand everything else, start reading main(). + */ +#include <errno.h> +#include <locale.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif /* XINERAMA */ +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +//#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]) || C->issticky) +#define ISVISIBLEONTAG(C,T) ((C->tags & T)) +#define ISVISIBLE(C) ISVISIBLEONTAG(C, C->mon->tagset[C->mon->seltags] || C->issticky) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#define SYSTEM_TRAY_REQUEST_DOCK 0 +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MAPPED (1 << 0) +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define VERSION_MAJOR 0 +#define VERSION_MINOR 0 +#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel, SchemeStatus, SchemeTagsSel, SchemeTagsNorm, SchemeInfoSel, SchemeInfoNorm }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, issticky; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int monitor; +} Rule; + +typedef struct Systray Systray; +struct Systray { + Window win; + Client *icons; +}; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachaside(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static int drawstatusbar(Monitor *m, int bh, char* text); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static unsigned int getsystraywidth(); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void moveresize(const Arg *arg); +static void moveresizeedge(const Arg *arg); +static void movemouse(const Arg *arg); +static Client *nexttagged(Client *c); +static Client *nexttiled(Client *c); +static void pop(Client *); +static void propertynotify(XEvent *e); +static void quit(const Arg *arg); +static Monitor *recttomon(int x, int y, int w, int h); +static void removesystrayicon(Client *i); +static void resize(Client *c, int x, int y, int w, int h, int interact); +static void resizebarwin(Monitor *m); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void resizerequest(XEvent *e); +static void restack(Monitor *m); +static void run(void); +static void scan(void); +static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void setgaps(const Arg *arg); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void seturgent(Client *c, int urg); +static void showhide(Client *c); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static Monitor *systraytomon(Monitor *m); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglesticky(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatesystray(void); +static void updatesystrayicongeom(Client* i, int w, int h); +static void updatesystrayiconstate(Client* i, XPropertyEvent* ev); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static Client* wintosystrayicon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +/* variables */ +static Systray* systray = NULL; +static const char broken[] = "broken"; +static char stext[1024]; +static int screen; +static int sw, sh; /* X display screen geometry width, height */ +static int bh, blw = 0; /* bar geometry */ +static int lrpad; /* sum of left and right padding for text */ +static int (*xerrorxlib)(Display *, XErrorEvent *); +static unsigned int numlockmask = 0; +static void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureRequest] = configurerequest, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MappingNotify] = mappingnotify, + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, + [ResizeRequest] = resizerequest, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast]; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* compile-time check if all tags fit into an unsigned int bit array. */ +struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +applyrules(Client *c) +{ + const char *class, *instance; + unsigned int i; + const Rule *r; + Monitor *m; + XClassHint ch = { NULL, NULL }; + + /* rule matching */ + c->isfloating = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < LENGTH(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + /* see last two sentences in ICCCM 4.1.2.3 */ + baseismin = c->basew == c->minw && c->baseh == c->minh; + if (!baseismin) { /* temporarily remove base dimensions */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for aspect limits */ + if (c->mina > 0 && c->maxa > 0) { + if (c->maxa < (float)*w / *h) + *w = *h * c->maxa + 0.5; + else if (c->mina < (float)*h / *w) + *h = *w * c->mina + 0.5; + } + if (baseismin) { /* increment calculation requires this */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for increment value */ + if (c->incw) + *w -= *w % c->incw; + if (c->inch) + *h -= *h % c->inch; + /* restore base dimensions */ + *w = MAX(*w + c->basew, c->minw); + *h = MAX(*h + c->baseh, c->minh); + if (c->maxw) + *w = MIN(*w, c->maxw); + if (c->maxh) + *h = MIN(*h, c->maxh); + } + return *x != c->x || *y != c->y || *w != c->w || *h != c->h; +} + +void +arrange(Monitor *m) +{ + if (m) + showhide(m->stack); + else for (m = mons; m; m = m->next) + showhide(m->stack); + if (m) { + arrangemon(m); + restack(m); + } else for (m = mons; m; m = m->next) + arrangemon(m); +} + +void +arrangemon(Monitor *m) +{ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); +} + +void +attach(Client *c) +{ + c->next = c->mon->clients; + c->mon->clients = c; +} + +void +attachaside(Client *c) { + Client *at = nexttagged(c); + if(!at) { + attach(c); + return; + } + c->next = at->next; + at->next = c; +} + +void +attachstack(Client *c) +{ + c->snext = c->mon->stack; + c->mon->stack = c; +} + +void +buttonpress(XEvent *e) +{ + unsigned int i, x, click; + Arg arg = {0}; + Client *c; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + if (ev->window == selmon->barwin) { + i = x = 0; + do + x += TEXTW(tags[i]); + while (ev->x >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; + else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth()) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + if (showsystray) { + XUnmapWindow(dpy, systray->win); + XDestroyWindow(dpy, systray->win); + free(systray); + } + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors) + 1; i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); +} + +void +cleanupmon(Monitor *mon) +{ + Monitor *m; + + if (mon == mons) + mons = mons->next; + else { + for (m = mons; m && m->next != mon; m = m->next); + m->next = mon->next; + } + XUnmapWindow(dpy, mon->barwin); + XDestroyWindow(dpy, mon->barwin); + free(mon); +} + +void +clientmessage(XEvent *e) +{ + XWindowAttributes wa; + XSetWindowAttributes swa; + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (showsystray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) { + /* add systray icons */ + if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { + if (!(c = (Client *)calloc(1, sizeof(Client)))) + die("fatal: could not malloc() %u bytes\n", sizeof(Client)); + if (!(c->win = cme->data.l[2])) { + free(c); + return; + } + c->mon = selmon; + c->next = systray->icons; + systray->icons = c; + if (!XGetWindowAttributes(dpy, c->win, &wa)) { + /* use sane defaults */ + wa.width = bh; + wa.height = bh; + wa.border_width = 0; + } + c->x = c->oldx = c->y = c->oldy = 0; + c->w = c->oldw = wa.width; + c->h = c->oldh = wa.height; + c->oldbw = wa.border_width; + c->bw = 0; + c->isfloating = True; + /* reuse tags field as mapped status */ + c->tags = 1; + updatesizehints(c); + updatesystrayicongeom(c, wa.width, wa.height); + XAddToSaveSet(dpy, c->win); + XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask); + XReparentWindow(dpy, c->win, systray->win, 0, 0); + /* use parents background color */ + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa); + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + /* FIXME not sure if I have to send these events, too */ + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_FOCUS_IN, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_MODALITY_ON, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + XSync(dpy, False); + resizebarwin(selmon); + updatesystray(); + setclientstate(c, NormalState); + } + return; + } + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + resizebarwin(m); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); + else if ((c = wintosystrayicon(ev->window))) { + removesystrayicon(c); + resizebarwin(selmon); + updatesystray(); + } +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +//int +//drawstatusbar(Monitor *m, int bh, char* stext) { +// int ret, i, w, x, len; +// short isCode = 0; +// char *text; +// char *p; +// +// len = strlen(stext) + 1 ; +// if (!(text = (char*) malloc(sizeof(char)*len))) +// die("malloc"); +// p = text; +// memcpy(text, stext, len); +// +// /* compute width of the status text */ +// w = 0; +// i = -1; +// while (text[++i]) { +// if (text[i] == '^') { +// if (!isCode) { +// isCode = 1; +// text[i] = '\0'; +// w += TEXTW(text) - lrpad; +// text[i] = '^'; +// if (text[++i] == 'f') +// w += atoi(text + ++i); +// } else { +// isCode = 0; +// text = text + i + 1; +// i = -1; +// } +// } +// } +// if (!isCode) +// w += TEXTW(text) - lrpad; +// else +// isCode = 0; +// text = p; +// +// w += 2; /* 1px padding on both sides */ +// ret = x = m->ww - w; +// +// drw_setscheme(drw, scheme[LENGTH(colors)]); +// drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; +// drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; +// drw_rect(drw, x, 0, w, bh, 1, 1); +// x++; +// +// /* process status text */ +// i = -1; +// while (text[++i]) { +// if (text[i] == '^' && !isCode) { +// isCode = 1; +// +// text[i] = '\0'; +// w = TEXTW(text) - lrpad; +// drw_text(drw, x, 0, w, bh, 0, text, 0); +// +// x += w; +// +// /* process code */ +// while (text[++i] != '^') { +// if (text[i] == 'c') { +// char buf[8]; +// memcpy(buf, (char*)text+i+1, 7); +// buf[7] = '\0'; +// drw_clr_create(drw, &drw->scheme[ColFg], buf); +// i += 7; +// } else if (text[i] == 'b') { +// char buf[8]; +// memcpy(buf, (char*)text+i+1, 7); +// buf[7] = '\0'; +// drw_clr_create(drw, &drw->scheme[ColBg], buf); +// i += 7; +// } else if (text[i] == 'd') { +// drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; +// drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; +// } else if (text[i] == 'r') { +// int rx = atoi(text + ++i); +// while (text[++i] != ','); +// int ry = atoi(text + ++i); +// while (text[++i] != ','); +// int rw = atoi(text + ++i); +// while (text[++i] != ','); +// int rh = atoi(text + ++i); +// +// drw_rect(drw, rx + x, ry, rw, rh, 1, 0); +// } else if (text[i] == 'f') { +// x += atoi(text + ++i); +// } +// } +// +// text = text + i + 1; +// i=-1; +// isCode = 0; +// } +// } +// +// if (!isCode) { +// w = TEXTW(text) - lrpad; +// drw_text(drw, x, 0, w, bh, 0, text, 0); +// } +// +// drw_setscheme(drw, scheme[SchemeNorm]); +// free(p); +// +// return ret; +//} +// +int +drawstatusbar(Monitor *m, int bh, char* stext) { + int ret, i, w, x, len; + short isCode = 0; + char *text; + char *p; + + len = strlen(stext) + 1 ; + if (!(text = (char*) malloc(sizeof(char)*len))) + die("malloc"); + p = text; + memcpy(text, stext, len); + + /* compute width of the status text */ + w = 0; + i = -1; + while (text[++i]) { + if (text[i] == '^') { + if (!isCode) { + isCode = 1; + text[i] = '\0'; + w += TEXTW(text) - lrpad; + text[i] = '^'; + if (text[++i] == 'f') + w += atoi(text + ++i); + } else { + isCode = 0; + text = text + i + 1; + i = -1; + } + } + } + if (!isCode) + w += TEXTW(text) - lrpad; + else + isCode = 0; + text = p; + + w += 2; /* 1px padding on both sides */ + ret = m->ww - w; + x = m->ww - w - getsystraywidth(); + + drw_setscheme(drw, scheme[LENGTH(colors)]); + drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; + drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; + drw_rect(drw, x, 0, w, bh, 1, 1); + x++; + + /* process status text */ + i = -1; + while (text[++i]) { + if (text[i] == '^' && !isCode) { + isCode = 1; + + text[i] = '\0'; + w = TEXTW(text) - lrpad; + drw_text(drw, x, 0, w, bh, 0, text, 0); + + x += w; + + /* process code */ + while (text[++i] != '^') { + if (text[i] == 'c') { + char buf[8]; + memcpy(buf, (char*)text+i+1, 7); + buf[7] = '\0'; + drw_clr_create(drw, &drw->scheme[ColFg], buf); + i += 7; + } else if (text[i] == 'b') { + char buf[8]; + memcpy(buf, (char*)text+i+1, 7); + buf[7] = '\0'; + drw_clr_create(drw, &drw->scheme[ColBg], buf); + i += 7; + } else if (text[i] == 'd') { + drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; + drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; + } else if (text[i] == 'r') { + int rx = atoi(text + ++i); + while (text[++i] != ','); + int ry = atoi(text + ++i); + while (text[++i] != ','); + int rw = atoi(text + ++i); + while (text[++i] != ','); + int rh = atoi(text + ++i); + + drw_rect(drw, rx + x, ry, rw, rh, 1, 0); + } else if (text[i] == 'f') { + x += atoi(text + ++i); + } + } + + text = text + i + 1; + i=-1; + isCode = 0; + } + } + + if (!isCode) { + w = TEXTW(text) - lrpad; + drw_text(drw, x, 0, w, bh, 0, text, 0); + } + + drw_setscheme(drw, scheme[SchemeNorm]); + free(p); + + return ret; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0, stw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + if (showsystray && m == systraytomon(m) && !systrayonleft) + stw = getsystraywidth(); + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + tw = m->ww - drawstatusbar(m, bh, stext); + } + + resizebarwin(m); + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeTagsSel : SchemeTagsNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = blw = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - stw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) { + drawbar(m); + if (m == selmon) + updatesystray(); + } +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + /* FIXME getatomprop should return the number of items and a pointer to + * the stored data instead of this workaround */ + Atom req = XA_ATOM; + if (prop == xatom[XembedInfo]) + req = xatom[XembedInfo]; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + if (da == xatom[XembedInfo] && dl == 2) + atom = ((Atom *)p)[1]; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +unsigned int +getsystraywidth() +{ + unsigned int w = 0; + Client *i; + if(showsystray) + for(i = systray->icons; i; w += i->w + systrayspacing, i = i->next) ; + return w ? w + systrayspacing : 1; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) + strncpy(text, (char *)name.value, size - 1); + else { + if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + + if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + } + + if (c->x + WIDTH(c) > c->mon->mx + c->mon->mw) + c->x = c->mon->mx + c->mon->mw - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->my + c->mon->mh) + c->y = c->mon->my + c->mon->mh - HEIGHT(c); + c->x = MAX(c->x, c->mon->mx); + /* only fix client y-offset, if the client center might cover the bar */ + c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) + && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); + c->bw = borderpx; + + wc.border_width = c->bw; + XConfigureWindow(dpy, w, CWBorderWidth, &wc); + XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel); + configure(c); /* propagates border_width, if size doesn't change */ + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + attachaside(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + Client *i; + if ((i = wintosystrayicon(ev->window))) { + sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION); + resizebarwin(selmon); + updatesystray(); + } + + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttagged(Client *c) { + Client *walked = c->mon->clients; + for(; + walked && (walked->isfloating || !ISVISIBLEONTAG(walked, c->tags)); + walked = walked->next + ); + return walked; +} + +void +moveresize(const Arg *arg) { + /* only floating windows can be moved */ + Client *c; + c = selmon->sel; + int x, y, w, h, nx, ny, nw, nh, ox, oy, ow, oh; + char xAbs, yAbs, wAbs, hAbs; + int msx, msy, dx, dy, nmx, nmy; + unsigned int dui; + Window dummy; + + if (!c || !arg) + return; + if (selmon->lt[selmon->sellt]->arrange && !c->isfloating) + return; + if (sscanf((char *)arg->v, "%d%c %d%c %d%c %d%c", &x, &xAbs, &y, &yAbs, &w, &wAbs, &h, &hAbs) != 8) + return; + + /* compute new window position; prevent window from be positioned outside the current monitor */ + nw = c->w + w; + if (wAbs == 'W') + nw = w < selmon->mw - 2 * c->bw ? w : selmon->mw - 2 * c->bw; + + nh = c->h + h; + if (hAbs == 'H') + nh = h < selmon->mh - 2 * c->bw ? h : selmon->mh - 2 * c->bw; + + nx = c->x + x; + if (xAbs == 'X') { + if (x < selmon->mx) + nx = selmon->mx; + else if (x > selmon->mx + selmon->mw) + nx = selmon->mx + selmon->mw - nw - 2 * c->bw; + else + nx = x; + } + + ny = c->y + y; + if (yAbs == 'Y') { + if (y < selmon->my) + ny = selmon->my; + else if (y > selmon->my + selmon->mh) + ny = selmon->my + selmon->mh - nh - 2 * c->bw; + else + ny = y; + } + + ox = c->x; + oy = c->y; + ow = c->w; + oh = c->h; + + XRaiseWindow(dpy, c->win); + Bool xqp = XQueryPointer(dpy, root, &dummy, &dummy, &msx, &msy, &dx, &dy, &dui); + resize(c, nx, ny, nw, nh, True); + + /* move cursor along with the window to avoid problems caused by the sloppy focus */ + if (xqp && ox <= msx && (ox + ow) >= msx && oy <= msy && (oy + oh) >= msy) + { + nmx = c->x - ox + c->w - ow; + nmy = c->y - oy + c->h - oh; + /* make sure the cursor stays inside the window */ + if ((msx + nmx) > c->x && (msy + nmy) > c->y) + XWarpPointer(dpy, None, None, 0, 0, 0, 0, nmx, nmy); + } +} + +void +moveresizeedge(const Arg *arg) { + /* move or resize floating window to edge of screen */ + Client *c; + c = selmon->sel; + char e; + int nx, ny, nw, nh, ox, oy, ow, oh, bp; + int msx, msy, dx, dy, nmx, nmy; + int starty; + unsigned int dui; + Window dummy; + + nx = c->x; + ny = c->y; + nw = c->w; + nh = c->h; + + starty = selmon->showbar && topbar ? bh : 0; + bp = selmon->showbar && !topbar ? bh : 0; + + if (!c || !arg) + return; + if (selmon->lt[selmon->sellt]->arrange && !c->isfloating) + return; + if(sscanf((char *)arg->v, "%c", &e) != 1) + return; + + if(e == 't') + ny = starty; + + if(e == 'b') + ny = c->h > selmon->mh - 2 * c->bw ? c->h - bp : selmon->mh - c->h - 2 * c->bw - bp; + + if(e == 'l') + nx = selmon->mx; + + if(e == 'r') + nx = c->w > selmon->mw - 2 * c->bw ? selmon->mx + c->w : selmon->mx + selmon->mw - c->w - 2 * c->bw; + + if(e == 'T') { + /* if you click to resize again, it will return to old size/position */ + if(c->h + starty == c->oldh + c->oldy) { + nh = c->oldh; + ny = c->oldy; + } else { + nh = c->h + c->y - starty; + ny = starty; + } + } + + if(e == 'B') + nh = c->h + c->y + 2 * c->bw + bp == selmon->mh ? c->oldh : selmon->mh - c->y - 2 * c->bw - bp; + + if(e == 'L') { + if(selmon->mx + c->w == c->oldw + c->oldx) { + nw = c->oldw; + nx = c->oldx; + } else { + nw = c->w + c->x - selmon->mx; + nx = selmon->mx; + } + } + + if(e == 'R') + nw = c->w + c->x + 2 * c->bw == selmon->mx + selmon->mw ? c->oldw : selmon->mx + selmon->mw - c->x - 2 * c->bw; + + ox = c->x; + oy = c->y; + ow = c->w; + oh = c->h; + + XRaiseWindow(dpy, c->win); + Bool xqp = XQueryPointer(dpy, root, &dummy, &dummy, &msx, &msy, &dx, &dy, &dui); + resize(c, nx, ny, nw, nh, True); + + /* move cursor along with the window to avoid problems caused by the sloppy focus */ + if (xqp && ox <= msx && (ox + ow) >= msx && oy <= msy && (oy + oh) >= msy) { + nmx = c->x - ox + c->w - ow; + nmy = c->y - oy + c->h - oh; + /* make sure the cursor stays inside the window */ + if ((msx + nmx) > c->x && (msy + nmy) > c->y) + XWarpPointer(dpy, None, None, 0, 0, 0, 0, nmx, nmy); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((c = wintosystrayicon(ev->window))) { + if (ev->atom == XA_WM_NORMAL_HINTS) { + updatesizehints(c); + updatesystrayicongeom(c, c->w, c->h); + } + else + updatesystrayiconstate(c, ev); + resizebarwin(selmon); + updatesystray(); + } + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + updatesizehints(c); + break; + case XA_WM_HINTS: + updatewmhints(c); + drawbars(); + break; + } + if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +quit(const Arg *arg) +{ + running = 0; +} + +Monitor * +recttomon(int x, int y, int w, int h) +{ + Monitor *m, *r = selmon; + int a, area = 0; + + for (m = mons; m; m = m->next) + if ((a = INTERSECT(x, y, w, h, m)) > area) { + area = a; + r = m; + } + return r; +} + +void +removesystrayicon(Client *i) +{ + Client **ii; + + if (!showsystray || !i) + return; + for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next); + if (ii) + *ii = i->next; + free(i); +} + +void +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizebarwin(Monitor *m) { + unsigned int w = m->ww; + if (showsystray && m == systraytomon(m) && !systrayonleft) + w -= getsystraywidth(); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +resizemouse(const Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(NULL); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +void +resizerequest(XEvent *e) +{ + XResizeRequestEvent *ev = &e->xresizerequest; + Client *i; + + if ((i = wintosystrayicon(ev->window))) { + updatesystrayicongeom(i, ev->width, ev->height); + resizebarwin(selmon); + updatesystray(); + } +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +scan(void) +{ + unsigned int i, num; + Window d1, d2, *wins = NULL; + XWindowAttributes wa; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) + manage(wins[i], &wa); + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) + manage(wins[i], &wa); + } + if (wins) + XFree(wins); + } +} + +void +sendmon(Client *c, Monitor *m) +{ + if (c->mon == m) + return; + unfocus(c, 1); + detach(c); + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attachaside(c); + attachstack(c); + focus(NULL); + arrange(NULL); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *)data, 2); +} + +int +sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4) +{ + int n; + Atom *protocols, mt; + int exists = 0; + XEvent ev; + + if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) { + mt = wmatom[WMProtocols]; + if (XGetWMProtocols(dpy, w, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + } + else { + exists = True; + mt = proto; + } + + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = w; + ev.xclient.message_type = mt; + ev.xclient.format = 32; + ev.xclient.data.l[0] = d0; + ev.xclient.data.l[1] = d1; + ev.xclient.data.l[2] = d2; + ev.xclient.data.l[3] = d3; + ev.xclient.data.l[4] = d4; + XSendEvent(dpy, w, False, mask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); + sh = DisplayHeight(dpy, screen); + root = RootWindow(dpy, screen); + drw = drw_create(dpy, screen, root, sw, sh); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + bh = drw->fonts->h + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False); + netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); + netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False); + netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + xatom[Manager] = XInternAtom(dpy, "MANAGER", False); + xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False); + xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ + scheme = ecalloc(LENGTH(colors) + 1, sizeof(Clr *)); + scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], 3); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init system tray */ + updatesystray(); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) +{ + if (arg->v == dmenucmd) + dmenumon[0] = '0' + selmon->num; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]); + perror(" failed"); + exit(EXIT_SUCCESS); + } +} + +void +tag(const Arg *arg) +{ + if (selmon->sel && arg->ui & TAGMASK) { + selmon->sel->tags = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i=0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next),i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) < m->wh) + my += HEIGHT(c) + m->gappx; + } else { + h = (m->wh - ty) / (n - i) - m->gappx; + resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); + if (ty + HEIGHT(c) < m->wh) + ty += HEIGHT(c) + m->gappx; + } +} + +void +togglebar(const Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + resizebarwin(selmon); + if (showsystray) { + XWindowChanges wc; + if (!selmon->showbar) + wc.y = -bh; + else if (selmon->showbar) { + wc.y = 0; + if (!selmon->topbar) + wc.y = selmon->mh - bh; + } + XConfigureWindow(dpy, systray->win, CWY, &wc); + } + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +togglesticky(const Arg *arg) +{ + if (!selmon->sel) + return; + selmon->sel->issticky = !selmon->sel->issticky; + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + unsigned int newtags; + + if (!selmon->sel) + return; + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + focus(NULL); + arrange(selmon); + } +} + +void +toggleview(const Arg *arg) +{ + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + setclientstate(c, WithdrawnState); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } + free(c); + focus(NULL); + updateclientlist(); + arrange(m); +} + +void +unmapnotify(XEvent *e) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if ((c = wintoclient(ev->window))) { + if (ev->send_event) + setclientstate(c, WithdrawnState); + else + unmanage(c, 0); + } + else if ((c = wintosystrayicon(ev->window))) { + /* KLUDGE! sometimes icons occasionally unmap their windows, but do + * _not_ destroy them. We map those windows back */ + XMapRaised(dpy, c->win); + updatesystray(); + } +} + +void +updatebars(void) +{ + unsigned int w; + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + w = m->ww; + if (showsystray && m == systraytomon(m)) + w -= getsystraywidth(); + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, w, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + if (showsystray && m == systraytomon(m)) + XMapRaised(dpy, systray->win); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + if (n <= nn) { /* new monitors available */ + for (i = 0; i < (nn - n); i++) { + for (m = mons; m && m->next; m = m->next); + if (m) + m->next = createmon(); + else + mons = createmon(); + } + for (i = 0, m = mons; i < nn && m; m = m->next, i++) + if (i >= n + || unique[i].x_org != m->mx || unique[i].y_org != m->my + || unique[i].width != m->mw || unique[i].height != m->mh) + { + dirty = 1; + m->num = i; + m->mx = m->wx = unique[i].x_org; + m->my = m->wy = unique[i].y_org; + m->mw = m->ww = unique[i].width; + m->mh = m->wh = unique[i].height; + updatebarpos(m); + } + } else { /* less monitors available nn < n */ + for (i = nn; i < n; i++) { + for (m = mons; m && m->next; m = m->next); + while ((c = m->clients)) { + dirty = 1; + m->clients = c->next; + detachstack(c); + c->mon = mons; + attachaside(c); + attachstack(c); + } + if (m == selmon) + selmon = mons; + cleanupmon(m); + } + } + free(unique); + } else +#endif /* XINERAMA */ + { /* default monitor setup */ + if (!mons) + mons = createmon(); + if (mons->mw != sw || mons->mh != sh) { + dirty = 1; + mons->mw = mons->ww = sw; + mons->mh = mons->wh = sh; + updatebarpos(mons); + } + } + if (dirty) { + selmon = mons; + selmon = wintomon(root); + } + return dirty; +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) + for (j = 0; j < modmap->max_keypermod; j++) + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +void +updatesizehints(Client *c) +{ + long msize; + XSizeHints size; + + if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if (size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } else if (size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else + c->basew = c->baseh = 0; + if (size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } else + c->incw = c->inch = 0; + if (size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else + c->maxw = c->maxh = 0; + if (size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } else if (size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else + c->minw = c->minh = 0; + if (size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else + c->maxa = c->mina = 0.0; + c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); +} + +void +updatestatus(void) +{ + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); + updatesystray(); +} + + +void +updatesystrayicongeom(Client *i, int w, int h) +{ + if (i) { + i->h = bh; + if (w == h) + i->w = bh; + else if (h == bh) + i->w = w; + else + i->w = (int) ((float)bh * ((float)w / (float)h)); + applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False); + /* force icons into the systray dimensions if they don't want to */ + if (i->h > bh) { + if (i->w == i->h) + i->w = bh; + else + i->w = (int) ((float)bh * ((float)i->w / (float)i->h)); + i->h = bh; + } + } +} + +void +updatesystrayiconstate(Client *i, XPropertyEvent *ev) +{ + long flags; + int code = 0; + + if (!showsystray || !i || ev->atom != xatom[XembedInfo] || + !(flags = getatomprop(i, xatom[XembedInfo]))) + return; + + if (flags & XEMBED_MAPPED && !i->tags) { + i->tags = 1; + code = XEMBED_WINDOW_ACTIVATE; + XMapRaised(dpy, i->win); + setclientstate(i, NormalState); + } + else if (!(flags & XEMBED_MAPPED) && i->tags) { + i->tags = 0; + code = XEMBED_WINDOW_DEACTIVATE; + XUnmapWindow(dpy, i->win); + setclientstate(i, WithdrawnState); + } + else + return; + sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0, + systray->win, XEMBED_EMBEDDED_VERSION); +} + +void +updatesystray(void) +{ + XSetWindowAttributes wa; + XWindowChanges wc; + Client *i; + Monitor *m = systraytomon(NULL); + unsigned int x = m->mx + m->mw; + unsigned int sw = TEXTW(stext) - lrpad + systrayspacing; + unsigned int w = 1; + + if (!showsystray) + return; + if (systrayonleft) + x -= sw + lrpad / 2; + if (!systray) { + /* init systray */ + if (!(systray = (Systray *)calloc(1, sizeof(Systray)))) + die("fatal: could not malloc() %u bytes\n", sizeof(Systray)); + systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh, 0, 0, scheme[SchemeSel][ColBg].pixel); + wa.event_mask = ButtonPressMask | ExposureMask; + wa.override_redirect = True; + wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + XSelectInput(dpy, systray->win, SubstructureNotifyMask); + XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation], XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&netatom[NetSystemTrayOrientationHorz], 1); + XChangeWindowAttributes(dpy, systray->win, CWEventMask|CWOverrideRedirect|CWBackPixel, &wa); + XMapRaised(dpy, systray->win); + XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime); + if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) { + sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime, netatom[NetSystemTray], systray->win, 0, 0); + XSync(dpy, False); + } + else { + fprintf(stderr, "dwm: unable to obtain system tray.\n"); + free(systray); + systray = NULL; + return; + } + } + for (w = 0, i = systray->icons; i; i = i->next) { + /* make sure the background color stays the same */ + wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa); + XMapRaised(dpy, i->win); + w += systrayspacing; + i->x = w; + XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h); + w += i->w; + if (i->mon != m) + i->mon = m; + } + w = w ? w + systrayspacing : 1; + x -= w; + XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh); + wc.x = x; wc.y = m->by; wc.width = w; wc.height = bh; + wc.stack_mode = Above; wc.sibling = m->barwin; + XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSibling|CWStackMode, &wc); + XMapWindow(dpy, systray->win); + XMapSubwindows(dpy, systray->win); + /* redraw background */ + XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel); + XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh); + XSync(dpy, False); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Client * +wintosystrayicon(Window w) { + Client *i = NULL; + + if (!showsystray || !w) + return i; + for (i = systray->icons; i && i->win != w; i = i->next) ; + return i; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +Monitor * +systraytomon(Monitor *m) { + Monitor *t; + int i, n; + if(!systraypinning) { + if(!m) + return selmon; + return m == selmon ? m : NULL; + } + for(n = 1, t = mons; t && t->next; n++, t = t->next) ; + for(i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next) ; + if(systraypinningfailfirst && n < systraypinning) + return mons; + return t; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange + || (selmon->sel && selmon->sel->isfloating)) + return; + if (c == nexttiled(selmon->clients)) + if (!c || !(c = nexttiled(c->next))) + return; + pop(c); +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + die("dwm-"VERSION); + else if (argc != 1) + die("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); + checkotherwm(); + setup(); +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec", NULL) == -1) + die("pledge"); +#endif /* __OpenBSD__ */ + scan(); + run(); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/portatil/dwm/dwm.c.bak b/portatil/dwm/dwm.c.bak @@ -0,0 +1,2168 @@ +/* See LICENSE file for copyright and license details. + * + * dynamic window manager is designed like any other X client as well. It is + * driven through handling X events. In contrast to other X clients, a window + * manager selects for SubstructureRedirectMask on the root window, to receive + * events about window (dis-)appearance. Only one X connection at a time is + * allowed to select for this event mask. + * + * The event handlers of dwm are organized in an array which is accessed + * whenever a new event has been fetched. This allows event dispatching + * in O(1) time. + * + * Each child of the root window is called a client, except windows which have + * set the override_redirect flag. Clients are organized in a linked client + * list on each monitor, the focus history is remembered through a stack list + * on each monitor. Each client contains a bit array to indicate the tags of a + * client. + * + * Keys and tagging rules are organized as arrays and defined in config.h. + * + * To understand everything else, start reading main(). + */ +#include <errno.h> +#include <locale.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif /* XINERAMA */ +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int monitor; +} Rule; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void movemouse(const Arg *arg); +static Client *nexttiled(Client *c); +static void pop(Client *); +static void propertynotify(XEvent *e); +static void quit(const Arg *arg); +static Monitor *recttomon(int x, int y, int w, int h); +static void resize(Client *c, int x, int y, int w, int h, int interact); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void restack(Monitor *m); +static void run(void); +static void scan(void); +static int sendevent(Client *c, Atom proto); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void setgaps(const Arg *arg); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void seturgent(Client *c, int urg); +static void showhide(Client *c); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static char stext[256]; +static int screen; +static int sw, sh; /* X display screen geometry width, height */ +static int bh, blw = 0; /* bar geometry */ +static int lrpad; /* sum of left and right padding for text */ +static int (*xerrorxlib)(Display *, XErrorEvent *); +static unsigned int numlockmask = 0; +static void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureRequest] = configurerequest, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MappingNotify] = mappingnotify, + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast]; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* compile-time check if all tags fit into an unsigned int bit array. */ +struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +applyrules(Client *c) +{ + const char *class, *instance; + unsigned int i; + const Rule *r; + Monitor *m; + XClassHint ch = { NULL, NULL }; + + /* rule matching */ + c->isfloating = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < LENGTH(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + /* see last two sentences in ICCCM 4.1.2.3 */ + baseismin = c->basew == c->minw && c->baseh == c->minh; + if (!baseismin) { /* temporarily remove base dimensions */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for aspect limits */ + if (c->mina > 0 && c->maxa > 0) { + if (c->maxa < (float)*w / *h) + *w = *h * c->maxa + 0.5; + else if (c->mina < (float)*h / *w) + *h = *w * c->mina + 0.5; + } + if (baseismin) { /* increment calculation requires this */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for increment value */ + if (c->incw) + *w -= *w % c->incw; + if (c->inch) + *h -= *h % c->inch; + /* restore base dimensions */ + *w = MAX(*w + c->basew, c->minw); + *h = MAX(*h + c->baseh, c->minh); + if (c->maxw) + *w = MIN(*w, c->maxw); + if (c->maxh) + *h = MIN(*h, c->maxh); + } + return *x != c->x || *y != c->y || *w != c->w || *h != c->h; +} + +void +arrange(Monitor *m) +{ + if (m) + showhide(m->stack); + else for (m = mons; m; m = m->next) + showhide(m->stack); + if (m) { + arrangemon(m); + restack(m); + } else for (m = mons; m; m = m->next) + arrangemon(m); +} + +void +arrangemon(Monitor *m) +{ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); +} + +void +attach(Client *c) +{ + c->next = c->mon->clients; + c->mon->clients = c; +} + +void +attachstack(Client *c) +{ + c->snext = c->mon->stack; + c->mon->stack = c; +} + +void +buttonpress(XEvent *e) +{ + unsigned int i, x, click; + Arg arg = {0}; + Client *c; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + if (ev->window == selmon->barwin) { + i = x = 0; + do + x += TEXTW(tags[i]); + while (ev->x >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; + else if (ev->x > selmon->ww - (int)TEXTW(stext)) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors); i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); +} + +void +cleanupmon(Monitor *mon) +{ + Monitor *m; + + if (mon == mons) + mons = mons->next; + else { + for (m = mons; m && m->next != mon; m = m->next); + m->next = mon->next; + } + XUnmapWindow(dpy, mon->barwin); + XDestroyWindow(dpy, mon->barwin); + free(mon); +} + +void +clientmessage(XEvent *e) +{ + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drw_setscheme(drw, scheme[SchemeNorm]); + tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ + drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + } + + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = blw = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) + drawbar(m); +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) + strncpy(text, (char *)name.value, size - 1); + else { + if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + if (!sendevent(selmon->sel, wmatom[WMDelete])) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + } + + if (c->x + WIDTH(c) > c->mon->mx + c->mon->mw) + c->x = c->mon->mx + c->mon->mw - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->my + c->mon->mh) + c->y = c->mon->my + c->mon->mh - HEIGHT(c); + c->x = MAX(c->x, c->mon->mx); + /* only fix client y-offset, if the client center might cover the bar */ + c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) + && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); + c->bw = borderpx; + + wc.border_width = c->bw; + XConfigureWindow(dpy, w, CWBorderWidth, &wc); + XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel); + configure(c); /* propagates border_width, if size doesn't change */ + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + attach(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + updatesizehints(c); + break; + case XA_WM_HINTS: + updatewmhints(c); + drawbars(); + break; + } + if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +quit(const Arg *arg) +{ + running = 0; +} + +Monitor * +recttomon(int x, int y, int w, int h) +{ + Monitor *m, *r = selmon; + int a, area = 0; + + for (m = mons; m; m = m->next) + if ((a = INTERSECT(x, y, w, h, m)) > area) { + area = a; + r = m; + } + return r; +} + +void +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +resizemouse(const Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(NULL); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +scan(void) +{ + unsigned int i, num; + Window d1, d2, *wins = NULL; + XWindowAttributes wa; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) + manage(wins[i], &wa); + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) + manage(wins[i], &wa); + } + if (wins) + XFree(wins); + } +} + +void +sendmon(Client *c, Monitor *m) +{ + if (c->mon == m) + return; + unfocus(c, 1); + detach(c); + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attach(c); + attachstack(c); + focus(NULL); + arrange(NULL); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *)data, 2); +} + +int +sendevent(Client *c, Atom proto) +{ + int n; + Atom *protocols; + int exists = 0; + XEvent ev; + + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c, wmatom[WMTakeFocus]); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); + sh = DisplayHeight(dpy, screen); + root = RootWindow(dpy, screen); + drw = drw_create(dpy, screen, root, sw, sh); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + bh = drw->fonts->h + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ + scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) +{ + if (arg->v == dmenucmd) + dmenumon[0] = '0' + selmon->num; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]); + perror(" failed"); + exit(EXIT_SUCCESS); + } +} + +void +tag(const Arg *arg) +{ + if (selmon->sel && arg->ui & TAGMASK) { + selmon->sel->tags = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww; + for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i); + resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0); + if (my + HEIGHT(c) < m->wh) + my += HEIGHT(c); + } else { + h = (m->wh - ty) / (n - i); + resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0); + if (ty + HEIGHT(c) < m->wh) + ty += HEIGHT(c); + } +} + +void +togglebar(const Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + unsigned int newtags; + + if (!selmon->sel) + return; + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + focus(NULL); + arrange(selmon); + } +} + +void +toggleview(const Arg *arg) +{ + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + setclientstate(c, WithdrawnState); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } + free(c); + focus(NULL); + updateclientlist(); + arrange(m); +} + +void +unmapnotify(XEvent *e) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if ((c = wintoclient(ev->window))) { + if (ev->send_event) + setclientstate(c, WithdrawnState); + else + unmanage(c, 0); + } +} + +void +updatebars(void) +{ + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + if (n <= nn) { /* new monitors available */ + for (i = 0; i < (nn - n); i++) { + for (m = mons; m && m->next; m = m->next); + if (m) + m->next = createmon(); + else + mons = createmon(); + } + for (i = 0, m = mons; i < nn && m; m = m->next, i++) + if (i >= n + || unique[i].x_org != m->mx || unique[i].y_org != m->my + || unique[i].width != m->mw || unique[i].height != m->mh) + { + dirty = 1; + m->num = i; + m->mx = m->wx = unique[i].x_org; + m->my = m->wy = unique[i].y_org; + m->mw = m->ww = unique[i].width; + m->mh = m->wh = unique[i].height; + updatebarpos(m); + } + } else { /* less monitors available nn < n */ + for (i = nn; i < n; i++) { + for (m = mons; m && m->next; m = m->next); + while ((c = m->clients)) { + dirty = 1; + m->clients = c->next; + detachstack(c); + c->mon = mons; + attach(c); + attachstack(c); + } + if (m == selmon) + selmon = mons; + cleanupmon(m); + } + } + free(unique); + } else +#endif /* XINERAMA */ + { /* default monitor setup */ + if (!mons) + mons = createmon(); + if (mons->mw != sw || mons->mh != sh) { + dirty = 1; + mons->mw = mons->ww = sw; + mons->mh = mons->wh = sh; + updatebarpos(mons); + } + } + if (dirty) { + selmon = mons; + selmon = wintomon(root); + } + return dirty; +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) + for (j = 0; j < modmap->max_keypermod; j++) + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +void +updatesizehints(Client *c) +{ + long msize; + XSizeHints size; + + if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if (size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } else if (size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else + c->basew = c->baseh = 0; + if (size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } else + c->incw = c->inch = 0; + if (size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else + c->maxw = c->maxh = 0; + if (size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } else if (size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else + c->minw = c->minh = 0; + if (size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else + c->maxa = c->mina = 0.0; + c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); +} + +void +updatestatus(void) +{ + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange + || (selmon->sel && selmon->sel->isfloating)) + return; + if (c == nexttiled(selmon->clients)) + if (!c || !(c = nexttiled(c->next))) + return; + pop(c); +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + die("dwm-"VERSION); + else if (argc != 1) + die("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); + checkotherwm(); + setup(); +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec", NULL) == -1) + die("pledge"); +#endif /* __OpenBSD__ */ + scan(); + run(); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/portatil/dwm/dwm.c.orig b/portatil/dwm/dwm.c.orig @@ -0,0 +1,2776 @@ +/* See LICENSE file for copyright and license details. + * + * dynamic window manager is designed like any other X client as well. It is + * driven through handling X events. In contrast to other X clients, a window + * manager selects for SubstructureRedirectMask on the root window, to receive + * events about window (dis-)appearance. Only one X connection at a time is + * allowed to select for this event mask. + * + * The event handlers of dwm are organized in an array which is accessed + * whenever a new event has been fetched. This allows event dispatching + * in O(1) time. + * + * Each child of the root window is called a client, except windows which have + * set the override_redirect flag. Clients are organized in a linked client + * list on each monitor, the focus history is remembered through a stack list + * on each monitor. Each client contains a bit array to indicate the tags of a + * client. + * + * Keys and tagging rules are organized as arrays and defined in config.h. + * + * To understand everything else, start reading main(). + */ +#include <errno.h> +#include <locale.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif /* XINERAMA */ +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +//#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]) || C->issticky) +#define ISVISIBLEONTAG(C,T) ((C->tags & T)) +#define ISVISIBLE(C) ISVISIBLEONTAG(C, C->mon->tagset[C->mon->seltags] || C->issticky) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#define SYSTEM_TRAY_REQUEST_DOCK 0 +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MAPPED (1 << 0) +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define VERSION_MAJOR 0 +#define VERSION_MINOR 0 +#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel, SchemeStatus, SchemeTagsSel, SchemeTagsNorm, SchemeInfoSel, SchemeInfoNorm }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, issticky; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int monitor; +} Rule; + +typedef struct Systray Systray; +struct Systray { + Window win; + Client *icons; +}; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachaside(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static int drawstatusbar(Monitor *m, int bh, char* text); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static unsigned int getsystraywidth(); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void movemouse(const Arg *arg); +static Client *nexttagged(Client *c); +static Client *nexttiled(Client *c); +static void pop(Client *); +static void propertynotify(XEvent *e); +static void quit(const Arg *arg); +static Monitor *recttomon(int x, int y, int w, int h); +static void removesystrayicon(Client *i); +static void resize(Client *c, int x, int y, int w, int h, int interact); +static void resizebarwin(Monitor *m); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void resizerequest(XEvent *e); +static void restack(Monitor *m); +static void run(void); +static void scan(void); +static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void setgaps(const Arg *arg); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void seturgent(Client *c, int urg); +static void showhide(Client *c); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static Monitor *systraytomon(Monitor *m); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglesticky(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatesystray(void); +static void updatesystrayicongeom(Client* i, int w, int h); +static void updatesystrayiconstate(Client* i, XPropertyEvent* ev); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static Client* wintosystrayicon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +/* variables */ +static Systray* systray = NULL; +static const char broken[] = "broken"; +static char stext[1024]; +static int screen; +static int sw, sh; /* X display screen geometry width, height */ +static int bh, blw = 0; /* bar geometry */ +static int lrpad; /* sum of left and right padding for text */ +static int (*xerrorxlib)(Display *, XErrorEvent *); +static unsigned int numlockmask = 0; +static void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureRequest] = configurerequest, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MappingNotify] = mappingnotify, + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, + [ResizeRequest] = resizerequest, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast]; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* compile-time check if all tags fit into an unsigned int bit array. */ +struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +applyrules(Client *c) +{ + const char *class, *instance; + unsigned int i; + const Rule *r; + Monitor *m; + XClassHint ch = { NULL, NULL }; + + /* rule matching */ + c->isfloating = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < LENGTH(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + /* see last two sentences in ICCCM 4.1.2.3 */ + baseismin = c->basew == c->minw && c->baseh == c->minh; + if (!baseismin) { /* temporarily remove base dimensions */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for aspect limits */ + if (c->mina > 0 && c->maxa > 0) { + if (c->maxa < (float)*w / *h) + *w = *h * c->maxa + 0.5; + else if (c->mina < (float)*h / *w) + *h = *w * c->mina + 0.5; + } + if (baseismin) { /* increment calculation requires this */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for increment value */ + if (c->incw) + *w -= *w % c->incw; + if (c->inch) + *h -= *h % c->inch; + /* restore base dimensions */ + *w = MAX(*w + c->basew, c->minw); + *h = MAX(*h + c->baseh, c->minh); + if (c->maxw) + *w = MIN(*w, c->maxw); + if (c->maxh) + *h = MIN(*h, c->maxh); + } + return *x != c->x || *y != c->y || *w != c->w || *h != c->h; +} + +void +arrange(Monitor *m) +{ + if (m) + showhide(m->stack); + else for (m = mons; m; m = m->next) + showhide(m->stack); + if (m) { + arrangemon(m); + restack(m); + } else for (m = mons; m; m = m->next) + arrangemon(m); +} + +void +arrangemon(Monitor *m) +{ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); +} + +void +attach(Client *c) +{ + c->next = c->mon->clients; + c->mon->clients = c; +} + +void +attachaside(Client *c) { + Client *at = nexttagged(c); + if(!at) { + attach(c); + return; + } + c->next = at->next; + at->next = c; +} + +void +attachstack(Client *c) +{ + c->snext = c->mon->stack; + c->mon->stack = c; +} + +void +buttonpress(XEvent *e) +{ + unsigned int i, x, click; + Arg arg = {0}; + Client *c; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + if (ev->window == selmon->barwin) { + i = x = 0; + do + x += TEXTW(tags[i]); + while (ev->x >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; + else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth()) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + if (showsystray) { + XUnmapWindow(dpy, systray->win); + XDestroyWindow(dpy, systray->win); + free(systray); + } + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors) + 1; i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); +} + +void +cleanupmon(Monitor *mon) +{ + Monitor *m; + + if (mon == mons) + mons = mons->next; + else { + for (m = mons; m && m->next != mon; m = m->next); + m->next = mon->next; + } + XUnmapWindow(dpy, mon->barwin); + XDestroyWindow(dpy, mon->barwin); + free(mon); +} + +void +clientmessage(XEvent *e) +{ + XWindowAttributes wa; + XSetWindowAttributes swa; + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (showsystray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) { + /* add systray icons */ + if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { + if (!(c = (Client *)calloc(1, sizeof(Client)))) + die("fatal: could not malloc() %u bytes\n", sizeof(Client)); + if (!(c->win = cme->data.l[2])) { + free(c); + return; + } + c->mon = selmon; + c->next = systray->icons; + systray->icons = c; + if (!XGetWindowAttributes(dpy, c->win, &wa)) { + /* use sane defaults */ + wa.width = bh; + wa.height = bh; + wa.border_width = 0; + } + c->x = c->oldx = c->y = c->oldy = 0; + c->w = c->oldw = wa.width; + c->h = c->oldh = wa.height; + c->oldbw = wa.border_width; + c->bw = 0; + c->isfloating = True; + /* reuse tags field as mapped status */ + c->tags = 1; + updatesizehints(c); + updatesystrayicongeom(c, wa.width, wa.height); + XAddToSaveSet(dpy, c->win); + XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask); + XReparentWindow(dpy, c->win, systray->win, 0, 0); + /* use parents background color */ + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa); + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + /* FIXME not sure if I have to send these events, too */ + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_FOCUS_IN, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_MODALITY_ON, 0 , systray->win, XEMBED_EMBEDDED_VERSION); + XSync(dpy, False); + resizebarwin(selmon); + updatesystray(); + setclientstate(c, NormalState); + } + return; + } + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + resizebarwin(m); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); + else if ((c = wintosystrayicon(ev->window))) { + removesystrayicon(c); + resizebarwin(selmon); + updatesystray(); + } +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +//int +//drawstatusbar(Monitor *m, int bh, char* stext) { +// int ret, i, w, x, len; +// short isCode = 0; +// char *text; +// char *p; +// +// len = strlen(stext) + 1 ; +// if (!(text = (char*) malloc(sizeof(char)*len))) +// die("malloc"); +// p = text; +// memcpy(text, stext, len); +// +// /* compute width of the status text */ +// w = 0; +// i = -1; +// while (text[++i]) { +// if (text[i] == '^') { +// if (!isCode) { +// isCode = 1; +// text[i] = '\0'; +// w += TEXTW(text) - lrpad; +// text[i] = '^'; +// if (text[++i] == 'f') +// w += atoi(text + ++i); +// } else { +// isCode = 0; +// text = text + i + 1; +// i = -1; +// } +// } +// } +// if (!isCode) +// w += TEXTW(text) - lrpad; +// else +// isCode = 0; +// text = p; +// +// w += 2; /* 1px padding on both sides */ +// ret = x = m->ww - w; +// +// drw_setscheme(drw, scheme[LENGTH(colors)]); +// drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; +// drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; +// drw_rect(drw, x, 0, w, bh, 1, 1); +// x++; +// +// /* process status text */ +// i = -1; +// while (text[++i]) { +// if (text[i] == '^' && !isCode) { +// isCode = 1; +// +// text[i] = '\0'; +// w = TEXTW(text) - lrpad; +// drw_text(drw, x, 0, w, bh, 0, text, 0); +// +// x += w; +// +// /* process code */ +// while (text[++i] != '^') { +// if (text[i] == 'c') { +// char buf[8]; +// memcpy(buf, (char*)text+i+1, 7); +// buf[7] = '\0'; +// drw_clr_create(drw, &drw->scheme[ColFg], buf); +// i += 7; +// } else if (text[i] == 'b') { +// char buf[8]; +// memcpy(buf, (char*)text+i+1, 7); +// buf[7] = '\0'; +// drw_clr_create(drw, &drw->scheme[ColBg], buf); +// i += 7; +// } else if (text[i] == 'd') { +// drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; +// drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; +// } else if (text[i] == 'r') { +// int rx = atoi(text + ++i); +// while (text[++i] != ','); +// int ry = atoi(text + ++i); +// while (text[++i] != ','); +// int rw = atoi(text + ++i); +// while (text[++i] != ','); +// int rh = atoi(text + ++i); +// +// drw_rect(drw, rx + x, ry, rw, rh, 1, 0); +// } else if (text[i] == 'f') { +// x += atoi(text + ++i); +// } +// } +// +// text = text + i + 1; +// i=-1; +// isCode = 0; +// } +// } +// +// if (!isCode) { +// w = TEXTW(text) - lrpad; +// drw_text(drw, x, 0, w, bh, 0, text, 0); +// } +// +// drw_setscheme(drw, scheme[SchemeNorm]); +// free(p); +// +// return ret; +//} +// +int +drawstatusbar(Monitor *m, int bh, char* stext) { + int ret, i, w, x, len; + short isCode = 0; + char *text; + char *p; + + len = strlen(stext) + 1 ; + if (!(text = (char*) malloc(sizeof(char)*len))) + die("malloc"); + p = text; + memcpy(text, stext, len); + + /* compute width of the status text */ + w = 0; + i = -1; + while (text[++i]) { + if (text[i] == '^') { + if (!isCode) { + isCode = 1; + text[i] = '\0'; + w += TEXTW(text) - lrpad; + text[i] = '^'; + if (text[++i] == 'f') + w += atoi(text + ++i); + } else { + isCode = 0; + text = text + i + 1; + i = -1; + } + } + } + if (!isCode) + w += TEXTW(text) - lrpad; + else + isCode = 0; + text = p; + + w += 2; /* 1px padding on both sides */ + ret = m->ww - w; + x = m->ww - w - getsystraywidth(); + + drw_setscheme(drw, scheme[LENGTH(colors)]); + drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; + drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; + drw_rect(drw, x, 0, w, bh, 1, 1); + x++; + + /* process status text */ + i = -1; + while (text[++i]) { + if (text[i] == '^' && !isCode) { + isCode = 1; + + text[i] = '\0'; + w = TEXTW(text) - lrpad; + drw_text(drw, x, 0, w, bh, 0, text, 0); + + x += w; + + /* process code */ + while (text[++i] != '^') { + if (text[i] == 'c') { + char buf[8]; + memcpy(buf, (char*)text+i+1, 7); + buf[7] = '\0'; + drw_clr_create(drw, &drw->scheme[ColFg], buf); + i += 7; + } else if (text[i] == 'b') { + char buf[8]; + memcpy(buf, (char*)text+i+1, 7); + buf[7] = '\0'; + drw_clr_create(drw, &drw->scheme[ColBg], buf); + i += 7; + } else if (text[i] == 'd') { + drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; + drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; + } else if (text[i] == 'r') { + int rx = atoi(text + ++i); + while (text[++i] != ','); + int ry = atoi(text + ++i); + while (text[++i] != ','); + int rw = atoi(text + ++i); + while (text[++i] != ','); + int rh = atoi(text + ++i); + + drw_rect(drw, rx + x, ry, rw, rh, 1, 0); + } else if (text[i] == 'f') { + x += atoi(text + ++i); + } + } + + text = text + i + 1; + i=-1; + isCode = 0; + } + } + + if (!isCode) { + w = TEXTW(text) - lrpad; + drw_text(drw, x, 0, w, bh, 0, text, 0); + } + + drw_setscheme(drw, scheme[SchemeNorm]); + free(p); + + return ret; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0, stw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + if (showsystray && m == systraytomon(m) && !systrayonleft) + stw = getsystraywidth(); + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + tw = m->ww - drawstatusbar(m, bh, stext); + } + + resizebarwin(m); + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeTagsSel : SchemeTagsNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = blw = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - stw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) { + drawbar(m); + if (m == selmon) + updatesystray(); + } +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + /* FIXME getatomprop should return the number of items and a pointer to + * the stored data instead of this workaround */ + Atom req = XA_ATOM; + if (prop == xatom[XembedInfo]) + req = xatom[XembedInfo]; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + if (da == xatom[XembedInfo] && dl == 2) + atom = ((Atom *)p)[1]; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +unsigned int +getsystraywidth() +{ + unsigned int w = 0; + Client *i; + if(showsystray) + for(i = systray->icons; i; w += i->w + systrayspacing, i = i->next) ; + return w ? w + systrayspacing : 1; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) + strncpy(text, (char *)name.value, size - 1); + else { + if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + + if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + } + + if (c->x + WIDTH(c) > c->mon->mx + c->mon->mw) + c->x = c->mon->mx + c->mon->mw - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->my + c->mon->mh) + c->y = c->mon->my + c->mon->mh - HEIGHT(c); + c->x = MAX(c->x, c->mon->mx); + /* only fix client y-offset, if the client center might cover the bar */ + c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) + && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); + c->bw = borderpx; + + wc.border_width = c->bw; + XConfigureWindow(dpy, w, CWBorderWidth, &wc); + XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel); + configure(c); /* propagates border_width, if size doesn't change */ + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + attachaside(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + Client *i; + if ((i = wintosystrayicon(ev->window))) { + sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION); + resizebarwin(selmon); + updatesystray(); + } + + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttagged(Client *c) { + Client *walked = c->mon->clients; + for(; + walked && (walked->isfloating || !ISVISIBLEONTAG(walked, c->tags)); + walked = walked->next + ); + return walked; +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((c = wintosystrayicon(ev->window))) { + if (ev->atom == XA_WM_NORMAL_HINTS) { + updatesizehints(c); + updatesystrayicongeom(c, c->w, c->h); + } + else + updatesystrayiconstate(c, ev); + resizebarwin(selmon); + updatesystray(); + } + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + updatesizehints(c); + break; + case XA_WM_HINTS: + updatewmhints(c); + drawbars(); + break; + } + if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +quit(const Arg *arg) +{ + running = 0; +} + +Monitor * +recttomon(int x, int y, int w, int h) +{ + Monitor *m, *r = selmon; + int a, area = 0; + + for (m = mons; m; m = m->next) + if ((a = INTERSECT(x, y, w, h, m)) > area) { + area = a; + r = m; + } + return r; +} + +void +removesystrayicon(Client *i) +{ + Client **ii; + + if (!showsystray || !i) + return; + for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next); + if (ii) + *ii = i->next; + free(i); +} + +void +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizebarwin(Monitor *m) { + unsigned int w = m->ww; + if (showsystray && m == systraytomon(m) && !systrayonleft) + w -= getsystraywidth(); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +resizemouse(const Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(NULL); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +void +resizerequest(XEvent *e) +{ + XResizeRequestEvent *ev = &e->xresizerequest; + Client *i; + + if ((i = wintosystrayicon(ev->window))) { + updatesystrayicongeom(i, ev->width, ev->height); + resizebarwin(selmon); + updatesystray(); + } +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +scan(void) +{ + unsigned int i, num; + Window d1, d2, *wins = NULL; + XWindowAttributes wa; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) + manage(wins[i], &wa); + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) + manage(wins[i], &wa); + } + if (wins) + XFree(wins); + } +} + +void +sendmon(Client *c, Monitor *m) +{ + if (c->mon == m) + return; + unfocus(c, 1); + detach(c); + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attachaside(c); + attachstack(c); + focus(NULL); + arrange(NULL); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *)data, 2); +} + +int +sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4) +{ + int n; + Atom *protocols, mt; + int exists = 0; + XEvent ev; + + if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) { + mt = wmatom[WMProtocols]; + if (XGetWMProtocols(dpy, w, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + } + else { + exists = True; + mt = proto; + } + + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = w; + ev.xclient.message_type = mt; + ev.xclient.format = 32; + ev.xclient.data.l[0] = d0; + ev.xclient.data.l[1] = d1; + ev.xclient.data.l[2] = d2; + ev.xclient.data.l[3] = d3; + ev.xclient.data.l[4] = d4; + XSendEvent(dpy, w, False, mask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); + sh = DisplayHeight(dpy, screen); + root = RootWindow(dpy, screen); + drw = drw_create(dpy, screen, root, sw, sh); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + bh = drw->fonts->h + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False); + netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); + netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False); + netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + xatom[Manager] = XInternAtom(dpy, "MANAGER", False); + xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False); + xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ + scheme = ecalloc(LENGTH(colors) + 1, sizeof(Clr *)); + scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], 3); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init system tray */ + updatesystray(); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) +{ + if (arg->v == dmenucmd) + dmenumon[0] = '0' + selmon->num; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]); + perror(" failed"); + exit(EXIT_SUCCESS); + } +} + +void +tag(const Arg *arg) +{ + if (selmon->sel && arg->ui & TAGMASK) { + selmon->sel->tags = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i=0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next),i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) < m->wh) + my += HEIGHT(c) + m->gappx; + } else { + h = (m->wh - ty) / (n - i) - m->gappx; + resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); + if (ty + HEIGHT(c) < m->wh) + ty += HEIGHT(c) + m->gappx; + } +} + +void +togglebar(const Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + resizebarwin(selmon); + if (showsystray) { + XWindowChanges wc; + if (!selmon->showbar) + wc.y = -bh; + else if (selmon->showbar) { + wc.y = 0; + if (!selmon->topbar) + wc.y = selmon->mh - bh; + } + XConfigureWindow(dpy, systray->win, CWY, &wc); + } + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +togglesticky(const Arg *arg) +{ + if (!selmon->sel) + return; + selmon->sel->issticky = !selmon->sel->issticky; + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + unsigned int newtags; + + if (!selmon->sel) + return; + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + focus(NULL); + arrange(selmon); + } +} + +void +toggleview(const Arg *arg) +{ + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + setclientstate(c, WithdrawnState); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } + free(c); + focus(NULL); + updateclientlist(); + arrange(m); +} + +void +unmapnotify(XEvent *e) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if ((c = wintoclient(ev->window))) { + if (ev->send_event) + setclientstate(c, WithdrawnState); + else + unmanage(c, 0); + } + else if ((c = wintosystrayicon(ev->window))) { + /* KLUDGE! sometimes icons occasionally unmap their windows, but do + * _not_ destroy them. We map those windows back */ + XMapRaised(dpy, c->win); + updatesystray(); + } +} + +void +updatebars(void) +{ + unsigned int w; + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + w = m->ww; + if (showsystray && m == systraytomon(m)) + w -= getsystraywidth(); + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, w, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + if (showsystray && m == systraytomon(m)) + XMapRaised(dpy, systray->win); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + if (n <= nn) { /* new monitors available */ + for (i = 0; i < (nn - n); i++) { + for (m = mons; m && m->next; m = m->next); + if (m) + m->next = createmon(); + else + mons = createmon(); + } + for (i = 0, m = mons; i < nn && m; m = m->next, i++) + if (i >= n + || unique[i].x_org != m->mx || unique[i].y_org != m->my + || unique[i].width != m->mw || unique[i].height != m->mh) + { + dirty = 1; + m->num = i; + m->mx = m->wx = unique[i].x_org; + m->my = m->wy = unique[i].y_org; + m->mw = m->ww = unique[i].width; + m->mh = m->wh = unique[i].height; + updatebarpos(m); + } + } else { /* less monitors available nn < n */ + for (i = nn; i < n; i++) { + for (m = mons; m && m->next; m = m->next); + while ((c = m->clients)) { + dirty = 1; + m->clients = c->next; + detachstack(c); + c->mon = mons; + attachaside(c); + attachstack(c); + } + if (m == selmon) + selmon = mons; + cleanupmon(m); + } + } + free(unique); + } else +#endif /* XINERAMA */ + { /* default monitor setup */ + if (!mons) + mons = createmon(); + if (mons->mw != sw || mons->mh != sh) { + dirty = 1; + mons->mw = mons->ww = sw; + mons->mh = mons->wh = sh; + updatebarpos(mons); + } + } + if (dirty) { + selmon = mons; + selmon = wintomon(root); + } + return dirty; +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) + for (j = 0; j < modmap->max_keypermod; j++) + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +void +updatesizehints(Client *c) +{ + long msize; + XSizeHints size; + + if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if (size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } else if (size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else + c->basew = c->baseh = 0; + if (size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } else + c->incw = c->inch = 0; + if (size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else + c->maxw = c->maxh = 0; + if (size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } else if (size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else + c->minw = c->minh = 0; + if (size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else + c->maxa = c->mina = 0.0; + c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); +} + +void +updatestatus(void) +{ + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); + updatesystray(); +} + + +void +updatesystrayicongeom(Client *i, int w, int h) +{ + if (i) { + i->h = bh; + if (w == h) + i->w = bh; + else if (h == bh) + i->w = w; + else + i->w = (int) ((float)bh * ((float)w / (float)h)); + applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False); + /* force icons into the systray dimensions if they don't want to */ + if (i->h > bh) { + if (i->w == i->h) + i->w = bh; + else + i->w = (int) ((float)bh * ((float)i->w / (float)i->h)); + i->h = bh; + } + } +} + +void +updatesystrayiconstate(Client *i, XPropertyEvent *ev) +{ + long flags; + int code = 0; + + if (!showsystray || !i || ev->atom != xatom[XembedInfo] || + !(flags = getatomprop(i, xatom[XembedInfo]))) + return; + + if (flags & XEMBED_MAPPED && !i->tags) { + i->tags = 1; + code = XEMBED_WINDOW_ACTIVATE; + XMapRaised(dpy, i->win); + setclientstate(i, NormalState); + } + else if (!(flags & XEMBED_MAPPED) && i->tags) { + i->tags = 0; + code = XEMBED_WINDOW_DEACTIVATE; + XUnmapWindow(dpy, i->win); + setclientstate(i, WithdrawnState); + } + else + return; + sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0, + systray->win, XEMBED_EMBEDDED_VERSION); +} + +void +updatesystray(void) +{ + XSetWindowAttributes wa; + XWindowChanges wc; + Client *i; + Monitor *m = systraytomon(NULL); + unsigned int x = m->mx + m->mw; + unsigned int sw = TEXTW(stext) - lrpad + systrayspacing; + unsigned int w = 1; + + if (!showsystray) + return; + if (systrayonleft) + x -= sw + lrpad / 2; + if (!systray) { + /* init systray */ + if (!(systray = (Systray *)calloc(1, sizeof(Systray)))) + die("fatal: could not malloc() %u bytes\n", sizeof(Systray)); + systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh, 0, 0, scheme[SchemeSel][ColBg].pixel); + wa.event_mask = ButtonPressMask | ExposureMask; + wa.override_redirect = True; + wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + XSelectInput(dpy, systray->win, SubstructureNotifyMask); + XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation], XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&netatom[NetSystemTrayOrientationHorz], 1); + XChangeWindowAttributes(dpy, systray->win, CWEventMask|CWOverrideRedirect|CWBackPixel, &wa); + XMapRaised(dpy, systray->win); + XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime); + if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) { + sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime, netatom[NetSystemTray], systray->win, 0, 0); + XSync(dpy, False); + } + else { + fprintf(stderr, "dwm: unable to obtain system tray.\n"); + free(systray); + systray = NULL; + return; + } + } + for (w = 0, i = systray->icons; i; i = i->next) { + /* make sure the background color stays the same */ + wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa); + XMapRaised(dpy, i->win); + w += systrayspacing; + i->x = w; + XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h); + w += i->w; + if (i->mon != m) + i->mon = m; + } + w = w ? w + systrayspacing : 1; + x -= w; + XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh); + wc.x = x; wc.y = m->by; wc.width = w; wc.height = bh; + wc.stack_mode = Above; wc.sibling = m->barwin; + XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSibling|CWStackMode, &wc); + XMapWindow(dpy, systray->win); + XMapSubwindows(dpy, systray->win); + /* redraw background */ + XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel); + XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh); + XSync(dpy, False); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Client * +wintosystrayicon(Window w) { + Client *i = NULL; + + if (!showsystray || !w) + return i; + for (i = systray->icons; i && i->win != w; i = i->next) ; + return i; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +Monitor * +systraytomon(Monitor *m) { + Monitor *t; + int i, n; + if(!systraypinning) { + if(!m) + return selmon; + return m == selmon ? m : NULL; + } + for(n = 1, t = mons; t && t->next; n++, t = t->next) ; + for(i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next) ; + if(systraypinningfailfirst && n < systraypinning) + return mons; + return t; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange + || (selmon->sel && selmon->sel->isfloating)) + return; + if (c == nexttiled(selmon->clients)) + if (!c || !(c = nexttiled(c->next))) + return; + pop(c); +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + die("dwm-"VERSION); + else if (argc != 1) + die("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); + checkotherwm(); + setup(); +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec", NULL) == -1) + die("pledge"); +#endif /* __OpenBSD__ */ + scan(); + run(); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/portatil/dwm/dwm.c.rej b/portatil/dwm/dwm.c.rej @@ -0,0 +1,12 @@ +--- dwm.c ++++ dwm.c +@@ -49,7 +49,8 @@ + #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) + #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +-#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) ++#define ISVISIBLEONTAG(C, T) ((C->tags & T)) ++#define ISVISIBLE(C) ISVISIBLEONTAG(C, C->mon->tagset[C->mon->seltags]) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define MOUSEMASK (BUTTONMASK|PointerMotionMask) + #define WIDTH(X) ((X)->w + 2 * (X)->bw) diff --git a/portatil/dwm/dwm.o b/portatil/dwm/dwm.o Binary files differ. diff --git a/portatil/dwm/dwm.png b/portatil/dwm/dwm.png Binary files differ. diff --git a/portatil/dwm/dwm_status2d-6.3.diff b/portatil/dwm/dwm_status2d-6.3.diff @@ -0,0 +1,166 @@ +diff --git a/dwm.c b/dwm.c +index a96f33c..24b1eeb 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -163,6 +163,7 @@ static void detachstack(Client *c); + static Monitor *dirtomon(int dir); + static void drawbar(Monitor *m); + static void drawbars(void); ++static int drawstatusbar(Monitor *m, int bh, char* text); + static void enternotify(XEvent *e); + static void expose(XEvent *e); + static void focus(Client *c); +@@ -237,7 +238,7 @@ static void zoom(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +-static char stext[256]; ++static char stext[1024]; + static int screen; + static int sw, sh; /* X display screen geometry width, height */ + static int bh, blw = 0; /* bar geometry */ +@@ -485,7 +486,7 @@ cleanup(void) + cleanupmon(mons); + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); +- for (i = 0; i < LENGTH(colors); i++) ++ for (i = 0; i < LENGTH(colors) + 1; i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); +@@ -693,6 +694,114 @@ dirtomon(int dir) + return m; + } + ++int ++drawstatusbar(Monitor *m, int bh, char* stext) { ++ int ret, i, w, x, len; ++ short isCode = 0; ++ char *text; ++ char *p; ++ ++ len = strlen(stext) + 1 ; ++ if (!(text = (char*) malloc(sizeof(char)*len))) ++ die("malloc"); ++ p = text; ++ memcpy(text, stext, len); ++ ++ /* compute width of the status text */ ++ w = 0; ++ i = -1; ++ while (text[++i]) { ++ if (text[i] == '^') { ++ if (!isCode) { ++ isCode = 1; ++ text[i] = '\0'; ++ w += TEXTW(text) - lrpad; ++ text[i] = '^'; ++ if (text[++i] == 'f') ++ w += atoi(text + ++i); ++ } else { ++ isCode = 0; ++ text = text + i + 1; ++ i = -1; ++ } ++ } ++ } ++ if (!isCode) ++ w += TEXTW(text) - lrpad; ++ else ++ isCode = 0; ++ text = p; ++ ++ w += 2; /* 1px padding on both sides */ ++ ret = x = m->ww - w; ++ ++ drw_setscheme(drw, scheme[LENGTH(colors)]); ++ drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; ++ drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; ++ drw_rect(drw, x, 0, w, bh, 1, 1); ++ x++; ++ ++ /* process status text */ ++ i = -1; ++ while (text[++i]) { ++ if (text[i] == '^' && !isCode) { ++ isCode = 1; ++ ++ text[i] = '\0'; ++ w = TEXTW(text) - lrpad; ++ drw_text(drw, x, 0, w, bh, 0, text, 0); ++ ++ x += w; ++ ++ /* process code */ ++ while (text[++i] != '^') { ++ if (text[i] == 'c') { ++ char buf[8]; ++ memcpy(buf, (char*)text+i+1, 7); ++ buf[7] = '\0'; ++ drw_clr_create(drw, &drw->scheme[ColFg], buf); ++ i += 7; ++ } else if (text[i] == 'b') { ++ char buf[8]; ++ memcpy(buf, (char*)text+i+1, 7); ++ buf[7] = '\0'; ++ drw_clr_create(drw, &drw->scheme[ColBg], buf); ++ i += 7; ++ } else if (text[i] == 'd') { ++ drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; ++ drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; ++ } else if (text[i] == 'r') { ++ int rx = atoi(text + ++i); ++ while (text[++i] != ','); ++ int ry = atoi(text + ++i); ++ while (text[++i] != ','); ++ int rw = atoi(text + ++i); ++ while (text[++i] != ','); ++ int rh = atoi(text + ++i); ++ ++ drw_rect(drw, rx + x, ry, rw, rh, 1, 0); ++ } else if (text[i] == 'f') { ++ x += atoi(text + ++i); ++ } ++ } ++ ++ text = text + i + 1; ++ i=-1; ++ isCode = 0; ++ } ++ } ++ ++ if (!isCode) { ++ w = TEXTW(text) - lrpad; ++ drw_text(drw, x, 0, w, bh, 0, text, 0); ++ } ++ ++ drw_setscheme(drw, scheme[SchemeNorm]); ++ free(p); ++ ++ return ret; ++} ++ + void + drawbar(Monitor *m) + { +@@ -707,9 +816,7 @@ drawbar(Monitor *m) + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ +- drw_setscheme(drw, scheme[SchemeNorm]); +- tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ +- drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); ++ tw = m->ww - drawstatusbar(m, bh, stext); + } + + for (c = m->clients; c; c = c->next) { +@@ -1571,7 +1678,8 @@ setup(void) + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ +- scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); ++ scheme = ecalloc(LENGTH(colors) + 1, sizeof(Clr *)); ++ scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], 3); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init bars */ diff --git a/portatil/dwm/fullgaps.diff b/portatil/dwm/fullgaps.diff @@ -0,0 +1,95 @@ +diff --git a/config.def.h b/config.def.h +index 1c0b587..38d2f6c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,7 @@ + + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappx = 5; /* gaps between windows */ + static const unsigned int snap = 32; /* snap pixel */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ +@@ -84,6 +85,9 @@ static Key keys[] = { + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, ++ { MODKEY, XK_minus, setgaps, {.i = -1 } }, ++ { MODKEY, XK_equal, setgaps, {.i = +1 } }, ++ { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) +diff --git a/dwm.c b/dwm.c +index 4465af1..4363627 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -119,6 +119,7 @@ struct Monitor { + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ ++ int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; +@@ -199,6 +200,7 @@ static void sendmon(Client *c, Monitor *m); + static void setclientstate(Client *c, long state); + static void setfocus(Client *c); + static void setfullscreen(Client *c, int fullscreen); ++static void setgaps(const Arg *arg); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setup(void); +@@ -638,6 +640,7 @@ createmon(void) + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; ++ m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); +@@ -1497,6 +1500,16 @@ setfullscreen(Client *c, int fullscreen) + } + } + ++void ++setgaps(const Arg *arg) ++{ ++ if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) ++ selmon->gappx = 0; ++ else ++ selmon->gappx += arg->i; ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -1683,16 +1696,16 @@ tile(Monitor *m) + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else +- mw = m->ww; +- for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) ++ mw = m->ww - m->gappx; ++ for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { +- h = (m->wh - my) / (MIN(n, m->nmaster) - i); +- resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0); +- my += HEIGHT(c); ++ h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; ++ resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); ++ my += HEIGHT(c) + m->gappx; + } else { +- h = (m->wh - ty) / (n - i); +- resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0); +- ty += HEIGHT(c); ++ h = (m->wh - ty) / (n - i) - m->gappx; ++ resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); ++ ty += HEIGHT(c) + m->gappx; + } + } + +-- +2.20.1 + diff --git a/portatil/dwm/move-resize.diff b/portatil/dwm/move-resize.diff @@ -0,0 +1,202 @@ +diff --git a/config.h b/config.h +index a2ac963..87baa38 100644 +--- a/config.h ++++ b/config.h +@@ -79,6 +79,22 @@ static Key keys[] = { + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, ++ { MODKEY, XK_Down, moveresize, {.v = "0x 25y 0w 0h" } }, ++ { MODKEY, XK_Up, moveresize, {.v = "0x -25y 0w 0h" } }, ++ { MODKEY, XK_Right, moveresize, {.v = "25x 0y 0w 0h" } }, ++ { MODKEY, XK_Left, moveresize, {.v = "-25x 0y 0w 0h" } }, ++ { MODKEY|ShiftMask, XK_Down, moveresize, {.v = "0x 0y 0w 25h" } }, ++ { MODKEY|ShiftMask, XK_Up, moveresize, {.v = "0x 0y 0w -25h" } }, ++ { MODKEY|ShiftMask, XK_Right, moveresize, {.v = "0x 0y 25w 0h" } }, ++ { MODKEY|ShiftMask, XK_Left, moveresize, {.v = "0x 0y -25w 0h" } }, ++ { MODKEY|ControlMask, XK_Up, moveresizeedge, {.v = "t"} }, ++ { MODKEY|ControlMask, XK_Down, moveresizeedge, {.v = "b"} }, ++ { MODKEY|ControlMask, XK_Left, moveresizeedge, {.v = "l"} }, ++ { MODKEY|ControlMask, XK_Right, moveresizeedge, {.v = "r"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Up, moveresizeedge, {.v = "T"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Down, moveresizeedge, {.v = "B"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Left, moveresizeedge, {.v = "L"} }, ++ { MODKEY|ControlMask|ShiftMask, XK_Right, moveresizeedge, {.v = "R"} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, +diff --git a/dwm.c b/dwm.c +index 5e4d494..0898236 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -183,6 +183,8 @@ static void mappingnotify(XEvent *e); + static void maprequest(XEvent *e); + static void monocle(Monitor *m); + static void motionnotify(XEvent *e); ++static void moveresize(const Arg *arg); ++static void moveresizeedge(const Arg *arg); + static void movemouse(const Arg *arg); + static Client *nexttiled(Client *c); + static void pop(Client *); +@@ -1193,6 +1195,158 @@ movemouse(const Arg *arg) + } + } + ++void ++moveresize(const Arg *arg) { ++ /* only floating windows can be moved */ ++ Client *c; ++ c = selmon->sel; ++ int x, y, w, h, nx, ny, nw, nh, ox, oy, ow, oh; ++ char xAbs, yAbs, wAbs, hAbs; ++ int msx, msy, dx, dy, nmx, nmy; ++ unsigned int dui; ++ Window dummy; ++ ++ if (!c || !arg) ++ return; ++ if (selmon->lt[selmon->sellt]->arrange && !c->isfloating) ++ return; ++ if (sscanf((char *)arg->v, "%d%c %d%c %d%c %d%c", &x, &xAbs, &y, &yAbs, &w, &wAbs, &h, &hAbs) != 8) ++ return; ++ ++ /* compute new window position; prevent window from be positioned outside the current monitor */ ++ nw = c->w + w; ++ if (wAbs == 'W') ++ nw = w < selmon->mw - 2 * c->bw ? w : selmon->mw - 2 * c->bw; ++ ++ nh = c->h + h; ++ if (hAbs == 'H') ++ nh = h < selmon->mh - 2 * c->bw ? h : selmon->mh - 2 * c->bw; ++ ++ nx = c->x + x; ++ if (xAbs == 'X') { ++ if (x < selmon->mx) ++ nx = selmon->mx; ++ else if (x > selmon->mx + selmon->mw) ++ nx = selmon->mx + selmon->mw - nw - 2 * c->bw; ++ else ++ nx = x; ++ } ++ ++ ny = c->y + y; ++ if (yAbs == 'Y') { ++ if (y < selmon->my) ++ ny = selmon->my; ++ else if (y > selmon->my + selmon->mh) ++ ny = selmon->my + selmon->mh - nh - 2 * c->bw; ++ else ++ ny = y; ++ } ++ ++ ox = c->x; ++ oy = c->y; ++ ow = c->w; ++ oh = c->h; ++ ++ XRaiseWindow(dpy, c->win); ++ Bool xqp = XQueryPointer(dpy, root, &dummy, &dummy, &msx, &msy, &dx, &dy, &dui); ++ resize(c, nx, ny, nw, nh, True); ++ ++ /* move cursor along with the window to avoid problems caused by the sloppy focus */ ++ if (xqp && ox <= msx && (ox + ow) >= msx && oy <= msy && (oy + oh) >= msy) ++ { ++ nmx = c->x - ox + c->w - ow; ++ nmy = c->y - oy + c->h - oh; ++ /* make sure the cursor stays inside the window */ ++ if ((msx + nmx) > c->x && (msy + nmy) > c->y) ++ XWarpPointer(dpy, None, None, 0, 0, 0, 0, nmx, nmy); ++ } ++} ++ ++void ++moveresizeedge(const Arg *arg) { ++ /* move or resize floating window to edge of screen */ ++ Client *c; ++ c = selmon->sel; ++ char e; ++ int nx, ny, nw, nh, ox, oy, ow, oh, bp; ++ int msx, msy, dx, dy, nmx, nmy; ++ int starty; ++ unsigned int dui; ++ Window dummy; ++ ++ nx = c->x; ++ ny = c->y; ++ nw = c->w; ++ nh = c->h; ++ ++ starty = selmon->showbar && topbar ? bh : 0; ++ bp = selmon->showbar && !topbar ? bh : 0; ++ ++ if (!c || !arg) ++ return; ++ if (selmon->lt[selmon->sellt]->arrange && !c->isfloating) ++ return; ++ if(sscanf((char *)arg->v, "%c", &e) != 1) ++ return; ++ ++ if(e == 't') ++ ny = starty; ++ ++ if(e == 'b') ++ ny = c->h > selmon->mh - 2 * c->bw ? c->h - bp : selmon->mh - c->h - 2 * c->bw - bp; ++ ++ if(e == 'l') ++ nx = selmon->mx; ++ ++ if(e == 'r') ++ nx = c->w > selmon->mw - 2 * c->bw ? selmon->mx + c->w : selmon->mx + selmon->mw - c->w - 2 * c->bw; ++ ++ if(e == 'T') { ++ /* if you click to resize again, it will return to old size/position */ ++ if(c->h + starty == c->oldh + c->oldy) { ++ nh = c->oldh; ++ ny = c->oldy; ++ } else { ++ nh = c->h + c->y - starty; ++ ny = starty; ++ } ++ } ++ ++ if(e == 'B') ++ nh = c->h + c->y + 2 * c->bw + bp == selmon->mh ? c->oldh : selmon->mh - c->y - 2 * c->bw - bp; ++ ++ if(e == 'L') { ++ if(selmon->mx + c->w == c->oldw + c->oldx) { ++ nw = c->oldw; ++ nx = c->oldx; ++ } else { ++ nw = c->w + c->x - selmon->mx; ++ nx = selmon->mx; ++ } ++ } ++ ++ if(e == 'R') ++ nw = c->w + c->x + 2 * c->bw == selmon->mx + selmon->mw ? c->oldw : selmon->mx + selmon->mw - c->x - 2 * c->bw; ++ ++ ox = c->x; ++ oy = c->y; ++ ow = c->w; ++ oh = c->h; ++ ++ XRaiseWindow(dpy, c->win); ++ Bool xqp = XQueryPointer(dpy, root, &dummy, &dummy, &msx, &msy, &dx, &dy, &dui); ++ resize(c, nx, ny, nw, nh, True); ++ ++ /* move cursor along with the window to avoid problems caused by the sloppy focus */ ++ if (xqp && ox <= msx && (ox + ow) >= msx && oy <= msy && (oy + oh) >= msy) { ++ nmx = c->x - ox + c->w - ow; ++ nmy = c->y - oy + c->h - oh; ++ /* make sure the cursor stays inside the window */ ++ if ((msx + nmx) > c->x && (msy + nmy) > c->y) ++ XWarpPointer(dpy, None, None, 0, 0, 0, 0, nmx, nmy); ++ } ++} ++ + Client * + nexttiled(Client *c) + { +-- +2.33.0 + diff --git a/portatil/dwm/proba.c b/portatil/dwm/proba.c @@ -0,0 +1,8 @@ +#include <gcolors.h> +#include <stdio.h> + + +int main(){ + printf("%s", COL_BG_0); + return 0; +} diff --git a/portatil/dwm/transient.c b/portatil/dwm/transient.c @@ -0,0 +1,42 @@ +/* cc transient.c -o transient -lX11 */ + +#include <stdlib.h> +#include <unistd.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +int main(void) { + Display *d; + Window r, f, t = None; + XSizeHints h; + XEvent e; + + d = XOpenDisplay(NULL); + if (!d) + exit(1); + r = DefaultRootWindow(d); + + f = XCreateSimpleWindow(d, r, 100, 100, 400, 400, 0, 0, 0); + h.min_width = h.max_width = h.min_height = h.max_height = 400; + h.flags = PMinSize | PMaxSize; + XSetWMNormalHints(d, f, &h); + XStoreName(d, f, "floating"); + XMapWindow(d, f); + + XSelectInput(d, f, ExposureMask); + while (1) { + XNextEvent(d, &e); + + if (t == None) { + sleep(5); + t = XCreateSimpleWindow(d, r, 50, 50, 100, 100, 0, 0, 0); + XSetTransientForHint(d, t, f); + XStoreName(d, t, "transient"); + XMapWindow(d, t); + XSelectInput(d, t, ExposureMask); + } + } + + XCloseDisplay(d); + exit(0); +} diff --git a/portatil/dwm/util.c b/portatil/dwm/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/portatil/dwm/util.h b/portatil/dwm/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/portatil/dwm/util.o b/portatil/dwm/util.o Binary files differ. diff --git a/portatil/dwmblocks/.gitignore b/portatil/dwmblocks/.gitignore @@ -0,0 +1,56 @@ +# Custom blocks file +blocks.h + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex +dwmblocks + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/portatil/dwmblocks/LICENSE b/portatil/dwmblocks/LICENSE @@ -0,0 +1,7 @@ +ISC License (ISC) + +Copyright 2020 torrinfail + +Permission to use, copy, modify, and/or 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/portatil/dwmblocks/Makefile b/portatil/dwmblocks/Makefile @@ -0,0 +1,36 @@ +PREFIX := /usr/local +CC := cc +CFLAGS := -pedantic -Wall -Wno-deprecated-declarations -Os +LDFLAGS := -lX11 + +# FreeBSD (uncomment) +#LDFLAGS += -L/usr/local/lib -I/usr/local/include +# # OpenBSD (uncomment) +#LDFLAGS += -L/usr/X11R6/lib -I/usr/X11R6/include + +all: options dwmblocks + +options: + @echo dwmblocks build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +dwmblocks: dwmblocks.c blocks.def.h blocks.h + ${CC} -o dwmblocks dwmblocks.c ${CFLAGS} ${LDFLAGS} + +blocks.h: + cp blocks.def.h $@ + +clean: + rm -f *.o *.gch dwmblocks + +install: dwmblocks + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f dwmblocks ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwmblocks + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwmblocks + +.PHONY: all options clean install uninstall diff --git a/portatil/dwmblocks/README.md b/portatil/dwmblocks/README.md @@ -0,0 +1,15 @@ +# dwmblocks +Modular status bar for dwm written in c. +# usage +To use dwmblocks first run 'make' and then install it with 'sudo make install'. +After that you can put dwmblocks in your xinitrc or other startup script to have it start with dwm. +# modifying blocks +The statusbar is made from text output from commandline programs. +Blocks are added and removed by editing the blocks.h header file. +By default the blocks.h header file is created the first time you run make which copies the default config from blocks.def.h. +This is so you can edit your status bar commands and they will not get overwritten in a future update. +# patches +Here are some patches to dwmblocks that add features that I either don't want to merge in, or that require a dwm patch to work. +I do not maintain these but I will take pull requests to update them. +<br> +<a href=https://gist.github.com/IGeraGera/e4a5583b91b3eec2e81fdceb44dea717>dwmblocks-statuscmd-b6b0be4.diff</a> diff --git a/portatil/dwmblocks/batt.sh b/portatil/dwmblocks/batt.sh @@ -0,0 +1,44 @@ + +#!/usr/bin/env sh + +colors() { + [ "$1" -lt "15" ] && printf "$COL_VERMELL" && exit + [ "$1" -lt "35" ] && printf "$COL_TARONJA" && exit + [ "$1" -lt "60" ] && printf "$COL_GROC" && exit + printf "$COL_BLANC" +} + +icona() { + [ "$1" -lt "15" ] && printf "" && exit + [ "$1" -lt "35" ] && printf "" && exit + [ "$1" -lt "60" ] && printf "" && exit + [ "$1" -lt "80" ] && printf "" && exit + printf "" +} + +IC_CURRENT="^c$COL_GROC^" +IC_PLUG="^c$COL_BLANC^" + +acpi > /tmp/acpi_info +while read line; do + percent="$(echo "$line" | awk -F ', ' '{print $2}' | sed 's/.$//')" + + if echo "$line" | grep "Discharging," > /dev/null; then + # DESCARREGANT + printf "^c$(colors "$percent")^" + printf "$(icona "$percent") " + printf "$percent%%" + + time_left="$(echo "$line" | awk '{print $5}' | sed "s/:[0-9][0-9]$//g")" + printf " ($time_left)" + + elif echo "$line" | grep "Full" > /dev/null; then + # COMPLETAMENT CARREGADA + printf "$IC_PLUG 100%%" + + elif ! echo "$line" | grep "Unknown" > /dev/null; then + # CARREGANT + printf "$IC_CURRENT $IC_PLUG $percent%%" + + fi +done < /tmp/acpi_info diff --git a/portatil/dwmblocks/blocks.def.h b/portatil/dwmblocks/blocks.def.h @@ -0,0 +1,11 @@ +//Modify this file to change what commands output to your statusbar, and recompile using the make command. +static const Block blocks[] = { + /*Icon*/ /*Command*/ /*Update Interval*/ /*Update Signal*/ + {"Mem:", "free -h | awk '/^Mem/ { print $3\"/\"$2 }' | sed s/i//g", 30, 0}, + + {"", "date '+%b %d (%a) %I:%M%p'", 5, 0}, +}; + +//sets delimeter between status commands. NULL character ('\0') means no delimeter. +static char delim[] = " | "; +static unsigned int delimLen = 5; diff --git a/portatil/dwmblocks/cousa.txt b/portatil/dwmblocks/cousa.txt @@ -0,0 +1 @@ +ola ola ola diff --git a/portatil/dwmblocks/dwmblocks.c b/portatil/dwmblocks/dwmblocks.c @@ -0,0 +1,251 @@ +#include<stdlib.h> +#include<stdio.h> +#include<string.h> +#include<unistd.h> +#include<signal.h> +#include<sys/wait.h> +#ifndef NO_X +#include<X11/Xlib.h> +#endif +#ifdef __OpenBSD__ +#define SIGPLUS SIGUSR1+1 +#define SIGMINUS SIGUSR1-1 +#else +#define SIGPLUS SIGRTMIN +#define SIGMINUS SIGRTMIN +#endif +#define LENGTH(X) (sizeof(X) / sizeof (X[0])) +#define CMDLENGTH 120// default was 50 +#define MIN( a, b ) ( ( a < b) ? a : b ) +#define STATUSLENGTH (LENGTH(blocks) * CMDLENGTH + 1) + +typedef struct { + char* icon; + char* command; + unsigned int interval; + unsigned int signal; +} Block; +#ifndef __OpenBSD__ +void dummysighandler(int num); +#endif +//void sighandler(int num); +void getcmds(int time); +void getsigcmds(unsigned int signal); +void setupsignals(); +void sighandler(int signum, siginfo_t *si, void *ucontext); +int getstatus(char *str, char *last); +void statusloop(); +void chldhandler(); +void termhandler(); +void pstdout(); +#ifndef NO_X +void setroot(); +static void (*writestatus) () = setroot; +static int setupX(); +static Display *dpy; +static int screen; +static Window root; +#else +static void (*writestatus) () = pstdout; +#endif + + +#include "blocks.h" + +static char statusbar[LENGTH(blocks)][CMDLENGTH] = {0}; +static char statusstr[2][STATUSLENGTH]; +static char button[] = "\0"; +static int statusContinue = 1; +static int returnStatus = 0; + +//opens process *cmd and stores output in *output +void getcmd(const Block *block, char *output) +{ + if (block->signal) + *output++ = block->signal; + strcpy(output, block->icon); + FILE *cmdf = popen(block->command, "r"); + if (!cmdf) + return; + int i = strlen(block->icon); + fgets(output+i, CMDLENGTH-i-delimLen, cmdf); + i = strlen(output); + if (i == 0) { + //return if block and command output are both empty + pclose(cmdf); + return; + } + //only chop off newline if one is present at the end + i = output[i-1] == '\n' ? i-1 : i; + if (delim[0] != '\0') { + strncpy(output+i, delim, delimLen); + } + else + output[i++] = '\0'; + pclose(cmdf); +} + +void getcmds(int time) +{ + const Block* current; + for (unsigned int i = 0; i < LENGTH(blocks); i++) { + current = blocks + i; + if ((current->interval != 0 && time % current->interval == 0) || time == -1) + getcmd(current,statusbar[i]); + } +} + +void getsigcmds(unsigned int signal) +{ + const Block *current; + for (unsigned int i = 0; i < LENGTH(blocks); i++) { + current = blocks + i; + if (current->signal == signal) + getcmd(current,statusbar[i]); + } +} + +void setupsignals() +{ + struct sigaction sa = { .sa_sigaction = sighandler, .sa_flags = SA_SIGINFO }; +#ifndef __OpenBSD__ + /* initialize all real time signals with dummy handler */ + for (int i = SIGRTMIN; i <= SIGRTMAX; i++){ + signal(i, dummysighandler); + sigaddset(&sa.sa_mask, i); + } +#endif + + for (unsigned int i = 0; i < LENGTH(blocks); i++) { + if (blocks[i].signal > 0) + sigaction(SIGMINUS+blocks[i].signal, &sa, NULL); + } + +} + +int getstatus(char *str, char *last) +{ + strcpy(last, str); + str[0] = '\0'; + for (unsigned int i = 0; i < LENGTH(blocks); i++) + strcat(str, statusbar[i]); + str[strlen(str)-strlen(delim)] = '\0'; + return strcmp(str, last);//0 if they are the same +} + +#ifndef NO_X +void setroot() +{ + if (!getstatus(statusstr[0], statusstr[1]))//Only set root if text has changed. + return; + XStoreName(dpy, root, statusstr[0]); + XFlush(dpy); +} + +int setupX() +{ + dpy = XOpenDisplay(NULL); + if (!dpy) { + fprintf(stderr, "dwmblocks: Failed to open display\n"); + return 0; + } + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + return 1; +} +#endif + +void pstdout() +{ + if (!getstatus(statusstr[0], statusstr[1]))//Only write out if text has changed. + return; + printf("%s\n",statusstr[0]); + fflush(stdout); +} + + +void statusloop() +{ + setupsignals(); + int i = 0; + getcmds(-1); + while (1) { + getcmds(i++); + writestatus(); + if (!statusContinue) + break; + sleep(1.0); + } +} + +#ifndef __OpenBSD__ +/* this signal handler should do nothing */ +void dummysighandler(int signum) +{ + return; +} +#endif + +void sighandler(int signum, siginfo_t* si, void* ucontext) +{ + //getsigcmds(signum-SIGPLUS); + //writestatus(); + + if (si->si_value.sival_int) { + pid_t parent = getpid(); + if (fork() == 0) { + #ifndef NO_X + if (dpy) + close(ConnectionNumber(dpy)); + #endif + int i; + for (i = 0; i < LENGTH(blocks) && blocks[i].signal != signum-SIGRTMIN; i++); + + char shcmd[1024]; + sprintf(shcmd, "%s; kill -%d %d", blocks[i].command, SIGRTMIN+blocks[i].signal, parent); + char *cmd[] = { "/bin/sh", "-c", shcmd, NULL }; + char button[2] = { '0' + si->si_value.sival_int, '\0' }; + setenv("BLOCK_BUTTON", button, 1); + setsid(); + execvp(cmd[0], cmd); + perror(cmd[0]); + exit(EXIT_SUCCESS); + } + } else { + getsigcmds(signum-SIGPLUS); + writestatus(); + } +} + +void termhandler() +{ + statusContinue = 0; +} + +void chldhandler(){ + while(0 < waitpid(-1, NULL, WNOHANG)); +} + +int main(int argc, char** argv) +{ + for (int i = 0; i < argc; i++) {//Handle command line arguments + if (!strcmp("-d",argv[i])) + strncpy(delim, argv[++i], delimLen); + else if (!strcmp("-p",argv[i])) + writestatus = pstdout; + } +#ifndef NO_X + if (!setupX()) + return 1; +#endif + delimLen = MIN(delimLen, strlen(delim)); + delim[delimLen++] = '\0'; + signal(SIGTERM, termhandler); + signal(SIGINT, termhandler); + signal(SIGCHLD, chldhandler); + statusloop(); +#ifndef NO_X + XCloseDisplay(dpy); +#endif + return 0; +} diff --git a/portatil/dwmblocks/dwmblocks.diff b/portatil/dwmblocks/dwmblocks.diff @@ -0,0 +1,86 @@ +6c6 +< #include<syswait.h> +--- +> #include<sys/wait.h> +18c18 +< #define CMDLENGTH 50 +--- +> #define CMDLENGTH 120 // default was 50 +31d30 +< void sighandler(int num); +35c34 +< void sighandler(int signum); +--- +> void sighandler(int signum, siginfo_t *si, void *ucontext); +38a38 +> void chldhandler(); +63,64c63,64 +< if (block->signal) +< *output++ = block->signal; +--- +> if (block->signal) +> *output++ = block->signal; +77,78d76 +< //only chop off newline if one is present at the end +< i = output[i-1] == '\n' ? i-1 : i; +79a78,79 +> //only chop off newline if one is present at the end +> i = output[i-1] == '\n' ? i-1 : i; +109c109 +< struct sigaction sa = { .sa_sigaction = sighandler, .sa_flags = SA_SIGINFO }; +--- +> struct sigaction sa = { .sa_sigaction = sighandler, .sa_flags = SA_SIGINFO }; +112c112 +< for (int i = SIGRTMIN; i <= SIGRTMAX; i++){ +--- +> for (int i = SIGRTMIN; i <= SIGRTMAX; i++) { +114,115c114,115 +< sigaddset(&sa.sa_mask, i); +< } +--- +> sigaddset(&sa.sa_mask, i); +> } +120c120 +< signal(SIGMINUS+blocks[i].signal, sighandler); +--- +> sigaction(SIGMINUS+blocks[i].signal, &sa, NULL); +188c188 +< void sighandler(int signum) +--- +> void sighandler(int signum, siginfo_t *si, void *ucontext) +190,191c190,213 +< getsigcmds(signum-SIGPLUS); +< writestatus(); +--- +> if (si->si_value.sival_int) { +> pid_t parent = getpid(); +> if (fork() == 0) { +> #ifndef NO_X +> if (dpy) +> close(ConnectionNumber(dpy)); +> #endif +> int i; +> for (i = 0; i < LENGTH(blocks) && blocks[i].signal != signum-SIGRTMIN; i++); +> +> char shcmd[1024]; +> sprintf(shcmd, "%s; kill -%d %d", blocks[i].command, SIGRTMIN+blocks[i].signal, parent); +> char *cmd[] = { "/bin/sh", "-c", shcmd, NULL }; +> char button[2] = { '0' + si->si_value.sival_int, '\0' }; +> setenv("BLOCK_BUTTON", button, 1); +> setsid(); +> execvp(cmd[0], cmd); +> perror(cmd[0]); +> exit(EXIT_SUCCESS); +> } +> } else { +> getsigcmds(signum-SIGPLUS); +> writestatus(); +> } +198a221,225 +> void chldhandler() +> { +> while (0 < waitpid(-1, NULL, WNOHANG)); +> } +> +214a242 +> signal(SIGCHLD, chldhandler); diff --git a/portatil/dwmblocks/dwmblocks_gerard.c b/portatil/dwmblocks/dwmblocks_gerard.c @@ -0,0 +1,248 @@ +#include<stdlib.h> +#include<stdio.h> +#include<string.h> +#include<unistd.h> +#include<signal.h> +#include<sys/wait.h> +#ifndef NO_X +#include<X11/Xlib.h> +#endif +#ifdef __OpenBSD__ +#define SIGPLUS SIGUSR1+1 +#define SIGMINUS SIGUSR1-1 +#else +#define SIGPLUS SIGRTMIN +#define SIGMINUS SIGRTMIN +#endif +#define LENGTH(X) (sizeof(X) / sizeof (X[0])) +#define CMDLENGTH 120 // default was 50 +#define MIN( a, b ) ( ( a < b) ? a : b ) +#define STATUSLENGTH (LENGTH(blocks) * CMDLENGTH + 1) + +typedef struct { + char* icon; + char* command; + unsigned int interval; + unsigned int signal; +} Block; +#ifndef __OpenBSD__ +void dummysighandler(int num); +#endif +void getcmds(int time); +void getsigcmds(unsigned int signal); +void setupsignals(); +void sighandler(int signum, siginfo_t *si, void *ucontext); +int getstatus(char *str, char *last); +void statusloop(); +void termhandler(); +void chldhandler(); +void pstdout(); +#ifndef NO_X +void setroot(); +static void (*writestatus) () = setroot; +static int setupX(); +static Display *dpy; +static int screen; +static Window root; +#else +static void (*writestatus) () = pstdout; +#endif + + +#include "blocks.h" + +static char statusbar[LENGTH(blocks)][CMDLENGTH] = {0}; +static char statusstr[2][STATUSLENGTH]; +static char button[] = "\0"; +static int statusContinue = 1; +static int returnStatus = 0; + +//opens process *cmd and stores output in *output +void getcmd(const Block *block, char *output) +{ + if (block->signal) + *output++ = block->signal; + strcpy(output, block->icon); + FILE *cmdf = popen(block->command, "r"); + if (!cmdf) + return; + int i = strlen(block->icon); + fgets(output+i, CMDLENGTH-i-delimLen, cmdf); + i = strlen(output); + if (i == 0) { + //return if block and command output are both empty + pclose(cmdf); + return; + } + if (delim[0] != '\0') { + //only chop off newline if one is present at the end + i = output[i-1] == '\n' ? i-1 : i; + strncpy(output+i, delim, delimLen); + } + else + output[i++] = '\0'; + pclose(cmdf); +} + +void getcmds(int time) +{ + const Block* current; + for (unsigned int i = 0; i < LENGTH(blocks); i++) { + current = blocks + i; + if ((current->interval != 0 && time % current->interval == 0) || time == -1) + getcmd(current,statusbar[i]); + } +} + +void getsigcmds(unsigned int signal) +{ + const Block *current; + for (unsigned int i = 0; i < LENGTH(blocks); i++) { + current = blocks + i; + if (current->signal == signal) + getcmd(current,statusbar[i]); + } +} + +void setupsignals() +{ + struct sigaction sa = { .sa_sigaction = sighandler, .sa_flags = SA_SIGINFO }; +#ifndef __OpenBSD__ + /* initialize all real time signals with dummy handler */ + for (int i = SIGRTMIN; i <= SIGRTMAX; i++) { + signal(i, dummysighandler); + sigaddset(&sa.sa_mask, i); + } +#endif + + for (unsigned int i = 0; i < LENGTH(blocks); i++) { + if (blocks[i].signal > 0) + sigaction(SIGMINUS+blocks[i].signal, &sa, NULL); + } + +} + +int getstatus(char *str, char *last) +{ + strcpy(last, str); + str[0] = '\0'; + for (unsigned int i = 0; i < LENGTH(blocks); i++) + strcat(str, statusbar[i]); + str[strlen(str)-strlen(delim)] = '\0'; + return strcmp(str, last);//0 if they are the same +} + +#ifndef NO_X +void setroot() +{ + if (!getstatus(statusstr[0], statusstr[1]))//Only set root if text has changed. + return; + XStoreName(dpy, root, statusstr[0]); + XFlush(dpy); +} + +int setupX() +{ + dpy = XOpenDisplay(NULL); + if (!dpy) { + fprintf(stderr, "dwmblocks: Failed to open display\n"); + return 0; + } + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + return 1; +} +#endif + +void pstdout() +{ + if (!getstatus(statusstr[0], statusstr[1]))//Only write out if text has changed. + return; + printf("%s\n",statusstr[0]); + fflush(stdout); +} + + +void statusloop() +{ + setupsignals(); + int i = 0; + getcmds(-1); + while (1) { + getcmds(i++); + writestatus(); + if (!statusContinue) + break; + sleep(1.0); + } +} + +#ifndef __OpenBSD__ +/* this signal handler should do nothing */ +void dummysighandler(int signum) +{ + return; +} +#endif + +void sighandler(int signum, siginfo_t *si, void *ucontext) +{ + if (si->si_value.sival_int) { + pid_t parent = getpid(); + if (fork() == 0) { +#ifndef NO_X + if (dpy) + close(ConnectionNumber(dpy)); +#endif + int i; + for (i = 0; i < LENGTH(blocks) && blocks[i].signal != signum-SIGRTMIN; i++); + + char shcmd[1024]; + sprintf(shcmd, "%s; kill -%d %d", blocks[i].command, SIGRTMIN+blocks[i].signal, parent); + char *cmd[] = { "/bin/sh", "-c", shcmd, NULL }; + char button[2] = { '0' + si->si_value.sival_int, '\0' }; + setenv("BLOCK_BUTTON", button, 1); + setsid(); + execvp(cmd[0], cmd); + perror(cmd[0]); + exit(EXIT_SUCCESS); + } + } else { + getsigcmds(signum-SIGPLUS); + writestatus(); + } +} + +void termhandler() +{ + statusContinue = 0; +} + +void chldhandler() +{ + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +int main(int argc, char** argv) +{ + for (int i = 0; i < argc; i++) {//Handle command line arguments + if (!strcmp("-d",argv[i])) + strncpy(delim, argv[++i], delimLen); + else if (!strcmp("-p",argv[i])) + writestatus = pstdout; + } +#ifndef NO_X + if (!setupX()) + return 1; +#endif + delimLen = MIN(delimLen, strlen(delim)); + delim[delimLen++] = '\0'; + signal(SIGTERM, termhandler); + signal(SIGINT, termhandler); + signal(SIGCHLD, chldhandler); + statusloop(); +#ifndef NO_X + XCloseDisplay(dpy); +#endif + return 0; +} diff --git a/portatil/st/FAQ b/portatil/st/FAQ @@ -0,0 +1,250 @@ +## Why does st not handle utmp entries? + +Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. + + +## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! + +It means that st doesn’t have any terminfo entry on your system. Chances are +you did not `make install`. If you just want to test it without installing it, +you can manually run `tic -sx st.info`. + + +## Nothing works, and nothing is said about an unknown terminal! + +* Some programs just assume they’re running in xterm i.e. they don’t rely on + terminfo. What you see is the current state of the “xterm compliance”. +* Some programs don’t complain about the lacking st description and default to + another terminal. In that case see the question about terminfo. + + +## How do I scroll back up? + +* Using a terminal multiplexer. + * `st -e tmux` using C-b [ + * `st -e screen` using C-a ESC +* Using the excellent tool of [scroll](https://git.suckless.org/scroll/). +* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). + + +## I would like to have utmp and/or scroll functionality by default + +You can add the absolute path of both programs in your config.h file. You only +have to modify the value of utmp and scroll variables. + + +## Why doesn't the Del key work in some programs? + +Taken from the terminfo manpage: + + If the terminal has a keypad that transmits codes when the keys + are pressed, this information can be given. Note that it is not + possible to handle terminals where the keypad only works in + local (this applies, for example, to the unshifted HP 2621 keys). + If the keypad can be set to transmit or not transmit, give these + codes as smkx and rmkx. Otherwise the keypad is assumed to + always transmit. + +In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that +applications which want to test against keypad keys send these +sequences. + +But buggy applications (like bash and irssi, for example) don't do this. A fast +solution for them is to use the following command: + + $ printf '\033[?1h\033=' >/dev/tty + +or + $ tput smkx + +In the case of bash, readline is used. Readline has a different note in its +manpage about this issue: + + enable-keypad (Off) + When set to On, readline will try to enable the + application keypad when it is called. Some systems + need this to enable arrow keys. + +Adding this option to your .inputrc will fix the keypad problem for all +applications using readline. + +If you are using zsh, then read the zsh FAQ +<http://zsh.sourceforge.net/FAQ/zshfaq03.html#l25>: + + It should be noted that the O / [ confusion can occur with other keys + such as Home and End. Some systems let you query the key sequences + sent by these keys from the system's terminal database, terminfo. + Unfortunately, the key sequences given there typically apply to the + mode that is not the one zsh uses by default (it's the "application" + mode rather than the "raw" mode). Explaining the use of terminfo is + outside of the scope of this FAQ, but if you wish to use the key + sequences given there you can tell the line editor to turn on + "application" mode when it starts and turn it off when it stops: + + function zle-line-init () { echoti smkx } + function zle-line-finish () { echoti rmkx } + zle -N zle-line-init + zle -N zle-line-finish + +Putting these lines into your .zshrc will fix the problems. + + +## How can I use meta in 8bit mode? + +St supports meta in 8bit mode, but the default terminfo entry doesn't +use this capability. If you want it, you have to use the 'st-meta' value +in TERM. + + +## I cannot compile st in OpenBSD + +OpenBSD lacks librt, despite it being mandatory in POSIX +<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html#tag_20_11_13>. +If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and +st will compile without any loss of functionality, because all the functions are +included in libc on this platform. + + +## The Backspace Case + +St is emulating the Linux way of handling backspace being delete and delete being +backspace. + +This is an issue that was discussed in suckless mailing list +<https://lists.suckless.org/dev/1404/20697.html>. Here is why some old grumpy +terminal users wants its backspace to be how he feels it: + + Well, I am going to comment why I want to change the behaviour + of this key. When ASCII was defined in 1968, communication + with computers was done using punched cards, or hardcopy + terminals (basically a typewriter machine connected with the + computer using a serial port). ASCII defines DELETE as 7F, + because, in punched-card terms, it means all the holes of the + card punched; it is thus a kind of 'physical delete'. In the + same way, the BACKSPACE key was a non-destructive backspace, + as on a typewriter. So, if you wanted to delete a character, + you had to BACKSPACE and then DELETE. Another use of BACKSPACE + was to type accented characters, for example 'a BACKSPACE `'. + The VT100 had no BACKSPACE key; it was generated using the + CONTROL key as another control character (CONTROL key sets to + 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code + 0x08)), but it had a DELETE key in a similar position where + the BACKSPACE key is located today on common PC keyboards. + All the terminal emulators emulated the difference between + these keys correctly: the backspace key generated a BACKSPACE + (^H) and delete key generated a DELETE (^?). + + But a problem arose when Linus Torvalds wrote Linux. Unlike + earlier terminals, the Linux virtual terminal (the terminal + emulator integrated in the kernel) returned a DELETE when + backspace was pressed, due to the VT100 having a DELETE key in + the same position. This created a lot of problems (see [1] + and [2]). Since Linux has become the king, a lot of terminal + emulators today generate a DELETE when the backspace key is + pressed in order to avoid problems with Linux. The result is + that the only way of generating a BACKSPACE on these systems + is by using CONTROL + H. (I also think that emacs had an + important point here because the CONTROL + H prefix is used + in emacs in some commands (help commands).) + + From point of view of the kernel, you can change the key + for deleting a previous character with stty erase. When you + connect a real terminal into a machine you describe the type + of terminal, so getty configures the correct value of stty + erase for this terminal. In the case of terminal emulators, + however, you don't have any getty that can set the correct + value of stty erase, so you always get the default value. + For this reason, it is necessary to add 'stty erase ^H' to your + profile if you have changed the value of the backspace key. + Of course, another solution is for st itself to modify the + value of stty erase. I usually have the inverse problem: + when I connect to non-Unix machines, I have to press CONTROL + + h to get a BACKSPACE. The inverse problem occurs when a user + connects to my Unix machines from a different system with a + correct backspace key. + + [1] http://www.ibb.net/~anne/keyboard.html + [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html + + +## But I really want the old grumpy behaviour of my terminal + +Apply [1]. + +[1] https://st.suckless.org/patches/delkey + + +## Why do images not work in st using the w3m image hack? + +w3mimg uses a hack that draws an image on top of the terminal emulator Drawable +window. The hack relies on the terminal to use a single buffer to draw its +contents directly. + +st uses double-buffered drawing so the image is quickly replaced and may show a +short flicker effect. + +Below is a patch example to change st double-buffering to a single Drawable +buffer. + +diff --git a/x.c b/x.c +--- a/x.c ++++ b/x.c +@@ -732,10 +732,6 @@ xresize(int col, int row) + win.tw = col * win.cw; + win.th = row * win.ch; + +- XFreePixmap(xw.dpy, xw.buf); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); +- XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +@@ -1148,8 +1144,7 @@ xinit(int cols, int rows) + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.buf = xw.win; + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) + void + xfinishdraw(void) + { +- XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, +- win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); + + +## BadLength X error in Xft when trying to render emoji + +Xft makes st crash when rendering color emojis with the following error: + +"X Error of failed request: BadLength (poly request too large or internal Xlib length error)" + Major opcode of failed request: 139 (RENDER) + Minor opcode of failed request: 20 (RenderAddGlyphs) + Serial number of failed request: 1595 + Current serial number in output stream: 1818" + +This is a known bug in Xft (not st) which happens on some platforms and +combination of particular fonts and fontconfig settings. + +See also: +https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 +https://bugs.freedesktop.org/show_bug.cgi?id=107534 +https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + +The solution is to remove color emoji fonts or disable this in the fontconfig +XML configuration. As an ugly workaround (which may work only on newer +fontconfig versions (FC_COLOR)), the following code can be used to mask color +fonts: + + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + +Please don't bother reporting this bug to st, but notify the upstream Xft +developers about fixing this bug. diff --git a/portatil/st/LEGACY b/portatil/st/LEGACY @@ -0,0 +1,17 @@ +A STATEMENT ON LEGACY SUPPORT + +In the terminal world there is much cruft that comes from old and unsup‐ +ported terminals that inherit incompatible modes and escape sequences +which noone is able to know, except when he/she comes from that time and +developed a graphical vt100 emulator at that time. + +One goal of st is to only support what is really needed. When you en‐ +counter a sequence which you really need, implement it. But while you +are at it, do not add the other cruft you might encounter while sneek‐ +ing at other terminal emulators. History has bloated them and there is +no real evidence that most of the sequences are used today. + + +Christoph Lohmann <20h@r-36.net> +2012-09-13T07:00:36.081271045+02:00 + diff --git a/portatil/st/LICENSE b/portatil/st/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2022 Hiltjo Posthuma <hiltjo at codemadness dot org> +© 2018 Devin J. Pohly <djpohly at gmail dot com> +© 2014-2017 Quentin Rameau <quinq at fifth dot space> +© 2009-2012 Aurélien APTEL <aurelien dot aptel at gmail dot com> +© 2008-2017 Anselm R Garbe <garbeam at gmail dot com> +© 2012-2017 Roberto E. Vargas Caballero <k0ga at shike2 dot com> +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon <esjeon at hyunmu dot am> +© 2013 Alexander Sedov <alex0player at gmail dot com> +© 2013 Mark Edgar <medgar123 at gmail dot com> +© 2013-2014 Eric Pruitt <eric.pruitt at gmail dot com> +© 2013 Michael Forney <mforney at mforney dot org> +© 2013-2014 Markus Teich <markus dot teich at stusta dot mhn dot de> +© 2014-2015 Laslo Hunhold <dev at frign dot de> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/portatil/st/Makefile b/portatil/st/Makefile @@ -0,0 +1,57 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c +OBJ = $(SRC:.c=.o) + +all: options st + +options: + @echo st build options: + @echo "CFLAGS = $(STCFLAGS)" + @echo "LDFLAGS = $(STLDFLAGS)" + @echo "CC = $(CC)" + +config.h: + cp config.def.h config.h + +.c.o: + $(CC) $(STCFLAGS) -c $< + +st.o: config.h st.h win.h +x.o: arg.h config.h st.h win.h + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ + config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + tic -sx st.info + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + +.PHONY: all options clean dist install uninstall diff --git a/portatil/st/README b/portatil/st/README @@ -0,0 +1,34 @@ +st - simple terminal +-------------------- +st is a simple terminal emulator for X which sucks less. + + +Requirements +------------ +In order to build st you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (st is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install st (if +necessary as root): + + make clean install + + +Running st +---------- +If you did not install st with make clean install, you must compile +the st terminfo entry with the following command: + + tic -sx st.info + +See the man page for additional details. + +Credits +------- +Based on Aurélien APTEL <aurelien dot aptel at gmail dot com> bt source code. + diff --git a/portatil/st/TODO b/portatil/st/TODO @@ -0,0 +1,28 @@ +vt emulation +------------ + +* double-height support + +code & interface +---------------- + +* add a simple way to do multiplexing + +drawing +------- +* add diacritics support to xdraws() + * switch to a suckless font drawing library +* make the font cache simpler +* add better support for brightening of the upper colors + +bugs +---- + +* fix shift up/down (shift selection in emacs) +* remove DEC test sequence when appropriate + +misc +---- + + $ grep -nE 'XXX|TODO' st.c + diff --git a/portatil/st/alpha.diff b/portatil/st/alpha.diff @@ -0,0 +1,146 @@ +diff --git a/config.def.h b/config.def.h +index 91ab8ca..6af616e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -93,6 +93,9 @@ char *termname = "st-256color"; + */ + unsigned int tabspaces = 8; + ++/* bg opacity */ ++float alpha = 0.8; ++ + /* Terminal colors (16 first used in escape sequence) */ + static const char *colorname[] = { + /* 8 normal colors */ +diff --git a/config.mk b/config.mk +index 4c4c5d5..0114bad 100644 +--- a/config.mk ++++ b/config.mk +@@ -16,7 +16,7 @@ PKG_CONFIG = pkg-config + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +diff --git a/st.h b/st.h +index 519b9bd..8bb533d 100644 +--- a/st.h ++++ b/st.h +@@ -126,3 +126,4 @@ extern unsigned int tabspaces; + extern unsigned int defaultfg; + extern unsigned int defaultbg; + extern unsigned int defaultcs; ++extern float alpha; +diff --git a/x.c b/x.c +index 8a16faa..ddf4178 100644 +--- a/x.c ++++ b/x.c +@@ -105,6 +105,7 @@ typedef struct { + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ ++ int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ + } XWindow; +@@ -243,6 +244,7 @@ static char *usedfont = NULL; + static double usedfontsize = 0; + static double defaultfontsize = 0; + ++static char *opt_alpha = NULL; + static char *opt_class = NULL; + static char **opt_cmd = NULL; + static char *opt_embed = NULL; +@@ -736,7 +738,7 @@ xresize(int col, int row) + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + +@@ -796,6 +798,13 @@ xloadcols(void) + else + die("could not allocate color %d\n", i); + } ++ ++ /* set alpha value of bg color */ ++ if (opt_alpha) ++ alpha = strtof(opt_alpha, NULL); ++ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); ++ dc.col[defaultbg].pixel &= 0x00FFFFFF; ++ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; + } + +@@ -1118,11 +1127,23 @@ xinit(int cols, int rows) + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; ++ XWindowAttributes attr; ++ XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); +- xw.vis = XDefaultVisual(xw.dpy, xw.scr); ++ ++ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { ++ parent = XRootWindow(xw.dpy, xw.scr); ++ xw.depth = 32; ++ } else { ++ XGetWindowAttributes(xw.dpy, parent, &attr); ++ xw.depth = attr.depth; ++ } ++ ++ XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); ++ xw.vis = vis.visual; + + /* font */ + if (!FcInit()) +@@ -1132,7 +1153,7 @@ xinit(int cols, int rows) + xloadfonts(usedfont, 0); + + /* colors */ +- xw.cmap = XDefaultColormap(xw.dpy, xw.scr); ++ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ +@@ -1152,19 +1173,15 @@ xinit(int cols, int rows) + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + +- if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) +- parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, +- win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, ++ win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; +- dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, +- &gcvalues); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); ++ dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -2019,6 +2036,9 @@ main(int argc, char *argv[]) + case 'a': + allowaltscreen = 0; + break; ++ case 'A': ++ opt_alpha = EARGF(usage()); ++ break; + case 'c': + opt_class = EARGF(usage()); + break; diff --git a/portatil/st/arg.h b/portatil/st/arg.h @@ -0,0 +1,50 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/portatil/st/config.def.h b/portatil/st/config.def.h @@ -0,0 +1,480 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "mononoki Nerd Font Mono:pixelsize=18:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + //"black", /* default background colour */ + "#151c26", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/portatil/st/config.def.h.orig b/portatil/st/config.def.h.orig @@ -0,0 +1,478 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "mononoki Nerd Font Mono:pixelsize=18:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + //"black", /* default background colour */ + "#151c26", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/portatil/st/config.h b/portatil/st/config.h @@ -0,0 +1,521 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "mononoki Nerd Font Mono:pixelsize=18:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* */ +#include <gcolors.h> +//#define DRACULA + +#ifdef DRACULA + +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + //"black", /* default background colour */ + "#151c26", /* default background colour */ +}; + +#else + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + COL_NEGRE,//"black", + COL_VERMELL,//"red3", + COL_VERD,//"green3", + COL_GROC,//"yellow3", + COL_BLAU,//"blue2", + COL_MAGENTA,//"magenta3", + COL_CIAN,//"cyan3", + COL_BLANC,//"gray90", + + /* 8 bright colors */ + "#6c768a",//"gray50", + COL_VERMELL,//"red", + COL_VERD,//"green", + COL_GROC,//"yellow", + COL_BLAU,//"#5c5cff", + COL_MAGENTA,//"magenta", + COL_CIAN,//"cyan", + COL_W0,//"white", + + [255] = 0, +// [256] = "#1a1f2a", // background +// [257] = "#151c26", // foreground + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + //"black", /* default background colour */ + "#151c26", /* default background colour */ +}; + +#endif + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/portatil/st/config.h.bak b/portatil/st/config.h.bak @@ -0,0 +1,478 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "mononoki Nerd Font Mono:pixelsize=18:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + //"black", /* default background colour */ + "#151c26", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/portatil/st/config.mk b/portatil/st/config.mk @@ -0,0 +1,35 @@ +# st version +VERSION = 0.8.5 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` + +# compiler and linker +# CC = c99 diff --git a/portatil/st/st b/portatil/st/st Binary files differ. diff --git a/portatil/st/st-scrollback-0.8.5.diff b/portatil/st/st-scrollback-0.8.5.diff @@ -0,0 +1,350 @@ +diff --git a/config.def.h b/config.def.h +index 91ab8ca..e3b469b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -201,6 +201,8 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, ++ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + }; + + /* +diff --git a/st.c b/st.c +index 51049ba..cd750f2 100644 +--- a/st.c ++++ b/st.c +@@ -35,6 +35,7 @@ + #define ESC_ARG_SIZ 16 + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ ++#define HISTSIZE 2000 + + /* macros */ + #define IS_SET(flag) ((term.mode & (flag)) != 0) +@@ -42,6 +43,9 @@ + #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) + #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) + #define ISDELIM(u) (u && wcschr(worddelimiters, u)) ++#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ ++ term.scr + HISTSIZE + 1) % HISTSIZE] : \ ++ term.line[(y) - term.scr]) + + enum term_mode { + MODE_WRAP = 1 << 0, +@@ -115,6 +119,9 @@ typedef struct { + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ ++ Line hist[HISTSIZE]; /* history buffer */ ++ int histi; /* history index */ ++ int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ +@@ -184,8 +191,8 @@ static void tnewline(int); + static void tputtab(int); + static void tputc(Rune); + static void treset(void); +-static void tscrollup(int, int); +-static void tscrolldown(int, int); ++static void tscrollup(int, int, int); ++static void tscrolldown(int, int, int); + static void tsetattr(const int *, int); + static void tsetchar(Rune, const Glyph *, int, int); + static void tsetdirt(int, int); +@@ -416,10 +423,10 @@ tlinelen(int y) + { + int i = term.col; + +- if (term.line[y][i - 1].mode & ATTR_WRAP) ++ if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + +- while (i > 0 && term.line[y][i - 1].u == ' ') ++ while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction) + * Snap around if the word wraps around at the end or + * beginning of a line. + */ +- prevgp = &term.line[*y][*x]; ++ prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; +@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction) + yt = *y, xt = *x; + else + yt = newy, xt = newx; +- if (!(term.line[yt][xt].mode & ATTR_WRAP)) ++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + +- gp = &term.line[newy][newx]; ++ gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) +@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction) + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode ++ if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode ++ if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } +@@ -609,13 +616,13 @@ getsel(void) + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &term.line[y][sel.nb.x]; ++ gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; ++ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } +- last = &term.line[y][MIN(lastx, linelen-1)]; ++ last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + +@@ -851,6 +858,9 @@ void + ttywrite(const char *s, size_t n, int may_echo) + { + const char *next; ++ Arg arg = (Arg) { .i = term.scr }; ++ ++ kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); +@@ -1062,12 +1072,52 @@ tswapscreen(void) + } + + void +-tscrolldown(int orig, int n) ++kscrolldown(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (n > term.scr) ++ n = term.scr; ++ ++ if (term.scr > 0) { ++ term.scr -= n; ++ selscroll(0, -n); ++ tfulldirt(); ++ } ++} ++ ++void ++kscrollup(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (term.scr <= HISTSIZE-n) { ++ term.scr += n; ++ selscroll(0, n); ++ tfulldirt(); ++ } ++} ++ ++void ++tscrolldown(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); ++ if (copyhist) { ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[term.bot]; ++ term.line[term.bot] = temp; ++ } ++ + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); +@@ -1078,17 +1128,28 @@ tscrolldown(int orig, int n) + term.line[i-n] = temp; + } + +- selscroll(orig, n); ++ if (term.scr == 0) ++ selscroll(orig, n); + } + + void +-tscrollup(int orig, int n) ++tscrollup(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + ++ if (copyhist) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[orig]; ++ term.line[orig] = temp; ++ } ++ ++ if (term.scr > 0 && term.scr < HISTSIZE) ++ term.scr = MIN(term.scr + n, HISTSIZE-1); ++ + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + +@@ -1098,7 +1159,8 @@ tscrollup(int orig, int n) + term.line[i+n] = temp; + } + +- selscroll(orig, -n); ++ if (term.scr == 0) ++ selscroll(orig, -n); + } + + void +@@ -1127,7 +1189,7 @@ tnewline(int first_col) + int y = term.c.y; + + if (y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + y++; + } +@@ -1292,14 +1354,14 @@ void + tinsertblankline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrolldown(term.c.y, n); ++ tscrolldown(term.c.y, n, 0); + } + + void + tdeleteline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrollup(term.c.y, n); ++ tscrollup(term.c.y, n, 0); + } + + int32_t +@@ -1736,11 +1798,11 @@ csihandle(void) + break; + case 'S': /* SU -- Scroll <n> line up */ + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0]); ++ tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll <n> line down */ + DEFAULT(csiescseq.arg[0], 1); +- tscrolldown(term.top, csiescseq.arg[0]); ++ tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert <n> blank lines */ + DEFAULT(csiescseq.arg[0], 1); +@@ -2330,7 +2392,7 @@ eschandle(uchar ascii) + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } +@@ -2343,7 +2405,7 @@ eschandle(uchar ascii) + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { +- tscrolldown(term.top, 1); ++ tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } +@@ -2557,7 +2619,7 @@ twrite(const char *buf, int buflen, int show_ctrl) + void + tresize(int col, int row) + { +- int i; ++ int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; +@@ -2594,6 +2656,14 @@ tresize(int col, int row) + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + ++ for (i = 0; i < HISTSIZE; i++) { ++ term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); ++ for (j = mincol; j < col; j++) { ++ term.hist[i][j] = term.c.attr; ++ term.hist[i][j].u = ' '; ++ } ++ } ++ + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); +@@ -2652,7 +2722,7 @@ drawregion(int x1, int y1, int x2, int y2) + continue; + + term.dirty[y] = 0; +- xdrawline(term.line[y], x1, y, x2); ++ xdrawline(TLINE(y), x1, y, x2); + } + } + +@@ -2673,8 +2743,9 @@ draw(void) + cx--; + + drawregion(0, 0, term.col, term.row); +- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ if (term.scr == 0) ++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 519b9bd..da36b34 100644 +--- a/st.h ++++ b/st.h +@@ -81,6 +81,8 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void kscrolldown(const Arg *); ++void kscrollup(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); diff --git a/portatil/st/st.1 b/portatil/st/st.1 @@ -0,0 +1,177 @@ +.TH ST 1 st\-VERSION +.SH NAME +st \- simple terminal +.SH SYNOPSIS +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-l +.IR line ] +.RB [ \-w +.IR windowid ] +.RB [[ \-e ] +.IR command +.RI [ arguments ...]] +.PP +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-w +.IR windowid ] +.RB \-l +.IR line +.RI [ stty_args ...] +.SH DESCRIPTION +.B st +is a simple terminal emulator. +.SH OPTIONS +.TP +.B \-a +disable alternate screens in terminal +.TP +.BI \-c " class" +defines the window class (default $TERM). +.TP +.BI \-f " font" +defines the +.I font +to use when st is run. +.TP +.BI \-g " geometry" +defines the X11 geometry string. +The form is [=][<cols>{xX}<rows>][{+-}<xoffset>{+-}<yoffset>]. See +.BR XParseGeometry (3) +for further details. +.TP +.B \-i +will fixate the position given with the -g option. +.TP +.BI \-n " name" +defines the window instance name (default $TERM). +.TP +.BI \-o " iofile" +writes all the I/O to +.I iofile. +This feature is useful when recording st sessions. A value of "-" means +standard output. +.TP +.BI \-T " title" +defines the window title (default 'st'). +.TP +.BI \-t " title" +defines the window title (default 'st'). +.TP +.BI \-w " windowid" +embeds st within the window identified by +.I windowid +.TP +.BI \-l " line" +use a tty +.I line +instead of a pseudo terminal. +.I line +should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port +0). +When this flag is given +remaining arguments are used as flags for +.BR stty(1). +By default st initializes the serial line to 8 bits, no parity, 1 stop bit +and a 38400 baud rate. The speed is set by appending it as last argument +(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are +.BR stty(1) +flags. If you want to set odd parity on 115200 baud use for example 'st -l +/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for +example 'st -l /dev/ttyS0 cs7 115200'. See +.BR stty(1) +for more arguments and cases. +.TP +.B \-v +prints version information to stderr, then exits. +.TP +.BI \-e " command " [ " arguments " "... ]" +st executes +.I command +instead of the shell. If this is used it +.B must be the last option +on the command line, as in xterm / rxvt. +This option is only intended for compatibility, +and all the remaining arguments are used as a command +even without it. +.SH SHORTCUTS +.TP +.B Break +Send a break in the serial line. +Break key is obtained in PC keyboards +pressing at the same time control and pause. +.TP +.B Ctrl-Print Screen +Toggle if st should print to the +.I iofile. +.TP +.B Shift-Print Screen +Print the full screen to the +.I iofile. +.TP +.B Print Screen +Print the selection to the +.I iofile. +.TP +.B Ctrl-Shift-Page Up +Increase font size. +.TP +.B Ctrl-Shift-Page Down +Decrease font size. +.TP +.B Ctrl-Shift-Home +Reset to default font size. +.TP +.B Ctrl-Shift-y +Paste from primary selection (middle mouse button). +.TP +.B Ctrl-Shift-c +Copy the selected text to the clipboard selection. +.TP +.B Ctrl-Shift-v +Paste from the clipboard selection. +.SH CUSTOMIZATION +.B st +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR tabbed (1), +.BR utmp (1), +.BR stty (1), +.BR scroll (1) +.SH BUGS +See the TODO file in the distribution. + diff --git a/portatil/st/st.c b/portatil/st/st.c @@ -0,0 +1,2761 @@ +/* See LICENSE for license details. */ +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include <pty.h> +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include <util.h> +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include <libutil.h> +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert <n> blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor <n> Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor <n> Down */ + case 'e': /* VPR --Cursor <n> Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it <n> more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor <n> Forward */ + case 'a': /* HPR -- Cursor <n> Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor <n> Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor <n> Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor <n> Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to <col> */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to <row> <col> */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll <n> line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll <n> line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert <n> blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete <n> lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase <n> char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete <n> char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to <row> */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc4_color_response(int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(num, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); + return; + } + + n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +osc_color_response(int index, int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultfg, 10); + else if (xsetcolorname(defaultfg, p)) + fprintf(stderr, "erresc: invalid foreground color: %s\n", p); + else + tfulldirt(); + return; + case 11: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultbg, 11); + else if (xsetcolorname(defaultbg, p)) + fprintf(stderr, "erresc: invalid background color: %s\n", p); + else + tfulldirt(); + return; + case 12: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultcs, 12); + else if (xsetcolorname(defaultcs, p)) + fprintf(stderr, "erresc: invalid cursor color: %s\n", p); + else + tfulldirt(); + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) + osc4_color_response(j); + else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/portatil/st/st.c.bak b/portatil/st/st.c.bak @@ -0,0 +1,2690 @@ +/* See LICENSE for license details. */ +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include <pty.h> +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include <util.h> +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include <libutil.h> +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert <n> blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor <n> Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor <n> Down */ + case 'e': /* VPR --Cursor <n> Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it <n> more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor <n> Forward */ + case 'a': /* HPR -- Cursor <n> Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor <n> Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor <n> Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor <n> Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to <col> */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to <row> <col> */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll <n> line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll <n> line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert <n> blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete <n> lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase <n> char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete <n> char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to <row> */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc4_color_response(int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(num, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); + return; + } + + n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +osc_color_response(int index, int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultfg, 10); + else if (xsetcolorname(defaultfg, p)) + fprintf(stderr, "erresc: invalid foreground color: %s\n", p); + else + tfulldirt(); + return; + case 11: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultbg, 11); + else if (xsetcolorname(defaultbg, p)) + fprintf(stderr, "erresc: invalid background color: %s\n", p); + else + tfulldirt(); + return; + case 12: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultcs, 12); + else if (xsetcolorname(defaultcs, p)) + fprintf(stderr, "erresc: invalid cursor color: %s\n", p); + else + tfulldirt(); + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) + osc4_color_response(j); + else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/portatil/st/st.h b/portatil/st/st.h @@ -0,0 +1,131 @@ +/* See LICENSE for license details. */ + +#include <stdint.h> +#include <sys/types.h> + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; +extern float alpha; diff --git a/portatil/st/st.info b/portatil/st/st.info @@ -0,0 +1,239 @@ +st-mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/portatil/st/st.o b/portatil/st/st.o Binary files differ. diff --git a/portatil/st/win.h b/portatil/st/win.h @@ -0,0 +1,40 @@ +/* See LICENSE for license details. */ + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 13, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, +}; + +void xbell(void); +void xclipcopy(void); +void xdrawcursor(int, int, Glyph, int, int, Glyph); +void xdrawline(Line, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +int xsetcolorname(int, const char *); +void xseticontitle(char *); +void xsettitle(char *); +int xsetcursor(int); +void xsetmode(int, unsigned int); +void xsetpointermotion(int); +void xsetsel(char *); +int xstartdraw(void); +void xximspot(int, int); diff --git a/portatil/st/x.c b/portatil/st/x.c @@ -0,0 +1,2116 @@ +/* See LICENSE for license details. */ +#include <errno.h> +#include <math.h> +#include <limits.h> +#include <locale.h> +#include <signal.h> +#include <sys/select.h> +#include <time.h> +#include <unistd.h> +#include <libgen.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xft/Xft.h> +#include <X11/XKBlib.h> + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Button<N>mask for Button<N> - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/portatil/st/x.c.orig b/portatil/st/x.c.orig @@ -0,0 +1,2096 @@ +/* See LICENSE for license details. */ +#include <errno.h> +#include <math.h> +#include <limits.h> +#include <locale.h> +#include <signal.h> +#include <sys/select.h> +#include <time.h> +#include <unistd.h> +#include <libgen.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xft/Xft.h> +#include <X11/XKBlib.h> + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Button<N>mask for Button<N> - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/portatil/st/x.o b/portatil/st/x.o Binary files differ. diff --git a/sxiv/.gitignore b/sxiv/.gitignore @@ -0,0 +1,5 @@ +config.h +version.h +*.d +*.o +sxiv diff --git a/sxiv/LICENSE b/sxiv/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sxiv/Makefile b/sxiv/Makefile @@ -0,0 +1,88 @@ +version = 26 + +srcdir = . +VPATH = $(srcdir) + +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +# autoreload backend: inotify/nop +AUTORELOAD = inotify + +# enable features requiring giflib (-lgif) +HAVE_GIFLIB = 1 + +# enable features requiring libexif (-lexif) +HAVE_LIBEXIF = 1 + +cflags = -std=c99 -Wall -pedantic $(CFLAGS) +cppflags = -I. $(CPPFLAGS) -D_XOPEN_SOURCE=700 \ + -DHAVE_GIFLIB=$(HAVE_GIFLIB) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ + -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 + +lib_exif_0 = +lib_exif_1 = -lexif +lib_gif_0 = +lib_gif_1 = -lgif +ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \ + $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) + +objs = autoreload_$(AUTORELOAD).o commands.o image.o main.o options.o \ + thumbs.o util.o window.o + +all: sxiv + +.PHONY: all clean install uninstall +.SUFFIXES: +.SUFFIXES: .c .o +$(V).SILENT: + +sxiv: $(objs) + @echo "LINK $@" + $(CC) $(LDFLAGS) -o $@ $(objs) $(ldlibs) + +$(objs): Makefile sxiv.h commands.lst config.h +options.o: version.h +window.o: icon/data.h + +.c.o: + @echo "CC $@" + $(CC) $(cflags) $(cppflags) -c -o $@ $< + +config.h: + @echo "GEN $@" + cp $(srcdir)/config.def.h $@ + +version.h: Makefile .git/index + @echo "GEN $@" + v="$$(cd $(srcdir); git describe 2>/dev/null)"; \ + echo "#define VERSION \"$${v:-$(version)}\"" >$@ + +.git/index: + +clean: + rm -f *.o sxiv + +install: all + @echo "INSTALL bin/sxiv" + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp sxiv $(DESTDIR)$(PREFIX)/bin/ + chmod 755 $(DESTDIR)$(PREFIX)/bin/sxiv + @echo "INSTALL sxiv.1" + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s!PREFIX!$(PREFIX)!g; s!VERSION!$(version)!g" sxiv.1 \ + >$(DESTDIR)$(MANPREFIX)/man1/sxiv.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 + @echo "INSTALL share/sxiv/" + mkdir -p $(DESTDIR)$(PREFIX)/share/sxiv/exec + cp exec/* $(DESTDIR)$(PREFIX)/share/sxiv/exec/ + chmod 755 $(DESTDIR)$(PREFIX)/share/sxiv/exec/* + +uninstall: + @echo "REMOVE bin/sxiv" + rm -f $(DESTDIR)$(PREFIX)/bin/sxiv + @echo "REMOVE sxiv.1" + rm -f $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 + @echo "REMOVE share/sxiv/" + rm -rf $(DESTDIR)$(PREFIX)/share/sxiv + diff --git a/sxiv/README.md b/sxiv/README.md @@ -0,0 +1,244 @@ +![sxiv](http://muennich.github.com/sxiv/img/logo.png "sxiv") + +**Simple X Image Viewer** + +The sole purpose of sxiv is to be the perfect image viewer for me. It is free +software so that you can use it and modify it for your needs. Please file a bug +report if something does not work as documented or expected. Contributions are +welcome but there is no guarantee that they will be incorporated. + + +Features +-------- + +* Basic image operations, e.g. zooming, panning, rotating +* Customizable key and mouse button mappings (in *config.h*) +* Thumbnail mode: grid of selectable previews of all images +* Ability to cache thumbnails for fast re-loading +* Basic support for multi-frame images +* Load all frames from GIF files and play GIF animations +* Display image information in status bar + + +Screenshots +----------- + +**Image mode:** + +![Image](http://muennich.github.io/sxiv/img/image.png "Image mode") + +**Thumbnail mode:** + +![Thumb](http://muennich.github.io/sxiv/img/thumb.png "Thumb mode") + + +Dependencies +------------ + +sxiv requires the following software to be installed: + + * Imlib2 + * X11 + * Xft + * freetype2 + * fontconfig + * giflib (optional, disabled with `HAVE_GIFLIB=0`) + * libexif (optional, disabled with `HAVE_LIBEXIF=0`) + +Please make sure to install the corresponding development packages in case that +you want to build sxiv on a distribution with separate runtime and development +packages (e.g. *-dev on Debian). + + +Building +-------- + +sxiv is built using the commands: + + $ make + # make install + +Please note, that the latter one requires root privileges. +By default, sxiv is installed using the prefix "/usr/local", so the full path +of the executable will be "/usr/local/bin/sxiv". + +You can install sxiv into a directory of your choice by changing the second +command to: + + # make PREFIX="/your/dir" install + +The build-time specific settings of sxiv can be found in the file *config.h*. +Please check and change them, so that they fit your needs. +If the file *config.h* does not already exist, then you have to create it with +the following command: + + $ make config.h + + +Usage +----- + +Please see the [man page](http://muennich.github.com/sxiv/sxiv.1.html) for +information on how to use sxiv. + + +Download & Changelog +-------------------- + +You can [browse](https://github.com/muennich/sxiv) the source code repository +on GitHub or get a copy using git with the following command: + + git clone https://github.com/muennich/sxiv.git + +**Stable releases** + +**[v26](https://github.com/muennich/sxiv/archive/v26.tar.gz)** +*(January 16, 2020)* + + * Maintenance release + +**[v25](https://github.com/muennich/sxiv/archive/v25.tar.gz)** +*(January 26, 2019)* + + * Support font fallback for missing glyphs + * Fix busy loop when built without inotify + * Use background/foreground colors from X resource database + +**[v24](https://github.com/muennich/sxiv/archive/v24.tar.gz)** +*(October 27, 2017)* + + * Automatically reload the current image whenever it changes + * Support embedding into other X windows with -e (e.g. tabbed) + * New option -p prevents sxiv from creating cache and temporary files + * Simpler mouse mappings, the most basic features are accessible with the + mouse only (navigate, zoom, pan) + +**[v1.3.2](https://github.com/muennich/sxiv/archive/v1.3.2.tar.gz)** +*(December 20, 2015)* + + * external key handler gets file paths on stdin, not as arguments + * Cache out-of-view thumbnails in the background + * Apply gamma correction to thumbnails + +**[v1.3.1](https://github.com/muennich/sxiv/archive/v1.3.1.tar.gz)** +*(November 16, 2014)* + + * Fixed build error, caused by delayed config.h creation + * Fixed segfault when run with -c + +**[v1.3](https://github.com/muennich/sxiv/archive/v1.3.tar.gz)** +*(October 24, 2014)* + + * Extract thumbnails from EXIF tags (requires libexif) + * Zoomable thumbnails, supported sizes defined in config.h + * Fixed build error with giflib version >= 5.1.0 + +**[v1.2](https://github.com/muennich/sxiv/archive/v1.2.tar.gz)** +*(April 24, 2014)* + + * Added external key handler, called on keys prefixed with `Ctrl-x` + * New keybinding `{`/`}` to change gamma (by András Mohari) + * Support for slideshows, enabled with `-S` option & toggled with `s` + * Added application icon (created by 0ion9) + * Checkerboard background for alpha layer + * Option `-o` only prints files marked with `m` key + * Fixed rotation/flipping of multi-frame images (gifs) + +**[v1.1.1](https://github.com/muennich/sxiv/archive/v1.1.1.tar.gz)** +*(June 2, 2013)* + + * Various bug fixes + +**[v1.1](https://github.com/muennich/sxiv/archive/v1.1.tar.gz)** +*(March 30, 2013)* + + * Added status bar on bottom of window with customizable content + * New keyboard shortcuts `\`/`|`: flip image vertically/horizontally + * New keyboard shortcut `Ctrl-6`: go to last/alternate image + * Added own EXIF orientation handling, removed dependency on libexif + * Fixed various bugs + +**[v1.0](https://github.com/muennich/sxiv/archive/v1.0.tar.gz)** +*(October 31, 2011)* + + * Support for multi-frame images & GIF animations + * POSIX compliant (IEEE Std 1003.1-2001) + +**[v0.9](https://github.com/muennich/sxiv/archive/v0.9.tar.gz)** +*(August 17, 2011)* + + * Made key and mouse mappings fully configurable in config.h + * Complete code refactoring + +**[v0.8.2](https://github.com/muennich/sxiv/archive/v0.8.2.tar.gz)** +*(June 29, 2011)* + + * POSIX-compliant Makefile; compiles under NetBSD + +**[v0.8.1](https://github.com/muennich/sxiv/archive/v0.8.1.tar.gz)** +*(May 8, 2011)* + + * Fixed fullscreen under window managers, which are not fully EWMH-compliant + +**[v0.8](https://github.com/muennich/sxiv/archive/v0.8.tar.gz)** +*(April 18, 2011)* + + * Support for thumbnail caching + * Ability to run external commands (e.g. jpegtran, convert) on current image + +**[v0.7](https://github.com/muennich/sxiv/archive/v0.7.tar.gz)** +*(February 26, 2011)* + + * Sort directory entries when using `-r` command line option + * Hide cursor in image mode + * Full functional thumbnail mode, use Return key to switch between image and + thumbnail mode + +**[v0.6](https://github.com/muennich/sxiv/archive/v0.6.tar.gz)** +*(February 16, 2011)* + + * Bug fix: Correctly display filenames with umlauts in window title + * Basic support of thumbnails + +**[v0.5](https://github.com/muennich/sxiv/archive/v0.5.tar.gz)** +*(February 6, 2011)* + + * New command line option: `-r`: open all images in given directories + * New key shortcuts: `w`: resize image to fit into window; `W`: resize window + to fit to image + +**[v0.4](https://github.com/muennich/sxiv/archive/v0.4.tar.gz)** +*(February 1, 2011)* + + * New command line option: `-F`, `-g`: use fixed window dimensions and apply + a given window geometry + * New key shortcut: `r`: reload current image + +**[v0.3.1](https://github.com/muennich/sxiv/archive/v0.3.1.tar.gz)** +*(January 30, 2011)* + + * Bug fix: Do not set setuid bit on executable when using `make install` + * Pan image with mouse while pressing middle mouse button + +**[v0.3](https://github.com/muennich/sxiv/archive/v0.3.tar.gz)** +*(January 29, 2011)* + + * New command line options: `-d`, `-f`, `-p`, `-s`, `-v`, `-w`, `-Z`, `-z` + * More mouse mappings: Go to next/previous image with left/right click, + scroll image with mouse wheel (horizontally if Shift key is pressed), + zoom image with mouse wheel if Ctrl key is pressed + +**[v0.2](https://github.com/muennich/sxiv/archive/v0.2.tar.gz)** +*(January 23, 2011)* + + * Bug fix: Handle window resizes correctly + * New keyboard shortcuts: `g`/`G`: go to first/last image; `[`/`]`: go 10 + images back/forward + * Support for mouse wheel zooming (by Dave Reisner) + * Added fullscreen mode + +**[v0.1](https://github.com/muennich/sxiv/archive/v0.1.tar.gz)** +*(January 21, 2011)* + + * Initial release + diff --git a/sxiv/TODO b/sxiv/TODO @@ -0,0 +1,5 @@ +- Load all frames from TIFF files. We have to write our own loader for this to + happen--just like we did for GIF images--because Imlib2 does not support + multiple frames. Issue #241. +- Add support for more embedded thumbnail formats. Right now, sxiv seems to use + the smallest one. Issue #238. diff --git a/sxiv/autoreload_inotify.c b/sxiv/autoreload_inotify.c @@ -0,0 +1,112 @@ +/* Copyright 2017 Max Voit, Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/inotify.h> + +void arl_init(arl_t *arl) +{ + arl->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); + arl->wd_dir = arl->wd_file = -1; + if (arl->fd == -1) + error(0, 0, "Could not initialize inotify, no automatic image reloading"); +} + +CLEANUP void arl_cleanup(arl_t *arl) +{ + if (arl->fd != -1) + close(arl->fd); + free(arl->filename); +} + +static void rm_watch(int fd, int *wd) +{ + if (*wd != -1) { + inotify_rm_watch(fd, *wd); + *wd = -1; + } +} + +static void add_watch(int fd, int *wd, const char *path, uint32_t mask) +{ + *wd = inotify_add_watch(fd, path, mask); + if (*wd == -1) + error(0, errno, "inotify: %s", path); +} + +void arl_setup(arl_t *arl, const char *filepath) +{ + char *base = strrchr(filepath, '/'); + + if (arl->fd == -1) + return; + + rm_watch(arl->fd, &arl->wd_dir); + rm_watch(arl->fd, &arl->wd_file); + + add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF); + + free(arl->filename); + arl->filename = estrdup(filepath); + + if (base != NULL) { + arl->filename[++base - filepath] = '\0'; + add_watch(arl->fd, &arl->wd_dir, arl->filename, IN_CREATE | IN_MOVED_TO); + strcpy(arl->filename, base); + } +} + +union { + char d[4096]; /* aligned buffer */ + struct inotify_event e; +} buf; + +bool arl_handle(arl_t *arl) +{ + bool reload = false; + char *ptr; + const struct inotify_event *e; + + for (;;) { + ssize_t len = read(arl->fd, buf.d, sizeof(buf.d)); + + if (len == -1) { + if (errno == EINTR) + continue; + break; + } + for (ptr = buf.d; ptr < buf.d + len; ptr += sizeof(*e) + e->len) { + e = (const struct inotify_event*) ptr; + if (e->wd == arl->wd_file && (e->mask & IN_CLOSE_WRITE)) { + reload = true; + } else if (e->wd == arl->wd_file && (e->mask & IN_DELETE_SELF)) { + rm_watch(arl->fd, &arl->wd_file); + } else if (e->wd == arl->wd_dir && (e->mask & (IN_CREATE | IN_MOVED_TO))) { + if (STREQ(e->name, arl->filename)) + reload = true; + } + } + } + return reload; +} + diff --git a/sxiv/autoreload_nop.c b/sxiv/autoreload_nop.c @@ -0,0 +1,42 @@ +/* Copyright 2017 Max Voit + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" + +void arl_init(arl_t *arl) +{ + arl->fd = -1; +} + +void arl_cleanup(arl_t *arl) +{ + (void) arl; +} + +void arl_setup(arl_t *arl, const char *filepath) +{ + (void) arl; + (void) filepath; +} + +bool arl_handle(arl_t *arl) +{ + (void) arl; + return false; +} + diff --git a/sxiv/commands.c b/sxiv/commands.c @@ -0,0 +1,455 @@ +/* Copyright 2011, 2012, 2014 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" +#define _IMAGE_CONFIG +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> + +void remove_file(int, bool); +void load_image(int); +bool mark_image(int, bool); +void close_info(void); +void open_info(void); +int ptr_third_x(void); +void redraw(void); +void reset_cursor(void); +void animate(void); +void slideshow(void); +void set_timeout(timeout_f, int, bool); +void reset_timeout(timeout_f); + +extern appmode_t mode; +extern img_t img; +extern tns_t tns; +extern win_t win; + +extern fileinfo_t *files; +extern int filecnt, fileidx; +extern int alternate; +extern int markcnt; +extern int markidx; + +extern int prefix; +extern bool extprefix; + +bool cg_quit(arg_t _) +{ + unsigned int i; + + if (options->to_stdout && markcnt > 0) { + for (i = 0; i < filecnt; i++) { + if (files[i].flags & FF_MARK) + printf("%s\n", files[i].name); + } + } + exit(EXIT_SUCCESS); +} + +bool cg_switch_mode(arg_t _) +{ + if (mode == MODE_IMAGE) { + if (tns.thumbs == NULL) + tns_init(&tns, files, &filecnt, &fileidx, &win); + img_close(&img, false); + reset_timeout(reset_cursor); + if (img.ss.on) { + img.ss.on = false; + reset_timeout(slideshow); + } + tns.dirty = true; + mode = MODE_THUMB; + } else { + load_image(fileidx); + mode = MODE_IMAGE; + } + return true; +} + +bool cg_toggle_fullscreen(arg_t _) +{ + win_toggle_fullscreen(&win); + /* redraw after next ConfigureNotify event */ + set_timeout(redraw, TO_REDRAW_RESIZE, false); + if (mode == MODE_IMAGE) + img.checkpan = img.dirty = true; + else + tns.dirty = true; + return false; +} + +bool cg_toggle_bar(arg_t _) +{ + win_toggle_bar(&win); + if (mode == MODE_IMAGE) { + if (win.bar.h > 0) + open_info(); + else + close_info(); + img.checkpan = img.dirty = true; + } else { + tns.dirty = true; + } + return true; +} + +bool cg_prefix_external(arg_t _) +{ + extprefix = true; + return false; +} + +bool cg_reload_image(arg_t _) +{ + if (mode == MODE_IMAGE) { + load_image(fileidx); + } else { + win_set_cursor(&win, CURSOR_WATCH); + if (!tns_load(&tns, fileidx, true, false)) { + remove_file(fileidx, false); + tns.dirty = true; + } + } + return true; +} + +bool cg_remove_image(arg_t _) +{ + remove_file(fileidx, true); + if (mode == MODE_IMAGE) + load_image(fileidx); + else + tns.dirty = true; + return true; +} + +bool cg_first(arg_t _) +{ + if (mode == MODE_IMAGE && fileidx != 0) { + load_image(0); + return true; + } else if (mode == MODE_THUMB && fileidx != 0) { + fileidx = 0; + tns.dirty = true; + return true; + } else { + return false; + } +} + +bool cg_n_or_last(arg_t _) +{ + int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1; + + if (mode == MODE_IMAGE && fileidx != n) { + load_image(n); + return true; + } else if (mode == MODE_THUMB && fileidx != n) { + fileidx = n; + tns.dirty = true; + return true; + } else { + return false; + } +} + +bool cg_scroll_screen(arg_t dir) +{ + if (mode == MODE_IMAGE) + return img_pan(&img, dir, -1); + else + return tns_scroll(&tns, dir, true); +} + +bool cg_zoom(arg_t d) +{ + if (mode == MODE_THUMB) + return tns_zoom(&tns, d); + else if (d > 0) + return img_zoom_in(&img); + else if (d < 0) + return img_zoom_out(&img); + else + return false; +} + +bool cg_toggle_image_mark(arg_t _) +{ + return mark_image(fileidx, !(files[fileidx].flags & FF_MARK)); +} + +bool cg_reverse_marks(arg_t _) +{ + int i; + + for (i = 0; i < filecnt; i++) { + files[i].flags ^= FF_MARK; + markcnt += files[i].flags & FF_MARK ? 1 : -1; + } + if (mode == MODE_THUMB) + tns.dirty = true; + return true; +} + +bool cg_mark_range(arg_t _) +{ + int d = markidx < fileidx ? 1 : -1, end, i; + bool dirty = false, on = !!(files[markidx].flags & FF_MARK); + + for (i = markidx + d, end = fileidx + d; i != end; i += d) + dirty |= mark_image(i, on); + return dirty; +} + +bool cg_unmark_all(arg_t _) +{ + int i; + + for (i = 0; i < filecnt; i++) + files[i].flags &= ~FF_MARK; + markcnt = 0; + if (mode == MODE_THUMB) + tns.dirty = true; + return true; +} + +bool cg_navigate_marked(arg_t n) +{ + int d, i; + int new = fileidx; + + if (prefix > 0) + n *= prefix; + d = n > 0 ? 1 : -1; + for (i = fileidx + d; n != 0 && i >= 0 && i < filecnt; i += d) { + if (files[i].flags & FF_MARK) { + n -= d; + new = i; + } + } + if (new != fileidx) { + if (mode == MODE_IMAGE) { + load_image(new); + } else { + fileidx = new; + tns.dirty = true; + } + return true; + } else { + return false; + } +} + +bool cg_change_gamma(arg_t d) +{ + if (img_change_gamma(&img, d * (prefix > 0 ? prefix : 1))) { + if (mode == MODE_THUMB) + tns.dirty = true; + return true; + } else { + return false; + } +} + +bool ci_navigate(arg_t n) +{ + if (prefix > 0) + n *= prefix; + n += fileidx; + if (n < 0) + n = 0; + if (n >= filecnt) + n = filecnt - 1; + + if (n != fileidx) { + load_image(n); + return true; + } else { + return false; + } +} + +bool ci_cursor_navigate(arg_t _) +{ + return ci_navigate(ptr_third_x() - 1); +} + +bool ci_alternate(arg_t _) +{ + load_image(alternate); + return true; +} + +bool ci_navigate_frame(arg_t d) +{ + if (prefix > 0) + d *= prefix; + return !img.multi.animate && img_frame_navigate(&img, d); +} + +bool ci_toggle_animation(arg_t _) +{ + bool dirty = false; + + if (img.multi.cnt > 0) { + img.multi.animate = !img.multi.animate; + if (img.multi.animate) { + dirty = img_frame_animate(&img); + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + } else { + reset_timeout(animate); + } + } + return dirty; +} + +bool ci_scroll(arg_t dir) +{ + return img_pan(&img, dir, prefix); +} + +bool ci_scroll_to_edge(arg_t dir) +{ + return img_pan_edge(&img, dir); +} + +bool ci_drag(arg_t mode) +{ + int x, y, ox, oy; + float px, py; + XEvent e; + + if ((int)(img.w * img.zoom) <= win.w && (int)(img.h * img.zoom) <= win.h) + return false; + + win_set_cursor(&win, CURSOR_DRAG); + + win_cursor_pos(&win, &x, &y); + ox = x; + oy = y; + + for (;;) { + if (mode == DRAG_ABSOLUTE) { + px = MIN(MAX(0.0, x - win.w*0.1), win.w*0.8) / (win.w*0.8) + * (win.w - img.w * img.zoom); + py = MIN(MAX(0.0, y - win.h*0.1), win.h*0.8) / (win.h*0.8) + * (win.h - img.h * img.zoom); + } else { + px = img.x + x - ox; + py = img.y + y - oy; + } + + if (img_pos(&img, px, py)) { + img_render(&img); + win_draw(&win); + } + XMaskEvent(win.env.dpy, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); + if (e.type == ButtonPress || e.type == ButtonRelease) + break; + while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); + ox = x; + oy = y; + x = e.xmotion.x; + y = e.xmotion.y; + } + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + + return true; +} + +bool ci_set_zoom(arg_t zl) +{ + return img_zoom(&img, (prefix ? prefix : zl) / 100.0); +} + +bool ci_fit_to_win(arg_t sm) +{ + return img_fit_win(&img, sm); +} + +bool ci_rotate(arg_t degree) +{ + img_rotate(&img, degree); + return true; +} + +bool ci_flip(arg_t dir) +{ + img_flip(&img, dir); + return true; +} + +bool ci_toggle_antialias(arg_t _) +{ + img_toggle_antialias(&img); + return true; +} + +bool ci_toggle_alpha(arg_t _) +{ + img.alpha = !img.alpha; + img.dirty = true; + return true; +} + +bool ci_slideshow(arg_t _) +{ + if (prefix > 0) { + img.ss.on = true; + img.ss.delay = prefix * 10; + set_timeout(slideshow, img.ss.delay * 100, true); + } else if (img.ss.on) { + img.ss.on = false; + reset_timeout(slideshow); + } else { + img.ss.on = true; + } + return true; +} + +bool ct_move_sel(arg_t dir) +{ + return tns_move_selection(&tns, dir, prefix); +} + +bool ct_reload_all(arg_t _) +{ + tns_free(&tns); + tns_init(&tns, files, &filecnt, &fileidx, &win); + tns.dirty = true; + return true; +} + + +#undef G_CMD +#define G_CMD(c) { -1, cg_##c }, +#undef I_CMD +#define I_CMD(c) { MODE_IMAGE, ci_##c }, +#undef T_CMD +#define T_CMD(c) { MODE_THUMB, ct_##c }, + +const cmd_t cmds[CMD_COUNT] = { +#include "commands.lst" +}; + diff --git a/sxiv/commands.lst b/sxiv/commands.lst @@ -0,0 +1,37 @@ +G_CMD(quit) +G_CMD(switch_mode) +G_CMD(toggle_fullscreen) +G_CMD(toggle_bar) +G_CMD(prefix_external) +G_CMD(reload_image) +G_CMD(remove_image) +G_CMD(first) +G_CMD(n_or_last) +G_CMD(scroll_screen) +G_CMD(zoom) +G_CMD(toggle_image_mark) +G_CMD(reverse_marks) +G_CMD(mark_range) +G_CMD(unmark_all) +G_CMD(navigate_marked) +G_CMD(change_gamma) + +I_CMD(navigate) +I_CMD(cursor_navigate) +I_CMD(alternate) +I_CMD(navigate_frame) +I_CMD(toggle_animation) +I_CMD(scroll) +I_CMD(scroll_to_edge) +I_CMD(drag) +I_CMD(set_zoom) +I_CMD(fit_to_win) +I_CMD(rotate) +I_CMD(flip) +I_CMD(toggle_antialias) +I_CMD(toggle_alpha) +I_CMD(slideshow) + +T_CMD(move_sel) +T_CMD(reload_all) + diff --git a/sxiv/config.def.h b/sxiv/config.def.h @@ -0,0 +1,152 @@ +#ifdef _WINDOW_CONFIG + +/* default window dimensions (overwritten via -g option): */ +enum { + WIN_WIDTH = 800, + WIN_HEIGHT = 600 +}; + +/* colors and font are configured with 'background', 'foreground' and + * 'font' X resource properties. + * See X(7) section Resources and xrdb(1) for more information. + */ + +#endif +#ifdef _IMAGE_CONFIG + +/* levels (in percent) to use when zooming via '-' and '+': + * (first/last value is used as min/max zoom level) + */ +static const float zoom_levels[] = { + 12.5, 25.0, 50.0, 75.0, + 100.0, 150.0, 200.0, 400.0, 800.0 +}; + +/* default slideshow delay (in sec, overwritten via -S option): */ +enum { SLIDESHOW_DELAY = 5 }; + +/* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and + * (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX]. + * */ +static const double GAMMA_MAX = 10.0; +static const int GAMMA_RANGE = 32; + +/* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ +static const int PAN_FRACTION = 5; + +/* if false, pixelate images at zoom level != 100%, + * toggled with 'a' key binding + */ +static const bool ANTI_ALIAS = true; + +/* if true, use a checkerboard background for alpha layer, + * toggled with 'A' key binding + */ +static const bool ALPHA_LAYER = false; + +#endif +#ifdef _THUMBS_CONFIG + +/* thumbnail sizes in pixels (width == height): */ +static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; + +/* thumbnail size at startup, index into thumb_sizes[]: */ +static const int THUMB_SIZE = 3; + +#endif +#ifdef _MAPPINGS_CONFIG + +/* keyboard mappings for image and thumbnail mode: */ +static const keymap_t keys[] = { + /* modifiers key function argument */ + { 0, XK_q, g_quit, None }, + { 0, XK_Return, g_switch_mode, None }, + { 0, XK_f, g_toggle_fullscreen, None }, + { 0, XK_b, g_toggle_bar, None }, + { ControlMask, XK_x, g_prefix_external, None }, + { 0, XK_g, g_first, None }, + { 0, XK_G, g_n_or_last, None }, + { 0, XK_r, g_reload_image, None }, + { 0, XK_D, g_remove_image, None }, + { ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, + { ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, + { ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, + { ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, + { ControlMask, XK_k, g_scroll_screen, DIR_UP }, + { ControlMask, XK_Up, g_scroll_screen, DIR_UP }, + { ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, + { ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, + { 0, XK_plus, g_zoom, +1 }, + { 0, XK_KP_Add, g_zoom, +1 }, + { 0, XK_minus, g_zoom, -1 }, + { 0, XK_KP_Subtract, g_zoom, -1 }, + { 0, XK_m, g_toggle_image_mark, None }, + { 0, XK_M, g_mark_range, None }, + { ControlMask, XK_m, g_reverse_marks, None }, + { ControlMask, XK_u, g_unmark_all, None }, + { 0, XK_N, g_navigate_marked, +1 }, + { 0, XK_P, g_navigate_marked, -1 }, + { 0, XK_braceleft, g_change_gamma, -1 }, + { 0, XK_braceright, g_change_gamma, +1 }, + { ControlMask, XK_g, g_change_gamma, 0 }, + + { 0, XK_h, t_move_sel, DIR_LEFT }, + { 0, XK_Left, t_move_sel, DIR_LEFT }, + { 0, XK_j, t_move_sel, DIR_DOWN }, + { 0, XK_Down, t_move_sel, DIR_DOWN }, + { 0, XK_k, t_move_sel, DIR_UP }, + { 0, XK_Up, t_move_sel, DIR_UP }, + { 0, XK_l, t_move_sel, DIR_RIGHT }, + { 0, XK_Right, t_move_sel, DIR_RIGHT }, + { 0, XK_R, t_reload_all, None }, + + { 0, XK_n, i_navigate, +1 }, + { 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, + { 0, XK_space, i_navigate, +1 }, + { 0, XK_p, i_navigate, -1 }, + { 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, + { 0, XK_BackSpace, i_navigate, -1 }, + { 0, XK_bracketright, i_navigate, +10 }, + { 0, XK_bracketleft, i_navigate, -10 }, + { ControlMask, XK_6, i_alternate, None }, + { ControlMask, XK_n, i_navigate_frame, +1 }, + { ControlMask, XK_p, i_navigate_frame, -1 }, + { ControlMask, XK_space, i_toggle_animation, None }, + { 0, XK_h, i_scroll, DIR_LEFT }, + { 0, XK_Left, i_scroll, DIR_LEFT }, + { 0, XK_j, i_scroll, DIR_DOWN }, + { 0, XK_Down, i_scroll, DIR_DOWN }, + { 0, XK_k, i_scroll, DIR_UP }, + { 0, XK_Up, i_scroll, DIR_UP }, + { 0, XK_l, i_scroll, DIR_RIGHT }, + { 0, XK_Right, i_scroll, DIR_RIGHT }, + { 0, XK_H, i_scroll_to_edge, DIR_LEFT }, + { 0, XK_J, i_scroll_to_edge, DIR_DOWN }, + { 0, XK_K, i_scroll_to_edge, DIR_UP }, + { 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, + { 0, XK_equal, i_set_zoom, 100 }, + { 0, XK_w, i_fit_to_win, SCALE_DOWN }, + { 0, XK_W, i_fit_to_win, SCALE_FIT }, + { 0, XK_e, i_fit_to_win, SCALE_WIDTH }, + { 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, + { 0, XK_less, i_rotate, DEGREE_270 }, + { 0, XK_greater, i_rotate, DEGREE_90 }, + { 0, XK_question, i_rotate, DEGREE_180 }, + { 0, XK_bar, i_flip, FLIP_HORIZONTAL }, + { 0, XK_underscore, i_flip, FLIP_VERTICAL }, + { 0, XK_a, i_toggle_antialias, None }, + { 0, XK_A, i_toggle_alpha, None }, + { 0, XK_s, i_slideshow, None }, +}; + +/* mouse button mappings for image mode: */ +static const button_t buttons[] = { + /* modifiers button function argument */ + { 0, 1, i_cursor_navigate, None }, + { 0, 2, i_drag, DRAG_ABSOLUTE }, + { 0, 3, g_switch_mode, None }, + { 0, 4, g_zoom, +1 }, + { 0, 5, g_zoom, -1 }, +}; + +#endif diff --git a/sxiv/exec/image-info b/sxiv/exec/image-info @@ -0,0 +1,20 @@ +#!/bin/sh + +# Example for $XDG_CONFIG_HOME/sxiv/exec/image-info +# Called by sxiv(1) whenever an image gets loaded. +# The output is displayed in sxiv's status bar. +# Arguments: +# $1: path to image file +# $2: image width +# $3: image height + +s=" " # field separator + +exec 2>/dev/null + +filename=$(basename -- "$1") +filesize=$(du -Hh -- "$1" | cut -f 1) +geometry="${2}x${3}" + +echo "${filesize}${s}${geometry}${s}${filename}" + diff --git a/sxiv/exec/key-handler b/sxiv/exec/key-handler @@ -0,0 +1,35 @@ +#!/bin/sh + +# Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler +# Called by sxiv(1) after the external prefix key (C-x by default) is pressed. +# The next key combo is passed as its first argument. Passed via stdin are the +# images to act upon, one path per line: all marked images, if in thumbnail +# mode and at least one image has been marked, otherwise the current image. +# sxiv(1) blocks until this script terminates. It then checks which images +# have been modified and reloads them. + +# The key combo argument has the following form: "[C-][M-][S-]KEY", +# where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X +# keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. + +rotate() { + degree="$1" + tr '\n' '\0' | xargs -0 realpath | sort | uniq | while read file; do + case "$(file -b -i "$file")" in + image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;; + *) mogrify -rotate "$degree" "$file" ;; + esac + done +} + +case "$1" in +"C-x") xclip -in -filter | tr '\n' ' ' | xclip -in -selection clipboard ;; +"C-c") while read file; do xclip -selection clipboard -target image/png "$file"; done ;; +"C-e") while read file; do urxvt -bg "#444" -fg "#eee" -sl 0 -title "$file" -e sh -c "exiv2 pr -q -pa '$file' | less" & done ;; +"C-g") tr '\n' '\0' | xargs -0 gimp & ;; +"C-r") while read file; do rawtherapee "$file" & done ;; +"C-comma") rotate 270 ;; +"C-period") rotate 90 ;; +"C-slash") rotate 180 ;; +esac + diff --git a/sxiv/icon/128x128.png b/sxiv/icon/128x128.png Binary files differ. diff --git a/sxiv/icon/16x16.png b/sxiv/icon/16x16.png Binary files differ. diff --git a/sxiv/icon/32x32.png b/sxiv/icon/32x32.png Binary files differ. diff --git a/sxiv/icon/48x48.png b/sxiv/icon/48x48.png Binary files differ. diff --git a/sxiv/icon/64x64.png b/sxiv/icon/64x64.png Binary files differ. diff --git a/sxiv/icon/Makefile b/sxiv/icon/Makefile @@ -0,0 +1,12 @@ +PREFIX = /usr/local +ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png + +all: + +install: + for f in $(ICONS); do \ + dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \ + mkdir -p "$$dir"; \ + cp "$$f" "$$dir/sxiv.png"; \ + chmod 644 "$$dir/sxiv.png"; \ + done diff --git a/sxiv/icon/dat2h.awk b/sxiv/icon/dat2h.awk @@ -0,0 +1,35 @@ +#!/usr/bin/awk -f + +function printchars() { + while (n > 0) { + x = n / 16 >= 1 ? 16 : n; + printf("0x%x%x,%s", x - 1, ref[c] - 1, ++i % 12 == 0 ? "\n" : " "); + n -= x; + } +} + +/^$/ { + printchars(); + printf("\n\n"); + c = ""; + i = 0; +} + +/./ { + if (!ref[$0]) { + col[cnt++] = $0; + ref[$0] = cnt; + } + if ($0 != c) { + if (c != "") + printchars(); + c = $0; + n = 0; + } + n++; +} + +END { + for (i = 0; i < cnt; i++) + printf("%s,%s", col[i], ++j % 4 == 0 || i + 1 == cnt ? "\n" : " "); +} diff --git a/sxiv/icon/data.h b/sxiv/icon/data.h @@ -0,0 +1,247 @@ +#ifndef ICON_DATA_H +#define ICON_DATA_H + +typedef struct { + unsigned int size; + unsigned int cnt; + const unsigned char *data; +} icon_data_t; + +static const unsigned int icon_colors[] = { + 0xff222034, 0xffffffff, 0xff306082, 0xff76428a, + 0xfffbf236, 0xff99e550, 0xffd95763, 0xff37946e, + 0xff6abe30, 0xffac3232 +}; + +static const unsigned char icon_data_16[] = { + 0xf0, 0x80, 0x01, 0xf0, 0x80, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x40, 0x01, 0x10, 0x01, 0x10, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x30, 0x11, 0x00, 0x01, 0x00, 0x51, 0xf0, 0x22, 0xa0, 0x42, 0x80, 0x62, + 0x03, 0x50, 0x02, 0x34, 0x05, 0x22, 0x13, 0x06, 0x10, 0x37, 0x08, 0x35, + 0x12, 0x13, 0x09, 0x06, 0x22, 0x47, 0x25, 0x02, 0x13, 0x09, 0x06, 0x32, + 0x47, 0x08, 0x05, 0x08, 0x03, 0x19, 0x16, 0x32, 0x47, 0x18, 0x29, 0x16, + 0x42, 0x37, 0x18, 0x19, 0x26, 0x42, 0x47, 0x08 +}; + +static const unsigned char icon_data_32[] = { + 0xf0, 0x10, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, + 0x10, 0x11, 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, + 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x90, 0x11, + 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x00, 0x22, 0x50, 0x11, + 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x50, 0x11, 0x30, + 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x30, 0x31, 0x10, 0x11, + 0x10, 0xb1, 0x52, 0x30, 0x31, 0x10, 0x11, 0x10, 0xb1, 0x52, 0x30, 0x31, + 0x10, 0x11, 0x10, 0xb1, 0x52, 0xf0, 0x10, 0xd2, 0xf0, 0x00, 0xe2, 0xf0, + 0x54, 0x92, 0x13, 0xc0, 0x84, 0x05, 0x62, 0x33, 0x80, 0x74, 0x55, 0x42, + 0x33, 0x09, 0x02, 0x30, 0x77, 0x08, 0x85, 0x32, 0x33, 0x19, 0x12, 0xc7, + 0x08, 0x65, 0x08, 0x12, 0x33, 0x19, 0x06, 0x52, 0x97, 0x08, 0x45, 0x18, + 0x02, 0x33, 0x19, 0x16, 0x62, 0x97, 0x08, 0x35, 0x18, 0x23, 0x29, 0x16, + 0x72, 0x97, 0x18, 0x15, 0x18, 0x23, 0x29, 0x26, 0x72, 0x97, 0x48, 0x13, + 0x39, 0x26, 0x72, 0x97, 0x48, 0x03, 0x39, 0x36, 0x82, 0x97, 0x38, 0x49, + 0x36, 0x82, 0x97, 0x38, 0x39, 0x46, 0x82, 0x97, 0x38, 0x29, 0x56, 0x92, + 0x97, 0x28, 0x29, 0x56, 0x92, 0x97, 0x28 +}; + +static const unsigned char icon_data_48[] = { + 0xf0, 0xa0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, + 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, + 0x20, 0x21, 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, + 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x81, + 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, + 0x50, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, + 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, + 0x20, 0x21, 0x10, 0x32, 0x80, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, + 0x20, 0x21, 0x52, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x00, 0x72, + 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, + 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, + 0xf0, 0xf0, 0x00, 0xe2, 0xf0, 0xe0, 0xf2, 0x02, 0xf0, 0xc0, 0xf2, 0x22, + 0xf0, 0xb0, 0xf2, 0x32, 0xf0, 0xa0, 0xf2, 0x42, 0xf0, 0x90, 0xf2, 0x52, + 0xf0, 0x80, 0x74, 0x15, 0xc2, 0x03, 0xf0, 0x50, 0xc4, 0x15, 0x92, 0x23, + 0xf0, 0x10, 0xe4, 0x35, 0x72, 0x33, 0x19, 0xc0, 0xc4, 0x85, 0x62, 0x43, + 0x19, 0x12, 0x70, 0x57, 0x18, 0xf5, 0x05, 0x52, 0x53, 0x19, 0x12, 0x40, + 0xb7, 0x18, 0xe5, 0x32, 0x53, 0x29, 0x22, 0xf7, 0x37, 0xb5, 0x08, 0x22, + 0x53, 0x29, 0x06, 0x72, 0xf7, 0xa5, 0x08, 0x12, 0x53, 0x29, 0x16, 0x92, + 0xe7, 0x85, 0x18, 0x02, 0x43, 0x39, 0x26, 0x92, 0xe7, 0x08, 0x65, 0x28, + 0x43, 0x39, 0x26, 0xa2, 0xe7, 0x18, 0x45, 0x28, 0x43, 0x39, 0x36, 0xa2, + 0xe7, 0x28, 0x25, 0x28, 0x33, 0x49, 0x36, 0xb2, 0xe7, 0x78, 0x33, 0x49, + 0x46, 0xb2, 0xe7, 0x68, 0x23, 0x59, 0x46, 0xb2, 0xe7, 0x68, 0x13, 0x59, + 0x56, 0xc2, 0xe7, 0x58, 0x79, 0x56, 0xc2, 0xe7, 0x58, 0x69, 0x66, 0xd2, + 0xd7, 0x58, 0x59, 0x76, 0xd2, 0xd7, 0x58, 0x49, 0x86, 0xe2, 0xe7, 0x38, + 0x39, 0x86, 0xf2, 0xe7, 0x38, 0x29, 0x86, 0xf2, 0x02, 0xe7, 0x38 +}; + +static const unsigned char icon_data_64[] = { + 0xf0, 0xf0, 0x30, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, + 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, + 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, + 0x31, 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, + 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, + 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, + 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, + 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, + 0xf0, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x20, + 0x42, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x00, + 0x62, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, + 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, + 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, 0x31, + 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0x70, 0x71, 0x30, + 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, + 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, + 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, + 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0xf0, 0xf0, 0x50, + 0xf2, 0x92, 0xf0, 0xf0, 0x30, 0xf2, 0xb2, 0xf0, 0xf0, 0x20, 0xf2, 0xc2, + 0xf0, 0xf0, 0x10, 0xf2, 0xd2, 0xf0, 0xf0, 0x00, 0x94, 0xf2, 0x42, 0xf0, + 0xe0, 0xe4, 0xf2, 0x12, 0x23, 0xf0, 0xa0, 0xf4, 0x14, 0x05, 0xe2, 0x43, + 0xf0, 0x70, 0xf4, 0x14, 0x35, 0xc2, 0x43, 0x19, 0xf0, 0x30, 0xf4, 0x95, + 0xa2, 0x53, 0x29, 0xe0, 0xf4, 0x04, 0xd5, 0x82, 0x63, 0x29, 0x02, 0x90, + 0xc7, 0xf5, 0x65, 0x62, 0x73, 0x29, 0x12, 0x50, 0xf7, 0x27, 0xf5, 0x35, + 0x52, 0x73, 0x29, 0x06, 0x32, 0xf7, 0x87, 0xf5, 0x05, 0x08, 0x42, 0x73, + 0x39, 0x06, 0x32, 0xf7, 0xa7, 0xd5, 0x28, 0x22, 0x73, 0x39, 0x16, 0xa2, + 0xf7, 0x47, 0xb5, 0x38, 0x12, 0x73, 0x39, 0x26, 0xb2, 0xf7, 0x47, 0xa5, + 0x38, 0x02, 0x73, 0x39, 0x26, 0xd2, 0xf7, 0x47, 0x08, 0x75, 0x48, 0x63, + 0x49, 0x36, 0xd2, 0xf7, 0x47, 0x18, 0x65, 0x38, 0x63, 0x49, 0x36, 0xe2, + 0xf7, 0x47, 0x28, 0x45, 0x38, 0x53, 0x59, 0x46, 0xe2, 0xf7, 0x47, 0x38, + 0x15, 0x48, 0x53, 0x59, 0x46, 0xf2, 0xf7, 0x37, 0xa8, 0x43, 0x69, 0x56, + 0xf2, 0xf7, 0x37, 0x98, 0x33, 0x79, 0x56, 0xf2, 0xf7, 0x37, 0x98, 0x23, + 0x79, 0x66, 0xf2, 0x02, 0xf7, 0x37, 0x88, 0x13, 0x89, 0x66, 0xf2, 0x02, + 0xf7, 0x37, 0x88, 0x03, 0x89, 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x99, + 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x89, 0x86, 0xf2, 0x12, 0xf7, 0x37, + 0x78, 0x79, 0x96, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, + 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x59, 0xb6, + 0xf2, 0x32, 0xf7, 0x37, 0x58, 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58, + 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58 +}; + +static const unsigned char icon_data_128[] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, + 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, + 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, + 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, + 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, + 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, + 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, + 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, + 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, + 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, + 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, + 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xa0, + 0x42, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, 0x71, + 0x60, 0x82, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, + 0x71, 0x40, 0xa2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, + 0x70, 0x71, 0x20, 0xc2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, + 0x71, 0x70, 0x71, 0x00, 0xe2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, + 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, + 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, + 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, + 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, + 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, + 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, + 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, + 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, + 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, + 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0xf2, 0xf2, + 0xf2, 0x42, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf2, 0xf2, 0xf2, 0x62, 0xf0, + 0xf0, 0xf0, 0xf0, 0x70, 0xf2, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, + 0x60, 0xf2, 0xf2, 0xf2, 0x82, 0xf0, 0xf0, 0xf0, 0xf0, 0x50, 0xf2, 0xf2, + 0xf2, 0x92, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf2, 0xf2, 0xf2, 0xa2, 0xf0, + 0xf0, 0xf0, 0xf0, 0x30, 0xf2, 0xf2, 0xf2, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x12, 0xa4, 0xf2, 0xf2, 0xe2, 0xf0, 0xf0, 0xf0, 0xf0, 0x20, 0xf4, + 0x14, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xf4, 0x64, 0xf2, + 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xe0, 0xf4, 0xb4, 0xf2, 0xf2, 0x42, 0xf0, + 0xf0, 0xf0, 0xd0, 0xf4, 0xd4, 0x15, 0xf2, 0xf2, 0x12, 0x43, 0xf0, 0xf0, + 0xf0, 0x60, 0xf4, 0xf4, 0x04, 0x35, 0xf2, 0xe2, 0x63, 0xf0, 0xf0, 0xf0, + 0x30, 0xf4, 0xf4, 0x24, 0x45, 0xf2, 0xc2, 0x83, 0xf0, 0xf0, 0xf0, 0x00, + 0xf4, 0xf4, 0x34, 0x65, 0xf2, 0xa2, 0x93, 0xf0, 0xf0, 0xe0, 0xf4, 0xf4, + 0x34, 0x95, 0xf2, 0x82, 0xa3, 0x19, 0xf0, 0xf0, 0x90, 0xf4, 0xf4, 0x44, + 0xc5, 0xf2, 0x62, 0xb3, 0x29, 0xf0, 0xf0, 0x40, 0xf4, 0xf4, 0x64, 0xf5, + 0xf2, 0x42, 0xc3, 0x39, 0xf0, 0xf0, 0xf4, 0xf4, 0x74, 0xf5, 0x35, 0xf2, + 0x22, 0xd3, 0x49, 0xf0, 0xa0, 0xf4, 0xf4, 0x84, 0xf5, 0x75, 0xf2, 0x02, + 0xd3, 0x59, 0x02, 0xf0, 0x50, 0xf7, 0x07, 0xf4, 0x74, 0xf5, 0xb5, 0x08, + 0xe2, 0xe3, 0x59, 0x12, 0xf0, 0x10, 0xf7, 0xd7, 0xf5, 0xf5, 0x95, 0x18, + 0xc2, 0xe3, 0x59, 0x06, 0x22, 0xd0, 0xf7, 0xf7, 0x37, 0xf5, 0xf5, 0x65, + 0x18, 0xb2, 0xf3, 0x59, 0x06, 0x32, 0x80, 0xf7, 0xf7, 0x97, 0xf5, 0xf5, + 0x45, 0x18, 0xa2, 0xf3, 0x59, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x07, 0xf5, + 0xf5, 0x25, 0x28, 0x82, 0xf3, 0x69, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x27, + 0xf5, 0xf5, 0x05, 0x38, 0x62, 0xf3, 0x69, 0x26, 0x82, 0xf7, 0xf7, 0xf7, + 0x37, 0xf5, 0xd5, 0x48, 0x52, 0xf3, 0x79, 0x26, 0xc2, 0xf7, 0xf7, 0xf7, + 0x07, 0xf5, 0xb5, 0x58, 0x42, 0xf3, 0x79, 0x36, 0xf2, 0x22, 0xf7, 0xf7, + 0xb7, 0xf5, 0xa5, 0x58, 0x32, 0xf3, 0x79, 0x46, 0xf2, 0x52, 0xf7, 0xf7, + 0x97, 0xf5, 0x85, 0x68, 0x22, 0xf3, 0x89, 0x36, 0xf2, 0x72, 0xf7, 0xf7, + 0x97, 0xf5, 0x65, 0x78, 0x12, 0xf3, 0x89, 0x46, 0xf2, 0x82, 0xf7, 0xf7, + 0x97, 0xf5, 0x55, 0x78, 0x02, 0xf3, 0x89, 0x46, 0xf2, 0xa2, 0xf7, 0xf7, + 0x87, 0x08, 0xf5, 0x35, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xb2, 0xf7, 0xf7, + 0x77, 0x08, 0xf5, 0x25, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xc2, 0xf7, 0xf7, + 0x77, 0x18, 0xf5, 0x05, 0x88, 0xd3, 0xa9, 0x66, 0xf2, 0xc2, 0xf7, 0xf7, + 0x77, 0x28, 0xf5, 0x78, 0xd3, 0xa9, 0x66, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, + 0x38, 0xd5, 0x78, 0xc3, 0xb9, 0x76, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x48, + 0xa5, 0x88, 0xc3, 0xb9, 0x76, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x58, 0x85, + 0x88, 0xb3, 0xc9, 0x86, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x68, 0x55, 0x98, + 0xb3, 0xc9, 0x86, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0x78, 0x25, 0xa8, 0xa3, + 0xc9, 0xa6, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x93, 0xd9, 0xa6, + 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x83, 0xe9, 0xb6, 0xf2, 0xf2, + 0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x73, 0xf9, 0xb6, 0xf2, 0xf2, 0xf7, 0xf7, + 0x77, 0xf8, 0x38, 0x63, 0xf9, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x28, 0x53, 0xf9, 0x09, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x28, 0x43, 0xf9, 0x09, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x18, 0x33, 0xf9, 0x19, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x18, 0x23, 0xf9, 0x19, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, + 0xf8, 0x08, 0x13, 0xf9, 0x29, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, + 0xf8, 0x08, 0x03, 0xf9, 0x29, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, + 0x77, 0xf8, 0xf9, 0x39, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, + 0xf8, 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, + 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, + 0x19, 0xf6, 0x26, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0x09, + 0xf6, 0x36, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0xf6, 0x56, + 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xe9, 0xf6, 0x66, 0xf2, 0xf2, + 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x32, 0xf7, + 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, + 0xc8, 0xc9, 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc9, + 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xb9, 0xf6, 0x96, + 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, + 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, + 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, + 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8 +}; + +#define ICON_(s) { s, ARRLEN(icon_data_##s), icon_data_##s } + +static const icon_data_t icons[] = { + ICON_(16), + ICON_(32), + ICON_(48), + ICON_(64), + ICON_(128) +}; + +#endif /* ICON_DATA_H */ + diff --git a/sxiv/image.c b/sxiv/image.c @@ -0,0 +1,797 @@ +/* Copyright 2011, 2012 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" +#define _IMAGE_CONFIG +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#if HAVE_LIBEXIF +#include <libexif/exif-data.h> +#endif + +#if HAVE_GIFLIB +#include <gif_lib.h> +enum { DEF_GIF_DELAY = 75 }; +#endif + +float zoom_min; +float zoom_max; + +static int zoomdiff(img_t *img, float z) +{ + return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom)); +} + +void img_init(img_t *img, win_t *win) +{ + zoom_min = zoom_levels[0] / 100.0; + zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0; + + imlib_context_set_display(win->env.dpy); + imlib_context_set_visual(win->env.vis); + imlib_context_set_colormap(win->env.cmap); + + img->im = NULL; + img->win = win; + img->scalemode = options->scalemode; + img->zoom = options->zoom; + img->zoom = MAX(img->zoom, zoom_min); + img->zoom = MIN(img->zoom, zoom_max); + img->checkpan = false; + img->dirty = false; + img->aa = ANTI_ALIAS; + img->alpha = ALPHA_LAYER; + img->multi.cap = img->multi.cnt = 0; + img->multi.animate = options->animate; + img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; + img->multi.length = 0; + + img->cmod = imlib_create_color_modifier(); + imlib_context_set_color_modifier(img->cmod); + img->gamma = MIN(MAX(options->gamma, -GAMMA_RANGE), GAMMA_RANGE); + + img->ss.on = options->slideshow > 0; + img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10; +} + +#if HAVE_LIBEXIF +void exif_auto_orientate(const fileinfo_t *file) +{ + ExifData *ed; + ExifEntry *entry; + int byte_order, orientation = 0; + + if ((ed = exif_data_new_from_file(file->path)) == NULL) + return; + byte_order = exif_data_get_byte_order(ed); + entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); + if (entry != NULL) + orientation = exif_get_short(entry->data, byte_order); + exif_data_unref(ed); + + switch (orientation) { + case 5: + imlib_image_orientate(1); + case 2: + imlib_image_flip_vertical(); + break; + case 3: + imlib_image_orientate(2); + break; + case 7: + imlib_image_orientate(1); + case 4: + imlib_image_flip_horizontal(); + break; + case 6: + imlib_image_orientate(1); + break; + case 8: + imlib_image_orientate(3); + break; + } +} +#endif + +#if HAVE_GIFLIB +bool img_load_gif(img_t *img, const fileinfo_t *file) +{ + GifFileType *gif; + GifRowType *rows = NULL; + GifRecordType rec; + ColorMapObject *cmap; + DATA32 bgpixel, *data, *ptr; + DATA32 *prev_frame = NULL; + Imlib_Image im; + int i, j, bg, r, g, b; + int x, y, w, h, sw, sh; + int px, py, pw, ph; + int intoffset[] = { 0, 4, 2, 1 }; + int intjump[] = { 8, 8, 4, 2 }; + int transp = -1; + unsigned int disposal = 0, prev_disposal = 0; + unsigned int delay = 0; + bool err = false; + + if (img->multi.cap == 0) { + img->multi.cap = 8; + img->multi.frames = (img_frame_t*) + emalloc(sizeof(img_frame_t) * img->multi.cap); + } + img->multi.cnt = img->multi.sel = 0; + img->multi.length = 0; + +#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 + gif = DGifOpenFileName(file->path, NULL); +#else + gif = DGifOpenFileName(file->path); +#endif + if (gif == NULL) { + error(0, 0, "%s: Error opening gif image", file->name); + return false; + } + bg = gif->SBackGroundColor; + sw = gif->SWidth; + sh = gif->SHeight; + px = py = pw = ph = 0; + + do { + if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { + err = true; + break; + } + if (rec == EXTENSION_RECORD_TYPE) { + int ext_code; + GifByteType *ext = NULL; + + DGifGetExtension(gif, &ext_code, &ext); + while (ext) { + if (ext_code == GRAPHICS_EXT_FUNC_CODE) { + if (ext[1] & 1) + transp = (int) ext[4]; + else + transp = -1; + + delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]); + disposal = (unsigned int) ext[1] >> 2 & 0x7; + } + ext = NULL; + DGifGetExtensionNext(gif, &ext); + } + } else if (rec == IMAGE_DESC_RECORD_TYPE) { + if (DGifGetImageDesc(gif) == GIF_ERROR) { + err = true; + break; + } + x = gif->Image.Left; + y = gif->Image.Top; + w = gif->Image.Width; + h = gif->Image.Height; + + rows = (GifRowType*) emalloc(h * sizeof(GifRowType)); + for (i = 0; i < h; i++) + rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType)); + if (gif->Image.Interlace) { + for (i = 0; i < 4; i++) { + for (j = intoffset[i]; j < h; j += intjump[i]) + DGifGetLine(gif, rows[j], w); + } + } else { + for (i = 0; i < h; i++) + DGifGetLine(gif, rows[i], w); + } + + ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh); + cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; + r = cmap->Colors[bg].Red; + g = cmap->Colors[bg].Green; + b = cmap->Colors[bg].Blue; + bgpixel = 0x00ffffff & (r << 16 | g << 8 | b); + + for (i = 0; i < sh; i++) { + for (j = 0; j < sw; j++) { + if (i < y || i >= y + h || j < x || j >= x + w || + rows[i-y][j-x] == transp) + { + if (prev_frame != NULL && (prev_disposal != 2 || + i < py || i >= py + ph || j < px || j >= px + pw)) + { + *ptr = prev_frame[i * sw + j]; + } else { + *ptr = bgpixel; + } + } else { + r = cmap->Colors[rows[i-y][j-x]].Red; + g = cmap->Colors[rows[i-y][j-x]].Green; + b = cmap->Colors[rows[i-y][j-x]].Blue; + *ptr = 0xffu << 24 | r << 16 | g << 8 | b; + } + ptr++; + } + } + + im = imlib_create_image_using_copied_data(sw, sh, data); + + for (i = 0; i < h; i++) + free(rows[i]); + free(rows); + free(data); + + if (im == NULL) { + err = true; + break; + } + + imlib_context_set_image(im); + imlib_image_set_format("gif"); + if (transp >= 0) + imlib_image_set_has_alpha(1); + + if (disposal != 3) + prev_frame = imlib_image_get_data_for_reading_only(); + prev_disposal = disposal; + px = x, py = y, pw = w, ph = h; + + if (img->multi.cnt == img->multi.cap) { + img->multi.cap *= 2; + img->multi.frames = (img_frame_t*) + erealloc(img->multi.frames, + img->multi.cap * sizeof(img_frame_t)); + } + img->multi.frames[img->multi.cnt].im = im; + delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay; + img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY; + img->multi.length += img->multi.frames[img->multi.cnt].delay; + img->multi.cnt++; + } + } while (rec != TERMINATE_RECORD_TYPE); + +#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1 + DGifCloseFile(gif, NULL); +#else + DGifCloseFile(gif); +#endif + + if (err && (file->flags & FF_WARN)) + error(0, 0, "%s: Corrupted gif file", file->name); + + if (img->multi.cnt > 1) { + imlib_context_set_image(img->im); + imlib_free_image(); + img->im = img->multi.frames[0].im; + } else if (img->multi.cnt == 1) { + imlib_context_set_image(img->multi.frames[0].im); + imlib_free_image(); + img->multi.cnt = 0; + } + + imlib_context_set_image(img->im); + + return !err; +} +#endif /* HAVE_GIFLIB */ + +Imlib_Image img_open(const fileinfo_t *file) +{ + struct stat st; + Imlib_Image im = NULL; + + if (access(file->path, R_OK) == 0 && + stat(file->path, &st) == 0 && S_ISREG(st.st_mode)) + { + im = imlib_load_image(file->path); + if (im != NULL) { + imlib_context_set_image(im); + if (imlib_image_get_data_for_reading_only() == NULL) { + imlib_free_image(); + im = NULL; + } + } + } + if (im == NULL && (file->flags & FF_WARN)) + error(0, 0, "%s: Error opening image", file->name); + return im; +} + +bool img_load(img_t *img, const fileinfo_t *file) +{ + const char *fmt; + + if ((img->im = img_open(file)) == NULL) + return false; + + imlib_image_set_changes_on_disk(); + +#if HAVE_LIBEXIF + exif_auto_orientate(file); +#endif + + if ((fmt = imlib_image_format()) != NULL) { +#if HAVE_GIFLIB + if (STREQ(fmt, "gif")) + img_load_gif(img, file); +#endif + } + img->w = imlib_image_get_width(); + img->h = imlib_image_get_height(); + img->checkpan = true; + img->dirty = true; + + return true; +} + +CLEANUP void img_close(img_t *img, bool decache) +{ + int i; + + if (img->multi.cnt > 0) { + for (i = 0; i < img->multi.cnt; i++) { + imlib_context_set_image(img->multi.frames[i].im); + imlib_free_image(); + } + img->multi.cnt = 0; + img->im = NULL; + } else if (img->im != NULL) { + imlib_context_set_image(img->im); + if (decache) + imlib_free_image_and_decache(); + else + imlib_free_image(); + img->im = NULL; + } +} + +void img_check_pan(img_t *img, bool moved) +{ + win_t *win; + float w, h, ox, oy; + + win = img->win; + w = img->w * img->zoom; + h = img->h * img->zoom; + ox = img->x; + oy = img->y; + + if (w < win->w) + img->x = (win->w - w) / 2; + else if (img->x > 0) + img->x = 0; + else if (img->x + w < win->w) + img->x = win->w - w; + if (h < win->h) + img->y = (win->h - h) / 2; + else if (img->y > 0) + img->y = 0; + else if (img->y + h < win->h) + img->y = win->h - h; + + if (!moved && (ox != img->x || oy != img->y)) + img->dirty = true; +} + +bool img_fit(img_t *img) +{ + float z, zw, zh; + + if (img->scalemode == SCALE_ZOOM) + return false; + + zw = (float) img->win->w / (float) img->w; + zh = (float) img->win->h / (float) img->h; + + switch (img->scalemode) { + case SCALE_WIDTH: + z = zw; + break; + case SCALE_HEIGHT: + z = zh; + break; + default: + z = MIN(zw, zh); + break; + } + z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max); + + if (zoomdiff(img, z) != 0) { + img->zoom = z; + img->dirty = true; + return true; + } else { + return false; + } +} + +void img_render(img_t *img) +{ + win_t *win; + int sx, sy, sw, sh; + int dx, dy, dw, dh; + Imlib_Image bg; + unsigned long c; + + win = img->win; + img_fit(img); + + if (img->checkpan) { + img_check_pan(img, false); + img->checkpan = false; + } + + if (!img->dirty) + return; + + /* calculate source and destination offsets: + * - part of image drawn on full window, or + * - full image drawn on part of window + */ + if (img->x <= 0) { + sx = -img->x / img->zoom + 0.5; + sw = win->w / img->zoom; + dx = 0; + dw = win->w; + } else { + sx = 0; + sw = img->w; + dx = img->x; + dw = img->w * img->zoom; + } + if (img->y <= 0) { + sy = -img->y / img->zoom + 0.5; + sh = win->h / img->zoom; + dy = 0; + dh = win->h; + } else { + sy = 0; + sh = img->h; + dy = img->y; + dh = img->h * img->zoom; + } + + win_clear(win); + + imlib_context_set_image(img->im); + imlib_context_set_anti_alias(img->aa); + imlib_context_set_drawable(win->buf.pm); + + if (imlib_image_has_alpha()) { + if ((bg = imlib_create_image(dw, dh)) == NULL) + error(EXIT_FAILURE, ENOMEM, NULL); + imlib_context_set_image(bg); + imlib_image_set_has_alpha(0); + + if (img->alpha) { + int i, c, r; + DATA32 col[2] = { 0xFF666666, 0xFF999999 }; + DATA32 * data = imlib_image_get_data(); + + for (r = 0; r < dh; r++) { + i = r * dw; + if (r == 0 || r == 8) { + for (c = 0; c < dw; c++) + data[i++] = col[!(c & 8) ^ !r]; + } else { + memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0])); + } + } + imlib_image_put_back_data(data); + } else { + c = win->bg.pixel; + imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF); + imlib_image_fill_rectangle(0, 0, dw, dh); + } + imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh); + imlib_context_set_color_modifier(NULL); + imlib_render_image_on_drawable(dx, dy); + imlib_free_image(); + imlib_context_set_color_modifier(img->cmod); + } else { + imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh); + } + img->dirty = false; +} + +bool img_fit_win(img_t *img, scalemode_t sm) +{ + float oz; + + oz = img->zoom; + img->scalemode = sm; + + if (img_fit(img)) { + img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz; + img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz; + img->checkpan = true; + return true; + } else { + return false; + } +} + +bool img_zoom(img_t *img, float z) +{ + z = MAX(z, zoom_min); + z = MIN(z, zoom_max); + + img->scalemode = SCALE_ZOOM; + + if (zoomdiff(img, z) != 0) { + int x, y; + + win_cursor_pos(img->win, &x, &y); + if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) { + x = img->win->w / 2; + y = img->win->h / 2; + } + img->x = x - (x - img->x) * z / img->zoom; + img->y = y - (y - img->y) * z / img->zoom; + img->zoom = z; + img->checkpan = true; + img->dirty = true; + return true; + } else { + return false; + } +} + +bool img_zoom_in(img_t *img) +{ + int i; + float z; + + for (i = 0; i < ARRLEN(zoom_levels); i++) { + z = zoom_levels[i] / 100.0; + if (zoomdiff(img, z) > 0) + return img_zoom(img, z); + } + return false; +} + +bool img_zoom_out(img_t *img) +{ + int i; + float z; + + for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) { + z = zoom_levels[i] / 100.0; + if (zoomdiff(img, z) < 0) + return img_zoom(img, z); + } + return false; +} + +bool img_pos(img_t *img, float x, float y) +{ + float ox, oy; + + ox = img->x; + oy = img->y; + + img->x = x; + img->y = y; + + img_check_pan(img, true); + + if (ox != img->x || oy != img->y) { + img->dirty = true; + return true; + } else { + return false; + } +} + +bool img_move(img_t *img, float dx, float dy) +{ + return img_pos(img, img->x + dx, img->y + dy); +} + +bool img_pan(img_t *img, direction_t dir, int d) +{ + /* d < 0: screen-wise + * d = 0: 1/PAN_FRACTION of screen + * d > 0: num of pixels + */ + float x, y; + + if (d > 0) { + x = y = MAX(1, (float) d * img->zoom); + } else { + x = img->win->w / (d < 0 ? 1 : PAN_FRACTION); + y = img->win->h / (d < 0 ? 1 : PAN_FRACTION); + } + + switch (dir) { + case DIR_LEFT: + return img_move(img, x, 0.0); + case DIR_RIGHT: + return img_move(img, -x, 0.0); + case DIR_UP: + return img_move(img, 0.0, y); + case DIR_DOWN: + return img_move(img, 0.0, -y); + } + return false; +} + +bool img_pan_edge(img_t *img, direction_t dir) +{ + float ox, oy; + + ox = img->x; + oy = img->y; + + if (dir & DIR_LEFT) + img->x = 0; + if (dir & DIR_RIGHT) + img->x = img->win->w - img->w * img->zoom; + if (dir & DIR_UP) + img->y = 0; + if (dir & DIR_DOWN) + img->y = img->win->h - img->h * img->zoom; + + img_check_pan(img, true); + + if (ox != img->x || oy != img->y) { + img->dirty = true; + return true; + } else { + return false; + } +} + +void img_rotate(img_t *img, degree_t d) +{ + int i, tmp; + float ox, oy; + + imlib_context_set_image(img->im); + imlib_image_orientate(d); + + for (i = 0; i < img->multi.cnt; i++) { + if (i != img->multi.sel) { + imlib_context_set_image(img->multi.frames[i].im); + imlib_image_orientate(d); + } + } + if (d == DEGREE_90 || d == DEGREE_270) { + ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom; + oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom; + + img->x = oy + (img->win->w - img->win->h) / 2; + img->y = ox + (img->win->h - img->win->w) / 2; + + tmp = img->w; + img->w = img->h; + img->h = tmp; + img->checkpan = true; + } + img->dirty = true; +} + +void img_flip(img_t *img, flipdir_t d) +{ + int i; + void (*imlib_flip_op[3])(void) = { + imlib_image_flip_horizontal, + imlib_image_flip_vertical, + imlib_image_flip_diagonal + }; + + d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1; + + if (d < 0 || d >= ARRLEN(imlib_flip_op)) + return; + + imlib_context_set_image(img->im); + imlib_flip_op[d](); + + for (i = 0; i < img->multi.cnt; i++) { + if (i != img->multi.sel) { + imlib_context_set_image(img->multi.frames[i].im); + imlib_flip_op[d](); + } + } + img->dirty = true; +} + +void img_toggle_antialias(img_t *img) +{ + img->aa = !img->aa; + imlib_context_set_image(img->im); + imlib_context_set_anti_alias(img->aa); + img->dirty = true; +} + +bool img_change_gamma(img_t *img, int d) +{ + /* d < 0: decrease gamma + * d = 0: reset gamma + * d > 0: increase gamma + */ + int gamma; + double range; + + if (d == 0) + gamma = 0; + else + gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE); + + if (img->gamma != gamma) { + imlib_reset_color_modifier(); + if (gamma != 0) { + range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0; + imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE)); + } + img->gamma = gamma; + img->dirty = true; + return true; + } else { + return false; + } +} + +bool img_frame_goto(img_t *img, int n) +{ + if (n < 0 || n >= img->multi.cnt || n == img->multi.sel) + return false; + + img->multi.sel = n; + img->im = img->multi.frames[n].im; + + imlib_context_set_image(img->im); + img->w = imlib_image_get_width(); + img->h = imlib_image_get_height(); + img->checkpan = true; + img->dirty = true; + + return true; +} + +bool img_frame_navigate(img_t *img, int d) +{ + if (img->multi.cnt == 0 || d == 0) + return false; + + d += img->multi.sel; + if (d < 0) + d = 0; + else if (d >= img->multi.cnt) + d = img->multi.cnt - 1; + + return img_frame_goto(img, d); +} + +bool img_frame_animate(img_t *img) +{ + if (img->multi.cnt == 0) + return false; + + if (img->multi.sel + 1 >= img->multi.cnt) + img_frame_goto(img, 0); + else + img_frame_goto(img, img->multi.sel + 1); + img->dirty = true; + return true; +} + diff --git a/sxiv/main.c b/sxiv/main.c @@ -0,0 +1,951 @@ +/* Copyright 2011-2013 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" +#define _MAPPINGS_CONFIG +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <locale.h> +#include <signal.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <time.h> +#include <X11/keysym.h> +#include <X11/XF86keysym.h> + +typedef struct { + struct timeval when; + bool active; + timeout_f handler; +} timeout_t; + +/* timeout handler functions: */ +void redraw(void); +void reset_cursor(void); +void animate(void); +void slideshow(void); +void clear_resize(void); + +appmode_t mode; +arl_t arl; +img_t img; +tns_t tns; +win_t win; + +fileinfo_t *files; +int filecnt, fileidx; +int alternate; +int markcnt; +int markidx; + +int prefix; +bool extprefix; + +bool resized = false; + +typedef struct { + int err; + char *cmd; +} extcmd_t; + +struct { + extcmd_t f; + int fd; + unsigned int i, lastsep; + pid_t pid; +} info; + +struct { + extcmd_t f; + bool warned; +} keyhandler; + +timeout_t timeouts[] = { + { { 0, 0 }, false, redraw }, + { { 0, 0 }, false, reset_cursor }, + { { 0, 0 }, false, animate }, + { { 0, 0 }, false, slideshow }, + { { 0, 0 }, false, clear_resize }, +}; + +cursor_t imgcursor[3] = { + CURSOR_ARROW, CURSOR_ARROW, CURSOR_ARROW +}; + +void cleanup(void) +{ + img_close(&img, false); + arl_cleanup(&arl); + tns_free(&tns); + win_close(&win); +} + +void check_add_file(char *filename, bool given) +{ + char *path; + + if (*filename == '\0') + return; + + if (access(filename, R_OK) < 0 || + (path = realpath(filename, NULL)) == NULL) + { + if (given) + error(0, errno, "%s", filename); + return; + } + + if (fileidx == filecnt) { + filecnt *= 2; + files = erealloc(files, filecnt * sizeof(*files)); + memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files)); + } + + files[fileidx].name = estrdup(filename); + files[fileidx].path = path; + if (given) + files[fileidx].flags |= FF_WARN; + fileidx++; +} + +void remove_file(int n, bool manual) +{ + if (n < 0 || n >= filecnt) + return; + + if (filecnt == 1) { + if (!manual) + fprintf(stderr, "sxiv: no more files to display, aborting\n"); + exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); + } + if (files[n].flags & FF_MARK) + markcnt--; + + if (files[n].path != files[n].name) + free((void*) files[n].path); + free((void*) files[n].name); + + if (n + 1 < filecnt) { + if (tns.thumbs != NULL) { + memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) * + sizeof(*tns.thumbs)); + memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs)); + } + memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files)); + } + filecnt--; + if (fileidx > n || fileidx == filecnt) + fileidx--; + if (alternate > n || alternate == filecnt) + alternate--; + if (markidx > n || markidx == filecnt) + markidx--; +} + +void set_timeout(timeout_f handler, int time, bool overwrite) +{ + int i; + + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == handler) { + if (!timeouts[i].active || overwrite) { + gettimeofday(&timeouts[i].when, 0); + TV_ADD_MSEC(&timeouts[i].when, time); + timeouts[i].active = true; + } + return; + } + } +} + +void reset_timeout(timeout_f handler) +{ + int i; + + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == handler) { + timeouts[i].active = false; + return; + } + } +} + +bool check_timeouts(struct timeval *t) +{ + int i = 0, tdiff, tmin = -1; + struct timeval now; + + while (i < ARRLEN(timeouts)) { + if (timeouts[i].active) { + gettimeofday(&now, 0); + tdiff = TV_DIFF(&timeouts[i].when, &now); + if (tdiff <= 0) { + timeouts[i].active = false; + if (timeouts[i].handler != NULL) + timeouts[i].handler(); + i = tmin = -1; + } else if (tmin < 0 || tdiff < tmin) { + tmin = tdiff; + } + } + i++; + } + if (tmin > 0 && t != NULL) + TV_SET_MSEC(t, tmin); + return tmin > 0; +} + +void close_info(void) +{ + if (info.fd != -1) { + kill(info.pid, SIGTERM); + close(info.fd); + info.fd = -1; + } +} + +void open_info(void) +{ + int pfd[2]; + char w[12], h[12]; + + if (info.f.err != 0 || info.fd >= 0 || win.bar.h == 0) + return; + win.bar.l.buf[0] = '\0'; + if (pipe(pfd) < 0) + return; + if ((info.pid = fork()) == 0) { + close(pfd[0]); + dup2(pfd[1], 1); + snprintf(w, sizeof(w), "%d", img.w); + snprintf(h, sizeof(h), "%d", img.h); + execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL); + error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd); + } + close(pfd[1]); + if (info.pid < 0) { + close(pfd[0]); + } else { + fcntl(pfd[0], F_SETFL, O_NONBLOCK); + info.fd = pfd[0]; + info.i = info.lastsep = 0; + } +} + +void read_info(void) +{ + ssize_t i, n; + char buf[BAR_L_LEN]; + + while (true) { + n = read(info.fd, buf, sizeof(buf)); + if (n < 0 && errno == EAGAIN) + return; + else if (n == 0) + goto end; + for (i = 0; i < n; i++) { + if (buf[i] == '\n') { + if (info.lastsep == 0) { + win.bar.l.buf[info.i++] = ' '; + info.lastsep = 1; + } + } else { + win.bar.l.buf[info.i++] = buf[i]; + info.lastsep = 0; + } + if (info.i + 1 == win.bar.l.size) + goto end; + } + } +end: + info.i -= info.lastsep; + win.bar.l.buf[info.i] = '\0'; + win_draw(&win); + close_info(); +} + +void load_image(int new) +{ + bool prev = new < fileidx; + static int current; + + if (new < 0 || new >= filecnt) + return; + + if (win.xwin != None) + win_set_cursor(&win, CURSOR_WATCH); + reset_timeout(slideshow); + + if (new != current) + alternate = current; + + img_close(&img, false); + while (!img_load(&img, &files[new])) { + remove_file(new, false); + if (new >= filecnt) + new = filecnt - 1; + else if (new > 0 && prev) + new--; + } + files[new].flags &= ~FF_WARN; + fileidx = current = new; + + close_info(); + open_info(); + arl_setup(&arl, files[fileidx].path); + + if (img.multi.cnt > 0 && img.multi.animate) + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + else + reset_timeout(animate); +} + +bool mark_image(int n, bool on) +{ + markidx = n; + if (!!(files[n].flags & FF_MARK) != on) { + files[n].flags ^= FF_MARK; + markcnt += on ? 1 : -1; + if (mode == MODE_THUMB) + tns_mark(&tns, n, on); + return true; + } + return false; +} + +void bar_put(win_bar_t *bar, const char *fmt, ...) +{ + size_t len = bar->size - (bar->p - bar->buf), n; + va_list ap; + + va_start(ap, fmt); + n = vsnprintf(bar->p, len, fmt, ap); + bar->p += MIN(len, n); + va_end(ap); +} + +#define BAR_SEP " " + +void update_info(void) +{ + unsigned int i, fn, fw; + const char * mark; + win_bar_t *l = &win.bar.l, *r = &win.bar.r; + + /* update bar contents */ + if (win.bar.h == 0) + return; + for (fw = 0, i = filecnt; i > 0; fw++, i /= 10); + mark = files[fileidx].flags & FF_MARK ? "* " : ""; + l->p = l->buf; + r->p = r->buf; + if (mode == MODE_THUMB) { + if (tns.loadnext < tns.end) + bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1); + else if (tns.initnext < filecnt) + bar_put(l, "Caching... %0*d", fw, tns.initnext + 1); + else + strncpy(l->buf, files[fileidx].name, l->size); + bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt); + } else { + bar_put(r, "%s", mark); + if (img.ss.on) { + if (img.ss.delay % 10 != 0) + bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10); + else + bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10); + } + if (img.gamma != 0) + bar_put(r, "G%+d" BAR_SEP, img.gamma); + bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0)); + if (img.multi.cnt > 0) { + for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10); + bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt); + } + bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt); + if (info.f.err) + strncpy(l->buf, files[fileidx].name, l->size); + } +} + +int ptr_third_x(void) +{ + int x, y; + + win_cursor_pos(&win, &x, &y); + return MAX(0, MIN(2, (x / (win.w * 0.33)))); +} + +void redraw(void) +{ + int t; + + if (mode == MODE_IMAGE) { + img_render(&img); + if (img.ss.on) { + t = img.ss.delay * 100; + if (img.multi.cnt > 0 && img.multi.animate) + t = MAX(t, img.multi.length); + set_timeout(slideshow, t, false); + } + } else { + tns_render(&tns); + } + update_info(); + win_draw(&win); + reset_timeout(redraw); + reset_cursor(); +} + +void reset_cursor(void) +{ + int c, i; + cursor_t cursor = CURSOR_NONE; + + if (mode == MODE_IMAGE) { + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == reset_cursor) { + if (timeouts[i].active) { + c = ptr_third_x(); + c = MAX(fileidx > 0 ? 0 : 1, c); + c = MIN(fileidx + 1 < filecnt ? 2 : 1, c); + cursor = imgcursor[c]; + } + break; + } + } + } else { + if (tns.loadnext < tns.end || tns.initnext < filecnt) + cursor = CURSOR_WATCH; + else + cursor = CURSOR_ARROW; + } + win_set_cursor(&win, cursor); +} + +void animate(void) +{ + if (img_frame_animate(&img)) { + redraw(); + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + } +} + +void slideshow(void) +{ + load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0); + redraw(); +} + +void clear_resize(void) +{ + resized = false; +} + +Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg) +{ + return ev->type == ButtonPress || ev->type == KeyPress; +} + +void run_key_handler(const char *key, unsigned int mask) +{ + pid_t pid; + FILE *pfs; + bool marked = mode == MODE_THUMB && markcnt > 0; + bool changed = false; + int f, i, pfd[2]; + int fcnt = marked ? markcnt : 1; + char kstr[32]; + struct stat *oldst, st; + XEvent dump; + + if (keyhandler.f.err != 0) { + if (!keyhandler.warned) { + error(0, keyhandler.f.err, "%s", keyhandler.f.cmd); + keyhandler.warned = true; + } + return; + } + if (key == NULL) + return; + + if (pipe(pfd) < 0) { + error(0, errno, "pipe"); + return; + } + if ((pfs = fdopen(pfd[1], "w")) == NULL) { + error(0, errno, "open pipe"); + close(pfd[0]), close(pfd[1]); + return; + } + oldst = emalloc(fcnt * sizeof(*oldst)); + + close_info(); + strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); + win_draw(&win); + win_set_cursor(&win, CURSOR_WATCH); + + snprintf(kstr, sizeof(kstr), "%s%s%s%s", + mask & ControlMask ? "C-" : "", + mask & Mod1Mask ? "M-" : "", + mask & ShiftMask ? "S-" : "", key); + + if ((pid = fork()) == 0) { + close(pfd[1]); + dup2(pfd[0], 0); + execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL); + error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd); + } + close(pfd[0]); + if (pid < 0) { + error(0, errno, "fork"); + fclose(pfs); + goto end; + } + + for (f = i = 0; f < fcnt; i++) { + if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { + stat(files[i].path, &oldst[f]); + fprintf(pfs, "%s\n", files[i].name); + f++; + } + } + fclose(pfs); + while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); + + for (f = i = 0; f < fcnt; i++) { + if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { + if (stat(files[i].path, &st) != 0 || + memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0) + { + if (tns.thumbs != NULL) { + tns_unload(&tns, i); + tns.loadnext = MIN(tns.loadnext, i); + } + changed = true; + } + f++; + } + } + /* drop user input events that occurred while running the key handler */ + while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL)); + +end: + if (mode == MODE_IMAGE) { + if (changed) { + img_close(&img, true); + load_image(fileidx); + } else { + open_info(); + } + } + free(oldst); + reset_cursor(); + redraw(); +} + +#define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask)) + +void on_keypress(XKeyEvent *kev) +{ + int i; + unsigned int sh = 0; + KeySym ksym, shksym; + char dummy, key; + bool dirty = false; + + XLookupString(kev, &key, 1, &ksym, NULL); + + if (kev->state & ShiftMask) { + kev->state &= ~ShiftMask; + XLookupString(kev, &dummy, 1, &shksym, NULL); + kev->state |= ShiftMask; + if (ksym != shksym) + sh = ShiftMask; + } + if (IsModifierKey(ksym)) + return; + if (ksym == XK_Escape && MODMASK(kev->state) == 0) { + extprefix = False; + } else if (extprefix) { + run_key_handler(XKeysymToString(ksym), kev->state & ~sh); + extprefix = False; + } else if (key >= '0' && key <= '9') { + /* number prefix for commands */ + prefix = prefix * 10 + (int) (key - '0'); + return; + } else for (i = 0; i < ARRLEN(keys); i++) { + if (keys[i].ksym == ksym && + MODMASK(keys[i].mask | sh) == MODMASK(kev->state) && + keys[i].cmd >= 0 && keys[i].cmd < CMD_COUNT && + (cmds[keys[i].cmd].mode < 0 || cmds[keys[i].cmd].mode == mode)) + { + if (cmds[keys[i].cmd].func(keys[i].arg)) + dirty = true; + } + } + if (dirty) + redraw(); + prefix = 0; +} + +void on_buttonpress(XButtonEvent *bev) +{ + int i, sel; + bool dirty = false; + static Time firstclick; + + if (mode == MODE_IMAGE) { + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + + for (i = 0; i < ARRLEN(buttons); i++) { + if (buttons[i].button == bev->button && + MODMASK(buttons[i].mask) == MODMASK(bev->state) && + buttons[i].cmd >= 0 && buttons[i].cmd < CMD_COUNT && + (cmds[buttons[i].cmd].mode < 0 || cmds[buttons[i].cmd].mode == mode)) + { + if (cmds[buttons[i].cmd].func(buttons[i].arg)) + dirty = true; + } + } + if (dirty) + redraw(); + } else { + /* thumbnail mode (hard-coded) */ + switch (bev->button) { + case Button1: + if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { + if (sel != fileidx) { + tns_highlight(&tns, fileidx, false); + tns_highlight(&tns, sel, true); + fileidx = sel; + firstclick = bev->time; + redraw(); + } else if (bev->time - firstclick <= TO_DOUBLE_CLICK) { + mode = MODE_IMAGE; + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + load_image(fileidx); + redraw(); + } else { + firstclick = bev->time; + } + } + break; + case Button3: + if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { + bool on = !(files[sel].flags & FF_MARK); + XEvent e; + + for (;;) { + if (sel >= 0 && mark_image(sel, on)) + redraw(); + XMaskEvent(win.env.dpy, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); + if (e.type == ButtonPress || e.type == ButtonRelease) + break; + while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); + sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y); + } + } + break; + case Button4: + case Button5: + if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN, + (bev->state & ControlMask) != 0)) + redraw(); + break; + } + } + prefix = 0; +} + +const struct timespec ten_ms = {0, 10000000}; + +void run(void) +{ + int xfd; + fd_set fds; + struct timeval timeout; + bool discard, init_thumb, load_thumb, to_set; + XEvent ev, nextev; + + while (true) { + to_set = check_timeouts(&timeout); + init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; + load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; + + if ((init_thumb || load_thumb || to_set || info.fd != -1 || + arl.fd != -1) && XPending(win.env.dpy) == 0) + { + if (load_thumb) { + set_timeout(redraw, TO_REDRAW_THUMBS, false); + if (!tns_load(&tns, tns.loadnext, false, false)) { + remove_file(tns.loadnext, false); + tns.dirty = true; + } + if (tns.loadnext >= tns.end) + redraw(); + } else if (init_thumb) { + set_timeout(redraw, TO_REDRAW_THUMBS, false); + if (!tns_load(&tns, tns.initnext, false, true)) + remove_file(tns.initnext, false); + } else { + xfd = ConnectionNumber(win.env.dpy); + FD_ZERO(&fds); + FD_SET(xfd, &fds); + if (info.fd != -1) { + FD_SET(info.fd, &fds); + xfd = MAX(xfd, info.fd); + } + if (arl.fd != -1) { + FD_SET(arl.fd, &fds); + xfd = MAX(xfd, arl.fd); + } + select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL); + if (info.fd != -1 && FD_ISSET(info.fd, &fds)) + read_info(); + if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) { + if (arl_handle(&arl)) { + /* when too fast, imlib2 can't load the image */ + nanosleep(&ten_ms, NULL); + img_close(&img, true); + load_image(fileidx); + redraw(); + } + } + } + continue; + } + + do { + XNextEvent(win.env.dpy, &ev); + discard = false; + if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) { + XPeekEvent(win.env.dpy, &nextev); + switch (ev.type) { + case ConfigureNotify: + case MotionNotify: + discard = ev.type == nextev.type; + break; + case KeyPress: + discard = (nextev.type == KeyPress || nextev.type == KeyRelease) + && ev.xkey.keycode == nextev.xkey.keycode; + break; + } + } + } while (discard); + + switch (ev.type) { + /* handle events */ + case ButtonPress: + on_buttonpress(&ev.xbutton); + break; + case ClientMessage: + if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW]) + cmds[g_quit].func(0); + break; + case ConfigureNotify: + if (win_configure(&win, &ev.xconfigure)) { + if (mode == MODE_IMAGE) { + img.dirty = true; + img.checkpan = true; + } else { + tns.dirty = true; + } + if (!resized) { + redraw(); + set_timeout(clear_resize, TO_REDRAW_RESIZE, false); + resized = true; + } else { + set_timeout(redraw, TO_REDRAW_RESIZE, false); + } + } + break; + case KeyPress: + on_keypress(&ev.xkey); + break; + case MotionNotify: + if (mode == MODE_IMAGE) { + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + } + break; + } + } +} + +int fncmp(const void *a, const void *b) +{ + return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name); +} + +void sigchld(int sig) +{ + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +void setup_signal(int sig, void (*handler)(int sig)) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(sig, &sa, 0) == -1) + error(EXIT_FAILURE, errno, "signal %d", sig); +} + +int main(int argc, char **argv) +{ + int i, start; + size_t n; + ssize_t len; + char *filename; + const char *homedir, *dsuffix = ""; + struct stat fstats; + r_dir_t dir; + + setup_signal(SIGCHLD, sigchld); + setup_signal(SIGPIPE, SIG_IGN); + + setlocale(LC_COLLATE, ""); + + parse_options(argc, argv); + + if (options->clean_cache) { + tns_init(&tns, NULL, NULL, NULL, NULL); + tns_clean_cache(&tns); + exit(EXIT_SUCCESS); + } + + if (options->filecnt == 0 && !options->from_stdin) { + print_usage(); + exit(EXIT_FAILURE); + } + + if (options->recursive || options->from_stdin) + filecnt = 1024; + else + filecnt = options->filecnt; + + files = emalloc(filecnt * sizeof(*files)); + memset(files, 0, filecnt * sizeof(*files)); + fileidx = 0; + + if (options->from_stdin) { + n = 0; + filename = NULL; + while ((len = getline(&filename, &n, stdin)) > 0) { + if (filename[len-1] == '\n') + filename[len-1] = '\0'; + check_add_file(filename, true); + } + free(filename); + } + + for (i = 0; i < options->filecnt; i++) { + filename = options->filenames[i]; + + if (stat(filename, &fstats) < 0) { + error(0, errno, "%s", filename); + continue; + } + if (!S_ISDIR(fstats.st_mode)) { + check_add_file(filename, true); + } else { + if (r_opendir(&dir, filename, options->recursive) < 0) { + error(0, errno, "%s", filename); + continue; + } + start = fileidx; + while ((filename = r_readdir(&dir, true)) != NULL) { + check_add_file(filename, false); + free((void*) filename); + } + r_closedir(&dir); + if (fileidx - start > 1) + qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp); + } + } + + if (fileidx == 0) + error(EXIT_FAILURE, 0, "No valid image file given, aborting"); + + filecnt = fileidx; + fileidx = options->startnum < filecnt ? options->startnum : 0; + + for (i = 0; i < ARRLEN(buttons); i++) { + if (buttons[i].cmd == i_cursor_navigate) { + imgcursor[0] = CURSOR_LEFT; + imgcursor[2] = CURSOR_RIGHT; + break; + } + } + + win_init(&win); + img_init(&img, &win); + arl_init(&arl); + + if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { + homedir = getenv("HOME"); + dsuffix = "/.config"; + } + if (homedir != NULL) { + extcmd_t *cmd[] = { &info.f, &keyhandler.f }; + const char *name[] = { "image-info", "key-handler" }; + + for (i = 0; i < ARRLEN(cmd); i++) { + n = strlen(homedir) + strlen(dsuffix) + strlen(name[i]) + 12; + cmd[i]->cmd = (char*) emalloc(n); + snprintf(cmd[i]->cmd, n, "%s%s/sxiv/exec/%s", homedir, dsuffix, name[i]); + if (access(cmd[i]->cmd, X_OK) != 0) + cmd[i]->err = errno; + } + } else { + error(0, 0, "Exec directory not found"); + } + info.fd = -1; + + if (options->thumb_mode) { + mode = MODE_THUMB; + tns_init(&tns, files, &filecnt, &fileidx, &win); + while (!tns_load(&tns, fileidx, false, false)) + remove_file(fileidx, false); + } else { + mode = MODE_IMAGE; + tns.thumbs = NULL; + load_image(fileidx); + } + win_open(&win); + win_set_cursor(&win, CURSOR_WATCH); + + atexit(cleanup); + + set_timeout(redraw, 25, false); + + run(); + + return 0; +} diff --git a/sxiv/options.c b/sxiv/options.c @@ -0,0 +1,180 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" +#define _IMAGE_CONFIG +#include "config.h" +#include "version.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +opt_t _options; +const opt_t *options = (const opt_t*) &_options; + +void print_usage(void) +{ + printf("usage: sxiv [-abcfhiopqrtvZ] [-A FRAMERATE] [-e WID] [-G GAMMA] " + "[-g GEOMETRY] [-N NAME] [-n NUM] [-S DELAY] [-s MODE] [-z ZOOM] " + "FILES...\n"); +} + +void print_version(void) +{ + puts("sxiv " VERSION); +} + +void parse_options(int argc, char **argv) +{ + int n, opt; + char *end, *s; + const char *scalemodes = "dfwh"; + + progname = strrchr(argv[0], '/'); + progname = progname ? progname + 1 : argv[0]; + + _options.from_stdin = false; + _options.to_stdout = false; + _options.recursive = false; + _options.startnum = 0; + + _options.scalemode = SCALE_DOWN; + _options.zoom = 1.0; + _options.animate = false; + _options.gamma = 0; + _options.slideshow = 0; + _options.framerate = 0; + + _options.fullscreen = false; + _options.embed = 0; + _options.hide_bar = false; + _options.geometry = NULL; + _options.res_name = NULL; + + _options.quiet = false; + _options.thumb_mode = false; + _options.clean_cache = false; + _options.private_mode = false; + + while ((opt = getopt(argc, argv, "A:abce:fG:g:hin:N:opqrS:s:tvZz:")) != -1) { + switch (opt) { + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 'A': + n = strtol(optarg, &end, 0); + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", optarg); + _options.framerate = n; + /* fall through */ + case 'a': + _options.animate = true; + break; + case 'b': + _options.hide_bar = true; + break; + case 'c': + _options.clean_cache = true; + break; + case 'e': + n = strtol(optarg, &end, 0); + if (*end != '\0') + error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", optarg); + _options.embed = n; + break; + case 'f': + _options.fullscreen = true; + break; + case 'G': + n = strtol(optarg, &end, 0); + if (*end != '\0') + error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", optarg); + _options.gamma = n; + break; + case 'g': + _options.geometry = optarg; + break; + case 'h': + print_usage(); + exit(EXIT_SUCCESS); + case 'i': + _options.from_stdin = true; + break; + case 'n': + n = strtol(optarg, &end, 0); + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", optarg); + _options.startnum = n - 1; + break; + case 'N': + _options.res_name = optarg; + break; + case 'o': + _options.to_stdout = true; + break; + case 'p': + _options.private_mode = true; + break; + case 'q': + _options.quiet = true; + break; + case 'r': + _options.recursive = true; + break; + case 'S': + n = strtof(optarg, &end) * 10; + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", optarg); + _options.slideshow = n; + break; + case 's': + s = strchr(scalemodes, optarg[0]); + if (s == NULL || *s == '\0' || strlen(optarg) != 1) + error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", optarg); + _options.scalemode = s - scalemodes; + break; + case 't': + _options.thumb_mode = true; + break; + case 'v': + print_version(); + exit(EXIT_SUCCESS); + case 'Z': + _options.scalemode = SCALE_ZOOM; + _options.zoom = 1.0; + break; + case 'z': + n = strtol(optarg, &end, 0); + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", optarg); + _options.scalemode = SCALE_ZOOM; + _options.zoom = (float) n / 100.0; + break; + } + } + + _options.filenames = argv + optind; + _options.filecnt = argc - optind; + + if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) { + _options.filenames++; + _options.filecnt--; + _options.from_stdin = true; + } +} diff --git a/sxiv/sxiv.1 b/sxiv/sxiv.1 @@ -0,0 +1,448 @@ +.TH SXIV 1 sxiv\-VERSION +.SH NAME +sxiv \- Simple X Image Viewer +.SH SYNOPSIS +.B sxiv +.RB [ \-abcfhiopqrtvZ ] +.RB [ \-A +.IR FRAMERATE ] +.RB [ \-e +.IR WID ] +.RB [ \-G +.IR GAMMA ] +.RB [ \-g +.IR GEOMETRY ] +.RB [ \-N +.IR NAME ] +.RB [ \-n +.IR NUM ] +.RB [ \-S +.IR DELAY ] +.RB [ \-s +.IR MODE ] +.RB [ \-z +.IR ZOOM ] +.IR FILE ... +.SH DESCRIPTION +sxiv is a simple image viewer for X. +.P +It has two modes of operation: image and thumbnail mode. The default is image +mode, in which only the current image is shown. In thumbnail mode a grid of +small previews is displayed, making it easy to choose an image to open. +.P +Please note, that the fullscreen mode requires an EWMH/NetWM compliant window +manager. +.SH OPTIONS +.TP +.BI "\-A " FRAMERATE +Play animations with a constant frame rate set to +.IR FRAMERATE . +.TP +.B \-a +Play animations of multi-frame images. +.TP +.B \-b +Do not show info bar on bottom of window. +.TP +.B \-c +Remove all orphaned cache files from the thumbnail cache directory and exit. +.TP +.BI "\-e " WID +Embed sxiv's window into window whose ID is +.IR WID . +.TP +.B \-f +Start in fullscreen mode. +.TP +.BI "\-G " GAMMA +Set image gamma to GAMMA (-32..32). +.TP +.BI "\-g " GEOMETRY +Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for +more information on GEOMETRY argument. +.TP +.BI "\-N " NAME +Set the resource name of sxiv's X window to NAME. +.TP +.BI "\-n " NUM +Start at picture number NUM. +.TP +.B \-h +Print brief usage information to standard output and exit. +.TP +.B \-i +Read names of files to open from standard input. Also done if FILE is `-'. +.TP +.B \-o +Write list of all marked files to standard output when quitting. In combination +with +.B \-i +sxiv can be used as a visual filter/pipe. +.TP +.B \-p +Enable private mode, in which sxiv does not write any cache or temporary files. +.TP +.B \-q +Be quiet, disable warnings to standard error stream. +.TP +.B \-r +Search the given directories recursively for images to view. +.TP +.BI "\-S " DELAY +Start in slideshow mode. Set the delay between images to +.I DELAY +seconds. +.I DELAY +may be a floating point number. +.TP +.BI "\-s " MODE +Set scale mode according to MODE character. Supported modes are: [d]own, +[f]it, [w]idth, [h]eight. +.TP +.B \-t +Start in thumbnail mode. +.TP +.B \-v +Print version information to standard output and exit. +.TP +.B \-Z +The same as `\-z 100'. +.TP +.BI "\-z " ZOOM +Set zoom level to ZOOM percent. +.SH KEYBOARD COMMANDS +.SS General +The following keyboard commands are available in both image and thumbnail mode: +.TP +.BR 0 \- 9 +Prefix the next command with a number (denoted via +.IR count ). +.TP +.B q +Quit sxiv. +.TP +.B Return +Switch to thumbnail mode / open selected image in image mode. +.TP +.B f +Toggle fullscreen mode. +.TP +.B b +Toggle visibility of info bar on bottom of window. +.TP +.B Ctrl-x +Send the next key to the external key-handler. See section EXTERNAL KEY HANDLER +for more information. +.TP +.B g +Go to the first image. +.TP +.B G +Go to the last image, or image number +.IR count . +.TP +.B r +Reload image. +.TP +.B D +Remove current image from file list and go to next image. +.TP +.BR Ctrl-h ", " Ctrl-Left +Scroll left one screen width. +.TP +.BR Ctrl-j ", " Ctrl-Down +Scroll down one screen height. +.TP +.BR Ctrl-k ", " Ctrl-Up +Scroll up one screen height. +.TP +.BR Ctrl-l ", " Ctrl-Right +Scroll right one screen width. +.TP +.BR + +Zoom in. +.TP +.B \- +Zoom out. +.TP +.B m +Mark/unmark the current image. +.TP +.B M +Reverse all image marks. +.TP +.B Ctrl-M +Repeat last mark action on all images from the last marked/unmarked up to the +current one. +.TP +.B Ctrl-m +Remove all image marks. +.TP +.B N +Go +.I count +marked images forward. +.TP +.B P +Go +.I count +marked images backward. +.TP +.B { +Decrease gamma correction by +.I count +steps. +.TP +.B } +Increase gamma correction by +.I count +steps. +.TP +.B Ctrl-g +Reset gamma correction. +.SS Thumbnail mode +The following keyboard commands are only available in thumbnail mode: +.TP +.BR h ", " Left +Move selection left +.I count +times. +.TP +.BR j ", " Down +Move selection down +.I count +times. +.TP +.BR k ", " Up +Move selection up +.I count +times. +.TP +.BR l ", " Right +Move selection right +.I count +times. +.TP +.B R +Reload all thumbnails. +.SS Image mode +The following keyboard commands are only available in image mode: +.TP +Navigate image list: +.TP +.BR n ", " Space +Go +.I count +images forward. +.TP +.BR p ", " Backspace +Go +.I count +images backward. +.TP +.B [ +Go +.I count +* 10 images backward. +.TP +.B ] +Go +.I count +* 10 images forward. +.TP +Handle multi-frame images: +.TP +.B Ctrl-n +Go +.I count +frames of a multi-frame image forward. +.TP +.B Ctrl-p +Go +.I count +frames of a multi-frame image backward. +.TP +.B Ctrl-Space +Play/stop animations of multi-frame images. +.TP +Panning: +.TP +.BR h ", " Left +Scroll image 1/5 of window width or +.I count +pixel left. +.TP +.BR j ", " Down +Scroll image 1/5 of window height or +.I count +pixel down. +.TP +.BR k ", " Up +Scroll image 1/5 of window height or +.I count +pixel up. +.TP +.BR l ", " Right +Scroll image 1/5 of window width or +.I count +pixel right. +.TP +.B H +Scroll to left image edge. +.TP +.B J +Scroll to bottom image edge. +.TP +.B K +Scroll to top image edge. +.TP +.B L +Scroll to right image edge. +.TP +Zooming: +.TP +.B = +Set zoom level to 100%, or +.IR count %. +.TP +.B w +Set zoom level to 100%, but fit large images into window. +.TP +.B W +Fit image to window. +.TP +.B e +Fit image to window width. +.TP +.B E +Fit image to window height. +.TP +Rotation: +.TP +.B < +Rotate image counter-clockwise by 90 degrees. +.TP +.B > +Rotate image clockwise by 90 degrees. +.TP +.B ? +Rotate image by 180 degrees. +.TP +Flipping: +.TP +.B | +Flip image horizontally. +.TP +.B _ +Flip image vertically. +.TP +Miscellaneous: +.TP +.B a +Toggle anti-aliasing. +.TP +.B A +Toggle visibility of alpha-channel, i.e. image transparency. +.TP +.B s +Toggle slideshow mode and/or set the delay between images to +.I count +seconds. +.SH MOUSE COMMANDS +The following mouse mappings are available in image mode: +.TP +General: +.TP +.B Button3 +Switch to thumbnail mode. +.TP +Navigate image list: +.TP +.B Button1 +Go to the next image if the mouse cursor is in the right part of the window or +to the previous image if it is in the left part. +.TP +Panning: +.TP +.B Button2 +Pan the image according to the mouse cursor position in the window while +keeping this button pressed down. +.TP +Zooming: +.TP +.B ScrollUp +Zoom in. +.TP +.B ScrollDown +Zoom out. +.SH CONFIGURATION +The following X resources are supported: +.TP +.B background +Color of the window background and bar foreground +.TP +.B foreground +Color of the window foreground and bar background +.TP +.B font +Name of Xft bar font +.TP +Please see xrdb(1) on how to change them. +.SH STATUS BAR +The information displayed on the left side of the status bar can be replaced +with the output of a user-provided script, which is called by sxiv whenever an +image gets loaded. The path of this script is +.I $XDG_CONFIG_HOME/sxiv/exec/image-info +and the arguments given to it are: 1) path to image file, 2) image width, +3) image height. +.P +There is also an example script installed together with sxiv as +.IR PREFIX/share/sxiv/exec/image-info . +.SH EXTERNAL KEY HANDLER +Additional external keyboard commands can be defined using a handler program +located in +.IR $XDG_CONFIG_HOME/sxiv/exec/key-handler . +The handler is invoked by pressing +.BR Ctrl-x . +The next key combo is passed as its first argument. Passed via stdin are the +images to act upon, one path per line: all marked images, if in thumbnail mode +and at least one image has been marked, otherwise the current image. +sxiv(1) will block until the handler terminates. It then checks which images +have been modified and reloads them. + +The key combo argument has the following form: "[C-][M-][S-]KEY", +where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X +keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. + +There is also an example script installed together with sxiv as +.IR PREFIX/share/sxiv/exec/key-handler . +.SH THUMBNAIL CACHING +sxiv stores all thumbnails under +.IR $XDG_CACHE_HOME/sxiv/ . +.P +Use the command line option +.I \-c +to remove all orphaned cache files. Additionally, run the following command +afterwards inside the cache directory to remove empty subdirectories: +.P +.RS +find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\; +.RE +.SH AUTHOR +.EX +Bert Muennich <ber.t at posteo.de> +.EE +.SH CONTRIBUTORS +.EX +Bastien Dejean <nihilhill at gmail.com> +Dave Reisner <d at falconindy.com> +Fung SzeTat <sthorde at gmail.com> +Max Voit <mvdev at with-eyes.net> +.EE +.SH HOMEPAGE +.EX +https://github.com/muennich/sxiv +.EE +.SH SEE ALSO +.BR X (7), +.BR xrdb (1) diff --git a/sxiv/sxiv.desktop b/sxiv/sxiv.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=sxiv +GenericName=Image Viewer +Exec=sxiv %F +MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap; +NoDisplay=true +Icon=sxiv diff --git a/sxiv/sxiv.h b/sxiv/sxiv.h @@ -0,0 +1,448 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SXIV_H +#define SXIV_H + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <sys/time.h> +#include <sys/types.h> +#include <Imlib2.h> +#include <X11/Xlib.h> + +/* + * Annotation for functions called in cleanup(). + * These functions are not allowed to call error(!0, ...) or exit(). + */ +#define CLEANUP + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) + +#define STREQ(s1,s2) (strcmp((s1), (s2)) == 0) + +#define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \ + ((t1)->tv_usec - (t2)->tv_usec) / 1000) + +#define TV_SET_MSEC(tv,t) { \ + (tv)->tv_sec = (t) / 1000; \ + (tv)->tv_usec = (t) % 1000 * 1000; \ +} + +#define TV_ADD_MSEC(tv,t) { \ + (tv)->tv_sec += (t) / 1000; \ + (tv)->tv_usec += (t) % 1000 * 1000; \ +} + +typedef enum { + BO_BIG_ENDIAN, + BO_LITTLE_ENDIAN +} byteorder_t; + +typedef enum { + MODE_IMAGE, + MODE_THUMB +} appmode_t; + +typedef enum { + DIR_LEFT = 1, + DIR_RIGHT = 2, + DIR_UP = 4, + DIR_DOWN = 8 +} direction_t; + +typedef enum { + DEGREE_90 = 1, + DEGREE_180 = 2, + DEGREE_270 = 3 +} degree_t; + +typedef enum { + FLIP_HORIZONTAL = 1, + FLIP_VERTICAL = 2 +} flipdir_t; + +typedef enum { + SCALE_DOWN, + SCALE_FIT, + SCALE_WIDTH, + SCALE_HEIGHT, + SCALE_ZOOM +} scalemode_t; + +typedef enum { + DRAG_RELATIVE, + DRAG_ABSOLUTE +} dragmode_t; + +typedef enum { + CURSOR_ARROW, + CURSOR_DRAG, + CURSOR_WATCH, + CURSOR_LEFT, + CURSOR_RIGHT, + CURSOR_NONE, + + CURSOR_COUNT +} cursor_t; + +typedef enum { + FF_WARN = 1, + FF_MARK = 2, + FF_TN_INIT = 4 +} fileflags_t; + +typedef struct { + const char *name; /* as given by user */ + const char *path; /* always absolute */ + fileflags_t flags; +} fileinfo_t; + +/* timeouts in milliseconds: */ +enum { + TO_REDRAW_RESIZE = 75, + TO_REDRAW_THUMBS = 200, + TO_CURSOR_HIDE = 1200, + TO_DOUBLE_CLICK = 300 +}; + +typedef void (*timeout_f)(void); + +typedef struct arl arl_t; +typedef struct img img_t; +typedef struct opt opt_t; +typedef struct tns tns_t; +typedef struct win win_t; + + +/* autoreload.c */ + +struct arl { + int fd; + int wd_dir; + int wd_file; + char *filename; +}; + +void arl_init(arl_t*); +void arl_cleanup(arl_t*); +void arl_setup(arl_t*, const char* /* result of realpath(3) */); +bool arl_handle(arl_t*); + + +/* commands.c */ + +typedef int arg_t; +typedef bool (*cmd_f)(arg_t); + +#define G_CMD(c) g_##c, +#define I_CMD(c) i_##c, +#define T_CMD(c) t_##c, + +typedef enum { +#include "commands.lst" + CMD_COUNT +} cmd_id_t; + +typedef struct { + int mode; + cmd_f func; +} cmd_t; + +typedef struct { + unsigned int mask; + KeySym ksym; + cmd_id_t cmd; + arg_t arg; +} keymap_t; + +typedef struct { + unsigned int mask; + unsigned int button; + cmd_id_t cmd; + arg_t arg; +} button_t; + +extern const cmd_t cmds[CMD_COUNT]; + + +/* image.c */ + +typedef struct { + Imlib_Image im; + unsigned int delay; +} img_frame_t; + +typedef struct { + img_frame_t *frames; + int cap; + int cnt; + int sel; + bool animate; + int framedelay; + int length; +} multi_img_t; + +struct img { + Imlib_Image im; + int w; + int h; + + win_t *win; + float x; + float y; + + scalemode_t scalemode; + float zoom; + + bool checkpan; + bool dirty; + bool aa; + bool alpha; + + Imlib_Color_Modifier cmod; + int gamma; + + struct { + bool on; + int delay; + } ss; + + multi_img_t multi; +}; + +void img_init(img_t*, win_t*); +bool img_load(img_t*, const fileinfo_t*); +CLEANUP void img_close(img_t*, bool); +void img_render(img_t*); +bool img_fit_win(img_t*, scalemode_t); +bool img_zoom(img_t*, float); +bool img_zoom_in(img_t*); +bool img_zoom_out(img_t*); +bool img_pos(img_t*, float, float); +bool img_move(img_t*, float, float); +bool img_pan(img_t*, direction_t, int); +bool img_pan_edge(img_t*, direction_t); +void img_rotate(img_t*, degree_t); +void img_flip(img_t*, flipdir_t); +void img_toggle_antialias(img_t*); +bool img_change_gamma(img_t*, int); +bool img_frame_navigate(img_t*, int); +bool img_frame_animate(img_t*); + + +/* options.c */ + +struct opt { + /* file list: */ + char **filenames; + bool from_stdin; + bool to_stdout; + bool recursive; + int filecnt; + int startnum; + + /* image: */ + scalemode_t scalemode; + float zoom; + bool animate; + int gamma; + int slideshow; + int framerate; + + /* window: */ + bool fullscreen; + bool hide_bar; + long embed; + char *geometry; + char *res_name; + + /* misc flags: */ + bool quiet; + bool thumb_mode; + bool clean_cache; + bool private_mode; +}; + +extern const opt_t *options; + +void print_usage(void); +void print_version(void); +void parse_options(int, char**); + + +/* thumbs.c */ + +typedef struct { + Imlib_Image im; + int w; + int h; + int x; + int y; +} thumb_t; + +struct tns { + fileinfo_t *files; + thumb_t *thumbs; + const int *cnt; + int *sel; + int initnext; + int loadnext; + int first, end; + int r_first, r_end; + + win_t *win; + int x; + int y; + int cols; + int rows; + int zl; + int bw; + int dim; + + bool dirty; +}; + +void tns_clean_cache(tns_t*); +void tns_init(tns_t*, fileinfo_t*, const int*, int*, win_t*); +CLEANUP void tns_free(tns_t*); +bool tns_load(tns_t*, int, bool, bool); +void tns_unload(tns_t*, int); +void tns_render(tns_t*); +void tns_mark(tns_t*, int, bool); +void tns_highlight(tns_t*, int, bool); +bool tns_move_selection(tns_t*, direction_t, int); +bool tns_scroll(tns_t*, direction_t, bool); +bool tns_zoom(tns_t*, int); +int tns_translate(tns_t*, int, int); + + +/* util.c */ + +#include <dirent.h> + +typedef struct { + DIR *dir; + char *name; + int d; + bool recursive; + + char **stack; + int stcap; + int stlen; +} r_dir_t; + +extern const char *progname; + +void* emalloc(size_t); +void* erealloc(void*, size_t); +char* estrdup(const char*); +void error(int, int, const char*, ...); +void size_readable(float*, const char**); +int r_opendir(r_dir_t*, const char*, bool); +int r_closedir(r_dir_t*); +char* r_readdir(r_dir_t*, bool); +int r_mkdir(char*); + + +/* window.c */ + +#include <X11/Xutil.h> +#include <X11/Xft/Xft.h> + +enum { + BAR_L_LEN = 512, + BAR_R_LEN = 64 +}; + +enum { + ATOM_WM_DELETE_WINDOW, + ATOM__NET_WM_NAME, + ATOM__NET_WM_ICON_NAME, + ATOM__NET_WM_ICON, + ATOM__NET_WM_STATE, + ATOM__NET_WM_STATE_FULLSCREEN, + ATOM_COUNT +}; + +typedef struct { + Display *dpy; + int scr; + int scrw, scrh; + Visual *vis; + Colormap cmap; + int depth; +} win_env_t; + +typedef struct { + size_t size; + char *p; + char *buf; +} win_bar_t; + +struct win { + Window xwin; + win_env_t env; + + XftColor bg; + XftColor fg; + + int x; + int y; + unsigned int w; + unsigned int h; /* = win height - bar height */ + unsigned int bw; + + struct { + int w; + int h; + Pixmap pm; + } buf; + + struct { + unsigned int h; + win_bar_t l; + win_bar_t r; + } bar; +}; + +extern Atom atoms[ATOM_COUNT]; + +void win_init(win_t*); +void win_open(win_t*); +CLEANUP void win_close(win_t*); +bool win_configure(win_t*, XConfigureEvent*); +void win_toggle_fullscreen(win_t*); +void win_toggle_bar(win_t*); +void win_clear(win_t*); +void win_draw(win_t*); +void win_draw_rect(win_t*, int, int, int, int, bool, int, unsigned long); +void win_set_title(win_t*, const char*); +void win_set_cursor(win_t*, cursor_t); +void win_cursor_pos(win_t*, int*, int*); + +#endif /* SXIV_H */ + diff --git a/sxiv/thumbs.c b/sxiv/thumbs.c @@ -0,0 +1,594 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" +#define _THUMBS_CONFIG +#include "config.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <utime.h> + +#if HAVE_LIBEXIF +#include <libexif/exif-data.h> +void exif_auto_orientate(const fileinfo_t*); +#endif +Imlib_Image img_open(const fileinfo_t*); + +static char *cache_dir; + +char* tns_cache_filepath(const char *filepath) +{ + size_t len; + char *cfile = NULL; + + if (*filepath != '/') + return NULL; + + if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) { + /* don't cache images inside the cache directory! */ + len = strlen(cache_dir) + strlen(filepath) + 2; + cfile = (char*) emalloc(len); + snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1); + } + return cfile; +} + +Imlib_Image tns_cache_load(const char *filepath, bool *outdated) +{ + char *cfile; + struct stat cstats, fstats; + Imlib_Image im = NULL; + + if (stat(filepath, &fstats) < 0) + return NULL; + + if ((cfile = tns_cache_filepath(filepath)) != NULL) { + if (stat(cfile, &cstats) == 0) { + if (cstats.st_mtime == fstats.st_mtime) + im = imlib_load_image(cfile); + else + *outdated = true; + } + free(cfile); + } + return im; +} + +void tns_cache_write(Imlib_Image im, const char *filepath, bool force) +{ + char *cfile, *dirend; + struct stat cstats, fstats; + struct utimbuf times; + Imlib_Load_Error err; + + if (options->private_mode) + return; + + if (stat(filepath, &fstats) < 0) + return; + + if ((cfile = tns_cache_filepath(filepath)) != NULL) { + if (force || stat(cfile, &cstats) < 0 || + cstats.st_mtime != fstats.st_mtime) + { + if ((dirend = strrchr(cfile, '/')) != NULL) { + *dirend = '\0'; + if (r_mkdir(cfile) == -1) { + error(0, errno, "%s", cfile); + goto end; + } + *dirend = '/'; + } + imlib_context_set_image(im); + if (imlib_image_has_alpha()) { + imlib_image_set_format("png"); + } else { + imlib_image_set_format("jpg"); + imlib_image_attach_data_value("quality", NULL, 90, NULL); + } + imlib_save_image_with_error_return(cfile, &err); + if (err) + goto end; + times.actime = fstats.st_atime; + times.modtime = fstats.st_mtime; + utime(cfile, &times); + } +end: + free(cfile); + } +} + +void tns_clean_cache(tns_t *tns) +{ + int dirlen; + char *cfile, *filename; + r_dir_t dir; + + if (r_opendir(&dir, cache_dir, true) < 0) { + error(0, errno, "%s", cache_dir); + return; + } + + dirlen = strlen(cache_dir); + + while ((cfile = r_readdir(&dir, false)) != NULL) { + filename = cfile + dirlen; + if (access(filename, F_OK) < 0) { + if (unlink(cfile) < 0) + error(0, errno, "%s", cfile); + } + free(cfile); + } + r_closedir(&dir); +} + + +void tns_init(tns_t *tns, fileinfo_t *files, const int *cnt, int *sel, + win_t *win) +{ + int len; + const char *homedir, *dsuffix = ""; + + if (cnt != NULL && *cnt > 0) { + tns->thumbs = (thumb_t*) emalloc(*cnt * sizeof(thumb_t)); + memset(tns->thumbs, 0, *cnt * sizeof(thumb_t)); + } else { + tns->thumbs = NULL; + } + tns->files = files; + tns->cnt = cnt; + tns->initnext = tns->loadnext = 0; + tns->first = tns->end = tns->r_first = tns->r_end = 0; + tns->sel = sel; + tns->win = win; + tns->dirty = false; + + tns->zl = THUMB_SIZE; + tns_zoom(tns, 0); + + if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') { + homedir = getenv("HOME"); + dsuffix = "/.cache"; + } + if (homedir != NULL) { + free(cache_dir); + len = strlen(homedir) + strlen(dsuffix) + 6; + cache_dir = (char*) emalloc(len); + snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix); + } else { + error(0, 0, "Cache directory not found"); + } +} + +CLEANUP void tns_free(tns_t *tns) +{ + int i; + + if (tns->thumbs != NULL) { + for (i = 0; i < *tns->cnt; i++) { + if (tns->thumbs[i].im != NULL) { + imlib_context_set_image(tns->thumbs[i].im); + imlib_free_image(); + } + } + free(tns->thumbs); + tns->thumbs = NULL; + } + + free(cache_dir); + cache_dir = NULL; +} + +Imlib_Image tns_scale_down(Imlib_Image im, int dim) +{ + int w, h; + float z, zw, zh; + + imlib_context_set_image(im); + w = imlib_image_get_width(); + h = imlib_image_get_height(); + zw = (float) dim / (float) w; + zh = (float) dim / (float) h; + z = MIN(zw, zh); + z = MIN(z, 1.0); + + if (z < 1.0) { + imlib_context_set_anti_alias(1); + im = imlib_create_cropped_scaled_image(0, 0, w, h, + MAX(z * w, 1), MAX(z * h, 1)); + if (im == NULL) + error(EXIT_FAILURE, ENOMEM, NULL); + imlib_free_image_and_decache(); + } + return im; +} + +bool tns_load(tns_t *tns, int n, bool force, bool cache_only) +{ + int maxwh = thumb_sizes[ARRLEN(thumb_sizes)-1]; + bool cache_hit = false; + char *cfile; + thumb_t *t; + fileinfo_t *file; + Imlib_Image im = NULL; + + if (n < 0 || n >= *tns->cnt) + return false; + file = &tns->files[n]; + if (file->name == NULL || file->path == NULL) + return false; + + t = &tns->thumbs[n]; + + if (t->im != NULL) { + imlib_context_set_image(t->im); + imlib_free_image(); + t->im = NULL; + } + + if (!force) { + if ((im = tns_cache_load(file->path, &force)) != NULL) { + imlib_context_set_image(im); + if (imlib_image_get_width() < maxwh && + imlib_image_get_height() < maxwh) + { + if ((cfile = tns_cache_filepath(file->path)) != NULL) { + unlink(cfile); + free(cfile); + } + imlib_free_image_and_decache(); + im = NULL; + } else { + cache_hit = true; + } +#if HAVE_LIBEXIF + } else if (!force && !options->private_mode) { + int pw = 0, ph = 0, w, h, x = 0, y = 0; + bool err; + float zw, zh; + ExifData *ed; + ExifEntry *entry; + ExifContent *ifd; + ExifByteOrder byte_order; + int tmpfd; + char tmppath[] = "/tmp/sxiv-XXXXXX"; + Imlib_Image tmpim; + + if ((ed = exif_data_new_from_file(file->path)) != NULL) { + if (ed->data != NULL && ed->size > 0 && + (tmpfd = mkstemp(tmppath)) >= 0) + { + err = write(tmpfd, ed->data, ed->size) != ed->size; + close(tmpfd); + + if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) { + byte_order = exif_data_get_byte_order(ed); + ifd = ed->ifd[EXIF_IFD_EXIF]; + entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION); + if (entry != NULL) + pw = exif_get_long(entry->data, byte_order); + entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION); + if (entry != NULL) + ph = exif_get_long(entry->data, byte_order); + + imlib_context_set_image(tmpim); + w = imlib_image_get_width(); + h = imlib_image_get_height(); + + if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) { + zw = (float) pw / (float) w; + zh = (float) ph / (float) h; + if (zw < zh) { + pw /= zh; + x = (w - pw) / 2; + w = pw; + } else if (zw > zh) { + ph /= zw; + y = (h - ph) / 2; + h = ph; + } + } + if (w >= maxwh || h >= maxwh) { + if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL) + error(EXIT_FAILURE, ENOMEM, NULL); + } + imlib_free_image_and_decache(); + } + unlink(tmppath); + } + exif_data_unref(ed); + } +#endif + } + } + + if (im == NULL) { + if ((im = img_open(file)) == NULL) + return false; + } + imlib_context_set_image(im); + + if (!cache_hit) { +#if HAVE_LIBEXIF + exif_auto_orientate(file); +#endif + im = tns_scale_down(im, maxwh); + imlib_context_set_image(im); + if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh) + tns_cache_write(im, file->path, true); + } + + if (cache_only) { + imlib_free_image_and_decache(); + } else { + t->im = tns_scale_down(im, thumb_sizes[tns->zl]); + imlib_context_set_image(t->im); + t->w = imlib_image_get_width(); + t->h = imlib_image_get_height(); + tns->dirty = true; + } + file->flags |= FF_TN_INIT; + + if (n == tns->initnext) + while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT)); + if (n == tns->loadnext && !cache_only) + while (++tns->loadnext < tns->end && (++t)->im != NULL); + + return true; +} + +void tns_unload(tns_t *tns, int n) +{ + thumb_t *t; + + if (n < 0 || n >= *tns->cnt) + return; + + t = &tns->thumbs[n]; + + if (t->im != NULL) { + imlib_context_set_image(t->im); + imlib_free_image(); + t->im = NULL; + } +} + +void tns_check_view(tns_t *tns, bool scrolled) +{ + int r; + + if (tns == NULL) + return; + + tns->first -= tns->first % tns->cols; + r = *tns->sel % tns->cols; + + if (scrolled) { + /* move selection into visible area */ + if (*tns->sel >= tns->first + tns->cols * tns->rows) + *tns->sel = tns->first + r + tns->cols * (tns->rows - 1); + else if (*tns->sel < tns->first) + *tns->sel = tns->first + r; + } else { + /* scroll to selection */ + if (tns->first + tns->cols * tns->rows <= *tns->sel) { + tns->first = *tns->sel - r - tns->cols * (tns->rows - 1); + tns->dirty = true; + } else if (tns->first > *tns->sel) { + tns->first = *tns->sel - r; + tns->dirty = true; + } + } +} + +void tns_render(tns_t *tns) +{ + thumb_t *t; + win_t *win; + int i, cnt, r, x, y; + + if (!tns->dirty) + return; + + win = tns->win; + win_clear(win); + imlib_context_set_drawable(win->buf.pm); + + tns->cols = MAX(1, win->w / tns->dim); + tns->rows = MAX(1, win->h / tns->dim); + + if (*tns->cnt < tns->cols * tns->rows) { + tns->first = 0; + cnt = *tns->cnt; + } else { + tns_check_view(tns, false); + cnt = tns->cols * tns->rows; + if ((r = tns->first + cnt - *tns->cnt) >= tns->cols) + tns->first -= r - r % tns->cols; + if (r > 0) + cnt -= r % tns->cols; + } + r = cnt % tns->cols ? 1 : 0; + tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3; + tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3; + tns->loadnext = *tns->cnt; + tns->end = tns->first + cnt; + + for (i = tns->r_first; i < tns->r_end; i++) { + if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL) + tns_unload(tns, i); + } + tns->r_first = tns->first; + tns->r_end = tns->end; + + for (i = tns->first; i < tns->end; i++) { + t = &tns->thumbs[i]; + if (t->im != NULL) { + t->x = x + (thumb_sizes[tns->zl] - t->w) / 2; + t->y = y + (thumb_sizes[tns->zl] - t->h) / 2; + imlib_context_set_image(t->im); + imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h); + if (tns->files[i].flags & FF_MARK) + tns_mark(tns, i, true); + } else { + tns->loadnext = MIN(tns->loadnext, i); + } + if ((i + 1) % tns->cols == 0) { + x = tns->x; + y += tns->dim; + } else { + x += tns->dim; + } + } + tns->dirty = false; + tns_highlight(tns, *tns->sel, true); +} + +void tns_mark(tns_t *tns, int n, bool mark) +{ + if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { + win_t *win = tns->win; + thumb_t *t = &tns->thumbs[n]; + unsigned long col = win->bg.pixel; + int x = t->x + t->w, y = t->y + t->h; + + win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col); + win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col); + + if (mark) + col = win->fg.pixel; + + win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col); + + if (!mark && n == *tns->sel) + tns_highlight(tns, n, true); + } +} + +void tns_highlight(tns_t *tns, int n, bool hl) +{ + if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { + win_t *win = tns->win; + thumb_t *t = &tns->thumbs[n]; + unsigned long col = hl ? win->fg.pixel : win->bg.pixel; + int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2; + + win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh, + false, tns->bw, col); + + if (tns->files[n].flags & FF_MARK) + tns_mark(tns, n, true); + } +} + +bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) +{ + int old, max; + + old = *tns->sel; + cnt = cnt > 1 ? cnt : 1; + + switch (dir) { + case DIR_UP: + *tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols); + break; + case DIR_DOWN: + max = tns->cols * ((*tns->cnt - 1) / tns->cols) + + MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols); + *tns->sel = MIN(*tns->sel + cnt * tns->cols, max); + break; + case DIR_LEFT: + *tns->sel = MAX(*tns->sel - cnt, 0); + break; + case DIR_RIGHT: + *tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1); + break; + } + + if (*tns->sel != old) { + tns_highlight(tns, old, false); + tns_check_view(tns, false); + if (!tns->dirty) + tns_highlight(tns, *tns->sel, true); + } + return *tns->sel != old; +} + +bool tns_scroll(tns_t *tns, direction_t dir, bool screen) +{ + int d, max, old; + + old = tns->first; + d = tns->cols * (screen ? tns->rows : 1); + + if (dir == DIR_DOWN) { + max = *tns->cnt - tns->cols * tns->rows; + if (*tns->cnt % tns->cols != 0) + max += tns->cols - *tns->cnt % tns->cols; + tns->first = MIN(tns->first + d, max); + } else if (dir == DIR_UP) { + tns->first = MAX(tns->first - d, 0); + } + + if (tns->first != old) { + tns_check_view(tns, true); + tns->dirty = true; + } + return tns->first != old; +} + +bool tns_zoom(tns_t *tns, int d) +{ + int i, oldzl; + + oldzl = tns->zl; + tns->zl += -(d < 0) + (d > 0); + tns->zl = MAX(tns->zl, 0); + tns->zl = MIN(tns->zl, ARRLEN(thumb_sizes)-1); + + tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1; + tns->bw = MIN(tns->bw, 4); + tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6; + + if (tns->zl != oldzl) { + for (i = 0; i < *tns->cnt; i++) + tns_unload(tns, i); + tns->dirty = true; + } + return tns->zl != oldzl; +} + +int tns_translate(tns_t *tns, int x, int y) +{ + int n; + + if (x < tns->x || y < tns->y) + return -1; + + n = tns->first + (y - tns->y) / tns->dim * tns->cols + + (x - tns->x) / tns->dim; + if (n >= *tns->cnt) + n = -1; + + return n; +} diff --git a/sxiv/utf8.h b/sxiv/utf8.h @@ -0,0 +1,68 @@ +/* Branchless UTF-8 decoder + * + * This is free and unencumbered software released into the public domain. + */ +#ifndef UTF8_H +#define UTF8_H + +#include <stdint.h> + +/* Decode the next character, C, from BUF, reporting errors in E. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in E, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +static void * +utf8_decode(void *buf, uint32_t *c, int *e) +{ + static const char lengths[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 + }; + static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + static const int shiftc[] = {0, 18, 12, 6, 0}; + static const int shifte[] = {0, 6, 4, 2, 0}; + + unsigned char *s = buf; + int len = lengths[s[0] >> 3]; + + /* Compute the pointer to the next character early so that the next + * iteration can start working on the next character. Neither Clang + * nor GCC figure out this reordering on their own. + */ + unsigned char *next = s + len + !len; + + /* Assume a four-byte character and load four bytes. Unused bits are + * shifted out. + */ + *c = (uint32_t)(s[0] & masks[len]) << 18; + *c |= (uint32_t)(s[1] & 0x3f) << 12; + *c |= (uint32_t)(s[2] & 0x3f) << 6; + *c |= (uint32_t)(s[3] & 0x3f) << 0; + *c >>= shiftc[len]; + + /* Accumulate the various error conditions. */ + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (s[1] & 0xc0) >> 2; + *e |= (s[2] & 0xc0) >> 4; + *e |= (s[3] ) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +#endif diff --git a/sxiv/util.c b/sxiv/util.c @@ -0,0 +1,213 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +const char *progname; + +void* emalloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + if (ptr == NULL) + error(EXIT_FAILURE, errno, NULL); + return ptr; +} + +void* erealloc(void *ptr, size_t size) +{ + ptr = realloc(ptr, size); + if (ptr == NULL) + error(EXIT_FAILURE, errno, NULL); + return ptr; +} + +char* estrdup(const char *s) +{ + char *d; + size_t n = strlen(s) + 1; + + d = malloc(n); + if (d == NULL) + error(EXIT_FAILURE, errno, NULL); + memcpy(d, s, n); + return d; +} + +void error(int eval, int err, const char* fmt, ...) +{ + va_list ap; + + if (eval == 0 && options->quiet) + return; + + fflush(stdout); + fprintf(stderr, "%s: ", progname); + va_start(ap, fmt); + if (fmt != NULL) + vfprintf(stderr, fmt, ap); + va_end(ap); + if (err != 0) + fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err)); + fputc('\n', stderr); + + if (eval != 0) + exit(eval); +} + +void size_readable(float *size, const char **unit) +{ + const char *units[] = { "", "K", "M", "G" }; + int i; + + for (i = 0; i < ARRLEN(units) && *size > 1024.0; i++) + *size /= 1024.0; + *unit = units[MIN(i, ARRLEN(units) - 1)]; +} + +int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive) +{ + if (*dirname == '\0') + return -1; + + if ((rdir->dir = opendir(dirname)) == NULL) { + rdir->name = NULL; + rdir->stack = NULL; + return -1; + } + + rdir->stcap = 512; + rdir->stack = (char**) emalloc(rdir->stcap * sizeof(char*)); + rdir->stlen = 0; + + rdir->name = (char*) dirname; + rdir->d = 0; + rdir->recursive = recursive; + + return 0; +} + +int r_closedir(r_dir_t *rdir) +{ + int ret = 0; + + if (rdir->stack != NULL) { + while (rdir->stlen > 0) + free(rdir->stack[--rdir->stlen]); + free(rdir->stack); + rdir->stack = NULL; + } + + if (rdir->dir != NULL) { + if ((ret = closedir(rdir->dir)) == 0) + rdir->dir = NULL; + } + + if (rdir->d != 0) { + free(rdir->name); + rdir->name = NULL; + } + + return ret; +} + +char* r_readdir(r_dir_t *rdir, bool skip_dotfiles) +{ + size_t len; + char *filename; + struct dirent *dentry; + struct stat fstats; + + while (true) { + if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) { + if (dentry->d_name[0] == '.') { + if (skip_dotfiles) + continue; + if (dentry->d_name[1] == '\0') + continue; + if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0') + continue; + } + + len = strlen(rdir->name) + strlen(dentry->d_name) + 2; + filename = (char*) emalloc(len); + snprintf(filename, len, "%s%s%s", rdir->name, + rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/", + dentry->d_name); + + if (stat(filename, &fstats) < 0) + continue; + if (S_ISDIR(fstats.st_mode)) { + /* put subdirectory on the stack */ + if (rdir->stlen == rdir->stcap) { + rdir->stcap *= 2; + rdir->stack = (char**) erealloc(rdir->stack, + rdir->stcap * sizeof(char*)); + } + rdir->stack[rdir->stlen++] = filename; + continue; + } + return filename; + } + + if (rdir->recursive && rdir->stlen > 0) { + /* open next subdirectory */ + closedir(rdir->dir); + if (rdir->d != 0) + free(rdir->name); + rdir->name = rdir->stack[--rdir->stlen]; + rdir->d = 1; + if ((rdir->dir = opendir(rdir->name)) == NULL) + error(0, errno, "%s", rdir->name); + continue; + } + /* no more entries */ + break; + } + return NULL; +} + +int r_mkdir(char *path) +{ + char c, *s = path; + struct stat st; + + while (*s != '\0') { + if (*s == '/') { + s++; + continue; + } + for (; *s != '\0' && *s != '/'; s++); + c = *s; + *s = '\0'; + if (mkdir(path, 0755) == -1) + if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) + return -1; + *s = c; + } + return 0; +} + diff --git a/sxiv/window.c b/sxiv/window.c @@ -0,0 +1,476 @@ +/* Copyright 2011-2013 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "sxiv.h" +#define _WINDOW_CONFIG +#include "config.h" +#include "icon/data.h" +#include "utf8.h" + +#include <stdlib.h> +#include <string.h> +#include <locale.h> +#include <X11/cursorfont.h> +#include <X11/Xatom.h> +#include <X11/Xresource.h> + +#define RES_CLASS "Sxiv" + +enum { + H_TEXT_PAD = 5, + V_TEXT_PAD = 1 +}; + +static struct { + int name; + Cursor icon; +} cursors[CURSOR_COUNT] = { + { XC_left_ptr }, { XC_dotbox }, { XC_watch }, + { XC_sb_left_arrow }, { XC_sb_right_arrow } +}; + +static GC gc; + +static XftFont *font; +static int fontheight; +static double fontsize; +static int barheight; + +Atom atoms[ATOM_COUNT]; + +void win_init_font(const win_env_t *e, const char *fontstr) +{ + if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL) + error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr); + fontheight = font->ascent + font->descent; + FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize); + barheight = fontheight + 2 * V_TEXT_PAD; +} + +void win_alloc_color(const win_env_t *e, const char *name, XftColor *col) +{ + if (!XftColorAllocName(e->dpy, DefaultVisual(e->dpy, e->scr), + DefaultColormap(e->dpy, e->scr), name, col)) + { + error(EXIT_FAILURE, 0, "Error allocating color '%s'", name); + } +} + +const char* win_res(XrmDatabase db, const char *name, const char *def) +{ + char *type; + XrmValue ret; + + if (db != None && + XrmGetResource(db, name, name, &type, &ret) && + STREQ(type, "String")) + { + return ret.addr; + } else { + return def; + } +} + +#define INIT_ATOM_(atom) \ + atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False); + +void win_init(win_t *win) +{ + win_env_t *e; + const char *bg, *fg, *f; + char *res_man; + XrmDatabase db; + + memset(win, 0, sizeof(win_t)); + + e = &win->env; + if ((e->dpy = XOpenDisplay(NULL)) == NULL) + error(EXIT_FAILURE, 0, "Error opening X display"); + + e->scr = DefaultScreen(e->dpy); + e->scrw = DisplayWidth(e->dpy, e->scr); + e->scrh = DisplayHeight(e->dpy, e->scr); + e->vis = DefaultVisual(e->dpy, e->scr); + e->cmap = DefaultColormap(e->dpy, e->scr); + e->depth = DefaultDepth(e->dpy, e->scr); + + if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0) + error(0, 0, "No locale support"); + + XrmInitialize(); + res_man = XResourceManagerString(e->dpy); + db = res_man != NULL ? XrmGetStringDatabase(res_man) : None; + + f = win_res(db, RES_CLASS ".font", "monospace-8"); + win_init_font(e, f); + + bg = win_res(db, RES_CLASS ".background", "white"); + fg = win_res(db, RES_CLASS ".foreground", "black"); + win_alloc_color(e, bg, &win->bg); + win_alloc_color(e, fg, &win->fg); + + win->bar.l.size = BAR_L_LEN; + win->bar.r.size = BAR_R_LEN; + /* 3 padding bytes needed by utf8_decode */ + win->bar.l.buf = emalloc(win->bar.l.size + 3); + win->bar.l.buf[0] = '\0'; + win->bar.r.buf = emalloc(win->bar.r.size + 3); + win->bar.r.buf[0] = '\0'; + win->bar.h = options->hide_bar ? 0 : barheight; + + INIT_ATOM_(WM_DELETE_WINDOW); + INIT_ATOM_(_NET_WM_NAME); + INIT_ATOM_(_NET_WM_ICON_NAME); + INIT_ATOM_(_NET_WM_ICON); + INIT_ATOM_(_NET_WM_STATE); + INIT_ATOM_(_NET_WM_STATE_FULLSCREEN); +} + +void win_open(win_t *win) +{ + int c, i, j, n; + long parent; + win_env_t *e; + XClassHint classhint; + unsigned long *icon_data; + XColor col; + Cursor *cnone = &cursors[CURSOR_NONE].icon; + char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + Pixmap none; + int gmask; + XSizeHints sizehints; + + e = &win->env; + parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr); + + sizehints.flags = PWinGravity; + sizehints.win_gravity = NorthWestGravity; + + /* determine window offsets, width & height */ + if (options->geometry == NULL) + gmask = 0; + else + gmask = XParseGeometry(options->geometry, &win->x, &win->y, + &win->w, &win->h); + if ((gmask & WidthValue) != 0) + sizehints.flags |= USSize; + else + win->w = WIN_WIDTH; + if ((gmask & HeightValue) != 0) + sizehints.flags |= USSize; + else + win->h = WIN_HEIGHT; + if ((gmask & XValue) != 0) { + if ((gmask & XNegative) != 0) { + win->x += e->scrw - win->w; + sizehints.win_gravity = NorthEastGravity; + } + sizehints.flags |= USPosition; + } else { + win->x = 0; + } + if ((gmask & YValue) != 0) { + if ((gmask & YNegative) != 0) { + win->y += e->scrh - win->h; + sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity + ? SouthEastGravity : SouthWestGravity; + } + sizehints.flags |= USPosition; + } else { + win->y = 0; + } + + win->xwin = XCreateWindow(e->dpy, parent, + win->x, win->y, win->w, win->h, 0, + e->depth, InputOutput, e->vis, 0, NULL); + if (win->xwin == None) + error(EXIT_FAILURE, 0, "Error creating X window"); + + XSelectInput(e->dpy, win->xwin, + ButtonReleaseMask | ButtonPressMask | KeyPressMask | + PointerMotionMask | StructureNotifyMask); + + for (i = 0; i < ARRLEN(cursors); i++) { + if (i != CURSOR_NONE) + cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name); + } + if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black", + &col, &col) == 0) + { + error(EXIT_FAILURE, 0, "Error allocating color 'black'"); + } + none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8); + *cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0); + + gc = XCreateGC(e->dpy, win->xwin, 0, None); + + n = icons[ARRLEN(icons)-1].size; + icon_data = emalloc((n * n + 2) * sizeof(*icon_data)); + + for (i = 0; i < ARRLEN(icons); i++) { + n = 0; + icon_data[n++] = icons[i].size; + icon_data[n++] = icons[i].size; + + for (j = 0; j < icons[i].cnt; j++) { + for (c = icons[i].data[j] >> 4; c >= 0; c--) + icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F]; + } + XChangeProperty(e->dpy, win->xwin, + atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32, + i == 0 ? PropModeReplace : PropModeAppend, + (unsigned char *) icon_data, n); + } + free(icon_data); + + win_set_title(win, "sxiv"); + + classhint.res_class = RES_CLASS; + classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv"; + XSetClassHint(e->dpy, win->xwin, &classhint); + + XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1); + + sizehints.width = win->w; + sizehints.height = win->h; + sizehints.x = win->x; + sizehints.y = win->y; + XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints); + + win->h -= win->bar.h; + + win->buf.w = e->scrw; + win->buf.h = e->scrh; + win->buf.pm = XCreatePixmap(e->dpy, win->xwin, + win->buf.w, win->buf.h, e->depth); + XSetForeground(e->dpy, gc, win->bg.pixel); + XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); + XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm); + + XMapWindow(e->dpy, win->xwin); + XFlush(e->dpy); + + if (options->fullscreen) + win_toggle_fullscreen(win); +} + +CLEANUP void win_close(win_t *win) +{ + int i; + + for (i = 0; i < ARRLEN(cursors); i++) + XFreeCursor(win->env.dpy, cursors[i].icon); + + XFreeGC(win->env.dpy, gc); + + XDestroyWindow(win->env.dpy, win->xwin); + XCloseDisplay(win->env.dpy); +} + +bool win_configure(win_t *win, XConfigureEvent *c) +{ + bool changed; + + changed = win->w != c->width || win->h + win->bar.h != c->height; + + win->x = c->x; + win->y = c->y; + win->w = c->width; + win->h = c->height - win->bar.h; + win->bw = c->border_width; + + return changed; +} + +void win_toggle_fullscreen(win_t *win) +{ + XEvent ev; + XClientMessageEvent *cm; + + memset(&ev, 0, sizeof(ev)); + ev.type = ClientMessage; + + cm = &ev.xclient; + cm->window = win->xwin; + cm->message_type = atoms[ATOM__NET_WM_STATE]; + cm->format = 32; + cm->data.l[0] = 2; // toggle + cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN]; + + XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False, + SubstructureNotifyMask | SubstructureRedirectMask, &ev); +} + +void win_toggle_bar(win_t *win) +{ + if (win->bar.h != 0) { + win->h += win->bar.h; + win->bar.h = 0; + } else { + win->bar.h = barheight; + win->h -= win->bar.h; + } +} + +void win_clear(win_t *win) +{ + win_env_t *e; + + e = &win->env; + + if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) { + XFreePixmap(e->dpy, win->buf.pm); + win->buf.w = MAX(win->buf.w, win->w); + win->buf.h = MAX(win->buf.h, win->h + win->bar.h); + win->buf.pm = XCreatePixmap(e->dpy, win->xwin, + win->buf.w, win->buf.h, e->depth); + } + XSetForeground(e->dpy, gc, win->bg.pixel); + XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); +} + +#define TEXTWIDTH(win, text, len) \ + win_draw_text(win, NULL, NULL, 0, 0, text, len, 0) + +int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y, + char *text, int len, int w) +{ + int err, tw = 0; + char *t, *next; + uint32_t rune; + XftFont *f; + FcCharSet *fccharset; + XGlyphInfo ext; + + for (t = text; t - text < len; t = next) { + next = utf8_decode(t, &rune, &err); + if (XftCharExists(win->env.dpy, font, rune)) { + f = font; + } else { /* fallback font */ + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, rune); + f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet, + fccharset, FC_SCALABLE, FcTypeBool, FcTrue, + FC_SIZE, FcTypeDouble, fontsize, NULL); + FcCharSetDestroy(fccharset); + } + XftTextExtentsUtf8(win->env.dpy, f, (XftChar8*)t, next - t, &ext); + tw += ext.xOff; + if (tw <= w) { + XftDrawStringUtf8(d, color, f, x, y, (XftChar8*)t, next - t); + x += ext.xOff; + } + if (f != font) + XftFontClose(win->env.dpy, f); + } + return tw; +} + +void win_draw_bar(win_t *win) +{ + int len, x, y, w, tw; + win_env_t *e; + win_bar_t *l, *r; + XftDraw *d; + + if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL) + return; + + e = &win->env; + y = win->h + font->ascent + V_TEXT_PAD; + w = win->w - 2*H_TEXT_PAD; + d = XftDrawCreate(e->dpy, win->buf.pm, DefaultVisual(e->dpy, e->scr), + DefaultColormap(e->dpy, e->scr)); + + XSetForeground(e->dpy, gc, win->fg.pixel); + XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h); + + XSetForeground(e->dpy, gc, win->bg.pixel); + XSetBackground(e->dpy, gc, win->fg.pixel); + + if ((len = strlen(r->buf)) > 0) { + if ((tw = TEXTWIDTH(win, r->buf, len)) > w) + return; + x = win->w - tw - H_TEXT_PAD; + w -= tw; + win_draw_text(win, d, &win->bg, x, y, r->buf, len, tw); + } + if ((len = strlen(l->buf)) > 0) { + x = H_TEXT_PAD; + w -= 2 * H_TEXT_PAD; /* gap between left and right parts */ + win_draw_text(win, d, &win->bg, x, y, l->buf, len, w); + } + XftDrawDestroy(d); +} + +void win_draw(win_t *win) +{ + if (win->bar.h > 0) + win_draw_bar(win); + + XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm); + XClearWindow(win->env.dpy, win->xwin); + XFlush(win->env.dpy); +} + +void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw, + unsigned long col) +{ + XGCValues gcval; + + gcval.line_width = lw; + gcval.foreground = col; + XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval); + + if (fill) + XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); + else + XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); +} + +void win_set_title(win_t *win, const char *title) +{ + XStoreName(win->env.dpy, win->xwin, title); + XSetIconName(win->env.dpy, win->xwin, title); + + XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME], + XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, + PropModeReplace, (unsigned char *) title, strlen(title)); + XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME], + XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, + PropModeReplace, (unsigned char *) title, strlen(title)); +} + +void win_set_cursor(win_t *win, cursor_t cursor) +{ + if (cursor >= 0 && cursor < ARRLEN(cursors)) { + XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon); + XFlush(win->env.dpy); + } +} + +void win_cursor_pos(win_t *win, int *x, int *y) +{ + int i; + unsigned int ui; + Window w; + + if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui)) + *x = *y = 0; +} + diff --git a/tabbed/LICENSE b/tabbed/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +© 2009-2011 Enno Boland <g s01 de> +© 2011,2015 Connor Lane Smith <cls@lubutu.com> +© 2012-2015 Christoph Lohmann <20h@r-36.net> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tabbed/Makefile b/tabbed/Makefile @@ -0,0 +1,65 @@ +# tabbed - tabbing interface +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = tabbed.c xembed.c +OBJ = ${SRC:.c=.o} +BIN = ${OBJ:.o=} + +all: options ${BIN} + +options: + @echo tabbed build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +.o: + @echo CC -o $@ + @${CC} -o $@ $< ${LDFLAGS} + +clean: + @echo cleaning + @rm -f ${BIN} ${OBJ} tabbed-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p tabbed-${VERSION} + @cp -R LICENSE Makefile README config.def.h config.mk \ + tabbed.1 arg.h ${SRC} tabbed-${VERSION} + @tar -cf tabbed-${VERSION}.tar tabbed-${VERSION} + @gzip tabbed-${VERSION}.tar + @rm -rf tabbed-${VERSION} + +install: all + @echo installing executable files to ${DESTDIR}${PREFIX}/bin + @mkdir -p "${DESTDIR}${PREFIX}/bin" + @cp -f ${BIN} "${DESTDIR}${PREFIX}/bin" + @chmod 755 "${DESTDIR}${PREFIX}/bin/tabbed" + @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p "${DESTDIR}${MANPREFIX}/man1" + @sed "s/VERSION/${VERSION}/g" < tabbed.1 > "${DESTDIR}${MANPREFIX}/man1/tabbed.1" + @chmod 644 "${DESTDIR}${MANPREFIX}/man1/tabbed.1" + @sed "s/VERSION/${VERSION}/g" < xembed.1 > "${DESTDIR}${MANPREFIX}/man1/xembed.1" + @chmod 644 "${DESTDIR}${MANPREFIX}/man1/xembed.1" + +uninstall: + @echo removing executable files from ${DESTDIR}${PREFIX}/bin + @rm -f "${DESTDIR}${PREFIX}/bin/tabbed" + @rm -f "${DESTDIR}${PREFIX}/bin/xembed" + @echo removing manual pages from ${DESTDIR}${MANPREFIX}/man1 + @rm -f "${DESTDIR}${MANPREFIX}/man1/tabbed.1" + @rm -f "${DESTDIR}${MANPREFIX}/man1/xembed.1" + +.PHONY: all options clean dist install uninstall diff --git a/tabbed/README b/tabbed/README @@ -0,0 +1,22 @@ +tabbed - generic tabbed interface +================================= +tabbed is a simple tabbed X window container. + +Requirements +------------ +In order to build tabbed you need the Xlib header files. + +Installation +------------ +Edit config.mk to match your local setup (tabbed is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install tabbed +(if necessary as root): + + make clean install + +Running tabbed +-------------- +See the man page for details. + diff --git a/tabbed/TODO b/tabbed/TODO @@ -0,0 +1,4 @@ +# TODO +* add some way to detach windows +* add some way to attach windows + diff --git a/tabbed/alpha.diff b/tabbed/alpha.diff @@ -0,0 +1,122 @@ +diff --git a/config.mk b/config.mk +index 3a71529..095cead 100644 +--- a/config.mk ++++ b/config.mk +@@ -9,7 +9,7 @@ MANPREFIX = ${PREFIX}/share/man + + # includes and libs + INCS = -I. -I/usr/include -I/usr/include/freetype2 +-LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft ++LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft -lXrender + + # flags + CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE +diff --git a/tabbed.c b/tabbed.c +index 9a44795..b4d47d1 100644 +--- a/tabbed.c ++++ b/tabbed.c +@@ -170,6 +170,9 @@ static char **cmd; + static char *wmname = "tabbed"; + static const char *geometry; + ++static Colormap cmap; ++static Visual *visual = NULL; ++ + char *argv0; + + /* configuration, allows nested code to access above variables */ +@@ -255,8 +258,8 @@ configurenotify(const XEvent *e) + ww = ev->width; + wh = ev->height; + XFreePixmap(dpy, dc.drawable); +- dc.drawable = XCreatePixmap(dpy, root, ww, wh, +- DefaultDepth(dpy, screen)); ++ dc.drawable = XCreatePixmap(dpy, win, ww, wh, ++ 32); + if (sel > -1) + resize(sel, ww, wh - bh); + XSync(dpy, False); +@@ -399,7 +402,7 @@ drawtext(const char *text, XftColor col[ColLast]) + ; + } + +- d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); ++ d = XftDrawCreate(dpy, dc.drawable, visual, cmap); + XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len); + XftDrawDestroy(d); + } +@@ -564,7 +567,7 @@ getcolor(const char *colstr) + { + XftColor color; + +- if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color)) ++ if (!XftColorAllocName(dpy, visual, cmap, colstr, &color)) + die("%s: cannot allocate color '%s'\n", argv0, colstr); + + return color; +@@ -1016,18 +1019,60 @@ setup(void) + wy = dh + wy - wh - 1; + } + ++ XVisualInfo *vis; ++ XRenderPictFormat *fmt; ++ int nvi; ++ int i; ++ ++ XVisualInfo tpl = { ++ .screen = screen, ++ .depth = 32, ++ .class = TrueColor ++ }; ++ ++ vis = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &tpl, &nvi); ++ for(i = 0; i < nvi; i ++) { ++ fmt = XRenderFindVisualFormat(dpy, vis[i].visual); ++ if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { ++ visual = vis[i].visual; ++ break; ++ } ++ } ++ ++ XFree(vis); ++ ++ if (! visual) { ++ fprintf(stderr, "Couldn't find ARGB visual.\n"); ++ exit(1); ++ } ++ ++ cmap = XCreateColormap( dpy, root, visual, None); + dc.norm[ColBG] = getcolor(normbgcolor); + dc.norm[ColFG] = getcolor(normfgcolor); + dc.sel[ColBG] = getcolor(selbgcolor); + dc.sel[ColFG] = getcolor(selfgcolor); + dc.urg[ColBG] = getcolor(urgbgcolor); + dc.urg[ColFG] = getcolor(urgfgcolor); +- dc.drawable = XCreatePixmap(dpy, root, ww, wh, +- DefaultDepth(dpy, screen)); +- dc.gc = XCreateGC(dpy, root, 0, 0); + +- win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, +- dc.norm[ColFG].pixel, dc.norm[ColBG].pixel); ++ XSetWindowAttributes attrs; ++ attrs.background_pixel = dc.norm[ColBG].pixel; ++ attrs.border_pixel = dc.norm[ColFG].pixel; ++ attrs.bit_gravity = NorthWestGravity; ++ attrs.event_mask = FocusChangeMask | KeyPressMask ++ | ExposureMask | VisibilityChangeMask | StructureNotifyMask ++ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; ++ attrs.background_pixmap = None ; ++ attrs.colormap = cmap; ++ ++ win = XCreateWindow(dpy, root, wx, wy, ++ ww, wh, 0, 32, InputOutput, ++ visual, CWBackPixmap | CWBorderPixel | CWBitGravity ++ | CWEventMask | CWColormap, &attrs); ++ ++ dc.drawable = XCreatePixmap(dpy, win, ww, wh, ++ 32); ++ dc.gc = XCreateGC(dpy, dc.drawable, 0, 0); ++ + XMapRaised(dpy, win); + XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask | + ButtonPressMask | ExposureMask | KeyPressMask | diff --git a/tabbed/arg.h b/tabbed/arg.h @@ -0,0 +1,48 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/tabbed/config.def.h b/tabbed/config.def.h @@ -0,0 +1,66 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const char font[] = "monospace:size=9"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char* urgbgcolor = "#111111"; +static const char* urgfgcolor = "#cc0000"; +static const char before[] = "<"; +static const char after[] = ">"; +static const char titletrim[] = "..."; +static const int tabwidth = 200; +static const Bool foreground = True; +static Bool urgentswitch = False; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' |" \ + "sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' |" \ + "xargs -0 printf %b | dmenu -l 10 -w $1`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { + /* modifier key function argument */ + { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, + { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_grave, spawn, SETPROP("_TABBED_SELECT_TAB") }, + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { MODKEY, XK_u, focusurgent, { 0 } }, + { MODKEY|ShiftMask, XK_u, toggle, { .v = (void*) &urgentswitch } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; diff --git a/tabbed/config.h b/tabbed/config.h @@ -0,0 +1,66 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const char font[] = "monospace:size=9"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char* urgbgcolor = "#111111"; +static const char* urgfgcolor = "#cc0000"; +static const char before[] = "<"; +static const char after[] = ">"; +static const char titletrim[] = "..."; +static const int tabwidth = 200; +static const Bool foreground = True; +static Bool urgentswitch = False; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' |" \ + "sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' |" \ + "xargs -0 printf %b | dmenu -l 10 -w $1`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { + /* modifier key function argument */ + { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, + { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_grave, spawn, SETPROP("_TABBED_SELECT_TAB") }, + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { MODKEY, XK_u, focusurgent, { 0 } }, + { MODKEY|ShiftMask, XK_u, toggle, { .v = (void*) &urgentswitch } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; diff --git a/tabbed/config.mk b/tabbed/config.mk @@ -0,0 +1,33 @@ +# tabbed version +VERSION = 0.6 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = ${X11INC}/freetype2 + +# includes and libs +INCS = -I. -I/usr/include -I$(X11INC) -I${FREETYPEINC} +LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 ${FREETYPELIBS} -lXrender + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/tabbed/config.mk.orig b/tabbed/config.mk.orig @@ -0,0 +1,33 @@ +# tabbed version +VERSION = 0.6 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = ${X11INC}/freetype2 + +# includes and libs +INCS = -I. -I/usr/include -I$(X11INC) -I${FREETYPEINC} +LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 ${FREETYPELIBS} + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/tabbed/config.mk.rej b/tabbed/config.mk.rej @@ -0,0 +1,11 @@ +--- config.mk ++++ config.mk +@@ -9,7 +9,7 @@ MANPREFIX = ${PREFIX}/share/man + + # includes and libs + INCS = -I. -I/usr/include -I/usr/include/freetype2 +-LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft ++LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft -lXrender + + # flags + CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE diff --git a/tabbed/tabbed b/tabbed/tabbed Binary files differ. diff --git a/tabbed/tabbed.1 b/tabbed/tabbed.1 @@ -0,0 +1,171 @@ +.TH TABBED 1 tabbed\-VERSION +.SH NAME +tabbed \- generic tabbed interface +.SH SYNOPSIS +.B tabbed +.RB [ \-c ] +.RB [ \-d ] +.RB [ \-k ] +.RB [ \-s ] +.RB [ \-v ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-p +.RB [ s {+/-} ] \fIpos\fR ] +.RB [ \-o +.IR normbgcol ] +.RB [ \-O +.IR normfgcol ] +.RB [ \-t +.IR selbgcol ] +.RB [ \-T +.IR selfgcol ] +.RB [ \-u +.IR urgbgcol ] +.RB [ \-U +.IR urgfgcol ] +.RB [ \-r +.IR narg ] +.RI [ "command ..." ] +.SH DESCRIPTION +.B tabbed +is a simple tabbed container for applications which support XEmbed. Tabbed +will then run the provided command with the xid of tabbed as appended +argument. (See EXAMPLES.) The automatic spawning of the command can be +disabled by providing the -s parameter. If no command is provided +tabbed will just print its xid and run no command. +.SH OPTIONS +.TP +.B \-c +close tabbed when the last tab is closed. Mutually exclusive with -f. +.TP +.B \-d +detaches tabbed from the terminal and prints its XID to stdout. +.TP +.B \-f +fill up tabbed again by spawning the provided command, when the last tab is +closed. Mutually exclusive with -c. +.TP +.BI \-g " geometry" +defines the X11 geometry string, which will fixate the height and width of +tabbed. +The syntax is +.RI [=][ width {xX} height ][{+-} xoffset {+-} yoffset ]. +See +.BR XParseGeometry (3) +for further details. +.TP +.B \-k +close foreground tabbed client (instead of tabbed and all clients) when +WM_DELETE_WINDOW is sent. +.TP +.BI \-n " name" +will set the WM_CLASS attribute to +.I name. +.TP +.BR \-p " [" s {+-}] \fIpos\fR +will set the absolute or relative position of where to start a new tab. When +.I pos +is is given without 's' in front it is an absolute position. Then negative +numbers will be the position from the last tab, where -1 is the last tab. +If 's' is given, then +.I pos +is a relative position to the current selected tab. If this reaches the limits +of the tabs; those limits then apply. +.TP +.BI \-r " narg" +will replace the +.I narg +th argument in +.I command +with the window id, rather than appending it to the end. +.TP +.B \-s +will disable automatic spawning of the command. +.TP +.BI \-o " normbgcol" +defines the normal background color. +.RI # RGB , +.RI # RRGGBB , +and X color names are supported. +.TP +.BI \-O " normfgcol" +defines the normal foreground color. +.TP +.BI \-t " selbgcol" +defines the selected background color. +.TP +.BI \-T " selfgbcol" +defines the selected foreground color. +.TP +.BI \-u " urgbgcol" +defines the urgent background color. +.TP +.BI \-U " urgfgbcol" +defines the urgent foreground color. +.TP +.B \-v +prints version information to stderr, then exits. +.SH USAGE +.TP +.B Ctrl\-Shift\-Return +open new tab +.TP +.B Ctrl\-Shift\-h +previous tab +.TP +.B Ctrl\-Shift\-l +next tab +.TP +.B Ctrl\-Shift\-j +move selected tab one to the left +.TP +.B Ctrl\-Shift\-k +move selected tab one to the right +.TP +.B Ctrl\-Shift\-u +toggle autofocus of urgent tabs +.TP +.B Ctrl\-Tab +toggle between the selected and last selected tab +.TP +.B Ctrl\-` +open dmenu to either create a new tab appending the entered string or select +an already existing tab. +.TP +.B Ctrl\-q +close tab +.TP +.B Ctrl\-u +focus next urgent tab +.TP +.B Ctrl\-[0..9] +jumps to nth tab +.TP +.B F11 +Toggle fullscreen mode. +.SH EXAMPLES +$ tabbed surf -e +.TP +$ tabbed urxvt -embed +.TP +$ tabbed xterm -into +.TP +$ $(tabbed -d >/tmp/tabbed.xid); urxvt -embed $(</tmp/tabbed.xid); +.TP +$ tabbed -r 2 st -w '' -e tmux +.SH CUSTOMIZATION +.B tabbed +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR st (1), +.BR xembed (1) +.SH BUGS +Please report them. diff --git a/tabbed/tabbed.c b/tabbed/tabbed.c @@ -0,0 +1,1421 @@ +/* + * See LICENSE file for copyright and license details. + */ + +#include <sys/wait.h> +#include <locale.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/Xft/Xft.h> + +#include "arg.h" + +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define XEMBED_REQUEST_FOCUS 3 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 +#define XEMBED_FOCUS_NEXT 6 +#define XEMBED_FOCUS_PREV 7 +/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MODALITY_OFF 11 +#define XEMBED_REGISTER_ACCELERATOR 12 +#define XEMBED_UNREGISTER_ACCELERATOR 13 +#define XEMBED_ACTIVATE_ACCELERATOR 14 + +/* Details for XEMBED_FOCUS_IN: */ +#define XEMBED_FOCUS_CURRENT 0 +#define XEMBED_FOCUS_FIRST 1 +#define XEMBED_FOCUS_LAST 2 + +/* Macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define LENGTH(x) (sizeof((x)) / sizeof(*(x))) +#define CLEANMASK(mask) (mask & ~(numlockmask | LockMask)) +#define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) + +enum { ColFG, ColBG, ColLast }; /* color */ +enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen, + XEmbed, WMSelectTab, WMLast }; /* default atoms */ + +typedef union { + int i; + const void *v; +} Arg; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + int x, y, w, h; + XftColor norm[ColLast]; + XftColor sel[ColLast]; + XftColor urg[ColLast]; + Drawable drawable; + GC gc; + struct { + int ascent; + int descent; + int height; + XftFont *xfont; + } font; +} DC; /* draw context */ + +typedef struct { + char name[256]; + Window win; + int tabx; + Bool urgent; + Bool closed; +} Client; + +/* function declarations */ +static void buttonpress(const XEvent *e); +static void cleanup(void); +static void clientmessage(const XEvent *e); +static void configurenotify(const XEvent *e); +static void configurerequest(const XEvent *e); +static void createnotify(const XEvent *e); +static void destroynotify(const XEvent *e); +static void die(const char *errstr, ...); +static void drawbar(void); +static void drawtext(const char *text, XftColor col[ColLast]); +static void *ecalloc(size_t n, size_t size); +static void *erealloc(void *o, size_t size); +static void expose(const XEvent *e); +static void focus(int c); +static void focusin(const XEvent *e); +static void focusonce(const Arg *arg); +static void focusurgent(const Arg *arg); +static void fullscreen(const Arg *arg); +static char *getatom(int a); +static int getclient(Window w); +static XftColor getcolor(const char *colstr); +static int getfirsttab(void); +static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void initfont(const char *fontstr); +static Bool isprotodel(int c); +static void keypress(const XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window win); +static void maprequest(const XEvent *e); +static void move(const Arg *arg); +static void movetab(const Arg *arg); +static void propertynotify(const XEvent *e); +static void resize(int c, int w, int h); +static void rotate(const Arg *arg); +static void run(void); +static void sendxembed(int c, long msg, long detail, long d1, long d2); +static void setcmd(int argc, char *argv[], int); +static void setup(void); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static int textnw(const char *text, unsigned int len); +static void toggle(const Arg *arg); +static void unmanage(int c); +static void unmapnotify(const XEvent *e); +static void updatenumlockmask(void); +static void updatetitle(int c); +static int xerror(Display *dpy, XErrorEvent *ee); +static void xsettitle(Window w, const char *str); + +/* variables */ +static int screen; +static void (*handler[LASTEvent]) (const XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureNotify] = configurenotify, + [ConfigureRequest] = configurerequest, + [CreateNotify] = createnotify, + [UnmapNotify] = unmapnotify, + [DestroyNotify] = destroynotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MapRequest] = maprequest, + [PropertyNotify] = propertynotify, +}; +static int bh, obh, wx, wy, ww, wh; +static unsigned int numlockmask; +static Bool running = True, nextfocus, doinitspawn = True, + fillagain = False, closelastclient = False, + killclientsfirst = False; +static Display *dpy; +static DC dc; +static Atom wmatom[WMLast]; +static Window root, win; +static Client **clients; +static int nclients, sel = -1, lastsel = -1; +static int (*xerrorxlib)(Display *, XErrorEvent *); +static int cmd_append_pos; +static char winid[64]; +static char **cmd; +static char *wmname = "tabbed"; +static const char *geometry; + +static Colormap cmap; +static Visual *visual = NULL; + +char *argv0; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +void +buttonpress(const XEvent *e) +{ + const XButtonPressedEvent *ev = &e->xbutton; + int i, fc; + Arg arg; + + if (ev->y < 0 || ev->y > bh) + return; + + if (((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)) || ev->x < 0) + return; + + for (i = fc; i < nclients; i++) { + if (clients[i]->tabx > ev->x) { + switch (ev->button) { + case Button1: + focus(i); + break; + case Button2: + focus(i); + killclient(NULL); + break; + case Button4: /* FALLTHROUGH */ + case Button5: + arg.i = ev->button == Button4 ? -1 : 1; + rotate(&arg); + break; + } + break; + } + } +} + +void +cleanup(void) +{ + int i; + + for (i = 0; i < nclients; i++) { + focus(i); + killclient(NULL); + XReparentWindow(dpy, clients[i]->win, root, 0, 0); + unmanage(i); + } + free(clients); + clients = NULL; + + XFreePixmap(dpy, dc.drawable); + XFreeGC(dpy, dc.gc); + XDestroyWindow(dpy, win); + XSync(dpy, False); + free(cmd); +} + +void +clientmessage(const XEvent *e) +{ + const XClientMessageEvent *ev = &e->xclient; + + if (ev->message_type == wmatom[WMProtocols] && + ev->data.l[0] == wmatom[WMDelete]) { + if (nclients > 1 && killclientsfirst) { + killclient(0); + return; + } + running = False; + } +} + +void +configurenotify(const XEvent *e) +{ + const XConfigureEvent *ev = &e->xconfigure; + + if (ev->window == win && (ev->width != ww || ev->height != wh)) { + ww = ev->width; + wh = ev->height; + XFreePixmap(dpy, dc.drawable); + dc.drawable = XCreatePixmap(dpy, win, ww, wh, + 32); + + if (!obh && (wh <= bh)) { + obh = bh; + bh = 0; + } else if (!bh && (wh > obh)) { + bh = obh; + obh = 0; + } + + if (sel > -1) + resize(sel, ww, wh - bh); + XSync(dpy, False); + } +} + +void +configurerequest(const XEvent *e) +{ + const XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + int c; + + if ((c = getclient(ev->window)) > -1) { + wc.x = 0; + wc.y = bh; + wc.width = ww; + wc.height = wh - bh; + wc.border_width = 0; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc); + } +} + +void +createnotify(const XEvent *e) +{ + const XCreateWindowEvent *ev = &e->xcreatewindow; + + if (ev->window != win && getclient(ev->window) < 0) + manage(ev->window); +} + +void +destroynotify(const XEvent *e) +{ + const XDestroyWindowEvent *ev = &e->xdestroywindow; + int c; + + if ((c = getclient(ev->window)) > -1) + unmanage(c); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void +drawbar(void) +{ + XftColor *col; + int c, cc, fc, width; + char *name = NULL; + + if (nclients == 0) { + dc.x = 0; + dc.w = ww; + XFetchName(dpy, win, &name); + drawtext(name ? name : "", dc.norm); + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); + + return; + } + + width = ww; + cc = ww / tabwidth; + if (nclients > cc) + cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; + + if ((fc = getfirsttab()) + cc < nclients) { + dc.w = TEXTW(after); + dc.x = width - dc.w; + drawtext(after, dc.sel); + width -= dc.w; + } + dc.x = 0; + + if (fc > 0) { + dc.w = TEXTW(before); + drawtext(before, dc.sel); + dc.x += dc.w; + width -= dc.w; + } + + cc = MIN(cc, nclients); + for (c = fc; c < fc + cc; c++) { + dc.w = width / cc; + if (c == sel) { + col = dc.sel; + dc.w += width % cc; + } else { + col = clients[c]->urgent ? dc.urg : dc.norm; + } + drawtext(clients[c]->name, col); + dc.x += dc.w; + clients[c]->tabx = dc.x; + } + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); +} + +void +drawtext(const char *text, XftColor col[ColLast]) +{ + int i, j, x, y, h, len, olen; + char buf[256]; + XftDraw *d; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + XSetForeground(dpy, dc.gc, col[ColBG].pixel); + XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); + if (!text) + return; + + olen = strlen(text); + h = dc.font.ascent + dc.font.descent; + y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent; + x = dc.x + (h / 2); + + /* shorten text if necessary */ + for (len = MIN(olen, sizeof(buf)); + len && textnw(text, len) > dc.w - h; len--); + + if (!len) + return; + + memcpy(buf, text, len); + if (len < olen) { + for (i = len, j = strlen(titletrim); j && i; + buf[--i] = titletrim[--j]) + ; + } + + d = XftDrawCreate(dpy, dc.drawable, visual, cmap); + XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len); + XftDrawDestroy(d); +} + +void * +ecalloc(size_t n, size_t size) +{ + void *p; + + if (!(p = calloc(n, size))) + die("%s: cannot calloc\n", argv0); + return p; +} + +void * +erealloc(void *o, size_t size) +{ + void *p; + + if (!(p = realloc(o, size))) + die("%s: cannot realloc\n", argv0); + return p; +} + +void +expose(const XEvent *e) +{ + const XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && win == ev->window) + drawbar(); +} + +void +focus(int c) +{ + char buf[BUFSIZ] = "tabbed-"VERSION" ::"; + size_t i, n; + XWMHints* wmh; + + /* If c, sel and clients are -1, raise tabbed-win itself */ + if (nclients == 0) { + cmd[cmd_append_pos] = NULL; + for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++) + n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]); + + xsettitle(win, buf); + XRaiseWindow(dpy, win); + + return; + } + + if (c < 0 || c >= nclients) + return; + + resize(c, ww, wh - bh); + XRaiseWindow(dpy, clients[c]->win); + XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime); + sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0); + sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); + xsettitle(win, clients[c]->name); + + if (sel != c) { + lastsel = sel; + sel = c; + } + + if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, clients[c]->win, wmh); + clients[c]->urgent = False; + XFree(wmh); + } + + drawbar(); + XSync(dpy, False); +} + +void +focusin(const XEvent *e) +{ + const XFocusChangeEvent *ev = &e->xfocus; + int dummy; + Window focused; + + if (ev->mode != NotifyUngrab) { + XGetInputFocus(dpy, &focused, &dummy); + if (focused == win) + focus(sel); + } +} + +void +focusonce(const Arg *arg) +{ + nextfocus = True; +} + +void +focusurgent(const Arg *arg) +{ + int c; + + if (sel < 0) + return; + + for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) { + if (clients[c]->urgent) { + focus(c); + return; + } + } +} + +void +fullscreen(const Arg *arg) +{ + XEvent e; + + e.type = ClientMessage; + e.xclient.window = win; + e.xclient.message_type = wmatom[WMState]; + e.xclient.format = 32; + e.xclient.data.l[0] = 2; + e.xclient.data.l[1] = wmatom[WMFullscreen]; + e.xclient.data.l[2] = 0; + XSendEvent(dpy, root, False, SubstructureNotifyMask, &e); +} + +char * +getatom(int a) +{ + static char buf[BUFSIZ]; + Atom adummy; + int idummy; + unsigned long ldummy; + unsigned char *p = NULL; + + XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING, + &adummy, &idummy, &ldummy, &ldummy, &p); + if (p) + strncpy(buf, (char *)p, LENGTH(buf)-1); + else + buf[0] = '\0'; + XFree(p); + + return buf; +} + +int +getclient(Window w) +{ + int i; + + for (i = 0; i < nclients; i++) { + if (clients[i]->win == w) + return i; + } + + return -1; +} + +XftColor +getcolor(const char *colstr) +{ + XftColor color; + + if (!XftColorAllocName(dpy, visual, cmap, colstr, &color)) + die("%s: cannot allocate color '%s'\n", argv0, colstr); + + return color; +} + +int +getfirsttab(void) +{ + int cc, ret; + + if (sel < 0) + return 0; + + cc = ww / tabwidth; + if (nclients > cc) + cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; + + ret = sel - cc / 2 + (cc + 1) % 2; + return ret < 0 ? 0 : + ret + cc > nclients ? MAX(0, nclients - cc) : + ret; +} + +Bool +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return False; + + text[0] = '\0'; + XGetTextProperty(dpy, w, &name, atom); + if (!name.nitems) + return False; + + if (name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success + && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + text[size - 1] = '\0'; + XFree(name.value); + + return True; +} + +void +initfont(const char *fontstr) +{ + if (!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr)) + && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed"))) + die("error, cannot load font: '%s'\n", fontstr); + + dc.font.ascent = dc.font.xfont->ascent; + dc.font.descent = dc.font.xfont->descent; + dc.font.height = dc.font.ascent + dc.font.descent; +} + +Bool +isprotodel(int c) +{ + int i, n; + Atom *protocols; + Bool ret = False; + + if (XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) { + for (i = 0; !ret && i < n; i++) { + if (protocols[i] == wmatom[WMDelete]) + ret = True; + } + XFree(protocols); + } + + return ret; +} + +void +keypress(const XEvent *e) +{ + const XKeyEvent *ev = &e->xkey; + unsigned int i; + KeySym keysym; + + keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); + for (i = 0; i < LENGTH(keys); i++) { + if (keysym == keys[i].keysym && + CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && + keys[i].func) + keys[i].func(&(keys[i].arg)); + } +} + +void +killclient(const Arg *arg) +{ + XEvent ev; + + if (sel < 0) + return; + + if (isprotodel(sel) && !clients[sel]->closed) { + ev.type = ClientMessage; + ev.xclient.window = clients[sel]->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = wmatom[WMDelete]; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev); + clients[sel]->closed = True; + } else { + XKillClient(dpy, clients[sel]->win); + } +} + +void +manage(Window w) +{ + updatenumlockmask(); + { + int i, j, nextpos; + unsigned int modifiers[] = { 0, LockMask, numlockmask, + numlockmask | LockMask }; + KeyCode code; + Client *c; + XEvent e; + + XWithdrawWindow(dpy, w, 0); + XReparentWindow(dpy, w, win, 0, bh); + XSelectInput(dpy, w, PropertyChangeMask | + StructureNotifyMask | EnterWindowMask); + XSync(dpy, False); + + for (i = 0; i < LENGTH(keys); i++) { + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { + for (j = 0; j < LENGTH(modifiers); j++) { + XGrabKey(dpy, code, keys[i].mod | + modifiers[j], w, True, + GrabModeAsync, GrabModeAsync); + } + } + } + + c = ecalloc(1, sizeof *c); + c->win = w; + + nclients++; + clients = erealloc(clients, sizeof(Client *) * nclients); + + if(npisrelative) { + nextpos = sel + newposition; + } else { + if (newposition < 0) + nextpos = nclients - newposition; + else + nextpos = newposition; + } + if (nextpos >= nclients) + nextpos = nclients - 1; + if (nextpos < 0) + nextpos = 0; + + if (nclients > 1 && nextpos < nclients - 1) + memmove(&clients[nextpos + 1], &clients[nextpos], + sizeof(Client *) * (nclients - nextpos - 1)); + + clients[nextpos] = c; + updatetitle(nextpos); + + XLowerWindow(dpy, w); + XMapWindow(dpy, w); + + e.xclient.window = w; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; + e.xclient.data.l[2] = 0; + e.xclient.data.l[3] = win; + e.xclient.data.l[4] = 0; + XSendEvent(dpy, root, False, NoEventMask, &e); + + XSync(dpy, False); + + /* Adjust sel before focus does set it to lastsel. */ + if (sel >= nextpos) + sel++; + focus(nextfocus ? nextpos : + sel < 0 ? 0 : + sel); + nextfocus = foreground; + } +} + +void +maprequest(const XEvent *e) +{ + const XMapRequestEvent *ev = &e->xmaprequest; + + if (getclient(ev->window) < 0) + manage(ev->window); +} + +void +move(const Arg *arg) +{ + if (arg->i >= 0 && arg->i < nclients) + focus(arg->i); +} + +void +movetab(const Arg *arg) +{ + int c; + Client *new; + + if (sel < 0) + return; + + c = (sel + arg->i) % nclients; + if (c < 0) + c += nclients; + + if (c == sel) + return; + + new = clients[sel]; + if (sel < c) + memmove(&clients[sel], &clients[sel+1], + sizeof(Client *) * (c - sel)); + else + memmove(&clients[c+1], &clients[c], + sizeof(Client *) * (sel - c)); + clients[c] = new; + sel = c; + + drawbar(); +} + +void +propertynotify(const XEvent *e) +{ + const XPropertyEvent *ev = &e->xproperty; + XWMHints *wmh; + int c; + char* selection = NULL; + Arg arg; + + if (ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) { + selection = getatom(WMSelectTab); + if (!strncmp(selection, "0x", 2)) { + arg.i = getclient(strtoul(selection, NULL, 0)); + move(&arg); + } else { + cmd[cmd_append_pos] = selection; + arg.v = cmd; + spawn(&arg); + } + } else if (ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS && + (c = getclient(ev->window)) > -1 && + (wmh = XGetWMHints(dpy, clients[c]->win))) { + if (wmh->flags & XUrgencyHint) { + XFree(wmh); + wmh = XGetWMHints(dpy, win); + if (c != sel) { + if (urgentswitch && wmh && + !(wmh->flags & XUrgencyHint)) { + /* only switch, if tabbed was focused + * since last urgency hint if WMHints + * could not be received, + * default to no switch */ + focus(c); + } else { + /* if no switch should be performed, + * mark tab as urgent */ + clients[c]->urgent = True; + drawbar(); + } + } + if (wmh && !(wmh->flags & XUrgencyHint)) { + /* update tabbed urgency hint + * if not set already */ + wmh->flags |= XUrgencyHint; + XSetWMHints(dpy, win, wmh); + } + } + XFree(wmh); + } else if (ev->state != PropertyDelete && ev->atom == XA_WM_NAME && + (c = getclient(ev->window)) > -1) { + updatetitle(c); + } +} + +void +resize(int c, int w, int h) +{ + XConfigureEvent ce; + XWindowChanges wc; + + ce.x = 0; + ce.y = wc.y = bh; + ce.width = wc.width = w; + ce.height = wc.height = h; + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = clients[c]->win; + ce.window = clients[c]->win; + ce.above = None; + ce.override_redirect = False; + ce.border_width = 0; + + XConfigureWindow(dpy, clients[c]->win, CWY | CWWidth | CWHeight, &wc); + XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask, + (XEvent *)&ce); +} + +void +rotate(const Arg *arg) +{ + int nsel = -1; + + if (sel < 0) + return; + + if (arg->i == 0) { + if (lastsel > -1) + focus(lastsel); + } else if (sel > -1) { + /* Rotating in an arg->i step around the clients. */ + nsel = sel + arg->i; + while (nsel >= nclients) + nsel -= nclients; + while (nsel < 0) + nsel += nclients; + focus(nsel); + } +} + +void +run(void) +{ + XEvent ev; + + /* main event loop */ + XSync(dpy, False); + drawbar(); + if (doinitspawn == True) + spawn(NULL); + + while (running) { + XNextEvent(dpy, &ev); + if (handler[ev.type]) + (handler[ev.type])(&ev); /* call handler */ + } +} + +void +sendxembed(int c, long msg, long detail, long d1, long d2) +{ + XEvent e = { 0 }; + + e.xclient.window = clients[c]->win; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = msg; + e.xclient.data.l[2] = detail; + e.xclient.data.l[3] = d1; + e.xclient.data.l[4] = d2; + XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e); +} + +void +setcmd(int argc, char *argv[], int replace) +{ + int i; + + cmd = ecalloc(argc + 3, sizeof(*cmd)); + if (argc == 0) + return; + for (i = 0; i < argc; i++) + cmd[i] = argv[i]; + cmd[replace > 0 ? replace : argc] = winid; + cmd_append_pos = argc + !replace; + cmd[cmd_append_pos] = cmd[cmd_append_pos + 1] = NULL; +} + +void +setup(void) +{ + int bitm, tx, ty, tw, th, dh, dw, isfixed; + XWMHints *wmh; + XClassHint class_hint; + XSizeHints *size_hint; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + initfont(font); + bh = dc.h = dc.font.height + 2; + + /* init atoms */ + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", + False); + wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); + wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False); + + /* init appearance */ + wx = 0; + wy = 0; + ww = 800; + wh = 600; + isfixed = 0; + + if (geometry) { + tx = ty = tw = th = 0; + bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw, + (unsigned *)&th); + if (bitm & XValue) + wx = tx; + if (bitm & YValue) + wy = ty; + if (bitm & WidthValue) + ww = tw; + if (bitm & HeightValue) + wh = th; + if (bitm & XNegative && wx == 0) + wx = -1; + if (bitm & YNegative && wy == 0) + wy = -1; + if (bitm & (HeightValue | WidthValue)) + isfixed = 1; + + dw = DisplayWidth(dpy, screen); + dh = DisplayHeight(dpy, screen); + if (wx < 0) + wx = dw + wx - ww - 1; + if (wy < 0) + wy = dh + wy - wh - 1; + } + + XVisualInfo *vis; + XRenderPictFormat *fmt; + int nvi; + int i; + + XVisualInfo tpl = { + .screen = screen, + .depth = 32, + .class = TrueColor + }; + + vis = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &tpl, &nvi); + for(i = 0; i < nvi; i ++) { + fmt = XRenderFindVisualFormat(dpy, vis[i].visual); + if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { + visual = vis[i].visual; + break; + } + } + + XFree(vis); + + if (! visual) { + fprintf(stderr, "Couldn't find ARGB visual.\n"); + exit(1); + } + + cmap = XCreateColormap( dpy, root, visual, None); + dc.norm[ColBG] = getcolor(normbgcolor); + dc.norm[ColFG] = getcolor(normfgcolor); + dc.sel[ColBG] = getcolor(selbgcolor); + dc.sel[ColFG] = getcolor(selfgcolor); + dc.urg[ColBG] = getcolor(urgbgcolor); + dc.urg[ColFG] = getcolor(urgfgcolor); + + XSetWindowAttributes attrs; + attrs.background_pixel = dc.norm[ColBG].pixel; + attrs.border_pixel = dc.norm[ColFG].pixel; + attrs.bit_gravity = NorthWestGravity; + attrs.event_mask = FocusChangeMask | KeyPressMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + attrs.background_pixmap = None ; + attrs.colormap = cmap; + + win = XCreateWindow(dpy, root, wx, wy, + ww, wh, 0, 32, InputOutput, + visual, CWBackPixmap | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &attrs); + + dc.drawable = XCreatePixmap(dpy, win, ww, wh, + 32); + dc.gc = XCreateGC(dpy, dc.drawable, 0, 0); + + XMapRaised(dpy, win); + XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask | + ButtonPressMask | ExposureMask | KeyPressMask | + PropertyChangeMask | StructureNotifyMask | + SubstructureRedirectMask); + xerrorxlib = XSetErrorHandler(xerror); + + class_hint.res_name = wmname; + class_hint.res_class = "tabbed"; + XSetClassHint(dpy, win, &class_hint); + + size_hint = XAllocSizeHints(); + if (!isfixed) { + size_hint->flags = PSize | PMinSize; + size_hint->height = wh; + size_hint->width = ww; + size_hint->min_height = bh + 1; + } else { + size_hint->flags = PMaxSize | PMinSize; + size_hint->min_width = size_hint->max_width = ww; + size_hint->min_height = size_hint->max_height = wh; + } + wmh = XAllocWMHints(); + XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL); + XFree(size_hint); + XFree(wmh); + + XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); + + snprintf(winid, sizeof(winid), "%lu", win); + setenv("XEMBED", winid, 1); + + nextfocus = foreground; + focus(-1); +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("%s: cannot install SIGCHLD handler", argv0); + + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + if(dpy) + close(ConnectionNumber(dpy)); + + setsid(); + if (arg && arg->v) { + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "%s: execvp %s", argv0, + ((char **)arg->v)[0]); + } else { + cmd[cmd_append_pos] = NULL; + execvp(cmd[0], cmd); + fprintf(stderr, "%s: execvp %s", argv0, cmd[0]); + } + perror(" failed"); + exit(0); + } +} + +int +textnw(const char *text, unsigned int len) +{ + XGlyphInfo ext; + XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext); + return ext.xOff; +} + +void +toggle(const Arg *arg) +{ + *(Bool*) arg->v = !*(Bool*) arg->v; +} + +void +unmanage(int c) +{ + if (c < 0 || c >= nclients) { + drawbar(); + XSync(dpy, False); + return; + } + + if (!nclients) + return; + + if (c == 0) { + /* First client. */ + nclients--; + free(clients[0]); + memmove(&clients[0], &clients[1], sizeof(Client *) * nclients); + } else if (c == nclients - 1) { + /* Last client. */ + nclients--; + free(clients[c]); + clients = erealloc(clients, sizeof(Client *) * nclients); + } else { + /* Somewhere inbetween. */ + free(clients[c]); + memmove(&clients[c], &clients[c+1], + sizeof(Client *) * (nclients - (c + 1))); + nclients--; + } + + if (nclients <= 0) { + lastsel = sel = -1; + + if (closelastclient) + running = False; + else if (fillagain && running) + spawn(NULL); + } else { + if (lastsel >= nclients) + lastsel = nclients - 1; + else if (lastsel > c) + lastsel--; + + if (c == sel && lastsel >= 0) { + focus(lastsel); + } else { + if (sel > c) + sel--; + if (sel >= nclients) + sel = nclients - 1; + + focus(sel); + } + } + + drawbar(); + XSync(dpy, False); +} + +void +unmapnotify(const XEvent *e) +{ + const XUnmapEvent *ev = &e->xunmap; + int c; + + if ((c = getclient(ev->window)) > -1) + unmanage(c); +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) { + for (j = 0; j < modmap->max_keypermod; j++) { + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + } + } + XFreeModifiermap(modmap); +} + +void +updatetitle(int c) +{ + if (!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name, + sizeof(clients[c]->name))) + gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name, + sizeof(clients[c]->name)); + if (sel == c) + xsettitle(win, clients[c]->name); + drawbar(); +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && + ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && + ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && + ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && + ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && + ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && + ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && + ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && + ee->error_code == BadDrawable)) + return 0; + + fprintf(stderr, "%s: fatal error: request code=%d, error code=%d\n", + argv0, ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +void +xsettitle(Window w, const char *str) +{ + XTextProperty xtp; + + if (XmbTextListToTextProperty(dpy, (char **)&str, 1, + XCompoundTextStyle, &xtp) == Success) { + XSetTextProperty(dpy, w, &xtp, wmatom[WMName]); + XSetTextProperty(dpy, w, &xtp, XA_WM_NAME); + XFree(xtp.value); + } +} + +void +usage(void) +{ + die("usage: %s [-dfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n" + " [-r narg] [-o color] [-O color] [-t color] [-T color]\n" + " [-u color] [-U color] command...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + Bool detach = False; + int replace = 0; + char *pstr; + + ARGBEGIN { + case 'c': + closelastclient = True; + fillagain = False; + break; + case 'd': + detach = True; + break; + case 'f': + fillagain = True; + break; + case 'g': + geometry = EARGF(usage()); + break; + case 'k': + killclientsfirst = True; + break; + case 'n': + wmname = EARGF(usage()); + break; + case 'O': + normfgcolor = EARGF(usage()); + break; + case 'o': + normbgcolor = EARGF(usage()); + break; + case 'p': + pstr = EARGF(usage()); + if (pstr[0] == 's') { + npisrelative = True; + newposition = atoi(&pstr[1]); + } else { + newposition = atoi(pstr); + } + break; + case 'r': + replace = atoi(EARGF(usage())); + break; + case 's': + doinitspawn = False; + break; + case 'T': + selfgcolor = EARGF(usage()); + break; + case 't': + selbgcolor = EARGF(usage()); + break; + case 'U': + urgfgcolor = EARGF(usage()); + break; + case 'u': + urgbgcolor = EARGF(usage()); + break; + case 'v': + die("tabbed-"VERSION", © 2009-2016 tabbed engineers, " + "see LICENSE for details.\n"); + break; + default: + usage(); + break; + } ARGEND; + + if (argc < 1) { + doinitspawn = False; + fillagain = False; + } + + setcmd(argc, argv, replace); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "%s: no locale support\n", argv0); + if (!(dpy = XOpenDisplay(NULL))) + die("%s: cannot open display\n", argv0); + + setup(); + printf("0x%lx\n", win); + fflush(NULL); + + if (detach) { + if (fork() == 0) { + fclose(stdout); + } else { + if (dpy) + close(ConnectionNumber(dpy)); + return EXIT_SUCCESS; + } + } + + run(); + cleanup(); + XCloseDisplay(dpy); + + return EXIT_SUCCESS; +} diff --git a/tabbed/tabbed.c.orig b/tabbed/tabbed.c.orig @@ -0,0 +1,1376 @@ +/* + * See LICENSE file for copyright and license details. + */ + +#include <sys/wait.h> +#include <locale.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/Xft/Xft.h> + +#include "arg.h" + +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define XEMBED_REQUEST_FOCUS 3 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 +#define XEMBED_FOCUS_NEXT 6 +#define XEMBED_FOCUS_PREV 7 +/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MODALITY_OFF 11 +#define XEMBED_REGISTER_ACCELERATOR 12 +#define XEMBED_UNREGISTER_ACCELERATOR 13 +#define XEMBED_ACTIVATE_ACCELERATOR 14 + +/* Details for XEMBED_FOCUS_IN: */ +#define XEMBED_FOCUS_CURRENT 0 +#define XEMBED_FOCUS_FIRST 1 +#define XEMBED_FOCUS_LAST 2 + +/* Macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define LENGTH(x) (sizeof((x)) / sizeof(*(x))) +#define CLEANMASK(mask) (mask & ~(numlockmask | LockMask)) +#define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) + +enum { ColFG, ColBG, ColLast }; /* color */ +enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen, + XEmbed, WMSelectTab, WMLast }; /* default atoms */ + +typedef union { + int i; + const void *v; +} Arg; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + int x, y, w, h; + XftColor norm[ColLast]; + XftColor sel[ColLast]; + XftColor urg[ColLast]; + Drawable drawable; + GC gc; + struct { + int ascent; + int descent; + int height; + XftFont *xfont; + } font; +} DC; /* draw context */ + +typedef struct { + char name[256]; + Window win; + int tabx; + Bool urgent; + Bool closed; +} Client; + +/* function declarations */ +static void buttonpress(const XEvent *e); +static void cleanup(void); +static void clientmessage(const XEvent *e); +static void configurenotify(const XEvent *e); +static void configurerequest(const XEvent *e); +static void createnotify(const XEvent *e); +static void destroynotify(const XEvent *e); +static void die(const char *errstr, ...); +static void drawbar(void); +static void drawtext(const char *text, XftColor col[ColLast]); +static void *ecalloc(size_t n, size_t size); +static void *erealloc(void *o, size_t size); +static void expose(const XEvent *e); +static void focus(int c); +static void focusin(const XEvent *e); +static void focusonce(const Arg *arg); +static void focusurgent(const Arg *arg); +static void fullscreen(const Arg *arg); +static char *getatom(int a); +static int getclient(Window w); +static XftColor getcolor(const char *colstr); +static int getfirsttab(void); +static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void initfont(const char *fontstr); +static Bool isprotodel(int c); +static void keypress(const XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window win); +static void maprequest(const XEvent *e); +static void move(const Arg *arg); +static void movetab(const Arg *arg); +static void propertynotify(const XEvent *e); +static void resize(int c, int w, int h); +static void rotate(const Arg *arg); +static void run(void); +static void sendxembed(int c, long msg, long detail, long d1, long d2); +static void setcmd(int argc, char *argv[], int); +static void setup(void); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static int textnw(const char *text, unsigned int len); +static void toggle(const Arg *arg); +static void unmanage(int c); +static void unmapnotify(const XEvent *e); +static void updatenumlockmask(void); +static void updatetitle(int c); +static int xerror(Display *dpy, XErrorEvent *ee); +static void xsettitle(Window w, const char *str); + +/* variables */ +static int screen; +static void (*handler[LASTEvent]) (const XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureNotify] = configurenotify, + [ConfigureRequest] = configurerequest, + [CreateNotify] = createnotify, + [UnmapNotify] = unmapnotify, + [DestroyNotify] = destroynotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MapRequest] = maprequest, + [PropertyNotify] = propertynotify, +}; +static int bh, obh, wx, wy, ww, wh; +static unsigned int numlockmask; +static Bool running = True, nextfocus, doinitspawn = True, + fillagain = False, closelastclient = False, + killclientsfirst = False; +static Display *dpy; +static DC dc; +static Atom wmatom[WMLast]; +static Window root, win; +static Client **clients; +static int nclients, sel = -1, lastsel = -1; +static int (*xerrorxlib)(Display *, XErrorEvent *); +static int cmd_append_pos; +static char winid[64]; +static char **cmd; +static char *wmname = "tabbed"; +static const char *geometry; + +char *argv0; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +void +buttonpress(const XEvent *e) +{ + const XButtonPressedEvent *ev = &e->xbutton; + int i, fc; + Arg arg; + + if (ev->y < 0 || ev->y > bh) + return; + + if (((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)) || ev->x < 0) + return; + + for (i = fc; i < nclients; i++) { + if (clients[i]->tabx > ev->x) { + switch (ev->button) { + case Button1: + focus(i); + break; + case Button2: + focus(i); + killclient(NULL); + break; + case Button4: /* FALLTHROUGH */ + case Button5: + arg.i = ev->button == Button4 ? -1 : 1; + rotate(&arg); + break; + } + break; + } + } +} + +void +cleanup(void) +{ + int i; + + for (i = 0; i < nclients; i++) { + focus(i); + killclient(NULL); + XReparentWindow(dpy, clients[i]->win, root, 0, 0); + unmanage(i); + } + free(clients); + clients = NULL; + + XFreePixmap(dpy, dc.drawable); + XFreeGC(dpy, dc.gc); + XDestroyWindow(dpy, win); + XSync(dpy, False); + free(cmd); +} + +void +clientmessage(const XEvent *e) +{ + const XClientMessageEvent *ev = &e->xclient; + + if (ev->message_type == wmatom[WMProtocols] && + ev->data.l[0] == wmatom[WMDelete]) { + if (nclients > 1 && killclientsfirst) { + killclient(0); + return; + } + running = False; + } +} + +void +configurenotify(const XEvent *e) +{ + const XConfigureEvent *ev = &e->xconfigure; + + if (ev->window == win && (ev->width != ww || ev->height != wh)) { + ww = ev->width; + wh = ev->height; + XFreePixmap(dpy, dc.drawable); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + + if (!obh && (wh <= bh)) { + obh = bh; + bh = 0; + } else if (!bh && (wh > obh)) { + bh = obh; + obh = 0; + } + + if (sel > -1) + resize(sel, ww, wh - bh); + XSync(dpy, False); + } +} + +void +configurerequest(const XEvent *e) +{ + const XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + int c; + + if ((c = getclient(ev->window)) > -1) { + wc.x = 0; + wc.y = bh; + wc.width = ww; + wc.height = wh - bh; + wc.border_width = 0; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc); + } +} + +void +createnotify(const XEvent *e) +{ + const XCreateWindowEvent *ev = &e->xcreatewindow; + + if (ev->window != win && getclient(ev->window) < 0) + manage(ev->window); +} + +void +destroynotify(const XEvent *e) +{ + const XDestroyWindowEvent *ev = &e->xdestroywindow; + int c; + + if ((c = getclient(ev->window)) > -1) + unmanage(c); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void +drawbar(void) +{ + XftColor *col; + int c, cc, fc, width; + char *name = NULL; + + if (nclients == 0) { + dc.x = 0; + dc.w = ww; + XFetchName(dpy, win, &name); + drawtext(name ? name : "", dc.norm); + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); + + return; + } + + width = ww; + cc = ww / tabwidth; + if (nclients > cc) + cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; + + if ((fc = getfirsttab()) + cc < nclients) { + dc.w = TEXTW(after); + dc.x = width - dc.w; + drawtext(after, dc.sel); + width -= dc.w; + } + dc.x = 0; + + if (fc > 0) { + dc.w = TEXTW(before); + drawtext(before, dc.sel); + dc.x += dc.w; + width -= dc.w; + } + + cc = MIN(cc, nclients); + for (c = fc; c < fc + cc; c++) { + dc.w = width / cc; + if (c == sel) { + col = dc.sel; + dc.w += width % cc; + } else { + col = clients[c]->urgent ? dc.urg : dc.norm; + } + drawtext(clients[c]->name, col); + dc.x += dc.w; + clients[c]->tabx = dc.x; + } + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); +} + +void +drawtext(const char *text, XftColor col[ColLast]) +{ + int i, j, x, y, h, len, olen; + char buf[256]; + XftDraw *d; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + XSetForeground(dpy, dc.gc, col[ColBG].pixel); + XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); + if (!text) + return; + + olen = strlen(text); + h = dc.font.ascent + dc.font.descent; + y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent; + x = dc.x + (h / 2); + + /* shorten text if necessary */ + for (len = MIN(olen, sizeof(buf)); + len && textnw(text, len) > dc.w - h; len--); + + if (!len) + return; + + memcpy(buf, text, len); + if (len < olen) { + for (i = len, j = strlen(titletrim); j && i; + buf[--i] = titletrim[--j]) + ; + } + + d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); + XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len); + XftDrawDestroy(d); +} + +void * +ecalloc(size_t n, size_t size) +{ + void *p; + + if (!(p = calloc(n, size))) + die("%s: cannot calloc\n", argv0); + return p; +} + +void * +erealloc(void *o, size_t size) +{ + void *p; + + if (!(p = realloc(o, size))) + die("%s: cannot realloc\n", argv0); + return p; +} + +void +expose(const XEvent *e) +{ + const XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && win == ev->window) + drawbar(); +} + +void +focus(int c) +{ + char buf[BUFSIZ] = "tabbed-"VERSION" ::"; + size_t i, n; + XWMHints* wmh; + + /* If c, sel and clients are -1, raise tabbed-win itself */ + if (nclients == 0) { + cmd[cmd_append_pos] = NULL; + for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++) + n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]); + + xsettitle(win, buf); + XRaiseWindow(dpy, win); + + return; + } + + if (c < 0 || c >= nclients) + return; + + resize(c, ww, wh - bh); + XRaiseWindow(dpy, clients[c]->win); + XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime); + sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0); + sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); + xsettitle(win, clients[c]->name); + + if (sel != c) { + lastsel = sel; + sel = c; + } + + if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, clients[c]->win, wmh); + clients[c]->urgent = False; + XFree(wmh); + } + + drawbar(); + XSync(dpy, False); +} + +void +focusin(const XEvent *e) +{ + const XFocusChangeEvent *ev = &e->xfocus; + int dummy; + Window focused; + + if (ev->mode != NotifyUngrab) { + XGetInputFocus(dpy, &focused, &dummy); + if (focused == win) + focus(sel); + } +} + +void +focusonce(const Arg *arg) +{ + nextfocus = True; +} + +void +focusurgent(const Arg *arg) +{ + int c; + + if (sel < 0) + return; + + for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) { + if (clients[c]->urgent) { + focus(c); + return; + } + } +} + +void +fullscreen(const Arg *arg) +{ + XEvent e; + + e.type = ClientMessage; + e.xclient.window = win; + e.xclient.message_type = wmatom[WMState]; + e.xclient.format = 32; + e.xclient.data.l[0] = 2; + e.xclient.data.l[1] = wmatom[WMFullscreen]; + e.xclient.data.l[2] = 0; + XSendEvent(dpy, root, False, SubstructureNotifyMask, &e); +} + +char * +getatom(int a) +{ + static char buf[BUFSIZ]; + Atom adummy; + int idummy; + unsigned long ldummy; + unsigned char *p = NULL; + + XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING, + &adummy, &idummy, &ldummy, &ldummy, &p); + if (p) + strncpy(buf, (char *)p, LENGTH(buf)-1); + else + buf[0] = '\0'; + XFree(p); + + return buf; +} + +int +getclient(Window w) +{ + int i; + + for (i = 0; i < nclients; i++) { + if (clients[i]->win == w) + return i; + } + + return -1; +} + +XftColor +getcolor(const char *colstr) +{ + XftColor color; + + if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color)) + die("%s: cannot allocate color '%s'\n", argv0, colstr); + + return color; +} + +int +getfirsttab(void) +{ + int cc, ret; + + if (sel < 0) + return 0; + + cc = ww / tabwidth; + if (nclients > cc) + cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; + + ret = sel - cc / 2 + (cc + 1) % 2; + return ret < 0 ? 0 : + ret + cc > nclients ? MAX(0, nclients - cc) : + ret; +} + +Bool +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return False; + + text[0] = '\0'; + XGetTextProperty(dpy, w, &name, atom); + if (!name.nitems) + return False; + + if (name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success + && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + text[size - 1] = '\0'; + XFree(name.value); + + return True; +} + +void +initfont(const char *fontstr) +{ + if (!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr)) + && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed"))) + die("error, cannot load font: '%s'\n", fontstr); + + dc.font.ascent = dc.font.xfont->ascent; + dc.font.descent = dc.font.xfont->descent; + dc.font.height = dc.font.ascent + dc.font.descent; +} + +Bool +isprotodel(int c) +{ + int i, n; + Atom *protocols; + Bool ret = False; + + if (XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) { + for (i = 0; !ret && i < n; i++) { + if (protocols[i] == wmatom[WMDelete]) + ret = True; + } + XFree(protocols); + } + + return ret; +} + +void +keypress(const XEvent *e) +{ + const XKeyEvent *ev = &e->xkey; + unsigned int i; + KeySym keysym; + + keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); + for (i = 0; i < LENGTH(keys); i++) { + if (keysym == keys[i].keysym && + CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && + keys[i].func) + keys[i].func(&(keys[i].arg)); + } +} + +void +killclient(const Arg *arg) +{ + XEvent ev; + + if (sel < 0) + return; + + if (isprotodel(sel) && !clients[sel]->closed) { + ev.type = ClientMessage; + ev.xclient.window = clients[sel]->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = wmatom[WMDelete]; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev); + clients[sel]->closed = True; + } else { + XKillClient(dpy, clients[sel]->win); + } +} + +void +manage(Window w) +{ + updatenumlockmask(); + { + int i, j, nextpos; + unsigned int modifiers[] = { 0, LockMask, numlockmask, + numlockmask | LockMask }; + KeyCode code; + Client *c; + XEvent e; + + XWithdrawWindow(dpy, w, 0); + XReparentWindow(dpy, w, win, 0, bh); + XSelectInput(dpy, w, PropertyChangeMask | + StructureNotifyMask | EnterWindowMask); + XSync(dpy, False); + + for (i = 0; i < LENGTH(keys); i++) { + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { + for (j = 0; j < LENGTH(modifiers); j++) { + XGrabKey(dpy, code, keys[i].mod | + modifiers[j], w, True, + GrabModeAsync, GrabModeAsync); + } + } + } + + c = ecalloc(1, sizeof *c); + c->win = w; + + nclients++; + clients = erealloc(clients, sizeof(Client *) * nclients); + + if(npisrelative) { + nextpos = sel + newposition; + } else { + if (newposition < 0) + nextpos = nclients - newposition; + else + nextpos = newposition; + } + if (nextpos >= nclients) + nextpos = nclients - 1; + if (nextpos < 0) + nextpos = 0; + + if (nclients > 1 && nextpos < nclients - 1) + memmove(&clients[nextpos + 1], &clients[nextpos], + sizeof(Client *) * (nclients - nextpos - 1)); + + clients[nextpos] = c; + updatetitle(nextpos); + + XLowerWindow(dpy, w); + XMapWindow(dpy, w); + + e.xclient.window = w; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; + e.xclient.data.l[2] = 0; + e.xclient.data.l[3] = win; + e.xclient.data.l[4] = 0; + XSendEvent(dpy, root, False, NoEventMask, &e); + + XSync(dpy, False); + + /* Adjust sel before focus does set it to lastsel. */ + if (sel >= nextpos) + sel++; + focus(nextfocus ? nextpos : + sel < 0 ? 0 : + sel); + nextfocus = foreground; + } +} + +void +maprequest(const XEvent *e) +{ + const XMapRequestEvent *ev = &e->xmaprequest; + + if (getclient(ev->window) < 0) + manage(ev->window); +} + +void +move(const Arg *arg) +{ + if (arg->i >= 0 && arg->i < nclients) + focus(arg->i); +} + +void +movetab(const Arg *arg) +{ + int c; + Client *new; + + if (sel < 0) + return; + + c = (sel + arg->i) % nclients; + if (c < 0) + c += nclients; + + if (c == sel) + return; + + new = clients[sel]; + if (sel < c) + memmove(&clients[sel], &clients[sel+1], + sizeof(Client *) * (c - sel)); + else + memmove(&clients[c+1], &clients[c], + sizeof(Client *) * (sel - c)); + clients[c] = new; + sel = c; + + drawbar(); +} + +void +propertynotify(const XEvent *e) +{ + const XPropertyEvent *ev = &e->xproperty; + XWMHints *wmh; + int c; + char* selection = NULL; + Arg arg; + + if (ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) { + selection = getatom(WMSelectTab); + if (!strncmp(selection, "0x", 2)) { + arg.i = getclient(strtoul(selection, NULL, 0)); + move(&arg); + } else { + cmd[cmd_append_pos] = selection; + arg.v = cmd; + spawn(&arg); + } + } else if (ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS && + (c = getclient(ev->window)) > -1 && + (wmh = XGetWMHints(dpy, clients[c]->win))) { + if (wmh->flags & XUrgencyHint) { + XFree(wmh); + wmh = XGetWMHints(dpy, win); + if (c != sel) { + if (urgentswitch && wmh && + !(wmh->flags & XUrgencyHint)) { + /* only switch, if tabbed was focused + * since last urgency hint if WMHints + * could not be received, + * default to no switch */ + focus(c); + } else { + /* if no switch should be performed, + * mark tab as urgent */ + clients[c]->urgent = True; + drawbar(); + } + } + if (wmh && !(wmh->flags & XUrgencyHint)) { + /* update tabbed urgency hint + * if not set already */ + wmh->flags |= XUrgencyHint; + XSetWMHints(dpy, win, wmh); + } + } + XFree(wmh); + } else if (ev->state != PropertyDelete && ev->atom == XA_WM_NAME && + (c = getclient(ev->window)) > -1) { + updatetitle(c); + } +} + +void +resize(int c, int w, int h) +{ + XConfigureEvent ce; + XWindowChanges wc; + + ce.x = 0; + ce.y = wc.y = bh; + ce.width = wc.width = w; + ce.height = wc.height = h; + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = clients[c]->win; + ce.window = clients[c]->win; + ce.above = None; + ce.override_redirect = False; + ce.border_width = 0; + + XConfigureWindow(dpy, clients[c]->win, CWY | CWWidth | CWHeight, &wc); + XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask, + (XEvent *)&ce); +} + +void +rotate(const Arg *arg) +{ + int nsel = -1; + + if (sel < 0) + return; + + if (arg->i == 0) { + if (lastsel > -1) + focus(lastsel); + } else if (sel > -1) { + /* Rotating in an arg->i step around the clients. */ + nsel = sel + arg->i; + while (nsel >= nclients) + nsel -= nclients; + while (nsel < 0) + nsel += nclients; + focus(nsel); + } +} + +void +run(void) +{ + XEvent ev; + + /* main event loop */ + XSync(dpy, False); + drawbar(); + if (doinitspawn == True) + spawn(NULL); + + while (running) { + XNextEvent(dpy, &ev); + if (handler[ev.type]) + (handler[ev.type])(&ev); /* call handler */ + } +} + +void +sendxembed(int c, long msg, long detail, long d1, long d2) +{ + XEvent e = { 0 }; + + e.xclient.window = clients[c]->win; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = msg; + e.xclient.data.l[2] = detail; + e.xclient.data.l[3] = d1; + e.xclient.data.l[4] = d2; + XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e); +} + +void +setcmd(int argc, char *argv[], int replace) +{ + int i; + + cmd = ecalloc(argc + 3, sizeof(*cmd)); + if (argc == 0) + return; + for (i = 0; i < argc; i++) + cmd[i] = argv[i]; + cmd[replace > 0 ? replace : argc] = winid; + cmd_append_pos = argc + !replace; + cmd[cmd_append_pos] = cmd[cmd_append_pos + 1] = NULL; +} + +void +setup(void) +{ + int bitm, tx, ty, tw, th, dh, dw, isfixed; + XWMHints *wmh; + XClassHint class_hint; + XSizeHints *size_hint; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + initfont(font); + bh = dc.h = dc.font.height + 2; + + /* init atoms */ + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", + False); + wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); + wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False); + + /* init appearance */ + wx = 0; + wy = 0; + ww = 800; + wh = 600; + isfixed = 0; + + if (geometry) { + tx = ty = tw = th = 0; + bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw, + (unsigned *)&th); + if (bitm & XValue) + wx = tx; + if (bitm & YValue) + wy = ty; + if (bitm & WidthValue) + ww = tw; + if (bitm & HeightValue) + wh = th; + if (bitm & XNegative && wx == 0) + wx = -1; + if (bitm & YNegative && wy == 0) + wy = -1; + if (bitm & (HeightValue | WidthValue)) + isfixed = 1; + + dw = DisplayWidth(dpy, screen); + dh = DisplayHeight(dpy, screen); + if (wx < 0) + wx = dw + wx - ww - 1; + if (wy < 0) + wy = dh + wy - wh - 1; + } + + dc.norm[ColBG] = getcolor(normbgcolor); + dc.norm[ColFG] = getcolor(normfgcolor); + dc.sel[ColBG] = getcolor(selbgcolor); + dc.sel[ColFG] = getcolor(selfgcolor); + dc.urg[ColBG] = getcolor(urgbgcolor); + dc.urg[ColFG] = getcolor(urgfgcolor); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + dc.gc = XCreateGC(dpy, root, 0, 0); + + win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, + dc.norm[ColFG].pixel, dc.norm[ColBG].pixel); + XMapRaised(dpy, win); + XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask | + ButtonPressMask | ExposureMask | KeyPressMask | + PropertyChangeMask | StructureNotifyMask | + SubstructureRedirectMask); + xerrorxlib = XSetErrorHandler(xerror); + + class_hint.res_name = wmname; + class_hint.res_class = "tabbed"; + XSetClassHint(dpy, win, &class_hint); + + size_hint = XAllocSizeHints(); + if (!isfixed) { + size_hint->flags = PSize | PMinSize; + size_hint->height = wh; + size_hint->width = ww; + size_hint->min_height = bh + 1; + } else { + size_hint->flags = PMaxSize | PMinSize; + size_hint->min_width = size_hint->max_width = ww; + size_hint->min_height = size_hint->max_height = wh; + } + wmh = XAllocWMHints(); + XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL); + XFree(size_hint); + XFree(wmh); + + XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); + + snprintf(winid, sizeof(winid), "%lu", win); + setenv("XEMBED", winid, 1); + + nextfocus = foreground; + focus(-1); +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("%s: cannot install SIGCHLD handler", argv0); + + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + if(dpy) + close(ConnectionNumber(dpy)); + + setsid(); + if (arg && arg->v) { + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "%s: execvp %s", argv0, + ((char **)arg->v)[0]); + } else { + cmd[cmd_append_pos] = NULL; + execvp(cmd[0], cmd); + fprintf(stderr, "%s: execvp %s", argv0, cmd[0]); + } + perror(" failed"); + exit(0); + } +} + +int +textnw(const char *text, unsigned int len) +{ + XGlyphInfo ext; + XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext); + return ext.xOff; +} + +void +toggle(const Arg *arg) +{ + *(Bool*) arg->v = !*(Bool*) arg->v; +} + +void +unmanage(int c) +{ + if (c < 0 || c >= nclients) { + drawbar(); + XSync(dpy, False); + return; + } + + if (!nclients) + return; + + if (c == 0) { + /* First client. */ + nclients--; + free(clients[0]); + memmove(&clients[0], &clients[1], sizeof(Client *) * nclients); + } else if (c == nclients - 1) { + /* Last client. */ + nclients--; + free(clients[c]); + clients = erealloc(clients, sizeof(Client *) * nclients); + } else { + /* Somewhere inbetween. */ + free(clients[c]); + memmove(&clients[c], &clients[c+1], + sizeof(Client *) * (nclients - (c + 1))); + nclients--; + } + + if (nclients <= 0) { + lastsel = sel = -1; + + if (closelastclient) + running = False; + else if (fillagain && running) + spawn(NULL); + } else { + if (lastsel >= nclients) + lastsel = nclients - 1; + else if (lastsel > c) + lastsel--; + + if (c == sel && lastsel >= 0) { + focus(lastsel); + } else { + if (sel > c) + sel--; + if (sel >= nclients) + sel = nclients - 1; + + focus(sel); + } + } + + drawbar(); + XSync(dpy, False); +} + +void +unmapnotify(const XEvent *e) +{ + const XUnmapEvent *ev = &e->xunmap; + int c; + + if ((c = getclient(ev->window)) > -1) + unmanage(c); +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) { + for (j = 0; j < modmap->max_keypermod; j++) { + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + } + } + XFreeModifiermap(modmap); +} + +void +updatetitle(int c) +{ + if (!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name, + sizeof(clients[c]->name))) + gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name, + sizeof(clients[c]->name)); + if (sel == c) + xsettitle(win, clients[c]->name); + drawbar(); +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && + ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && + ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && + ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && + ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && + ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && + ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && + ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && + ee->error_code == BadDrawable)) + return 0; + + fprintf(stderr, "%s: fatal error: request code=%d, error code=%d\n", + argv0, ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +void +xsettitle(Window w, const char *str) +{ + XTextProperty xtp; + + if (XmbTextListToTextProperty(dpy, (char **)&str, 1, + XCompoundTextStyle, &xtp) == Success) { + XSetTextProperty(dpy, w, &xtp, wmatom[WMName]); + XSetTextProperty(dpy, w, &xtp, XA_WM_NAME); + XFree(xtp.value); + } +} + +void +usage(void) +{ + die("usage: %s [-dfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n" + " [-r narg] [-o color] [-O color] [-t color] [-T color]\n" + " [-u color] [-U color] command...\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + Bool detach = False; + int replace = 0; + char *pstr; + + ARGBEGIN { + case 'c': + closelastclient = True; + fillagain = False; + break; + case 'd': + detach = True; + break; + case 'f': + fillagain = True; + break; + case 'g': + geometry = EARGF(usage()); + break; + case 'k': + killclientsfirst = True; + break; + case 'n': + wmname = EARGF(usage()); + break; + case 'O': + normfgcolor = EARGF(usage()); + break; + case 'o': + normbgcolor = EARGF(usage()); + break; + case 'p': + pstr = EARGF(usage()); + if (pstr[0] == 's') { + npisrelative = True; + newposition = atoi(&pstr[1]); + } else { + newposition = atoi(pstr); + } + break; + case 'r': + replace = atoi(EARGF(usage())); + break; + case 's': + doinitspawn = False; + break; + case 'T': + selfgcolor = EARGF(usage()); + break; + case 't': + selbgcolor = EARGF(usage()); + break; + case 'U': + urgfgcolor = EARGF(usage()); + break; + case 'u': + urgbgcolor = EARGF(usage()); + break; + case 'v': + die("tabbed-"VERSION", © 2009-2016 tabbed engineers, " + "see LICENSE for details.\n"); + break; + default: + usage(); + break; + } ARGEND; + + if (argc < 1) { + doinitspawn = False; + fillagain = False; + } + + setcmd(argc, argv, replace); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "%s: no locale support\n", argv0); + if (!(dpy = XOpenDisplay(NULL))) + die("%s: cannot open display\n", argv0); + + setup(); + printf("0x%lx\n", win); + fflush(NULL); + + if (detach) { + if (fork() == 0) { + fclose(stdout); + } else { + if (dpy) + close(ConnectionNumber(dpy)); + return EXIT_SUCCESS; + } + } + + run(); + cleanup(); + XCloseDisplay(dpy); + + return EXIT_SUCCESS; +} diff --git a/tabbed/tabbed.c.rej b/tabbed/tabbed.c.rej @@ -0,0 +1,13 @@ +--- tabbed.c ++++ tabbed.c +@@ -258,8 +261,8 @@ configurenotify(const XEvent *e) + ww = ev->width; + wh = ev->height; + XFreePixmap(dpy, dc.drawable); +- dc.drawable = XCreatePixmap(dpy, root, ww, wh, +- DefaultDepth(dpy, screen)); ++ dc.drawable = XCreatePixmap(dpy, win, ww, wh, ++ 32); + if (sel > -1) + resize(sel, ww, wh - bh); + XSync(dpy, False); diff --git a/tabbed/tabbed.o b/tabbed/tabbed.o Binary files differ. diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/LICENSE b/tabbed/tabbed_xdefecto/tabbed-0.6/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +© 2009-2011 Enno Boland <g s01 de> +© 2011 Connor Lane Smith <cls@lubutu.com> +© 2012 Christoph Lohmann <20h@r-36.net> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/Makefile b/tabbed/tabbed_xdefecto/tabbed-0.6/Makefile @@ -0,0 +1,60 @@ +# tabbed - tabbing interface +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = tabbed.c +OBJ = ${SRC:.c=.o} + +all: options tabbed + +options: + @echo tabbed build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +tabbed: tabbed.o + @echo CC -o $@ + @${CC} -o $@ tabbed.o ${LDFLAGS} + +clean: + @echo cleaning + @rm -f tabbed ${OBJ} tabbed-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p tabbed-${VERSION} + @cp -R LICENSE Makefile README config.def.h config.mk \ + tabbed.1 arg.h ${SRC} tabbed-${VERSION} + @tar -cf tabbed-${VERSION}.tar tabbed-${VERSION} + @gzip tabbed-${VERSION}.tar + @rm -rf tabbed-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f tabbed ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/tabbed + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g" < tabbed.1 > ${DESTDIR}${MANPREFIX}/man1/tabbed.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/tabbed.1 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/tabbed + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/tabbed.1 + +.PHONY: all options clean dist install uninstall diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/README b/tabbed/tabbed_xdefecto/tabbed-0.6/README @@ -0,0 +1,22 @@ +tabbed - generic tabbed interface +================================= +tabbed is a simple tabbed X window container. + +Requirements +------------ +In order to build tabbed you need the Xlib header files. + +Installation +------------ +Edit config.mk to match your local setup (tabbed is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install tabbed +(if necessary as root): + + make clean install + +Running tabbed +-------------- +See the man page for details. + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/TODO b/tabbed/tabbed_xdefecto/tabbed-0.6/TODO @@ -0,0 +1,4 @@ +# TODO +* add some way to detach windows +* add some way to attach windows + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/arg.h b/tabbed/tabbed_xdefecto/tabbed-0.6/arg.h @@ -0,0 +1,52 @@ +/* See the LICENSE file for copyright and license details. */ + +#ifndef __ARG_H__ +#define __ARG_H__ + +extern char *argv0; + +#define USED(x) ((void)(x)) + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char _argc;\ + char **_argv;\ + int brk;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk = 0, argv[0]++, _argv = argv;\ + argv[0][0] && !brk;\ + argv[0]++) {\ + if (_argv != argv)\ + break;\ + _argc = argv[0][0];\ + switch (_argc) + +#define ARGEND }\ + USED(_argc);\ + }\ + USED(argv);\ + USED(argc); + +#define ARGC() _argc + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/config.def.h b/tabbed/tabbed_xdefecto/tabbed-0.6/config.def.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const char font[] = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char before[] = "<"; +static const char after[] = ">"; +static const int tabwidth = 200; +static const Bool foreground = True; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' | sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' | xargs -0 printf %b | dmenu -l 10`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { \ + /* modifier key function argument */ + { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, + { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, + { MODKEY, XK_t, spawn, SETPROP("_TABBED_SELECT_TAB") }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/config.h b/tabbed/tabbed_xdefecto/tabbed-0.6/config.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const char font[] = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char before[] = "<"; +static const char after[] = ">"; +static const int tabwidth = 200; +static const Bool foreground = True; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' | sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' | xargs -0 printf %b | dmenu -l 10`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { \ + /* modifier key function argument */ + { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, + { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, + { MODKEY, XK_t, spawn, SETPROP("_TABBED_SELECT_TAB") }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/config.mk b/tabbed/tabbed_xdefecto/tabbed-0.6/config.mk @@ -0,0 +1,25 @@ +# tabbed version +VERSION = 0.6 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# includes and libs +INCS = -I. -I/usr/include +LIBS = -L/usr/lib -lc -lX11 + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed b/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed Binary files differ. diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed.1 b/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed.1 @@ -0,0 +1,144 @@ +.TH TABBED 1 tabbed\-VERSION +.SH NAME +tabbed \- generic tabbed interface +.SH SYNOPSIS +.B tabbed +.RB [ \-c ] +.RB [ \-d ] +.RB [ \-h ] +.RB [ \-s ] +.RB [ \-v ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-p +.IR [ s +/- ] pos ] +.RB [ \-r +.IR narg ] +.IR [ command ... ] +.SH DESCRIPTION +.B tabbed +is a simple tabbed container for applications which support XEmbed. Tabbed +will then run the provided command with the xid of tabbed as appended +argument. (See EXAMPLES.) The automatic spawning of the command can be +disabled by providing the -s parameter. If no command is provided +tabbed will just print its xid and run no command. +.SH OPTIONS +.TP +.B \-c +close tabbed when the last tab is closed. Mutually exclusive with -f. +.TP +.B \-d +detaches tabbed from the terminal and prints its XID to stdout. +.TP +.B \-f +fill up tabbed again by spawning the provided command, when the last tab is +closed. Mutually exclusive with -c. +.TP +.B \-h +will print the usage of tabbed. +.TP +.BI \-g " geometry" +defines the X11 geometry string, which will fixate the height and width of +tabbed. +Them form is [=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]. See +.BR XParseGeometry (3) +for further details. +.TP +.BI \-n " name" +will set the WM_CLASS attribute to +.I name. +.TP +.BI \-p " [ s +/-] pos" +will set the absolute or relative position of where to start a new tab. When +.I pos +is is given without 's' in front it is an absolute position. Then negative +numbers will be the position from the last tab, where -1 is the last tab. +If 's' is given, then +.I pos +is a relative position to the current selected tab. If this reaches the limits +of the tabs; those limits then apply. +.TP +.BI \-r " narg" +will replace the +.I narg +th argument in +.I command +with the window id, rather than appending it to the end. +.TP +.B \-s +will disable automatic spawning of the command. +.TP +.BI \-t " color" +defines the selected background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-T " color" +defines the selected foreground color. +.TP +.BI \-u " color" +defines the normal background color. +.TP +.BI \-U " color" +defines the normal foreground color. +.TP +.B \-v +prints version information to stderr, then exits. +.SH USAGE +.TP +.B Ctrl\-Shift\-Return +open new tab +.TP +.B Ctrl\-Shift\-h +previous tab +.TP +.B Ctrl\-Shift\-l +next tab +.TP +.B Ctrl\-Shift\-j +move selected tab one to the left +.TP +.B Ctrl\-Shift\-k +move selected tab one to the right +.TP +.B Ctrl\-Tab +toggle between the selected and last selected tab +.TP +.B Ctrl\-t +open dmenu to either create a new tab appending the entered string or select +an already existing tab. +.TP +.B Ctrl\-q +close tab +.TP +.B Ctrl\-[0..9] +jumps to nth tab +.TP +.B F11 +Toggle fullscreen mode. +.SH EXAMPLES +$ tabbed surf -e +.TP +$ tabbed urxvt -embed +.TP +$ tabbed xterm -into +.TP +$ $(tabbed -d >/tmp/tabbed.xid); urxvt -embed $(</tmp/tabbed.xid); +.TP +$ tabbed -r 2 st -w '' -e tmux +.SH CUSTOMIZATION +.B tabbed +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR st (1) +.SH BUGS +Please report them. + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed.c b/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed.c @@ -0,0 +1,1297 @@ +/* + * See LICENSE file for copyright and license details. + */ + +#include <sys/wait.h> +#include <locale.h> +#include <stdarg.h> +#include <unistd.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> + +#include "arg.h" + +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define XEMBED_REQUEST_FOCUS 3 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 +#define XEMBED_FOCUS_NEXT 6 +#define XEMBED_FOCUS_PREV 7 +/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MODALITY_OFF 11 +#define XEMBED_REGISTER_ACCELERATOR 12 +#define XEMBED_UNREGISTER_ACCELERATOR 13 +#define XEMBED_ACTIVATE_ACCELERATOR 14 + +/* Details for XEMBED_FOCUS_IN: */ +#define XEMBED_FOCUS_CURRENT 0 +#define XEMBED_FOCUS_FIRST 1 +#define XEMBED_FOCUS_LAST 2 + +/* Macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define LENGTH(x) (sizeof((x)) / sizeof(*(x))) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) +#define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) + +enum { ColFG, ColBG, ColLast }; /* color */ +enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen, + XEmbed, WMSelectTab, WMLast }; /* default atoms */ + +typedef union { + int i; + const void *v; +} Arg; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + int x, y, w, h; + unsigned long norm[ColLast]; + unsigned long sel[ColLast]; + Drawable drawable; + GC gc; + struct { + int ascent; + int descent; + int height; + XFontSet set; + XFontStruct *xfont; + } font; +} DC; /* draw context */ + +typedef struct Client { + char name[256]; + Window win; + int tabx; + Bool mapped; + Bool closed; +} Client; + +/* function declarations */ +static void buttonpress(const XEvent *e); +static void cleanup(void); +static void clientmessage(const XEvent *e); +static void configurenotify(const XEvent *e); +static void configurerequest(const XEvent *e); +static void createnotify(const XEvent *e); +static void destroynotify(const XEvent *e); +static void die(const char *errstr, ...); +static void drawbar(void); +static void drawtext(const char *text, unsigned long col[ColLast]); +static void *emallocz(size_t size); +static void *erealloc(void *o, size_t size); +static void expose(const XEvent *e); +static void focus(int c); +static void focusin(const XEvent *e); +static void focusonce(const Arg *arg); +static void fullscreen(const Arg *arg); +static char* getatom(int a); +static int getclient(Window w); +static unsigned long getcolor(const char *colstr); +static int getfirsttab(void); +static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void initfont(const char *fontstr); +static Bool isprotodel(int c); +static void keypress(const XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window win); +static void maprequest(const XEvent *e); +static void move(const Arg *arg); +static void movetab(const Arg *arg); +static void propertynotify(const XEvent *e); +static void resize(int c, int w, int h); +static void rotate(const Arg *arg); +static void run(void); +static void sendxembed(int c, long msg, long detail, long d1, long d2); +static void setup(void); +static void setcmd(int argc, char *argv[], int); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static int textnw(const char *text, unsigned int len); +static void unmanage(int c); +static void updatenumlockmask(void); +static void updatetitle(int c); +static int xerror(Display *dpy, XErrorEvent *ee); +static void xsettitle(Window w, const char *str); + +/* variables */ +static int screen; +static void (*handler[LASTEvent]) (const XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureNotify] = configurenotify, + [ConfigureRequest] = configurerequest, + [CreateNotify] = createnotify, + [DestroyNotify] = destroynotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MapRequest] = maprequest, + [PropertyNotify] = propertynotify, +}; +static int bh, wx, wy, ww, wh; +static unsigned int numlockmask = 0; +static Bool running = True, nextfocus, doinitspawn = True, + fillagain = False, closelastclient = False; +static Display *dpy; +static DC dc; +static Atom wmatom[WMLast]; +static Window root, win; +static Client **clients = NULL; +static int nclients = 0, sel = -1, lastsel = -1; +static int (*xerrorxlib)(Display *, XErrorEvent *); +static int cmd_append_pos = 0; +static char winid[64]; +static char **cmd = NULL; +static char *wmname = "tabbed"; +static const char *geometry = NULL; + +char *argv0; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +void +buttonpress(const XEvent *e) { + const XButtonPressedEvent *ev = &e->xbutton; + int i; + int fc; + Arg arg; + + fc = getfirsttab(); + + if((fc > 0 && ev->x < TEXTW(before)) || ev->x < 0) + return; + + if(ev->y < 0 || ev-> y > bh) + return; + + for(i = (fc > 0) ? fc : 0; i < nclients; i++) { + if(clients[i]->tabx > ev->x) { + switch(ev->button) { + case Button1: + focus(i); + break; + case Button2: + focus(i); + killclient(NULL); + break; + case Button4: + case Button5: + arg.i = ev->button == Button4 ? -1 : 1; + rotate(&arg); + break; + } + break; + } + } +} + +void +cleanup(void) { + int i; + + for(i = 0; i < nclients; i++) { + focus(i); + killclient(NULL); + killclient(NULL); + XReparentWindow(dpy, clients[i]->win, root, 0, 0); + unmanage(i); + } + free(clients); + clients = NULL; + + if(dc.font.set) { + XFreeFontSet(dpy, dc.font.set); + } else { + XFreeFont(dpy, dc.font.xfont); + } + + XFreePixmap(dpy, dc.drawable); + XFreeGC(dpy, dc.gc); + XDestroyWindow(dpy, win); + XSync(dpy, False); + free(cmd); +} + +void +clientmessage(const XEvent *e) { + const XClientMessageEvent *ev = &e->xclient; + + if(ev->message_type == wmatom[WMProtocols] + && ev->data.l[0] == wmatom[WMDelete]) { + running = False; + } +} + +void +configurenotify(const XEvent *e) { + const XConfigureEvent *ev = &e->xconfigure; + + if(ev->window == win && (ev->width != ww || ev->height != wh)) { + ww = ev->width; + wh = ev->height; + XFreePixmap(dpy, dc.drawable); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + if(sel > -1) + resize(sel, ww, wh - bh); + XSync(dpy, False); + } +} + +void +configurerequest(const XEvent *e) { + const XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + int c; + + if((c = getclient(ev->window)) > -1) { + wc.x = 0; + wc.y = bh; + wc.width = ww; + wc.height = wh - bh; + wc.border_width = 0; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc); + } +} + +void +createnotify(const XEvent *e) { + const XCreateWindowEvent *ev = &e->xcreatewindow; + + if(ev->window != win && getclient(ev->window) < 0) + manage(ev->window); +} + +void +destroynotify(const XEvent *e) { + const XDestroyWindowEvent *ev = &e->xdestroywindow; + int c; + + if((c = getclient(ev->window)) > -1) + unmanage(c); +} + +void +die(const char *errstr, ...) { + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void +drawbar(void) { + unsigned long *col; + int c, fc, width, n = 0; + char *name = NULL; + + if(nclients == 0) { + dc.x = 0; + dc.w = ww; + XFetchName(dpy, win, &name); + drawtext(name ? name : "", dc.norm); + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); + + return; + } + + width = ww; + clients[nclients-1]->tabx = -1; + fc = getfirsttab(); + if(fc > -1) + n = nclients - fc; + + if((n * tabwidth) > width) { + dc.w = TEXTW(after); + dc.x = width - dc.w; + drawtext(after, dc.sel); + width -= dc.w; + } + dc.x = 0; + + if(fc > 0) { + dc.w = TEXTW(before); + drawtext(before, dc.sel); + dc.x += dc.w; + width -= dc.w; + } + + for(c = (fc > 0)? fc : 0; c < nclients && dc.x < width; c++) { + dc.w = tabwidth; + if(c == sel) { + col = dc.sel; + if((n * tabwidth) > width) { + dc.w += width % tabwidth; + } else { + dc.w = width - (n - 1) * tabwidth; + } + } else { + col = dc.norm; + } + drawtext(clients[c]->name, col); + dc.x += dc.w; + clients[c]->tabx = dc.x; + } + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); +} + +void +drawtext(const char *text, unsigned long col[ColLast]) { + int i, x, y, h, len, olen; + char buf[256]; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + XSetForeground(dpy, dc.gc, col[ColBG]); + XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); + if(!text) + return; + + olen = strlen(text); + h = dc.font.ascent + dc.font.descent; + y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent; + x = dc.x + (h / 2); + + /* shorten text if necessary */ + for(len = MIN(olen, sizeof(buf)); + len && textnw(text, len) > dc.w - h; len--); + if(!len) + return; + + memcpy(buf, text, len); + if(len < olen) { + for(i = len; i && i > len - 3; buf[--i] = '.'); + } + + XSetForeground(dpy, dc.gc, col[ColFG]); + if(dc.font.set) { + XmbDrawString(dpy, dc.drawable, dc.font.set, + dc.gc, x, y, buf, len); + } else { + XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len); + } +} + +void * +emallocz(size_t size) { + void *p; + + if(!(p = calloc(1, size))) + die("tabbed: cannot malloc\n"); + return p; +} + +void * +erealloc(void *o, size_t size) { + void *p; + + if(!(p = realloc(o, size))) + die("tabbed: cannot realloc\n"); + return p; +} + +void +expose(const XEvent *e) { + const XExposeEvent *ev = &e->xexpose; + + if(ev->count == 0 && win == ev->window) + drawbar(); +} + +void +focus(int c) { + char buf[BUFSIZ] = "tabbed-"VERSION" ::"; + size_t i, n; + + /* If c, sel and clients are -1, raise tabbed-win itself */ + if(nclients == 0) { + cmd[cmd_append_pos] = NULL; + for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++) + n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]); + + xsettitle(win, buf); + XRaiseWindow(dpy, win); + + return; + } + + if(c < 0 || c >= nclients) + return; + + resize(c, ww, wh - bh); + XRaiseWindow(dpy, clients[c]->win); + XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime); + sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0); + sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); + xsettitle(win, clients[c]->name); + + /* If sel is already c, change nothing. */ + if(sel != c) { + lastsel = sel; + sel = c; + } + + drawbar(); + XSync(dpy, False); +} + +void +focusin(const XEvent *e) { + const XFocusChangeEvent *ev = &e->xfocus; + int dummy; + Window focused; + + if(ev->mode != NotifyUngrab) { + XGetInputFocus(dpy, &focused, &dummy); + if(focused == win) + focus(sel); + } +} + +void +focusonce(const Arg *arg) { + nextfocus = True; +} + +void +fullscreen(const Arg *arg) { + XEvent e; + + e.type = ClientMessage; + e.xclient.window = win; + e.xclient.message_type = wmatom[WMState]; + e.xclient.format = 32; + e.xclient.data.l[0] = 2; + e.xclient.data.l[1] = wmatom[WMFullscreen]; + e.xclient.data.l[2] = 0; + XSendEvent(dpy, root, False, SubstructureNotifyMask, &e); +} + +char * +getatom(int a) { + static char buf[BUFSIZ]; + Atom adummy; + int idummy; + unsigned long ldummy; + unsigned char *p = NULL; + + XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING, + &adummy, &idummy, &ldummy, &ldummy, &p); + if(p) { + strncpy(buf, (char *)p, LENGTH(buf)-1); + } else { + buf[0] = '\0'; + } + XFree(p); + + return buf; +} + +int +getclient(Window w) { + int i; + + for(i = 0; i < nclients; i++) { + if(clients[i]->win == w) + return i; + } + + return -1; +} + +unsigned long +getcolor(const char *colstr) { + Colormap cmap = DefaultColormap(dpy, screen); + XColor color; + + if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) + die("tabbed: cannot allocate color '%s'\n", colstr); + + return color.pixel; +} + +int +getfirsttab(void) { + int c, n, fc; + + if(sel < 0) + return -1; + + c = sel; + fc = 0; + n = nclients; + if((n * tabwidth) > ww) { + for(; (c * tabwidth) > (ww / 2) + && (n * tabwidth) > ww; + c--, n--, fc++); + } + + return fc; +} + +Bool +gettextprop(Window w, Atom atom, char *text, unsigned int size) { + char **list = NULL; + int n; + XTextProperty name; + + if(!text || size == 0) + return False; + + text[0] = '\0'; + XGetTextProperty(dpy, w, &name, atom); + if(!name.nitems) + return False; + + if(name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else { + if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success + && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + } + text[size - 1] = '\0'; + XFree(name.value); + + return True; +} + +void +initfont(const char *fontstr) { + char *def, **missing, **font_names; + int i, n; + XFontStruct **xfonts; + + missing = NULL; + if(dc.font.set) + XFreeFontSet(dpy, dc.font.set); + + dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); + if(missing) { + while(n--) + fprintf(stderr, "tabbed: missing fontset: %s\n", missing[n]); + XFreeStringList(missing); + } + + if(dc.font.set) { + dc.font.ascent = dc.font.descent = 0; + n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names); + for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) { + dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent); + dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent); + xfonts++; + } + } else { + if(dc.font.xfont) + XFreeFont(dpy, dc.font.xfont); + dc.font.xfont = NULL; + if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)) + && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) { + die("tabbed: cannot load font: '%s'\n", fontstr); + } + + dc.font.ascent = dc.font.xfont->ascent; + dc.font.descent = dc.font.xfont->descent; + } + dc.font.height = dc.font.ascent + dc.font.descent; +} + +Bool +isprotodel(int c) { + int i, n; + Atom *protocols; + Bool ret = False; + + if(XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) { + for(i = 0; !ret && i < n; i++) { + if(protocols[i] == wmatom[WMDelete]) + ret = True; + } + XFree(protocols); + } + + return ret; +} + +void +keypress(const XEvent *e) { + const XKeyEvent *ev = &e->xkey; + unsigned int i; + KeySym keysym; + + keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); + for(i = 0; i < LENGTH(keys); i++) { + if(keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) { + keys[i].func(&(keys[i].arg)); + } + } +} + +void +killclient(const Arg *arg) { + XEvent ev; + + if(sel < 0) + return; + + if(isprotodel(sel) && !clients[sel]->closed) { + ev.type = ClientMessage; + ev.xclient.window = clients[sel]->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = wmatom[WMDelete]; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev); + clients[sel]->closed = True; + } else { + XKillClient(dpy, clients[sel]->win); + } +} + +void +manage(Window w) { + updatenumlockmask(); + { + int i, j, nextpos; + unsigned int modifiers[] = { 0, LockMask, numlockmask, + numlockmask|LockMask }; + KeyCode code; + Client *c; + XEvent e; + + XWithdrawWindow(dpy, w, 0); + XReparentWindow(dpy, w, win, 0, bh); + XSelectInput(dpy, w, PropertyChangeMask + |StructureNotifyMask|EnterWindowMask); + XSync(dpy, False); + + for(i = 0; i < LENGTH(keys); i++) { + if((code = XKeysymToKeycode(dpy, keys[i].keysym))) { + for(j = 0; j < LENGTH(modifiers); j++) { + XGrabKey(dpy, code, keys[i].mod + | modifiers[j], w, + True, GrabModeAsync, + GrabModeAsync); + } + } + } + + c = emallocz(sizeof(*c)); + c->win = w; + + nclients++; + clients = erealloc(clients, sizeof(Client *) * nclients); + + if(npisrelative) { + nextpos = sel + newposition; + } else { + if(newposition < 0) { + nextpos = nclients - newposition; + } else { + nextpos = newposition; + } + } + if(nextpos >= nclients) + nextpos = nclients - 1; + if(nextpos < 0) + nextpos = 0; + + if(nclients > 1 && nextpos < nclients - 1) { + memmove(&clients[nextpos + 1], &clients[nextpos], + sizeof(Client *) * + (nclients - nextpos - 1)); + } + clients[nextpos] = c; + updatetitle(nextpos); + + XLowerWindow(dpy, w); + XMapWindow(dpy, w); + + e.xclient.window = w; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; + e.xclient.data.l[2] = 0; + e.xclient.data.l[3] = win; + e.xclient.data.l[4] = 0; + XSendEvent(dpy, root, False, NoEventMask, &e); + + XSync(dpy, False); + + /* Adjust sel before focus does set it to lastsel. */ + if(sel >= nextpos) + sel++; + focus((nextfocus)? nextpos : ((sel < 0)? 0 : sel)); + nextfocus = foreground; + } +} + +void +maprequest(const XEvent *e) { + const XMapRequestEvent *ev = &e->xmaprequest; + + if(getclient(ev->window) < 0) + manage(ev->window); +} + +void +move(const Arg *arg) { + if(arg->i >= 0 && arg->i < nclients) + focus(arg->i); +} + +void +movetab(const Arg *arg) { + int c; + Client *new; + + if(sel < 0 || (arg->i == 0)) + return; + + c = sel + arg->i; + while(c >= nclients) + c -= nclients; + while(c < 0) + c += nclients; + + new = clients[c]; + clients[c] = clients[sel]; + clients[sel] = new; + + sel = c; + + drawbar(); +} + +void +propertynotify(const XEvent *e) { + const XPropertyEvent *ev = &e->xproperty; + int c; + char* selection = NULL; + Arg arg; + + if(ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) { + selection = getatom(WMSelectTab); + if(!strncmp(selection, "0x", 2)) { + arg.i = getclient(strtoul(selection, NULL, 0)); + move(&arg); + } else { + cmd[cmd_append_pos] = selection; + arg.v = cmd; + spawn(&arg); + } + } else if(ev->state != PropertyDelete && ev->atom == XA_WM_NAME + && (c = getclient(ev->window)) > -1) { + updatetitle(c); + } +} + +void +resize(int c, int w, int h) { + XConfigureEvent ce; + XWindowChanges wc; + + ce.x = 0; + ce.y = bh; + ce.width = wc.width = w; + ce.height = wc.height = h; + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = clients[c]->win; + ce.window = clients[c]->win; + ce.above = None; + ce.override_redirect = False; + ce.border_width = 0; + + XConfigureWindow(dpy, clients[c]->win, CWWidth|CWHeight, &wc); + XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask, + (XEvent *)&ce); +} + +void +rotate(const Arg *arg) { + int nsel = -1; + + if(sel < 0) + return; + + if(arg->i == 0) { + if(lastsel > -1) + focus(lastsel); + } else if(sel > -1) { + /* Rotating in an arg->i step around the clients. */ + nsel = sel + arg->i; + while(nsel >= nclients) + nsel -= nclients; + while(nsel < 0) + nsel += nclients; + focus(nsel); + } +} + +void +run(void) { + XEvent ev; + + /* main event loop */ + XSync(dpy, False); + drawbar(); + if(doinitspawn == True) + spawn(NULL); + + while(running) { + XNextEvent(dpy, &ev); + if(handler[ev.type]) + (handler[ev.type])(&ev); /* call handler */ + } +} + +void +sendxembed(int c, long msg, long detail, long d1, long d2) { + XEvent e = { 0 }; + + e.xclient.window = clients[c]->win; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = msg; + e.xclient.data.l[2] = detail; + e.xclient.data.l[3] = d1; + e.xclient.data.l[4] = d2; + XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e); +} + +void +setcmd(int argc, char *argv[], int replace) { + int i; + + cmd = emallocz((argc+3) * sizeof(*cmd)); + if (argc == 0) + return; + for(i = 0; i < argc; i++) + cmd[i] = argv[i]; + cmd[(replace > 0)? replace : argc] = winid; + cmd_append_pos = argc + !replace; + cmd[cmd_append_pos] = cmd[cmd_append_pos+1] = NULL; +} + +void +setup(void) { + int bitm, tx, ty, tw, th, dh, dw, isfixed; + XClassHint class_hint; + XSizeHints *size_hint; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + initfont(font); + bh = dc.h = dc.font.height + 2; + + /* init atoms */ + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False); + wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); + + /* init appearance */ + wx = 0; + wy = 0; + ww = 800; + wh = 600; + isfixed = 0; + + if(geometry) { + tx = ty = tw = th = 0; + bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw, + (unsigned *)&th); + if(bitm & XValue) + wx = tx; + if(bitm & YValue) + wy = ty; + if(bitm & WidthValue) + ww = tw; + if(bitm & HeightValue) + wh = th; + if(bitm & XNegative && wx == 0) + wx = -1; + if(bitm & YNegative && wy == 0) + wy = -1; + if(bitm & (HeightValue|WidthValue)) + isfixed = 1; + + dw = DisplayWidth(dpy, screen); + dh = DisplayHeight(dpy, screen); + if(wx < 0) + wx = dw + wx - ww - 1; + if(wy < 0) + wy = dh + wy - wh - 1; + } + + dc.norm[ColBG] = getcolor(normbgcolor); + dc.norm[ColFG] = getcolor(normfgcolor); + dc.sel[ColBG] = getcolor(selbgcolor); + dc.sel[ColFG] = getcolor(selfgcolor); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + dc.gc = XCreateGC(dpy, root, 0, 0); + if(!dc.font.set) + XSetFont(dpy, dc.gc, dc.font.xfont->fid); + + win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, + dc.norm[ColFG], dc.norm[ColBG]); + XMapRaised(dpy, win); + XSelectInput(dpy, win, SubstructureNotifyMask|FocusChangeMask| + ButtonPressMask|ExposureMask|KeyPressMask|PropertyChangeMask| + StructureNotifyMask|SubstructureRedirectMask); + xerrorxlib = XSetErrorHandler(xerror); + + class_hint.res_name = wmname; + class_hint.res_class = "tabbed"; + XSetClassHint(dpy, win, &class_hint); + + size_hint = XAllocSizeHints(); + if(!isfixed) { + size_hint->flags = PSize; + size_hint->height = wh; + size_hint->width = ww; + } else { + size_hint->flags = PMaxSize | PMinSize; + size_hint->min_width = size_hint->max_width = ww; + size_hint->min_height = size_hint->max_height = wh; + } + XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, NULL, NULL); + XFree(size_hint); + + XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); + + snprintf(winid, sizeof(winid), "%lu", win); + setenv("XEMBED", winid, 1); + + nextfocus = foreground; + focus(-1); +} + +void +sigchld(int unused) { + if(signal(SIGCHLD, sigchld) == SIG_ERR) + die("tabbed: cannot install SIGCHLD handler"); + + while(0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) { + if(fork() == 0) { + if(dpy) + close(ConnectionNumber(dpy)); + + setsid(); + if(arg && arg->v) { + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "tabbed: execvp %s", + ((char **)arg->v)[0]); + } else { + cmd[cmd_append_pos] = NULL; + execvp(cmd[0], cmd); + fprintf(stderr, "tabbed: execvp %s", cmd[0]); + } + perror(" failed"); + exit(0); + } +} + +int +textnw(const char *text, unsigned int len) { + XRectangle r; + + if(dc.font.set) { + XmbTextExtents(dc.font.set, text, len, NULL, &r); + + return r.width; + } + + return XTextWidth(dc.font.xfont, text, len); +} + +void +unmanage(int c) { + if(c < 0 || c >= nclients) { + drawbar(); + XSync(dpy, False); + return; + } + + if(!nclients) { + return; + } else if(c == 0) { + /* First client. */ + nclients--; + free(clients[0]); + memmove(&clients[0], &clients[1], sizeof(Client *) * nclients); + } else if(c == nclients - 1) { + /* Last client. */ + nclients--; + free(clients[c]); + clients = erealloc(clients, sizeof(Client *) * nclients); + } else { + /* Somewhere inbetween. */ + free(clients[c]); + memmove(&clients[c], &clients[c+1], + sizeof(Client *) * (nclients - (c + 1))); + nclients--; + } + + if(nclients <= 0) { + sel = -1; + lastsel = -1; + + if (closelastclient) { + running = False; + } else if (fillagain && running) { + spawn(NULL); + } + } else { + if(c == lastsel) { + lastsel = -1; + } else if(lastsel > c) { + lastsel--; + } + lastsel = MIN(lastsel, nclients - 1); + + if(c == sel) { + /* Note that focus() will never set lastsel == sel, + * so if here lastsel == sel, it was decreased by above if() clause + * and was actually (sel + 1) before. + */ + if(lastsel > 0) { + focus(lastsel); + } else { + focus(0); + lastsel = 1; + } + } else { + if(sel > c) + sel -= 1; + if(sel >= nclients) + sel = nclients - 1; + + focus(sel); + } + } + + drawbar(); + XSync(dpy, False); +} + +void +updatenumlockmask(void) { + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for(i = 0; i < 8; i++) { + for(j = 0; j < modmap->max_keypermod; j++) { + if(modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, + XK_Num_Lock)) { + numlockmask = (1 << i); + } + } + } + XFreeModifiermap(modmap); +} + +void +updatetitle(int c) { + if(!gettextprop(clients[c]->win, wmatom[WMName], + clients[c]->name, sizeof(clients[c]->name))) { + gettextprop(clients[c]->win, XA_WM_NAME, + clients[c]->name, sizeof(clients[c]->name)); + } + if(sel == c) + xsettitle(win, clients[c]->name); + drawbar(); +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) { + if(ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus + && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 + && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle + && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment + && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow + && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton + && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey + && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea + && ee->error_code == BadDrawable)) { + return 0; + } + + fprintf(stderr, "tabbed: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +void +xsettitle(Window w, const char *str) { + XTextProperty xtp; + + if(XmbTextListToTextProperty(dpy, (char **)&str, 1, XCompoundTextStyle, + &xtp) == Success) { + XSetTextProperty(dpy, w, &xtp, wmatom[WMName]); + XSetTextProperty(dpy, w, &xtp, XA_WM_NAME); + XFree(xtp.value); + } +} + +char *argv0; + +void +usage(void) { + die("usage: %s [-dfhsv] [-g geometry] [-n name] [-p [s+/-]pos] [-r narg] " + "[-u color] [-U color] [-t color] [-T color] command...\n", argv0); +} + +int +main(int argc, char *argv[]) { + Bool detach = False; + int replace = 0; + char *pstr; + + ARGBEGIN { + case 'c': + closelastclient = True; + fillagain = False; + break; + case 'd': + detach = True; + break; + case 'f': + fillagain = True; + break; + case 'g': + geometry = EARGF(usage()); + break; + case 'n': + wmname = EARGF(usage()); + break; + case 'p': + pstr = EARGF(usage()); + if(pstr[0] == 's') { + npisrelative = True; + newposition = atoi(&pstr[1]); + } else { + newposition = atoi(pstr); + } + break; + case 'r': + replace = atoi(EARGF(usage())); + break; + case 's': + doinitspawn = False; + break; + case 'v': + die("tabbed-"VERSION", © 2009-2012" + " tabbed engineers, see LICENSE" + " for details.\n"); + break; + case 't': + selbgcolor = EARGF(usage()); + break; + case 'T': + selfgcolor = EARGF(usage()); + break; + case 'u': + normbgcolor = EARGF(usage()); + break; + case 'U': + normfgcolor = EARGF(usage()); + break; + default: + case 'h': + usage(); + } ARGEND; + + if(argc < 1) { + doinitspawn = False; + fillagain = False; + } + + setcmd(argc, argv, replace); + + if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "tabbed: no locale support\n"); + if(!(dpy = XOpenDisplay(NULL))) + die("tabbed: cannot open display\n"); + + setup(); + printf("0x%lx\n", win); + fflush(NULL); + + if(detach) { + if(fork() == 0) { + fclose(stdout); + } else { + if(dpy) + close(ConnectionNumber(dpy)); + return EXIT_SUCCESS; + } + } + + run(); + cleanup(); + XCloseDisplay(dpy); + + return EXIT_SUCCESS; +} + diff --git a/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed.o b/tabbed/tabbed_xdefecto/tabbed-0.6/tabbed.o Binary files differ. diff --git a/tabbed/xembed b/tabbed/xembed Binary files differ. diff --git a/tabbed/xembed.1 b/tabbed/xembed.1 @@ -0,0 +1,35 @@ +.TH XEMBED 1 tabbed\-VERSION +.SH NAME +xembed \- XEmbed foreground process +.SH SYNOPSIS +.B xembed +.I flag command +.RI [ "argument ..." ] +.SH DESCRIPTION +If the environment variable XEMBED is set, and +.B xembed +is in the foreground of its controlling tty, it will execute +.IP +command flag $XEMBED [argument ...] +.LP +Otherwise it will execute +.IP +command [argument ...] +.LP +.SH EXAMPLE +In a terminal emulator within a +.B tabbed +session, the shell alias +.IP +$ alias surf='xembed -e surf' +.LP +will cause `surf' to open in a new tab, unless it is run in the background, +i.e. `surf &', in which case it will instead open in a new window. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR tabbed (1) +.SH BUGS +Please report them. diff --git a/tabbed/xembed.c b/tabbed/xembed.c @@ -0,0 +1,45 @@ +/* + * See LICENSE file for copyright and license details. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int +main(int argc, char *argv[]) +{ + char *xembed; + int tty; + pid_t pgrp, tcpgrp; + + if (argc < 3) { + fprintf(stderr, "usage: %s flag cmd ...\n", argv[0]); + return 2; + } + + if (!(xembed = getenv("XEMBED"))) + goto noembed; + + if ((tty = open("/dev/tty", O_RDONLY)) < 0) + goto noembed; + + pgrp = getpgrp(); + tcpgrp = tcgetpgrp(tty); + + close(tty); + + if (pgrp == tcpgrp) { /* in foreground of tty */ + argv[0] = argv[2]; + argv[2] = xembed; + } else { +noembed: + argv += 2; + } + + execvp(argv[0], argv); + + perror(argv[0]); /* failed to execute */ + return 1; +} diff --git a/tabbed/xembed.o b/tabbed/xembed.o Binary files differ. diff --git a/wallpapers/0001.jpg b/wallpapers/0001.jpg Binary files differ. diff --git a/wallpapers/0002.jpg b/wallpapers/0002.jpg Binary files differ. diff --git a/wallpapers/0003.jpg b/wallpapers/0003.jpg Binary files differ. diff --git a/wallpapers/0007.jpg b/wallpapers/0007.jpg Binary files differ. diff --git a/wallpapers/0008.jpg b/wallpapers/0008.jpg Binary files differ. diff --git a/wallpapers/0023.jpg b/wallpapers/0023.jpg Binary files differ. diff --git a/wallpapers/0029.jpg b/wallpapers/0029.jpg Binary files differ. diff --git a/wallpapers/0035.jpg b/wallpapers/0035.jpg Binary files differ. diff --git a/wallpapers/0044.jpg b/wallpapers/0044.jpg Binary files differ. diff --git a/wallpapers/0056.jpg b/wallpapers/0056.jpg Binary files differ. diff --git a/wallpapers/0058.jpg b/wallpapers/0058.jpg Binary files differ. diff --git a/wallpapers/0066.jpg b/wallpapers/0066.jpg Binary files differ. diff --git a/wallpapers/0070.jpg b/wallpapers/0070.jpg Binary files differ. diff --git a/wallpapers/0072.jpg b/wallpapers/0072.jpg Binary files differ. diff --git a/wallpapers/0073.jpg b/wallpapers/0073.jpg Binary files differ. diff --git a/wallpapers/0076.jpg b/wallpapers/0076.jpg Binary files differ. diff --git a/wallpapers/0086.jpg b/wallpapers/0086.jpg Binary files differ. diff --git a/wallpapers/0088.jpg b/wallpapers/0088.jpg Binary files differ. diff --git a/wallpapers/0097.jpg b/wallpapers/0097.jpg Binary files differ. diff --git a/wallpapers/0104.jpg b/wallpapers/0104.jpg Binary files differ. diff --git a/wallpapers/0119.jpg b/wallpapers/0119.jpg Binary files differ. diff --git a/wallpapers/0121.jpg b/wallpapers/0121.jpg Binary files differ. diff --git a/wallpapers/0127.jpg b/wallpapers/0127.jpg Binary files differ. diff --git a/wallpapers/0132.jpg b/wallpapers/0132.jpg Binary files differ. diff --git a/wallpapers/0141.jpg b/wallpapers/0141.jpg Binary files differ. diff --git a/wallpapers/0145.jpg b/wallpapers/0145.jpg Binary files differ. diff --git a/wallpapers/0152.jpg b/wallpapers/0152.jpg Binary files differ. diff --git a/wallpapers/0162.jpg b/wallpapers/0162.jpg Binary files differ. diff --git a/wallpapers/0174.jpg b/wallpapers/0174.jpg Binary files differ. diff --git a/wallpapers/0180.jpg b/wallpapers/0180.jpg Binary files differ. diff --git a/wallpapers/0188.jpg b/wallpapers/0188.jpg Binary files differ. diff --git a/wallpapers/0189.jpg b/wallpapers/0189.jpg Binary files differ. diff --git a/wallpapers/0190.jpg b/wallpapers/0190.jpg Binary files differ. diff --git a/wallpapers/0196.jpg b/wallpapers/0196.jpg Binary files differ. diff --git a/wallpapers/0197.jpg b/wallpapers/0197.jpg Binary files differ. diff --git a/wallpapers/0198.jpg b/wallpapers/0198.jpg Binary files differ. diff --git a/wallpapers/0207.jpg b/wallpapers/0207.jpg Binary files differ. diff --git a/wallpapers/0208.jpg b/wallpapers/0208.jpg Binary files differ. diff --git a/wallpapers/0212.jpg b/wallpapers/0212.jpg Binary files differ. diff --git a/wallpapers/0214.jpg b/wallpapers/0214.jpg Binary files differ. diff --git a/wallpapers/0227.jpg b/wallpapers/0227.jpg Binary files differ. diff --git a/wallpapers/0228.jpg b/wallpapers/0228.jpg Binary files differ. diff --git a/wallpapers/0236.jpg b/wallpapers/0236.jpg Binary files differ. diff --git a/wallpapers/0239.jpg b/wallpapers/0239.jpg Binary files differ. diff --git a/wallpapers/0240.jpg b/wallpapers/0240.jpg Binary files differ. diff --git a/wallpapers/0248.jpg b/wallpapers/0248.jpg Binary files differ. diff --git a/wallpapers/0251.jpg b/wallpapers/0251.jpg Binary files differ. diff --git a/wallpapers/0253.jpg b/wallpapers/0253.jpg Binary files differ. diff --git a/wallpapers/0260.jpg b/wallpapers/0260.jpg Binary files differ. diff --git a/wallpapers/0264.jpg b/wallpapers/0264.jpg Binary files differ. diff --git a/wallpapers/0270.jpg b/wallpapers/0270.jpg Binary files differ. diff --git a/wallpapers/0272.jpg b/wallpapers/0272.jpg Binary files differ. diff --git a/wallpapers/0278.jpg b/wallpapers/0278.jpg Binary files differ. diff --git a/wallpapers/0291.jpg b/wallpapers/0291.jpg Binary files differ. diff --git a/wallpapers/0294.jpg b/wallpapers/0294.jpg Binary files differ. diff --git a/wallpapers/0308.jpg b/wallpapers/0308.jpg Binary files differ. diff --git a/wallpapers/21599.jpg b/wallpapers/21599.jpg Binary files differ. diff --git a/wallpapers/6315.jpg b/wallpapers/6315.jpg Binary files differ. diff --git a/wallpapers/632957.jpg b/wallpapers/632957.jpg Binary files differ. diff --git a/wallpapers/68747470733a2f2f692e696d6775722e636f6d2f523435754951762e6a7067.jpg b/wallpapers/68747470733a2f2f692e696d6775722e636f6d2f523435754951762e6a7067.jpg Binary files differ. diff --git a/wallpapers/736461.png b/wallpapers/736461.png Binary files differ. diff --git a/wallpapers/Noragami.png b/wallpapers/Noragami.png Binary files differ. diff --git a/wallpapers/background04.jpg b/wallpapers/background04.jpg Binary files differ. diff --git a/wallpapers/background05.jpg b/wallpapers/background05.jpg Binary files differ. diff --git a/wallpapers/background07.jpg b/wallpapers/background07.jpg Binary files differ. diff --git a/wallpapers/background09.jpg b/wallpapers/background09.jpg Binary files differ. diff --git a/wallpapers/background10.jpg b/wallpapers/background10.jpg Binary files differ. diff --git a/wallpapers/background12.jpg b/wallpapers/background12.jpg Binary files differ. diff --git a/wallpapers/background13.jpg b/wallpapers/background13.jpg Binary files differ. diff --git a/wallpapers/background14.jpg b/wallpapers/background14.jpg Binary files differ. diff --git a/wallpapers/background17.jpg b/wallpapers/background17.jpg Binary files differ. diff --git a/wallpapers/background20.jpg b/wallpapers/background20.jpg Binary files differ. diff --git a/wallpapers/background21.jpg b/wallpapers/background21.jpg Binary files differ. diff --git a/wallpapers/background22.jpg b/wallpapers/background22.jpg Binary files differ. diff --git a/wallpapers/background24.jpg b/wallpapers/background24.jpg Binary files differ. diff --git a/wallpapers/background30.jpg b/wallpapers/background30.jpg Binary files differ. diff --git a/wallpapers/background32.jpg b/wallpapers/background32.jpg Binary files differ. diff --git a/wallpapers/background33.jpg b/wallpapers/background33.jpg Binary files differ. diff --git a/wallpapers/background36.jpg b/wallpapers/background36.jpg Binary files differ. diff --git a/wallpapers/background45.jpg b/wallpapers/background45.jpg Binary files differ. diff --git a/wallpapers/background47.jpg b/wallpapers/background47.jpg Binary files differ. diff --git a/wallpapers/background48.jpg b/wallpapers/background48.jpg Binary files differ. diff --git a/wallpapers/background49.jpg b/wallpapers/background49.jpg Binary files differ. diff --git a/wallpapers/background51.jpg b/wallpapers/background51.jpg Binary files differ. diff --git a/wallpapers/background52.jpg b/wallpapers/background52.jpg Binary files differ. diff --git a/wallpapers/background55.jpg b/wallpapers/background55.jpg Binary files differ. diff --git a/wallpapers/background56.jpg b/wallpapers/background56.jpg Binary files differ. diff --git a/wallpapers/ign_waifu.png b/wallpapers/ign_waifu.png Binary files differ. diff --git a/wallpapers/nGlSFM9.png b/wallpapers/nGlSFM9.png Binary files differ. diff --git a/wallpapers/rocket-7680x4320.png b/wallpapers/rocket-7680x4320.png Binary files differ. diff --git a/wallpapers/valenberg1.png b/wallpapers/valenberg1.png Binary files differ. diff --git a/wallpapers/valenberg2.png b/wallpapers/valenberg2.png Binary files differ. diff --git a/wallpapers/valenberg9.png b/wallpapers/valenberg9.png Binary files differ. diff --git a/wallpapers/wallhaven-6olw9x.jpg b/wallpapers/wallhaven-6olw9x.jpg Binary files differ. diff --git a/wallpapers/wallhaven-g7d933.jpg b/wallpapers/wallhaven-g7d933.jpg Binary files differ. diff --git a/wallpapers/wallhaven-z89dgo.png b/wallpapers/wallhaven-z89dgo.png Binary files differ. diff --git a/wallpapers/wallhaven-z8dg9y.png b/wallpapers/wallhaven-z8dg9y.png Binary files differ. diff --git a/wallpapers/wallpaper.jpg b/wallpapers/wallpaper.jpg Binary files differ. diff --git a/wallpapers/wallpaperflare.com_wallpaper.jpg b/wallpapers/wallpaperflare.com_wallpaper.jpg Binary files differ. diff --git a/wallpapers/wp1809658-4k-pc-wallpapers.jpg b/wallpapers/wp1809658-4k-pc-wallpapers.jpg Binary files differ. diff --git a/wallpapers/wp1856268-shigatsu-wa-kimi-no-uso-wallpapers.jpg b/wallpapers/wp1856268-shigatsu-wa-kimi-no-uso-wallpapers.jpg Binary files differ. diff --git a/wallpapers/wp2673988-dark-anime-hd-wallpapers.jpg b/wallpapers/wp2673988-dark-anime-hd-wallpapers.jpg Binary files differ. diff --git a/wallpapers/wp2695245-4k-pc-wallpapers.jpg b/wallpapers/wp2695245-4k-pc-wallpapers.jpg Binary files differ. diff --git a/wallpapers/wp2821386-dark-anime-hd-wallpapers.jpg b/wallpapers/wp2821386-dark-anime-hd-wallpapers.jpg Binary files differ. diff --git a/wallpapers/xavier-cuenca-w4-3.jpg b/wallpapers/xavier-cuenca-w4-3.jpg Binary files differ.