From 271de5e6b3f5745106cf02a5d01666d7240bcefc Mon Sep 17 00:00:00 2001 From: Klein Date: Thu, 26 Sep 2024 23:40:15 -0400 Subject: [PATCH] initial commit --- README.md | 1 + aarch64dwm/LICENSE | 38 + aarch64dwm/Makefile | 45 + aarch64dwm/README | 48 + aarch64dwm/colors/default.h | 34 + aarch64dwm/colors/space.h | 34 + aarch64dwm/config.def.h | 148 + aarch64dwm/config.def.h.orig | 145 + aarch64dwm/config.def.h.rej | 19 + aarch64dwm/config.h | 34 + aarch64dwm/config.mk | 39 + aarch64dwm/config.mk.orig | 39 + aarch64dwm/drw.c | 525 +++ aarch64dwm/drw.c.orig | 450 +++ aarch64dwm/drw.h | 62 + aarch64dwm/drw.h.orig | 58 + aarch64dwm/drw.o | Bin 0 -> 14120 bytes aarch64dwm/dwm | Bin 0 -> 84008 bytes aarch64dwm/dwm.1 | 186 + aarch64dwm/dwm.c | 2774 +++++++++++++++ aarch64dwm/dwm.c.orig | 2774 +++++++++++++++ aarch64dwm/dwm.c.rej | 20 + aarch64dwm/dwm.o | Bin 0 -> 68176 bytes aarch64dwm/dwm.png | Bin 0 -> 373 bytes aarch64dwm/keys.h | 81 + aarch64dwm/layouts.h | 17 + aarch64dwm/movestack.c | 48 + .../dwm-alwayscenter-20200625-f04cac6.diff | 12 + aarch64dwm/patches/dwm-bar-height-6.2.diff | 25 + .../patches/dwm-centerfirstwindow-6.2.diff | 67 + ...-clientmonoclesymbol-20220417-d93ff48.diff | 41 + .../dwm-fakefullscreen-20210714-138b405.diff | 120 + .../dwm-keychain-20200729-053e3a2.diff | 266 ++ .../dwm-movestack-20211115-a786211.diff | 95 + aarch64dwm/patches/dwm-rainbowtags-6.2.diff | 59 + .../patches/dwm-restartsig-20180523-6.2.diff | 139 + .../patches/dwm-status2d-systray-6.4.diff | 879 +++++ .../dwm-titlecolor-20210815-ed3ab6b4.diff | 47 + aarch64dwm/patches/dwm-winicon-6.3-v2.1.diff | 371 ++ aarch64dwm/transient.c | 42 + aarch64dwm/util.c | 36 + aarch64dwm/util.h | 8 + aarch64dwm/util.o | Bin 0 -> 2256 bytes aarch64statusbar/colorvars.sh | 11 + aarch64statusbar/install.sh | 55 + aarch64statusbar/statusbar.service | 14 + aarch64statusbar/statusbar.sh | 234 ++ dmenu/LICENSE | 30 + dmenu/Makefile | 58 + dmenu/README | 24 + dmenu/arg.h | 49 + dmenu/config.def.h | 28 + dmenu/config.def.h.orig | 27 + dmenu/config.def.h.rej | 10 + dmenu/config.h | 27 + dmenu/config.mk | 32 + dmenu/dmenu | Bin 0 -> 77872 bytes dmenu/dmenu.1 | 199 ++ dmenu/dmenu.c | 838 +++++ dmenu/dmenu.c.orig | 820 +++++ dmenu/dmenu.c.rej | 20 + dmenu/dmenu.o | Bin 0 -> 28232 bytes dmenu/dmenu_path | 13 + dmenu/dmenu_run | 2 + dmenu/drw.c | 451 +++ dmenu/drw.h | 58 + dmenu/drw.o | Bin 0 -> 11280 bytes dmenu/patches/dmenu-border-5.2.diff | 37 + .../dmenu-bordercolor-20230512-0fe460d.diff | 71 + dmenu/patches/dmenu-center-4.8.diff | 56 + dmenu/patches/dmenu-date-5.2.diff | 57 + dmenu/patches/dmenu-grid-4.9.diff | 107 + dmenu/stest | Bin 0 -> 71384 bytes dmenu/stest.1 | 90 + dmenu/stest.c | 109 + dmenu/stest.o | Bin 0 -> 5240 bytes dmenu/util.c | 36 + dmenu/util.h | 9 + dmenu/util.o | Bin 0 -> 2256 bytes pinentry-dmenu/.gitignore | 2 + pinentry-dmenu/LICENSE | 280 ++ pinentry-dmenu/Makefile | 66 + pinentry-dmenu/README.md | 71 + pinentry-dmenu/config.h | 21 + pinentry-dmenu/config.mk | 33 + pinentry-dmenu/drw.c | 421 +++ pinentry-dmenu/drw.h | 57 + pinentry-dmenu/pinentry-dmenu | Bin 0 -> 69248 bytes pinentry-dmenu/pinentry-dmenu.1 | 228 ++ pinentry-dmenu/pinentry-dmenu.c | 803 +++++ pinentry-dmenu/pinentry/AUTHORS | 11 + pinentry-dmenu/pinentry/COPYING | 280 ++ pinentry-dmenu/pinentry/Makefile | 21 + pinentry-dmenu/pinentry/argparse.c | 1607 +++++++++ pinentry-dmenu/pinentry/argparse.h | 203 ++ pinentry-dmenu/pinentry/memory.h | 55 + pinentry-dmenu/pinentry/password-cache.c | 163 + pinentry-dmenu/pinentry/password-cache.h | 29 + pinentry-dmenu/pinentry/pinentry.c | 1308 +++++++ pinentry-dmenu/pinentry/pinentry.h | 280 ++ pinentry-dmenu/pinentry/secmem++.h | 91 + pinentry-dmenu/pinentry/secmem-util.h | 3 + pinentry-dmenu/pinentry/secmem.c | 460 +++ pinentry-dmenu/pinentry/util.c | 150 + pinentry-dmenu/pinentry/util.h | 66 + pinentry-dmenu/test | 43 + pinentry-dmenu/util.c | 35 + pinentry-dmenu/util.h | 8 + slstatus/.gitignore | 2 + slstatus/LICENSE | 41 + slstatus/Makefile | 68 + slstatus/README | 69 + slstatus/arg.h | 33 + slstatus/components/battery.c | 294 ++ slstatus/components/cpu.c | 157 + slstatus/components/datetime.c | 20 + slstatus/components/disk.c | 59 + slstatus/components/entropy.c | 28 + slstatus/components/hostname.c | 17 + slstatus/components/ip.c | 61 + slstatus/components/kernel_release.c | 19 + slstatus/components/keyboard_indicators.c | 50 + slstatus/components/keymap.c | 85 + slstatus/components/load_avg.c | 19 + slstatus/components/netspeeds.c | 129 + slstatus/components/num_files.c | 32 + slstatus/components/ram.c | 212 ++ slstatus/components/run_command.c | 31 + slstatus/components/swap.c | 274 ++ slstatus/components/temperature.c | 73 + slstatus/components/uptime.c | 34 + slstatus/components/user.c | 33 + slstatus/components/volume.c | 219 ++ slstatus/components/wifi.c | 267 ++ slstatus/config.def.h | 91 + slstatus/config.h | 91 + slstatus/config.mk | 22 + slstatus/slstatus.1 | 28 + slstatus/slstatus.c | 132 + slstatus/slstatus.h | 83 + slstatus/util.c | 144 + slstatus/util.h | 16 + st/FAQ | 253 ++ st/LEGACY | 17 + st/LICENSE | 34 + st/Makefile | 51 + st/README | 34 + st/TODO | 28 + st/arg.h | 50 + st/config.def.h | 478 +++ st/config.h | 478 +++ st/config.mk | 36 + st/patches/st-scrollback-0.8.5.diff | 350 ++ .../st-scrollback-mouse-20220127-2c5edf2.diff | 25 + ...back-mouse-altscreen-20220127-2c5edf2.diff | 78 + st/patches/st-scrollback-reflow-0.9.diff | 1466 ++++++++ st/st | Bin 0 -> 157256 bytes st/st.1 | 177 + st/st.c | 3104 +++++++++++++++++ st/st.c.orig | 3099 ++++++++++++++++ st/st.c.rej | 213 ++ st/st.h | 132 + st/st.h.orig | 131 + st/st.info | 243 ++ st/st.o | Bin 0 -> 82624 bytes st/win.h | 41 + st/x.c | 2107 +++++++++++ st/x.o | Bin 0 -> 72040 bytes 168 files changed, 35751 insertions(+) create mode 100644 README.md create mode 100644 aarch64dwm/LICENSE create mode 100644 aarch64dwm/Makefile create mode 100644 aarch64dwm/README create mode 100644 aarch64dwm/colors/default.h create mode 100644 aarch64dwm/colors/space.h create mode 100644 aarch64dwm/config.def.h create mode 100644 aarch64dwm/config.def.h.orig create mode 100644 aarch64dwm/config.def.h.rej create mode 100644 aarch64dwm/config.h create mode 100644 aarch64dwm/config.mk create mode 100644 aarch64dwm/config.mk.orig create mode 100644 aarch64dwm/drw.c create mode 100644 aarch64dwm/drw.c.orig create mode 100644 aarch64dwm/drw.h create mode 100644 aarch64dwm/drw.h.orig create mode 100644 aarch64dwm/drw.o create mode 100755 aarch64dwm/dwm create mode 100644 aarch64dwm/dwm.1 create mode 100644 aarch64dwm/dwm.c create mode 100644 aarch64dwm/dwm.c.orig create mode 100644 aarch64dwm/dwm.c.rej create mode 100644 aarch64dwm/dwm.o create mode 100644 aarch64dwm/dwm.png create mode 100644 aarch64dwm/keys.h create mode 100644 aarch64dwm/layouts.h create mode 100644 aarch64dwm/movestack.c create mode 100644 aarch64dwm/patches/dwm-alwayscenter-20200625-f04cac6.diff create mode 100644 aarch64dwm/patches/dwm-bar-height-6.2.diff create mode 100644 aarch64dwm/patches/dwm-centerfirstwindow-6.2.diff create mode 100644 aarch64dwm/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff create mode 100644 aarch64dwm/patches/dwm-fakefullscreen-20210714-138b405.diff create mode 100644 aarch64dwm/patches/dwm-keychain-20200729-053e3a2.diff create mode 100644 aarch64dwm/patches/dwm-movestack-20211115-a786211.diff create mode 100644 aarch64dwm/patches/dwm-rainbowtags-6.2.diff create mode 100644 aarch64dwm/patches/dwm-restartsig-20180523-6.2.diff create mode 100644 aarch64dwm/patches/dwm-status2d-systray-6.4.diff create mode 100644 aarch64dwm/patches/dwm-titlecolor-20210815-ed3ab6b4.diff create mode 100644 aarch64dwm/patches/dwm-winicon-6.3-v2.1.diff create mode 100644 aarch64dwm/transient.c create mode 100644 aarch64dwm/util.c create mode 100644 aarch64dwm/util.h create mode 100644 aarch64dwm/util.o create mode 100644 aarch64statusbar/colorvars.sh create mode 100755 aarch64statusbar/install.sh create mode 100644 aarch64statusbar/statusbar.service create mode 100755 aarch64statusbar/statusbar.sh create mode 100644 dmenu/LICENSE create mode 100644 dmenu/Makefile create mode 100644 dmenu/README create mode 100644 dmenu/arg.h create mode 100644 dmenu/config.def.h create mode 100644 dmenu/config.def.h.orig create mode 100644 dmenu/config.def.h.rej create mode 100644 dmenu/config.h create mode 100644 dmenu/config.mk create mode 100755 dmenu/dmenu create mode 100644 dmenu/dmenu.1 create mode 100644 dmenu/dmenu.c create mode 100644 dmenu/dmenu.c.orig create mode 100644 dmenu/dmenu.c.rej create mode 100644 dmenu/dmenu.o create mode 100755 dmenu/dmenu_path create mode 100755 dmenu/dmenu_run create mode 100644 dmenu/drw.c create mode 100644 dmenu/drw.h create mode 100644 dmenu/drw.o create mode 100644 dmenu/patches/dmenu-border-5.2.diff create mode 100644 dmenu/patches/dmenu-bordercolor-20230512-0fe460d.diff create mode 100644 dmenu/patches/dmenu-center-4.8.diff create mode 100644 dmenu/patches/dmenu-date-5.2.diff create mode 100644 dmenu/patches/dmenu-grid-4.9.diff create mode 100755 dmenu/stest create mode 100644 dmenu/stest.1 create mode 100644 dmenu/stest.c create mode 100644 dmenu/stest.o create mode 100644 dmenu/util.c create mode 100644 dmenu/util.h create mode 100644 dmenu/util.o create mode 100644 pinentry-dmenu/.gitignore create mode 100644 pinentry-dmenu/LICENSE create mode 100644 pinentry-dmenu/Makefile create mode 100644 pinentry-dmenu/README.md create mode 100644 pinentry-dmenu/config.h create mode 100644 pinentry-dmenu/config.mk create mode 100644 pinentry-dmenu/drw.c create mode 100644 pinentry-dmenu/drw.h create mode 100755 pinentry-dmenu/pinentry-dmenu create mode 100644 pinentry-dmenu/pinentry-dmenu.1 create mode 100644 pinentry-dmenu/pinentry-dmenu.c create mode 100644 pinentry-dmenu/pinentry/AUTHORS create mode 100644 pinentry-dmenu/pinentry/COPYING create mode 100644 pinentry-dmenu/pinentry/Makefile create mode 100644 pinentry-dmenu/pinentry/argparse.c create mode 100644 pinentry-dmenu/pinentry/argparse.h create mode 100644 pinentry-dmenu/pinentry/memory.h create mode 100644 pinentry-dmenu/pinentry/password-cache.c create mode 100644 pinentry-dmenu/pinentry/password-cache.h create mode 100644 pinentry-dmenu/pinentry/pinentry.c create mode 100644 pinentry-dmenu/pinentry/pinentry.h create mode 100644 pinentry-dmenu/pinentry/secmem++.h create mode 100644 pinentry-dmenu/pinentry/secmem-util.h create mode 100644 pinentry-dmenu/pinentry/secmem.c create mode 100644 pinentry-dmenu/pinentry/util.c create mode 100644 pinentry-dmenu/pinentry/util.h create mode 100755 pinentry-dmenu/test create mode 100644 pinentry-dmenu/util.c create mode 100644 pinentry-dmenu/util.h create mode 100644 slstatus/.gitignore create mode 100644 slstatus/LICENSE create mode 100644 slstatus/Makefile create mode 100644 slstatus/README create mode 100644 slstatus/arg.h create mode 100644 slstatus/components/battery.c create mode 100644 slstatus/components/cpu.c create mode 100644 slstatus/components/datetime.c create mode 100644 slstatus/components/disk.c create mode 100644 slstatus/components/entropy.c create mode 100644 slstatus/components/hostname.c create mode 100644 slstatus/components/ip.c create mode 100644 slstatus/components/kernel_release.c create mode 100644 slstatus/components/keyboard_indicators.c create mode 100644 slstatus/components/keymap.c create mode 100644 slstatus/components/load_avg.c create mode 100644 slstatus/components/netspeeds.c create mode 100644 slstatus/components/num_files.c create mode 100644 slstatus/components/ram.c create mode 100644 slstatus/components/run_command.c create mode 100644 slstatus/components/swap.c create mode 100644 slstatus/components/temperature.c create mode 100644 slstatus/components/uptime.c create mode 100644 slstatus/components/user.c create mode 100644 slstatus/components/volume.c create mode 100644 slstatus/components/wifi.c create mode 100644 slstatus/config.def.h create mode 100644 slstatus/config.h create mode 100644 slstatus/config.mk create mode 100644 slstatus/slstatus.1 create mode 100644 slstatus/slstatus.c create mode 100644 slstatus/slstatus.h create mode 100644 slstatus/util.c create mode 100644 slstatus/util.h create mode 100644 st/FAQ create mode 100644 st/LEGACY create mode 100644 st/LICENSE create mode 100644 st/Makefile create mode 100644 st/README create mode 100644 st/TODO create mode 100644 st/arg.h create mode 100644 st/config.def.h create mode 100644 st/config.h create mode 100644 st/config.mk create mode 100644 st/patches/st-scrollback-0.8.5.diff create mode 100644 st/patches/st-scrollback-mouse-20220127-2c5edf2.diff create mode 100644 st/patches/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff create mode 100644 st/patches/st-scrollback-reflow-0.9.diff create mode 100755 st/st create mode 100644 st/st.1 create mode 100644 st/st.c create mode 100644 st/st.c.orig create mode 100644 st/st.c.rej create mode 100644 st/st.h create mode 100644 st/st.h.orig create mode 100644 st/st.info create mode 100644 st/st.o create mode 100644 st/win.h create mode 100644 st/x.c create mode 100644 st/x.o diff --git a/README.md b/README.md new file mode 100644 index 0000000..6566ae4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# aarch64DWM diff --git a/aarch64dwm/LICENSE b/aarch64dwm/LICENSE new file mode 100644 index 0000000..995172f --- /dev/null +++ b/aarch64dwm/LICENSE @@ -0,0 +1,38 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich +© 2020-2022 Chris Down + +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/aarch64dwm/Makefile b/aarch64dwm/Makefile new file mode 100644 index 0000000..ffa69b4 --- /dev/null +++ b/aarch64dwm/Makefile @@ -0,0 +1,45 @@ +# 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: dwm + +.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 clean dist install uninstall diff --git a/aarch64dwm/README b/aarch64dwm/README new file mode 100644 index 0000000..95d4fd0 --- /dev/null +++ b/aarch64dwm/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/aarch64dwm/colors/default.h b/aarch64dwm/colors/default.h new file mode 100644 index 0000000..009acc3 --- /dev/null +++ b/aarch64dwm/colors/default.h @@ -0,0 +1,34 @@ +static const char col_gray1[] = "#ffffff"; +static const char col_gray3[] = "#ffffff"; +static const char col_gray4[] = "#ffffff"; +static const char col_gray2[] = "#ffffff"; +static const char col_cyan[] = "#ffffff"; + +static const char tag1[] = "#bf8427"; +static const char tag2[] = "#5865F2"; +static const char tag3[] = "#FF5500"; +static const char tag4[] = "#FF609A"; +static const char tag5[] = "#842291"; +static const char tag6[] = "#900C3F"; +static const char tag7[] = "#67AFA5"; +static const char tag8[] = "#1DB954"; +static const char tag9[] = "#FFFFFF"; + +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_cyan }, + [SchemeSel] = { col_gray4, col_cyan, col_gray2 }, + [SchemeTitle] = { col_gray4, col_cyan, col_cyan }, +}; + +static const char *tagsel[][2] = { + { tag1, col_gray1 }, + { tag2, col_gray1 }, + { tag3, col_gray1 }, + { tag4, col_gray1 }, + { tag5, col_gray1 }, + { tag6, col_gray1 }, + { tag7, col_gray1 }, + { tag8, col_gray1 }, + { tag9, col_gray1 }, +}; diff --git a/aarch64dwm/colors/space.h b/aarch64dwm/colors/space.h new file mode 100644 index 0000000..7972efe --- /dev/null +++ b/aarch64dwm/colors/space.h @@ -0,0 +1,34 @@ +static const char col_gray1[] = "#0f161e"; +static const char col_gray3[] = "#fff7d0"; +static const char col_gray4[] = "#FF609A"; +static const char col_gray2[] = "#282A2E"; +static const char col_cyan[] = "#0f161e"; + +static const char tag1[] = "#bf8427"; +static const char tag2[] = "#5865F2"; +static const char tag3[] = "#FF5500"; +static const char tag4[] = "#FF609A"; +static const char tag5[] = "#842291"; +static const char tag6[] = "#900C3F"; +static const char tag7[] = "#67AFA5"; +static const char tag8[] = "#1DB954"; +static const char tag9[] = "#FFFFFF"; + +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_cyan }, + [SchemeSel] = { col_gray4, col_cyan, col_gray2 }, + [SchemeTitle] = { col_gray4, col_cyan, col_cyan }, +}; + +static const char *tagsel[][2] = { + { tag1, col_gray1 }, + { tag2, col_gray1 }, + { tag3, col_gray1 }, + { tag4, col_gray1 }, + { tag5, col_gray1 }, + { tag6, col_gray1 }, + { tag7, col_gray1 }, + { tag8, col_gray1 }, + { tag9, col_gray1 }, +}; diff --git a/aarch64dwm/config.def.h b/aarch64dwm/config.def.h new file mode 100644 index 0000000..07987f0 --- /dev/null +++ b/aarch64dwm/config.def.h @@ -0,0 +1,148 @@ +/* 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 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 int user_bh = 0; /* 0 means that dwm will calculate bar height, >= 1 means dwm will user_bh as bar height */ +#define ICONSIZE 16 /*icon size*/ +#define ICONSPACING 5 /* Space between icn and title */ +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 }, + [SchemeTitle] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const char *tagsel[][2] = { + { "#ffffff", "#ff0000" }, + { "#ffffff", "#ff7f00" }, + { "#000000", "#ffff00" }, + { "#000000", "#00ff00" }, + { "#ffffff", "#0000ff" }, + { "#ffffff", "#4b0082" }, + { "#ffffff", "#9400d3" }, + { "#000000", "#ffffff" }, + { "#ffffff", "#000000" }, +}; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating CenterThisWindow? monitor */ + { "st", NULL, NULL, 0, 0, 1, -1 }, + { "Gimp", NULL, NULL, 0, 1, 0, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, 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 = 0; /* 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 }, +}; + +/* custom symbols for nr. of clients in monocle layout */ +/* when clients >= LENGTH(monocles), uses the last element */ +static const char *monocles[] = { "[1]", "[2]", "[3]", "[4]", "[5]", "[6]", "[7]", "[8]", "[9]", "[9+]" }; + +/* key definitions */ +#define MODKEY Mod1Mask +#define TAGKEYS(CHAIN,KEY,TAG) \ + { MODKEY, CHAIN, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, CHAIN, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, CHAIN, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, CHAIN, 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 }; + +#include "movestack.c" +static const Key keys[] = { + /* modifier chain key key function argument */ + { MODKEY, -1, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, -1, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, -1, XK_b, togglebar, {0} }, + { MODKEY, -1, XK_j, focusstack, {.i = +1 } }, + { MODKEY, -1, XK_k, focusstack, {.i = -1 } }, + { MODKEY, -1, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, -1, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, -1, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, -1, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, -1, XK_x, movestack, {.i = +1 }, + { MODKEY, -1, XK_z, movestack, {.i = -1 }, + { MODKEY, -1, XK_Return, zoom, {0} }, + { MODKEY, -1, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, -1, XK_c, killclient, {0} }, + { MODKEY, -1, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, -1, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, -1, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, -1, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, -1, XK_space, togglefloating, {0} }, + { MODKEY, -1, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, -1, XK_0, tag, {.ui = ~0 } }, + { MODKEY, -1, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, -1, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, -1, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, -1, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( -1, XK_1, 0) + TAGKEYS( -1, XK_2, 1) + TAGKEYS( -1, XK_3, 2) + TAGKEYS( -1, XK_4, 3) + TAGKEYS( -1, XK_5, 4) + TAGKEYS( -1, XK_6, 5) + TAGKEYS( -1, XK_7, 6) + TAGKEYS( -1, XK_8, 7) + TAGKEYS( -1, XK_9, 8) + { MODKEY|ShiftMask, -1, XK_q, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, -1, XK_q, quit, {1} }, + { MODKEY, XK_a, XK_d, spawn, {.v = dmenucmd } }, + { MODKEY, XK_a, XK_t, spawn, {.v = termcmd } }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const 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/aarch64dwm/config.def.h.orig b/aarch64dwm/config.def.h.orig new file mode 100644 index 0000000..a5a4a92 --- /dev/null +++ b/aarch64dwm/config.def.h.orig @@ -0,0 +1,145 @@ +/* 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 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 int user_bh = 0; /* 0 means that dwm will calculate bar height, >= 1 means dwm will user_bh as bar height */ +#define ICONSIZE 16 /*icon size*/ +#define ICONSPACING 5 /* Space between icn and title */ +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 }, + [SchemeTitle] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const char *tagsel[][2] = { + { "#ffffff", "#ff0000" }, + { "#ffffff", "#ff7f00" }, + { "#000000", "#ffff00" }, + { "#000000", "#00ff00" }, + { "#ffffff", "#0000ff" }, + { "#ffffff", "#4b0082" }, + { "#ffffff", "#9400d3" }, + { "#000000", "#ffffff" }, + { "#ffffff", "#000000" }, +}; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating CenterThisWindow? monitor */ + { "st", NULL, NULL, 0, 0, 1, -1 }, + { "Gimp", NULL, NULL, 0, 1, 0, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, 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 = 0; /* 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 }, +}; + +/* custom symbols for nr. of clients in monocle layout */ +/* when clients >= LENGTH(monocles), uses the last element */ +static const char *monocles[] = { "[1]", "[2]", "[3]", "[4]", "[5]", "[6]", "[7]", "[8]", "[9]", "[9+]" }; + +/* key definitions */ +#define MODKEY Mod1Mask +#define TAGKEYS(CHAIN,KEY,TAG) \ + { MODKEY, CHAIN, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, CHAIN, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, CHAIN, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, CHAIN, 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 const Key keys[] = { + /* modifier chain key key function argument */ + { MODKEY, -1, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, -1, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, -1, XK_b, togglebar, {0} }, + { MODKEY, -1, XK_j, focusstack, {.i = +1 } }, + { MODKEY, -1, XK_k, focusstack, {.i = -1 } }, + { MODKEY, -1, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, -1, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, -1, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, -1, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, -1, XK_Return, zoom, {0} }, + { MODKEY, -1, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, -1, XK_c, killclient, {0} }, + { MODKEY, -1, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, -1, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, -1, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, -1, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, -1, XK_space, togglefloating, {0} }, + { MODKEY, -1, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, -1, XK_0, tag, {.ui = ~0 } }, + { MODKEY, -1, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, -1, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, -1, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, -1, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( -1, XK_1, 0) + TAGKEYS( -1, XK_2, 1) + TAGKEYS( -1, XK_3, 2) + TAGKEYS( -1, XK_4, 3) + TAGKEYS( -1, XK_5, 4) + TAGKEYS( -1, XK_6, 5) + TAGKEYS( -1, XK_7, 6) + TAGKEYS( -1, XK_8, 7) + TAGKEYS( -1, XK_9, 8) + { MODKEY|ShiftMask, -1, XK_q, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, -1, XK_q, quit, {1} }, + { MODKEY, XK_a, XK_d, spawn, {.v = dmenucmd } }, + { MODKEY, XK_a, XK_t, spawn, {.v = termcmd } }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const 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/aarch64dwm/config.def.h.rej b/aarch64dwm/config.def.h.rej new file mode 100644 index 0000000..f117a17 --- /dev/null +++ b/aarch64dwm/config.def.h.rej @@ -0,0 +1,19 @@ +--- config.def.h ++++ config.def.h +@@ -60,6 +60,7 @@ 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 }; + ++#include "movestack.c" + static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, +@@ -71,6 +72,8 @@ static Key keys[] = { + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, ++ { MODKEY|ShiftMask, XK_j, movestack, {.i = +1 } }, ++ { MODKEY|ShiftMask, XK_k, movestack, {.i = -1 } }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, diff --git a/aarch64dwm/config.h b/aarch64dwm/config.h new file mode 100644 index 0000000..a9fb7bd --- /dev/null +++ b/aarch64dwm/config.h @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ + +#include "colors/space.h" +#include "keys.h" + +#define ICONSIZE 16 /* icon size */ +#define ICONSPACING 5 /* Space between icon and title */ + +/* appearance */ +static const unsigned int borderpx = 2; /* border pixel of 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: 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 int user_bh = 0; /* 0 means that dwm will calculate bar height, >= 1 means dwm will user_bh as bar height */ +static const char *fonts[] = { "monospace:size=9", "DroidSansMono Nerd Font:size=9", "Noto Color Emoji:size=9", "Cairo:size=9" }; + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "\uf17c", "\uf1d8", "\uf0ac", "\uf109", "\uf09b", "\uf02d", "\uf044", "\uf1bc", "\uf120" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating CenterThisWindow? monitor */ + { "st", NULL, NULL, 0, 0, 1, -1 }, + { "Gimp", NULL, NULL, 0, 1, 0, -1 }, +}; diff --git a/aarch64dwm/config.mk b/aarch64dwm/config.mk new file mode 100644 index 0000000..6e00981 --- /dev/null +++ b/aarch64dwm/config.mk @@ -0,0 +1,39 @@ +# dwm version +VERSION = 6.5 + +# 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 +#MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = -I${X11INC} -I${FREETYPEINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2 + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -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/aarch64dwm/config.mk.orig b/aarch64dwm/config.mk.orig new file mode 100644 index 0000000..8efca9a --- /dev/null +++ b/aarch64dwm/config.mk.orig @@ -0,0 +1,39 @@ +# dwm version +VERSION = 6.5 + +# 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 +#MANPREFIX = ${PREFIX}/man + +# 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=700L -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/aarch64dwm/drw.c b/aarch64dwm/drw.c new file mode 100644 index 0000000..74a9fcc --- /dev/null +++ b/aarch64dwm/drw.c @@ -0,0 +1,525 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#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->picture = XRenderCreatePicture(dpy, drw->drawable, XRenderFindVisualFormat(dpy, DefaultVisual(dpy, screen)), 0, NULL); + 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->picture) + XRenderFreePicture(drw->dpy, drw->picture); + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); + drw->picture = XRenderCreatePicture(drw->dpy, drw->drawable, XRenderFindVisualFormat(drw->dpy, DefaultVisual(drw->dpy, drw->screen)), 0, NULL); +} + +void +drw_free(Drw *drw) +{ + XRenderFreePicture(drw->dpy, drw->picture); + 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."); + } + + 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; +} + +Picture +drw_picture_create_resized(Drw *drw, char *src, unsigned int srcw, unsigned int srch, unsigned int dstw, unsigned int dsth) { + Pixmap pm; + Picture pic; + GC gc; + + if (srcw <= (dstw << 1u) && srch <= (dsth << 1u)) { + XImage img = { + srcw, srch, 0, ZPixmap, src, + ImageByteOrder(drw->dpy), BitmapUnit(drw->dpy), BitmapBitOrder(drw->dpy), 32, + 32, 0, 32, + 0, 0, 0 + }; + XInitImage(&img); + + pm = XCreatePixmap(drw->dpy, drw->root, srcw, srch, 32); + gc = XCreateGC(drw->dpy, pm, 0, NULL); + XPutImage(drw->dpy, pm, gc, &img, 0, 0, 0, 0, srcw, srch); + XFreeGC(drw->dpy, gc); + + pic = XRenderCreatePicture(drw->dpy, pm, XRenderFindStandardFormat(drw->dpy, PictStandardARGB32), 0, NULL); + XFreePixmap(drw->dpy, pm); + + XRenderSetPictureFilter(drw->dpy, pic, FilterBilinear, NULL, 0); + XTransform xf; + xf.matrix[0][0] = (srcw << 16u) / dstw; xf.matrix[0][1] = 0; xf.matrix[0][2] = 0; + xf.matrix[1][0] = 0; xf.matrix[1][1] = (srch << 16u) / dsth; xf.matrix[1][2] = 0; + xf.matrix[2][0] = 0; xf.matrix[2][1] = 0; xf.matrix[2][2] = 65536; + XRenderSetPictureTransform(drw->dpy, pic, &xf); + } else { + Imlib_Image origin = imlib_create_image_using_data(srcw, srch, (DATA32 *)src); + if (!origin) return None; + imlib_context_set_image(origin); + imlib_image_set_has_alpha(1); + Imlib_Image scaled = imlib_create_cropped_scaled_image(0, 0, srcw, srch, dstw, dsth); + imlib_free_image_and_decache(); + if (!scaled) return None; + imlib_context_set_image(scaled); + imlib_image_set_has_alpha(1); + + XImage img = { + dstw, dsth, 0, ZPixmap, (char *)imlib_image_get_data_for_reading_only(), + ImageByteOrder(drw->dpy), BitmapUnit(drw->dpy), BitmapBitOrder(drw->dpy), 32, + 32, 0, 32, + 0, 0, 0 + }; + XInitImage(&img); + + pm = XCreatePixmap(drw->dpy, drw->root, dstw, dsth, 32); + gc = XCreateGC(drw->dpy, pm, 0, NULL); + XPutImage(drw->dpy, pm, gc, &img, 0, 0, 0, 0, dstw, dsth); + imlib_free_image_and_decache(); + XFreeGC(drw->dpy, gc); + + pic = XRenderCreatePicture(drw->dpy, pm, XRenderFindStandardFormat(drw->dpy, PictStandardARGB32), 0, NULL); + XFreePixmap(drw->dpy, pm); + } + + return pic; +} + +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) +{ + int i, ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + 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, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } 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; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = 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) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + 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; + + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + + 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); + + 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); + nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic) +{ + if (!drw) + return; + XRenderComposite(drw->dpy, PictOpOver, pic, None, drw->picture, 0, 0, 0, 0, x, y, w, h); +} + +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); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +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/aarch64dwm/drw.c.orig b/aarch64dwm/drw.c.orig new file mode 100644 index 0000000..a58a2b4 --- /dev/null +++ b/aarch64dwm/drw.c.orig @@ -0,0 +1,450 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#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."); + } + + 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) +{ + int i, ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + 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, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } 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; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = 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) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + 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; + + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + + 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); + + 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); + nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: + 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); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +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/aarch64dwm/drw.h b/aarch64dwm/drw.h new file mode 100644 index 0000000..e42eead --- /dev/null +++ b/aarch64dwm/drw.h @@ -0,0 +1,62 @@ +/* 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; + Picture picture; + 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); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +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); + +Picture drw_picture_create_resized(Drw *drw, char *src, unsigned int src_w, unsigned int src_h, unsigned int dst_w, unsigned int dst_h); + +/* 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); +void drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/aarch64dwm/drw.h.orig b/aarch64dwm/drw.h.orig new file mode 100644 index 0000000..6471431 --- /dev/null +++ b/aarch64dwm/drw.h.orig @@ -0,0 +1,58 @@ +/* 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); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +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/aarch64dwm/drw.o b/aarch64dwm/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..e38f800e0adb59c364b86dd96242672ffe1b7c7d GIT binary patch literal 14120 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

;r{kvXaBk4S)VmPvCR-sUXa- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include + +#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) + +#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, SchemeTitle }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMIcon, 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, hintsvalid; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow; + unsigned int icw, ich; Picture icon; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym chain; + 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 */ + 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 CenterThisWindow; + 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 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 Picture geticonprop(Window w, unsigned int *icw, unsigned int *ich); +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 *nexttiled(Client *c); +static void pop(Client *c); +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 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 sighup(int unused); +static void sigterm(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 *m); +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 freeicon(Client *c); +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 updateicon(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; /* bar height */ +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 restart = 0; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Clr **tagscheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; +static KeySym keychain = -1; + +/* 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->CenterThisWindow = 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->CenterThisWindow = r->CenterThisWindow; + 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) { + if (!c->hintsvalid) + updatesizehints(c); + /* 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 + TEXTW(selmon->ltsymbol)) + 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]); + free(scheme); + 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 */)); + } 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; + 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) { + 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->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 = 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, (m->tagset[m->seltags] & 1 << i ? tagscheme[i] : scheme[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 = 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 ? SchemeTitle : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2 + (m->sel->icon ? m->sel->icw + ICONSPACING : 0), m->sel->name, 0); + if (m->sel->icon) drw_pic(drw, x + lrpad / 2, (bh - m->sel->ich) / 2, m->sel->icw, m->sel->ich, m->sel->icon); + 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; +} + +static uint32_t prealpha(uint32_t p) { + uint8_t a = p >> 24u; + uint32_t rb = (a * (p & 0xFF00FFu)) >> 8u; + uint32_t g = (a * (p & 0x00FF00u)) >> 8u; + return (rb & 0xFF00FFu) | (g & 0x00FF00u) | (a << 24u); +} + +Picture +geticonprop(Window win, unsigned int *picw, unsigned int *pich) +{ + int format; + unsigned long n, extra, *p = NULL; + Atom real; + + if (XGetWindowProperty(dpy, win, netatom[NetWMIcon], 0L, LONG_MAX, False, AnyPropertyType, + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return None; + if (n == 0 || format != 32) { XFree(p); return None; } + + unsigned long *bstp = NULL; + uint32_t w, h, sz; + { + unsigned long *i; const unsigned long *end = p + n; + uint32_t bstd = UINT32_MAX, d, m; + for (i = p; i < end - 1; i += sz) { + if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; } + if ((sz = w * h) > end - i) break; + if ((m = w > h ? w : h) >= ICONSIZE && (d = m - ICONSIZE) < bstd) { bstd = d; bstp = i; } + } + if (!bstp) { + for (i = p; i < end - 1; i += sz) { + if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; } + if ((sz = w * h) > end - i) break; + if ((d = ICONSIZE - (w > h ? w : h)) < bstd) { bstd = d; bstp = i; } + } + } + if (!bstp) { XFree(p); return None; } + } + + if ((w = *(bstp - 2)) == 0 || (h = *(bstp - 1)) == 0) { XFree(p); return None; } + + uint32_t icw, ich; + if (w <= h) { + ich = ICONSIZE; icw = w * ICONSIZE / h; + if (icw == 0) icw = 1; + } + else { + icw = ICONSIZE; ich = h * ICONSIZE / w; + if (ich == 0) ich = 1; + } + *picw = icw; *pich = ich; + + uint32_t i, *bstp32 = (uint32_t *)bstp; + for (sz = w * h, i = 0; i < sz; ++i) bstp32[i] = prealpha(bstp[i]); + + Picture ret = drw_picture_create_resized(drw, (char *)bstp, w, h, icw, ich); + XFree(p); + + return ret; +} + +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; + KeyCode chain; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { + if (keys[i].chain != -1 && + ((chain = XKeysymToKeycode(dpy, keys[i].chain)))) + code = chain; + 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, j; + KeySym keysym; + XKeyEvent *ev; + int current = 0; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) { + if (keysym == keys[i].keysym && keys[i].chain == -1 + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); + else if (keysym == keys[i].chain && keychain == -1 + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) { + current = 1; + keychain = keysym; + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, AnyKey, 0 | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } else if (!current && keysym == keys[i].keysym + && keychain != -1 + && keys[i].chain == keychain + && keys[i].func) + keys[i].func(&(keys[i].arg)); + } + if (!current) { + keychain = -1; + grabkeys(); + } +} + +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; + + updateicon(c); + 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->wx + c->mon->ww) + c->x = c->mon->wx + c->mon->ww - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->wy + c->mon->wh) + c->y = c->mon->wy + c->mon->wh - HEIGHT(c); + c->x = MAX(c->x, c->mon->wx); + c->y = MAX(c->y, c->mon->wy); + 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); + c->x = c->mon->mx + (c->mon->mw - WIDTH(c)) / 2; + c->y = c->mon->my + (c->mon->mh - HEIGHT(c)) / 2; + 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; + + 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) || 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, "%s", monocles[MIN(n, LENGTH(monocles)) - 1]); + 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; + 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 ((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: + c->hintsvalid = 0; + 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); + } + else if (ev->atom == netatom[NetWMIcon]) { + updateicon(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +quit(const Arg *arg) +{ + if(arg->i) restart = 1; + 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; + 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 */ + 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(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; + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + } +} + +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; + //struct sigaction sa; + + /* do not transform children into zombies when they terminate */ + //sigemptyset(&sa.sa_mask); + //sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; + //sa.sa_handler = SIG_IGN; + //sigaction(SIGCHLD, &sa, NULL); + + /* clean up any zombies (inherited from .xinitrc etc) immediately */ + //while (waitpid(-1, NULL, WNOHANG) > 0); + + sigchld(0); + + signal(SIGHUP, sighup); + signal(SIGTERM, sigterm); + + /* 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 = user_bh ? user_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[NetWMIcon] = XInternAtom(dpy, "_NET_WM_ICON", 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 */ + if (LENGTH(tags) > LENGTH(tagsel)) { + die("too few color schemes for the tags"); + } + 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); + } + tagscheme = ecalloc(LENGTH(tagsel), sizeof(Clr *)); + for (i = 0; i < LENGTH(tagsel); i++) { + tagscheme[i] = drw_scm_create(drw, tagsel[i], 2); + } + /* 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) + 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 +sighup(int unused) +{ + Arg a = {.i = 1}; + quit(&a); +} + +void +sigterm(int unused) +{ + Arg a = {.i = 0}; + quit(&a); +} + +void +spawn(const Arg *arg) +{ + struct sigaction sa; + + if (arg->v == dmenucmd) + dmenumon[0] = '0' + selmon->num; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]); + } +} + +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); + } + + if (n == 1 && selmon->sel->CenterThisWindow) + resizeclient(selmon->sel, + (selmon->mw - selmon->mw * 0.5) / 2, + (selmon->mh - selmon->mh * 0.5) / 2, + selmon->mw * 0.5, + selmon->mh * 0.5); +} + +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; + 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 +freeicon(Client *c) +{ + if (c->icon) { + XRenderFreePicture(dpy, c->icon); + c->icon = None; + } +} + +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); + freeicon(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XSelectInput(dpy, c->win, NoEventMask); + 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(void) +{ + 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; + + /* new monitors if nn > n */ + for (i = n; i < nn; 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); + } + /* removed monitors if n > nn */ + 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); + c->hintsvalid = 1; +} + +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 +updateicon(Client *c) +{ + freeicon(c); + c->icon = geticonprop(c->win, &c->icw, &c->ich); +} + +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 || !c || c->isfloating) + return; + if (c == nexttiled(selmon->clients) && !(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(); + if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/aarch64dwm/dwm.c.orig b/aarch64dwm/dwm.c.orig new file mode 100644 index 0000000..a4908b3 --- /dev/null +++ b/aarch64dwm/dwm.c.orig @@ -0,0 +1,2774 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include + +#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) + +#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, NetWMIcon, 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, hintsvalid; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow; + unsigned int icw, ich; Picture icon; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym chain; + 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 */ + 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 CenterThisWindow; + 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 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 Picture geticonprop(Window w, unsigned int *icw, unsigned int *ich); +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 *nexttiled(Client *c); +static void pop(Client *c); +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 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 sighup(int unused); +static void sigterm(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 *m); +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 freeicon(Client *c); +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 updateicon(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; /* bar height */ +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 restart = 0; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Clr **tagscheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; +static KeySym keychain = -1; + +/* 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->CenterThisWindow = 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->CenterThisWindow = r->CenterThisWindow; + 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) { + if (!c->hintsvalid) + updatesizehints(c); + /* 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 + TEXTW(selmon->ltsymbol)) + 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]); + free(scheme); + 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 */)); + } 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; + 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) { + 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->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 = 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, (m->tagset[m->seltags] & 1 << i ? tagscheme[i] : scheme[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 = 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->icon ? m->sel->icw + ICONSPACING : 0), m->sel->name, 0); + if (m->sel->icon) drw_pic(drw, x + lrpad / 2, (bh - m->sel->ich) / 2, m->sel->icw, m->sel->ich, m->sel->icon); + 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; +} + +static uint32_t prealpha(uint32_t p) { + uint8_t a = p >> 24u; + uint32_t rb = (a * (p & 0xFF00FFu)) >> 8u; + uint32_t g = (a * (p & 0x00FF00u)) >> 8u; + return (rb & 0xFF00FFu) | (g & 0x00FF00u) | (a << 24u); +} + +Picture +geticonprop(Window win, unsigned int *picw, unsigned int *pich) +{ + int format; + unsigned long n, extra, *p = NULL; + Atom real; + + if (XGetWindowProperty(dpy, win, netatom[NetWMIcon], 0L, LONG_MAX, False, AnyPropertyType, + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return None; + if (n == 0 || format != 32) { XFree(p); return None; } + + unsigned long *bstp = NULL; + uint32_t w, h, sz; + { + unsigned long *i; const unsigned long *end = p + n; + uint32_t bstd = UINT32_MAX, d, m; + for (i = p; i < end - 1; i += sz) { + if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; } + if ((sz = w * h) > end - i) break; + if ((m = w > h ? w : h) >= ICONSIZE && (d = m - ICONSIZE) < bstd) { bstd = d; bstp = i; } + } + if (!bstp) { + for (i = p; i < end - 1; i += sz) { + if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; } + if ((sz = w * h) > end - i) break; + if ((d = ICONSIZE - (w > h ? w : h)) < bstd) { bstd = d; bstp = i; } + } + } + if (!bstp) { XFree(p); return None; } + } + + if ((w = *(bstp - 2)) == 0 || (h = *(bstp - 1)) == 0) { XFree(p); return None; } + + uint32_t icw, ich; + if (w <= h) { + ich = ICONSIZE; icw = w * ICONSIZE / h; + if (icw == 0) icw = 1; + } + else { + icw = ICONSIZE; ich = h * ICONSIZE / w; + if (ich == 0) ich = 1; + } + *picw = icw; *pich = ich; + + uint32_t i, *bstp32 = (uint32_t *)bstp; + for (sz = w * h, i = 0; i < sz; ++i) bstp32[i] = prealpha(bstp[i]); + + Picture ret = drw_picture_create_resized(drw, (char *)bstp, w, h, icw, ich); + XFree(p); + + return ret; +} + +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; + KeyCode chain; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { + if (keys[i].chain != -1 && + ((chain = XKeysymToKeycode(dpy, keys[i].chain)))) + code = chain; + 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, j; + KeySym keysym; + XKeyEvent *ev; + int current = 0; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) { + if (keysym == keys[i].keysym && keys[i].chain == -1 + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); + else if (keysym == keys[i].chain && keychain == -1 + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) { + current = 1; + keychain = keysym; + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, AnyKey, 0 | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } else if (!current && keysym == keys[i].keysym + && keychain != -1 + && keys[i].chain == keychain + && keys[i].func) + keys[i].func(&(keys[i].arg)); + } + if (!current) { + keychain = -1; + grabkeys(); + } +} + +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; + + updateicon(c); + 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->wx + c->mon->ww) + c->x = c->mon->wx + c->mon->ww - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->wy + c->mon->wh) + c->y = c->mon->wy + c->mon->wh - HEIGHT(c); + c->x = MAX(c->x, c->mon->wx); + c->y = MAX(c->y, c->mon->wy); + 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); + c->x = c->mon->mx + (c->mon->mw - WIDTH(c)) / 2; + c->y = c->mon->my + (c->mon->mh - HEIGHT(c)) / 2; + 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; + + 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) || 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, "%s", monocles[MIN(n, LENGTH(monocles)) - 1]); + 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; + 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 ((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: + c->hintsvalid = 0; + 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); + } + else if (ev->atom == netatom[NetWMIcon]) { + updateicon(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +quit(const Arg *arg) +{ + if(arg->i) restart = 1; + 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; + 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 */ + 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(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; + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + } +} + +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; + //struct sigaction sa; + + /* do not transform children into zombies when they terminate */ + //sigemptyset(&sa.sa_mask); + //sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; + //sa.sa_handler = SIG_IGN; + //sigaction(SIGCHLD, &sa, NULL); + + /* clean up any zombies (inherited from .xinitrc etc) immediately */ + //while (waitpid(-1, NULL, WNOHANG) > 0); + + sigchld(0); + + signal(SIGHUP, sighup); + signal(SIGTERM, sigterm); + + /* 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 = user_bh ? user_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[NetWMIcon] = XInternAtom(dpy, "_NET_WM_ICON", 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 */ + if (LENGTH(tags) > LENGTH(tagsel)) { + die("too few color schemes for the tags"); + } + 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); + } + tagscheme = ecalloc(LENGTH(tagsel), sizeof(Clr *)); + for (i = 0; i < LENGTH(tagsel); i++) { + tagscheme[i] = drw_scm_create(drw, tagsel[i], 2); + } + /* 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) + 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 +sighup(int unused) +{ + Arg a = {.i = 1}; + quit(&a); +} + +void +sigterm(int unused) +{ + Arg a = {.i = 0}; + quit(&a); +} + +void +spawn(const Arg *arg) +{ + struct sigaction sa; + + if (arg->v == dmenucmd) + dmenumon[0] = '0' + selmon->num; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]); + } +} + +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); + } + + if (n == 1 && selmon->sel->CenterThisWindow) + resizeclient(selmon->sel, + (selmon->mw - selmon->mw * 0.5) / 2, + (selmon->mh - selmon->mh * 0.5) / 2, + selmon->mw * 0.5, + selmon->mh * 0.5); +} + +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; + 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 +freeicon(Client *c) +{ + if (c->icon) { + XRenderFreePicture(dpy, c->icon); + c->icon = None; + } +} + +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); + freeicon(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XSelectInput(dpy, c->win, NoEventMask); + 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(void) +{ + 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; + + /* new monitors if nn > n */ + for (i = n; i < nn; 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); + } + /* removed monitors if n > nn */ + 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); + c->hintsvalid = 1; +} + +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 +updateicon(Client *c) +{ + freeicon(c); + c->icon = geticonprop(c->win, &c->icw, &c->ich); +} + +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 || !c || c->isfloating) + return; + if (c == nexttiled(selmon->clients) && !(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(); + if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/aarch64dwm/dwm.c.rej b/aarch64dwm/dwm.c.rej new file mode 100644 index 0000000..9d07971 --- /dev/null +++ b/aarch64dwm/dwm.c.rej @@ -0,0 +1,20 @@ +--- dwm.c ++++ dwm.c +@@ -59,7 +59,7 @@ + + /* enums */ + enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +-enum { SchemeNorm, SchemeSel }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeTitle }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +@@ -731,7 +731,7 @@ drawbar(Monitor *m) + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { +- drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); ++ drw_setscheme(drw, scheme[m == selmon ? SchemeTitle : 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); diff --git a/aarch64dwm/dwm.o b/aarch64dwm/dwm.o new file mode 100644 index 0000000000000000000000000000000000000000..86de215d95fa2f86adf4295e3f31b3e244f7a4c7 GIT binary patch literal 68176 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 Ay#N3J literal 0 HcmV?d00001 diff --git a/aarch64dwm/dwm.png b/aarch64dwm/dwm.png new file mode 100644 index 0000000000000000000000000000000000000000..b1f9ba7e5f4cc7350ee2392ebcea5fcbe00fb49b GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0y~yU^u|Qz!1Q}1Y+HIE6l*az?S6g?!xdN1Q+aGKAC}m zfwRCPvY3H^TNs2H8D`CqU|?WiFY)wsWxvKQ%wx!QPg+ZWfq_A`#5JPCIX^cyHLrxh zxhOTUBsE2$JhLQ2AtWPJ!QIn0;C+f}9s>izO-~ockcwMx?>cfd81OJSD*yX``Xg^6 zTaZz4SYYHW?+Yi+q)+P5khOpNZ+qolCYF{40Rbi^MnwgdmIen04h}{|1pxsjCkGA= zh?J9q0!T_g04&AA!nDZw&Oe5$@0m;M>sM6OY71;LKv54jO6E(b|BHY2*SA&9y8V3S z8iyN)!0OOUb8_ejn#VrNZ2je0``gbZufgI1u!%>m)^#5eUAZ~`sxkuu1B0ilpUXO@ GgeCxv)opSB literal 0 HcmV?d00001 diff --git a/aarch64dwm/keys.h b/aarch64dwm/keys.h new file mode 100644 index 0000000..dd588c1 --- /dev/null +++ b/aarch64dwm/keys.h @@ -0,0 +1,81 @@ +#include "layouts.h" +#include "movestack.c" +#include + +#define TERMINAL "st" // default terminal + +/* key definitions */ +#define MODKEY Mod4Mask +#define ALTMOD Mod1Mask +#define TAGKEYS(CHAIN,KEY,TAG) \ + { MODKEY, CHAIN, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, CHAIN, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, CHAIN, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, CHAIN, 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 const char dmenufont[] = "monospace:size=10"; +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { TERMINAL, NULL }; + +static const Key keys[] = { + /* modifier chain key key function argument */ + { MODKEY, -1, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, -1, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, -1, XK_b, togglebar, {0} }, + { MODKEY, -1, XK_j, focusstack, {.i = +1 } }, + { MODKEY, -1, XK_k, focusstack, {.i = -1 } }, + { MODKEY, -1, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, -1, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, -1, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, -1, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, -1, XK_x, movestack, {.i = +1 } }, + { MODKEY, -1, XK_z, movestack, {.i = -1 } }, + { MODKEY, -1, XK_Return, zoom, {0} }, + { MODKEY, -1, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, -1, XK_c, killclient, {0} }, + { MODKEY, -1, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, -1, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, -1, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, -1, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, -1, XK_space, togglefloating, {0} }, + { MODKEY, -1, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, -1, XK_0, tag, {.ui = ~0 } }, + { MODKEY, -1, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, -1, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, -1, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, -1, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( -1, XK_1, 0) + TAGKEYS( -1, XK_2, 1) + TAGKEYS( -1, XK_3, 2) + TAGKEYS( -1, XK_4, 3) + TAGKEYS( -1, XK_5, 4) + TAGKEYS( -1, XK_6, 5) + TAGKEYS( -1, XK_7, 6) + TAGKEYS( -1, XK_8, 7) + TAGKEYS( -1, XK_9, 8) + { MODKEY|ShiftMask, -1, XK_q, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, -1, XK_q, quit, {1} }, + { MODKEY, XK_a, XK_d, spawn, {.v = dmenucmd } }, + { MODKEY, XK_a, XK_t, spawn, {.v = termcmd } }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const 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, 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/aarch64dwm/layouts.h b/aarch64dwm/layouts.h new file mode 100644 index 0000000..1eb5b09 --- /dev/null +++ b/aarch64dwm/layouts.h @@ -0,0 +1,17 @@ +/* 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 = 0; /* 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 }, +}; +/* custom symbols for nr. of clients in monocle layout */ +/* when clients >= LENGTH(monocles), uses the last element */ +static const char *monocles[] = { "[1]", "[2]", "[3]", "[4]", "[5]", "[6]", "[7]", "[8]", "[9]", "[9+]" }; + + diff --git a/aarch64dwm/movestack.c b/aarch64dwm/movestack.c new file mode 100644 index 0000000..520f4ae --- /dev/null +++ b/aarch64dwm/movestack.c @@ -0,0 +1,48 @@ +void +movestack(const Arg *arg) { + Client *c = NULL, *p = NULL, *pc = NULL, *i; + + if(arg->i > 0) { + /* find the client after selmon->sel */ + for(c = selmon->sel->next; c && (!ISVISIBLE(c) || c->isfloating); c = c->next); + if(!c) + for(c = selmon->clients; c && (!ISVISIBLE(c) || c->isfloating); c = c->next); + + } + else { + /* find the client before selmon->sel */ + for(i = selmon->clients; i != selmon->sel; i = i->next) + if(ISVISIBLE(i) && !i->isfloating) + c = i; + if(!c) + for(; i; i = i->next) + if(ISVISIBLE(i) && !i->isfloating) + c = i; + } + /* find the client before selmon->sel and c */ + for(i = selmon->clients; i && (!p || !pc); i = i->next) { + if(i->next == selmon->sel) + p = i; + if(i->next == c) + pc = i; + } + + /* swap c and selmon->sel selmon->clients in the selmon->clients list */ + if(c && c != selmon->sel) { + Client *temp = selmon->sel->next==c?selmon->sel:selmon->sel->next; + selmon->sel->next = c->next==selmon->sel?c:c->next; + c->next = temp; + + if(p && p != c) + p->next = c; + if(pc && pc != selmon->sel) + pc->next = selmon->sel; + + if(selmon->sel == selmon->clients) + selmon->clients = c; + else if(c == selmon->clients) + selmon->clients = selmon->sel; + + arrange(selmon); + } +} \ No newline at end of file diff --git a/aarch64dwm/patches/dwm-alwayscenter-20200625-f04cac6.diff b/aarch64dwm/patches/dwm-alwayscenter-20200625-f04cac6.diff new file mode 100644 index 0000000..03ea9ef --- /dev/null +++ b/aarch64dwm/patches/dwm-alwayscenter-20200625-f04cac6.diff @@ -0,0 +1,12 @@ +diff -up dwm/dwm.c dwmmod/dwm.c +--- dwm/dwm.c 2020-06-25 00:21:30.383692180 -0300 ++++ dwmmod/dwm.c 2020-06-25 00:20:35.643692330 -0300 +@@ -1057,6 +1057,8 @@ manage(Window w, XWindowAttributes *wa) + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); ++ c->x = c->mon->mx + (c->mon->mw - WIDTH(c)) / 2; ++ c->y = c->mon->my + (c->mon->mh - HEIGHT(c)) / 2; + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) diff --git a/aarch64dwm/patches/dwm-bar-height-6.2.diff b/aarch64dwm/patches/dwm-bar-height-6.2.diff new file mode 100644 index 0000000..a576111 --- /dev/null +++ b/aarch64dwm/patches/dwm-bar-height-6.2.diff @@ -0,0 +1,25 @@ +diff --git a/config.def.h b/config.def.h +index 1c0b587..9814500 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -5,6 +5,7 @@ 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 int user_bh = 0; /* 0 means that dwm will calculate bar height, >= 1 means dwm will user_bh as bar height */ + static const char *fonts[] = { "monospace:size=10" }; + static const char dmenufont[] = "monospace:size=10"; + static const char col_gray1[] = "#222222"; +diff --git a/dwm.c b/dwm.c +index 4465af1..2c27cb3 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -1545,7 +1545,7 @@ setup(void) + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; +- bh = drw->fonts->h + 2; ++ bh = user_bh ? user_bh : drw->fonts->h + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); diff --git a/aarch64dwm/patches/dwm-centerfirstwindow-6.2.diff b/aarch64dwm/patches/dwm-centerfirstwindow-6.2.diff new file mode 100644 index 0000000..707f2ec --- /dev/null +++ b/aarch64dwm/patches/dwm-centerfirstwindow-6.2.diff @@ -0,0 +1,67 @@ +diff -up dwm-6.2-orig/config.def.h dwm-6.2-modd/config.def.h +--- dwm-6.2-orig/config.def.h 2019-02-02 16:55:28.000000000 +0400 ++++ dwm-6.2-modd/config.def.h 2021-04-25 16:05:22.569759243 +0400 +@@ -26,9 +26,10 @@ static const Rule rules[] = { + * 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 }, ++ /* class instance title tags mask isfloating CenterThisWindow? monitor */ ++ { "st", NULL, NULL, 0, 0, 1, -1 }, ++ { "Gimp", NULL, NULL, 0, 1, 0, -1 }, ++ { "Firefox", NULL, NULL, 1 << 8, 0, 0, -1 }, + }; + + /* layout(s) */ +diff -up dwm-6.2-orig/dwm.c dwm-6.2-modd/dwm.c +--- dwm-6.2-orig/dwm.c 2019-02-02 16:55:28.000000000 +0400 ++++ dwm-6.2-modd/dwm.c 2021-04-25 16:06:15.368310756 +0400 +@@ -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, CenterThisWindow; + Client *next; + Client *snext; + Monitor *mon; +@@ -138,6 +138,7 @@ typedef struct { + const char *title; + unsigned int tags; + int isfloating; ++ int CenterThisWindow; + int monitor; + } Rule; + +@@ -286,6 +287,7 @@ applyrules(Client *c) + + /* rule matching */ + c->isfloating = 0; ++ c->CenterThisWindow = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; +@@ -298,6 +300,7 @@ applyrules(Client *c) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; ++ c->CenterThisWindow = r->CenterThisWindow; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) +@@ -1694,6 +1697,13 @@ tile(Monitor *m) + resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0); + ty += HEIGHT(c); + } ++ ++ if (n == 1 && selmon->sel->CenterThisWindow) ++ resizeclient(selmon->sel, ++ (selmon->mw - selmon->mw * 0.5) / 2, ++ (selmon->mh - selmon->mh * 0.5) / 2, ++ selmon->mw * 0.5, ++ selmon->mh * 0.5); + } + + void diff --git a/aarch64dwm/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff b/aarch64dwm/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff new file mode 100644 index 0000000..082461e --- /dev/null +++ b/aarch64dwm/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff @@ -0,0 +1,41 @@ +From 630859138bb960e2aea41d47e68c48ec020daf5c Mon Sep 17 00:00:00 2001 +From: ben evolver <> +Date: Sun, 17 Apr 2022 08:07:24 -0500 +Subject: [PATCH] add alternative symbols for nr. of active clients in monocle layout + +--- + config.def.h | 4 ++++ + dwm.c | 2 +- + 2 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index a2ac963..f49dfdf 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -44,6 +44,10 @@ static const Layout layouts[] = { + { "[M]", monocle }, + }; + ++/* custom symbols for nr. of clients in monocle layout */ ++/* when clients >= LENGTH(monocles), uses the last element */ ++static const char *monocles[] = { "[1]", "[2]", "[3]", "[4]", "[5]", "[6]", "[7]", "[8]", "[9]", "[9+]" }; ++ + /* key definitions */ + #define MODKEY Mod1Mask + #define TAGKEYS(KEY,TAG) \ +diff --git a/dwm.c b/dwm.c +index 0fc328a..e47ba70 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -1116,7 +1116,7 @@ monocle(Monitor *m) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ +- snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); ++ snprintf(m->ltsymbol, sizeof m->ltsymbol, "%s", monocles[MIN(n, LENGTH(monocles)) - 1]); + 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); + } +-- +2.35.1 + diff --git a/aarch64dwm/patches/dwm-fakefullscreen-20210714-138b405.diff b/aarch64dwm/patches/dwm-fakefullscreen-20210714-138b405.diff new file mode 100644 index 0000000..88ca9f8 --- /dev/null +++ b/aarch64dwm/patches/dwm-fakefullscreen-20210714-138b405.diff @@ -0,0 +1,120 @@ +From 33c7811ca7280be7890851f5a83fa8d1a3313374 Mon Sep 17 00:00:00 2001 +From: Sebastian LaVine +Date: Wed, 14 Jul 2021 11:22:34 -0400 +Subject: [PATCH] Set new lockfullscreen variable to 0 + +This more properly fixes the problem introduced by 67d76bd than the +previous patch revision does. +--- + config.def.h | 2 +- + dwm.c | 28 ++-------------------------- + 2 files changed, 3 insertions(+), 27 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a2ac963..1b46cb4 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -35,7 +35,7 @@ static const Rule rules[] = { + 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 int lockfullscreen = 0; /* 1 will force focus on the fullscreen window */ + + static const Layout layouts[] = { + /* symbol arrange function */ +diff --git a/dwm.c b/dwm.c +index 5e4d494..968e256 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -522,7 +522,7 @@ clientmessage(XEvent *e) + 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))); ++ || cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */)); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); +@@ -552,7 +552,6 @@ void + configurenotify(XEvent *e) + { + Monitor *m; +- Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + +@@ -565,9 +564,6 @@ configurenotify(XEvent *e) + 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); +@@ -1144,8 +1140,6 @@ movemouse(const Arg *arg) + + if (!(c = selmon->sel)) + return; +- if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ +- return; + restack(selmon); + ocx = c->x; + ocy = c->y; +@@ -1299,8 +1293,6 @@ resizemouse(const Arg *arg) + + if (!(c = selmon->sel)) + return; +- if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ +- return; + restack(selmon); + ocx = c->x; + ocy = c->y; +@@ -1477,24 +1469,10 @@ setfullscreen(Client *c, int fullscreen) + 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); + } + } + +@@ -1619,7 +1597,7 @@ showhide(Client *c) + 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) ++ if (!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { +@@ -1713,8 +1691,6 @@ 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, +-- +2.32.0 + diff --git a/aarch64dwm/patches/dwm-keychain-20200729-053e3a2.diff b/aarch64dwm/patches/dwm-keychain-20200729-053e3a2.diff new file mode 100644 index 0000000..cf3ad28 --- /dev/null +++ b/aarch64dwm/patches/dwm-keychain-20200729-053e3a2.diff @@ -0,0 +1,266 @@ +From e6c2d5fdc6010a22d6cd74485cb0b3e74467d0da Mon Sep 17 00:00:00 2001 +From: braunbearded +Date: Wed, 29 Jul 2020 18:37:47 +0200 +Subject: [PATCH 1/4] chain key bindings + +--- + dwm.c | 34 +++++++++++++++++++++++++++++----- + 1 file changed, 29 insertions(+), 5 deletions(-) + +diff --git a/dwm.c b/dwm.c +index 9fd0286..7298c5e 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -101,6 +101,7 @@ struct Client { + + typedef struct { + unsigned int mod; ++ KeySym chain; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +@@ -268,6 +269,7 @@ static Display *dpy; + static Drw *drw; + static Monitor *mons, *selmon; + static Window root, wmcheckwin; ++static KeySym keychain = -1; + + /* configuration, allows nested code to access above variables */ + #include "config.h" +@@ -954,13 +956,18 @@ grabkeys(void) + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; ++ KeyCode chain; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) +- if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) ++ if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { ++ if (keys[i].chain != -1 && ++ ((chain = XKeysymToKeycode(dpy, keys[i].chain)))) ++ code = chain; + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); ++ } + } + } + +@@ -989,14 +996,31 @@ keypress(XEvent *e) + unsigned int i; + KeySym keysym; + XKeyEvent *ev; ++ int current = 0; + + 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) ++ for (i = 0; i < LENGTH(keys); i++) { ++ if (keysym == keys[i].keysym && keys[i].chain == -1 ++ && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) ++ && keys[i].func) ++ keys[i].func(&(keys[i].arg)); ++ else if (keysym == keys[i].chain && keychain == -1 ++ && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) ++ && keys[i].func) { ++ current = 1; ++ keychain = keysym; ++ XGrabKey(dpy, AnyKey, AnyModifier, root, True, GrabModeAsync, ++ GrabModeAsync); ++ } else if (!current && keysym == keys[i].keysym ++ && keys[i].chain == keychain ++ && keys[i].func) + keys[i].func(&(keys[i].arg)); ++ } ++ if (!current) { ++ keychain = -1; ++ grabkeys(); ++ } + } + + void +-- +2.28.0 + + +From ad3d15cf7df3286d35728afef823c3163898e2db Mon Sep 17 00:00:00 2001 +From: braunbearded +Date: Wed, 29 Jul 2020 18:38:15 +0200 +Subject: [PATCH 2/4] update default bindings + +--- + config.def.h | 80 +++++++++++++++++++++++++++------------------------- + 1 file changed, 41 insertions(+), 39 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1c0b587..c7cab16 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -45,11 +45,11 @@ static const Layout layouts[] = { + + /* 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} }, ++#define TAGKEYS(CHAIN,KEY,TAG) \ ++ { MODKEY, CHAIN, KEY, view, {.ui = 1 << TAG} }, \ ++ { MODKEY|ControlMask, CHAIN, KEY, toggleview, {.ui = 1 << TAG} }, \ ++ { MODKEY|ShiftMask, CHAIN, KEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|ControlMask|ShiftMask, CHAIN, 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 } } +@@ -60,40 +60,42 @@ static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, + 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} }, ++ /* modifier chain key key function argument */ ++ { MODKEY, -1, XK_p, spawn, {.v = dmenucmd } }, ++ { MODKEY|ShiftMask, -1, XK_Return, spawn, {.v = termcmd } }, ++ { MODKEY, -1, XK_b, togglebar, {0} }, ++ { MODKEY, -1, XK_j, focusstack, {.i = +1 } }, ++ { MODKEY, -1, XK_k, focusstack, {.i = -1 } }, ++ { MODKEY, -1, XK_i, incnmaster, {.i = +1 } }, ++ { MODKEY, -1, XK_d, incnmaster, {.i = -1 } }, ++ { MODKEY, -1, XK_h, setmfact, {.f = -0.05} }, ++ { MODKEY, -1, XK_l, setmfact, {.f = +0.05} }, ++ { MODKEY, -1, XK_Return, zoom, {0} }, ++ { MODKEY, -1, XK_Tab, view, {0} }, ++ { MODKEY|ShiftMask, -1, XK_c, killclient, {0} }, ++ { MODKEY, -1, XK_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, -1, XK_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, -1, XK_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, -1, XK_space, setlayout, {0} }, ++ { MODKEY|ShiftMask, -1, XK_space, togglefloating, {0} }, ++ { MODKEY, -1, XK_0, view, {.ui = ~0 } }, ++ { MODKEY|ShiftMask, -1, XK_0, tag, {.ui = ~0 } }, ++ { MODKEY, -1, XK_comma, focusmon, {.i = -1 } }, ++ { MODKEY, -1, XK_period, focusmon, {.i = +1 } }, ++ { MODKEY|ShiftMask, -1, XK_comma, tagmon, {.i = -1 } }, ++ { MODKEY|ShiftMask, -1, XK_period, tagmon, {.i = +1 } }, ++ TAGKEYS( -1, XK_1, 0) ++ TAGKEYS( -1, XK_2, 1) ++ TAGKEYS( -1, XK_3, 2) ++ TAGKEYS( -1, XK_4, 3) ++ TAGKEYS( -1, XK_5, 4) ++ TAGKEYS( -1, XK_6, 5) ++ TAGKEYS( -1, XK_7, 6) ++ TAGKEYS( -1, XK_8, 7) ++ TAGKEYS( -1, XK_9, 8) ++ { MODKEY|ShiftMask, -1, XK_q, quit, {0} }, ++ { MODKEY, XK_a, XK_d, spawn, {.v = dmenucmd } }, ++ { MODKEY, XK_a, XK_t, spawn, {.v = termcmd } }, + }; + + /* button definitions */ +-- +2.28.0 + + +From e9f3eec82010fd6083dc57f058902a1aab2d14ea Mon Sep 17 00:00:00 2001 +From: braunbearded +Date: Wed, 29 Jul 2020 19:07:07 +0200 +Subject: [PATCH 3/4] fix bug for mod key ignore + +--- + dwm.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dwm.c b/dwm.c +index 7298c5e..aee56d4 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -1013,6 +1013,7 @@ keypress(XEvent *e) + XGrabKey(dpy, AnyKey, AnyModifier, root, True, GrabModeAsync, + GrabModeAsync); + } else if (!current && keysym == keys[i].keysym ++ && keychain != -1 + && keys[i].chain == keychain + && keys[i].func) + keys[i].func(&(keys[i].arg)); +-- +2.28.0 + + +From 053e3a2b2ff87805a15f3fe2f82a7d8bf0ab9b7a Mon Sep 17 00:00:00 2001 +From: braunbearded +Date: Wed, 29 Jul 2020 21:25:23 +0200 +Subject: [PATCH 4/4] listen for all keys inkl modifier after prefix + +--- + dwm.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/dwm.c b/dwm.c +index aee56d4..dea8f6a 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -993,10 +993,11 @@ isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) + void + keypress(XEvent *e) + { +- unsigned int i; ++ unsigned int i, j; + KeySym keysym; + XKeyEvent *ev; + int current = 0; ++ unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); +@@ -1010,8 +1011,9 @@ keypress(XEvent *e) + && keys[i].func) { + current = 1; + keychain = keysym; +- XGrabKey(dpy, AnyKey, AnyModifier, root, True, GrabModeAsync, +- GrabModeAsync); ++ for (j = 0; j < LENGTH(modifiers); j++) ++ XGrabKey(dpy, AnyKey, 0 | modifiers[j], root, ++ True, GrabModeAsync, GrabModeAsync); + } else if (!current && keysym == keys[i].keysym + && keychain != -1 + && keys[i].chain == keychain +-- +2.28.0 + diff --git a/aarch64dwm/patches/dwm-movestack-20211115-a786211.diff b/aarch64dwm/patches/dwm-movestack-20211115-a786211.diff new file mode 100644 index 0000000..134abb8 --- /dev/null +++ b/aarch64dwm/patches/dwm-movestack-20211115-a786211.diff @@ -0,0 +1,95 @@ +From 9a4037dc0ef56f91c009317e78e9e3790dafbb58 Mon Sep 17 00:00:00 2001 +From: BrunoCooper17 +Date: Mon, 15 Nov 2021 14:04:53 -0600 +Subject: [PATCH] MoveStack patch + +This plugin allows you to move clients around in the stack and swap them +with the master. It emulates the behavior off mod+shift+j and mod+shift+k +in Xmonad. movestack(+1) will swap the client with the current focus with +the next client. movestack(-1) will swap the client with the current focus +with the previous client. +--- + config.def.h | 3 +++ + movestack.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 51 insertions(+) + create mode 100644 movestack.c + +diff --git a/config.def.h b/config.def.h +index a2ac963..33efa5b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,7 @@ 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 }; + ++#include "movestack.c" + static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, +@@ -71,6 +72,8 @@ static Key keys[] = { + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, ++ { MODKEY|ShiftMask, XK_j, movestack, {.i = +1 } }, ++ { MODKEY|ShiftMask, XK_k, movestack, {.i = -1 } }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, +diff --git a/movestack.c b/movestack.c +new file mode 100644 +index 0000000..520f4ae +--- /dev/null ++++ b/movestack.c +@@ -0,0 +1,48 @@ ++void ++movestack(const Arg *arg) { ++ Client *c = NULL, *p = NULL, *pc = NULL, *i; ++ ++ if(arg->i > 0) { ++ /* find the client after selmon->sel */ ++ for(c = selmon->sel->next; c && (!ISVISIBLE(c) || c->isfloating); c = c->next); ++ if(!c) ++ for(c = selmon->clients; c && (!ISVISIBLE(c) || c->isfloating); c = c->next); ++ ++ } ++ else { ++ /* find the client before selmon->sel */ ++ for(i = selmon->clients; i != selmon->sel; i = i->next) ++ if(ISVISIBLE(i) && !i->isfloating) ++ c = i; ++ if(!c) ++ for(; i; i = i->next) ++ if(ISVISIBLE(i) && !i->isfloating) ++ c = i; ++ } ++ /* find the client before selmon->sel and c */ ++ for(i = selmon->clients; i && (!p || !pc); i = i->next) { ++ if(i->next == selmon->sel) ++ p = i; ++ if(i->next == c) ++ pc = i; ++ } ++ ++ /* swap c and selmon->sel selmon->clients in the selmon->clients list */ ++ if(c && c != selmon->sel) { ++ Client *temp = selmon->sel->next==c?selmon->sel:selmon->sel->next; ++ selmon->sel->next = c->next==selmon->sel?c:c->next; ++ c->next = temp; ++ ++ if(p && p != c) ++ p->next = c; ++ if(pc && pc != selmon->sel) ++ pc->next = selmon->sel; ++ ++ if(selmon->sel == selmon->clients) ++ selmon->clients = c; ++ else if(c == selmon->clients) ++ selmon->clients = selmon->sel; ++ ++ arrange(selmon); ++ } ++} +\ No newline at end of file +-- +2.33.1 + diff --git a/aarch64dwm/patches/dwm-rainbowtags-6.2.diff b/aarch64dwm/patches/dwm-rainbowtags-6.2.diff new file mode 100644 index 0000000..6c31062 --- /dev/null +++ b/aarch64dwm/patches/dwm-rainbowtags-6.2.diff @@ -0,0 +1,59 @@ +diff --git a/config.def.h b/config.def.h +index 1c0b587..3fb5cf8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,6 +21,18 @@ static const char *colors[][3] = { + /* tagging */ + static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + ++static const char *tagsel[][2] = { ++ { "#ffffff", "#ff0000" }, ++ { "#ffffff", "#ff7f00" }, ++ { "#000000", "#ffff00" }, ++ { "#000000", "#00ff00" }, ++ { "#ffffff", "#0000ff" }, ++ { "#ffffff", "#4b0082" }, ++ { "#ffffff", "#9400d3" }, ++ { "#000000", "#ffffff" }, ++ { "#ffffff", "#000000" }, ++}; ++ + static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class +diff --git a/dwm.c b/dwm.c +index b0b3466..c16d5f5 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -264,6 +264,7 @@ static Atom wmatom[WMLast], netatom[NetLast]; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; ++static Clr **tagscheme; + static Display *dpy; + static Drw *drw; + static Monitor *mons, *selmon; +@@ -717,7 +718,7 @@ drawbar(Monitor *m) + 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_setscheme(drw, (m->tagset[m->seltags] & 1 << i ? tagscheme[i] : scheme[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, +@@ -1568,9 +1569,14 @@ setup(void) + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ ++ if (LENGTH(tags) > LENGTH(tagsel)) ++ die("too few color schemes for the tags"); + scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); ++ tagscheme = ecalloc(LENGTH(tagsel), sizeof(Clr *)); ++ for (i = 0; i < LENGTH(tagsel); i++) ++ tagscheme[i] = drw_scm_create(drw, tagsel[i], 2); + /* init bars */ + updatebars(); + updatestatus(); diff --git a/aarch64dwm/patches/dwm-restartsig-20180523-6.2.diff b/aarch64dwm/patches/dwm-restartsig-20180523-6.2.diff new file mode 100644 index 0000000..f1f8680 --- /dev/null +++ b/aarch64dwm/patches/dwm-restartsig-20180523-6.2.diff @@ -0,0 +1,139 @@ +From 2991f37f0aaf44b9f9b11e7893ff0af8eb88f649 Mon Sep 17 00:00:00 2001 +From: Christopher Drelich +Date: Wed, 23 May 2018 22:50:38 -0400 +Subject: [PATCH] Modifies quit to handle restarts and adds SIGHUP and SIGTERM + handlers. + +Modified quit() to restart if it receives arg .i = 1 +MOD+CTRL+SHIFT+Q was added to confid.def.h to do just that. + +Signal handlers were handled for SIGHUP and SIGTERM. +If dwm receives these signals it calls quit() with +arg .i = to 1 or 0, respectively. + +To restart dwm: +MOD+CTRL+SHIFT+Q +or +kill -HUP dwmpid + +To quit dwm cleanly: +MOD+SHIFT+Q +or +kill -TERM dwmpid +--- + config.def.h | 1 + + dwm.1 | 10 ++++++++++ + dwm.c | 22 ++++++++++++++++++++++ + 3 files changed, 33 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a9ac303..e559429 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -94,6 +94,7 @@ static Key keys[] = { + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, ++ { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} }, + }; + + /* button definitions */ +diff --git a/dwm.1 b/dwm.1 +index 13b3729..36a331c 100644 +--- a/dwm.1 ++++ b/dwm.1 +@@ -142,6 +142,9 @@ Add/remove all windows with nth tag to/from the view. + .TP + .B Mod1\-Shift\-q + Quit dwm. ++.TP ++.B Mod1\-Control\-Shift\-q ++Restart dwm. + .SS Mouse commands + .TP + .B Mod1\-Button1 +@@ -155,6 +158,13 @@ Resize focused window while dragging. Tiled windows will be toggled to the float + .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 SIGNALS ++.TP ++.B SIGHUP - 1 ++Restart the dwm process. ++.TP ++.B SIGTERM - 15 ++Cleanly terminate the dwm process. + .SH SEE ALSO + .BR dmenu (1), + .BR st (1) +diff --git a/dwm.c b/dwm.c +index bb95e26..286eecd 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -205,6 +205,8 @@ static void setup(void); + static void seturgent(Client *c, int urg); + static void showhide(Client *c); + static void sigchld(int unused); ++static void sighup(int unused); ++static void sigterm(int unused); + static void spawn(const Arg *arg); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); +@@ -260,6 +262,7 @@ static void (*handler[LASTEvent]) (XEvent *) = { + [UnmapNotify] = unmapnotify + }; + static Atom wmatom[WMLast], netatom[NetLast]; ++static int restart = 0; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; +@@ -1248,6 +1251,7 @@ propertynotify(XEvent *e) + void + quit(const Arg *arg) + { ++ if(arg->i) restart = 1; + running = 0; + } + +@@ -1536,6 +1540,9 @@ setup(void) + /* clean up any zombies immediately */ + sigchld(0); + ++ signal(SIGHUP, sighup); ++ signal(SIGTERM, sigterm); ++ + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); +@@ -1637,6 +1644,20 @@ sigchld(int unused) + } + + void ++sighup(int unused) ++{ ++ Arg a = {.i = 1}; ++ quit(&a); ++} ++ ++void ++sigterm(int unused) ++{ ++ Arg a = {.i = 0}; ++ quit(&a); ++} ++ ++void + spawn(const Arg *arg) + { + if (arg->v == dmenucmd) +@@ -2139,6 +2160,7 @@ main(int argc, char *argv[]) + setup(); + scan(); + run(); ++ if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +-- +2.7.4 + diff --git a/aarch64dwm/patches/dwm-status2d-systray-6.4.diff b/aarch64dwm/patches/dwm-status2d-systray-6.4.diff new file mode 100644 index 0000000..d4a12e5 --- /dev/null +++ b/aarch64dwm/patches/dwm-status2d-systray-6.4.diff @@ -0,0 +1,879 @@ +diff --git a/config.def.h b/config.def.h +index 061ad66..a308a7e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -3,6 +3,11 @@ + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of 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: 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" }; +@@ -112,4 +117,3 @@ static const Button buttons[] = { + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + }; +- +diff --git a/dwm.c b/dwm.c +index e5efb6a..79a46f5 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 *c); + 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 *m); +@@ -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; /* bar height */ +@@ -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; +@@ -442,7 +475,7 @@ buttonpress(XEvent *e) + arg.ui = 1 << i; + } else if (ev->x < x + TEXTW(selmon->ltsymbol)) + 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; +@@ -485,9 +518,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]); + free(scheme); + XDestroyWindow(dpy, wmcheckwin); +@@ -516,9 +556,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]) { +@@ -571,7 +660,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); +@@ -656,6 +745,11 @@ destroynotify(XEvent *e) + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); ++ else if ((c = wintosystrayicon(ev->window))) { ++ removesystrayicon(c); ++ resizebarwin(selmon); ++ updatesystray(); ++ } + } + + void +@@ -696,10 +790,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; +@@ -708,13 +911,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) +@@ -735,7 +940,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); +@@ -746,7 +951,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 +@@ -783,8 +988,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 +@@ -870,9 +1078,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; +@@ -906,6 +1122,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) + { +@@ -1008,7 +1234,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); +@@ -1095,6 +1322,14 @@ 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) || wa.override_redirect) + return; + if (!wintoclient(ev->window)) +@@ -1216,7 +1451,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 */ +@@ -1266,6 +1512,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) + { +@@ -1273,6 +1532,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) + { +@@ -1345,6 +1612,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) + { +@@ -1434,26 +1714,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; + } +@@ -1467,7 +1758,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 +@@ -1555,22 +1846,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(); +@@ -1699,7 +2000,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); + } + +@@ -1795,11 +2107,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, +@@ -1810,10 +2129,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); + } +@@ -1990,6 +2314,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 +@@ -2057,6 +2500,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) + { +@@ -2110,6 +2563,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/aarch64dwm/patches/dwm-titlecolor-20210815-ed3ab6b4.diff b/aarch64dwm/patches/dwm-titlecolor-20210815-ed3ab6b4.diff new file mode 100644 index 0000000..f0e5b36 --- /dev/null +++ b/aarch64dwm/patches/dwm-titlecolor-20210815-ed3ab6b4.diff @@ -0,0 +1,47 @@ +From 45a45a0e67f3841d8c4aed2c52b57c2a7ddf2a9a Mon Sep 17 00:00:00 2001 +From: Jack Bird +Date: Sun, 15 Aug 2021 23:15:52 +0100 +Subject: [PATCH] Updated title color patch for 7162335 + +--- + config.def.h | 1 + + dwm.c | 4 ++-- + 2 files changed, 3 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a2ac963..5b9ae00 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -16,6 +16,7 @@ static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, ++ [SchemeTitle] = { col_gray4, col_cyan, col_cyan }, + }; + + /* tagging */ +diff --git a/dwm.c b/dwm.c +index 5e4d494..73d335e 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -59,7 +59,7 @@ + + /* enums */ + enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +-enum { SchemeNorm, SchemeSel }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeTitle }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +@@ -731,7 +731,7 @@ drawbar(Monitor *m) + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { +- drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); ++ drw_setscheme(drw, scheme[m == selmon ? SchemeTitle : 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); +-- +2.32.0 + diff --git a/aarch64dwm/patches/dwm-winicon-6.3-v2.1.diff b/aarch64dwm/patches/dwm-winicon-6.3-v2.1.diff new file mode 100644 index 0000000..4278431 --- /dev/null +++ b/aarch64dwm/patches/dwm-winicon-6.3-v2.1.diff @@ -0,0 +1,371 @@ +diff --git a/config.def.h b/config.def.h +index a2ac963..322d181 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -5,6 +5,8 @@ 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 */ ++#define ICONSIZE 16 /* icon size */ ++#define ICONSPACING 5 /* space between icon and title */ + static const char *fonts[] = { "monospace:size=10" }; + static const char dmenufont[] = "monospace:size=10"; + static const char col_gray1[] = "#222222"; +diff --git a/config.mk b/config.mk +index b6eb7e0..f3c01b0 100644 +--- a/config.mk ++++ b/config.mk +@@ -22,7 +22,7 @@ FREETYPEINC = /usr/include/freetype2 + + # includes and libs + INCS = -I${X11INC} -I${FREETYPEINC} +-LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} ++LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2 + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +diff --git a/drw.c b/drw.c +index 4cdbcbe..9b474c5 100644 +--- a/drw.c ++++ b/drw.c +@@ -4,6 +4,7 @@ + #include + #include + #include ++#include + + #include "drw.h" + #include "util.h" +@@ -71,6 +72,7 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); ++ drw->picture = XRenderCreatePicture(dpy, drw->drawable, XRenderFindVisualFormat(dpy, DefaultVisual(dpy, screen)), 0, NULL); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + +@@ -85,14 +87,18 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h) + + drw->w = w; + drw->h = h; ++ if (drw->picture) ++ XRenderFreePicture(drw->dpy, drw->picture); + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); ++ drw->picture = XRenderCreatePicture(drw->dpy, drw->drawable, XRenderFindVisualFormat(drw->dpy, DefaultVisual(drw->dpy, drw->screen)), 0, NULL); + } + + void + drw_free(Drw *drw) + { ++ XRenderFreePicture(drw->dpy, drw->picture); + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); +@@ -236,6 +242,67 @@ drw_setscheme(Drw *drw, Clr *scm) + drw->scheme = scm; + } + ++Picture ++drw_picture_create_resized(Drw *drw, char *src, unsigned int srcw, unsigned int srch, unsigned int dstw, unsigned int dsth) { ++ Pixmap pm; ++ Picture pic; ++ GC gc; ++ ++ if (srcw <= (dstw << 1u) && srch <= (dsth << 1u)) { ++ XImage img = { ++ srcw, srch, 0, ZPixmap, src, ++ ImageByteOrder(drw->dpy), BitmapUnit(drw->dpy), BitmapBitOrder(drw->dpy), 32, ++ 32, 0, 32, ++ 0, 0, 0 ++ }; ++ XInitImage(&img); ++ ++ pm = XCreatePixmap(drw->dpy, drw->root, srcw, srch, 32); ++ gc = XCreateGC(drw->dpy, pm, 0, NULL); ++ XPutImage(drw->dpy, pm, gc, &img, 0, 0, 0, 0, srcw, srch); ++ XFreeGC(drw->dpy, gc); ++ ++ pic = XRenderCreatePicture(drw->dpy, pm, XRenderFindStandardFormat(drw->dpy, PictStandardARGB32), 0, NULL); ++ XFreePixmap(drw->dpy, pm); ++ ++ XRenderSetPictureFilter(drw->dpy, pic, FilterBilinear, NULL, 0); ++ XTransform xf; ++ xf.matrix[0][0] = (srcw << 16u) / dstw; xf.matrix[0][1] = 0; xf.matrix[0][2] = 0; ++ xf.matrix[1][0] = 0; xf.matrix[1][1] = (srch << 16u) / dsth; xf.matrix[1][2] = 0; ++ xf.matrix[2][0] = 0; xf.matrix[2][1] = 0; xf.matrix[2][2] = 65536; ++ XRenderSetPictureTransform(drw->dpy, pic, &xf); ++ } else { ++ Imlib_Image origin = imlib_create_image_using_data(srcw, srch, (DATA32 *)src); ++ if (!origin) return None; ++ imlib_context_set_image(origin); ++ imlib_image_set_has_alpha(1); ++ Imlib_Image scaled = imlib_create_cropped_scaled_image(0, 0, srcw, srch, dstw, dsth); ++ imlib_free_image_and_decache(); ++ if (!scaled) return None; ++ imlib_context_set_image(scaled); ++ imlib_image_set_has_alpha(1); ++ ++ XImage img = { ++ dstw, dsth, 0, ZPixmap, (char *)imlib_image_get_data_for_reading_only(), ++ ImageByteOrder(drw->dpy), BitmapUnit(drw->dpy), BitmapBitOrder(drw->dpy), 32, ++ 32, 0, 32, ++ 0, 0, 0 ++ }; ++ XInitImage(&img); ++ ++ pm = XCreatePixmap(drw->dpy, drw->root, dstw, dsth, 32); ++ gc = XCreateGC(drw->dpy, pm, 0, NULL); ++ XPutImage(drw->dpy, pm, gc, &img, 0, 0, 0, 0, dstw, dsth); ++ imlib_free_image_and_decache(); ++ XFreeGC(drw->dpy, gc); ++ ++ pic = XRenderCreatePicture(drw->dpy, pm, XRenderFindStandardFormat(drw->dpy, PictStandardARGB32), 0, NULL); ++ XFreePixmap(drw->dpy, pm); ++ } ++ ++ return pic; ++} ++ + void + drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) + { +@@ -379,6 +446,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp + return x + (render ? w : 0); + } + ++void ++drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic) ++{ ++ if (!drw) ++ return; ++ XRenderComposite(drw->dpy, PictOpOver, pic, None, drw->picture, 0, 0, 0, 0, x, y, w, h); ++} ++ + void + drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) + { +diff --git a/drw.h b/drw.h +index 4bcd5ad..71aefa2 100644 +--- a/drw.h ++++ b/drw.h +@@ -21,6 +21,7 @@ typedef struct { + int screen; + Window root; + Drawable drawable; ++ Picture picture; + GC gc; + Clr *scheme; + Fnt *fonts; +@@ -49,9 +50,12 @@ void drw_cur_free(Drw *drw, Cur *cursor); + void drw_setfontset(Drw *drw, Fnt *set); + void drw_setscheme(Drw *drw, Clr *scm); + ++Picture drw_picture_create_resized(Drw *drw, char *src, unsigned int src_w, unsigned int src_h, unsigned int dst_w, unsigned int dst_h); ++ + /* 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); ++void drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic); + + /* Map functions */ + void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); +diff --git a/dwm.c b/dwm.c +index a96f33c..033ccec 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -28,6 +28,8 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include +@@ -60,7 +62,7 @@ + /* enums */ + enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ + enum { SchemeNorm, SchemeSel }; /* color schemes */ +-enum { NetSupported, NetWMName, NetWMState, NetWMCheck, ++enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ + enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +@@ -93,6 +95,7 @@ struct Client { + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; ++ unsigned int icw, ich; Picture icon; + Client *next; + Client *snext; + Monitor *mon; +@@ -170,6 +173,7 @@ 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 Picture geticonprop(Window w, unsigned int *icw, unsigned int *ich); + static int getrootptr(int *x, int *y); + static long getstate(Window w); + static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +@@ -214,6 +218,7 @@ 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 freeicon(Client *c); + static void unfocus(Client *c, int setfocus); + static void unmanage(Client *c, int destroyed); + static void unmapnotify(XEvent *e); +@@ -225,6 +230,7 @@ static void updatenumlockmask(void); + static void updatesizehints(Client *c); + static void updatestatus(void); + static void updatetitle(Client *c); ++static void updateicon(Client *c); + static void updatewindowtype(Client *c); + static void updatewmhints(Client *c); + static void view(const Arg *arg); +@@ -735,7 +741,8 @@ drawbar(Monitor *m) + 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); ++ drw_text(drw, x, 0, w, bh, lrpad / 2 + (m->sel->icon ? m->sel->icw + ICONSPACING : 0), m->sel->name, 0); ++ if (m->sel->icon) drw_pic(drw, x + lrpad / 2, (bh - m->sel->ich) / 2, m->sel->icw, m->sel->ich, m->sel->icon); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { +@@ -875,6 +882,67 @@ getatomprop(Client *c, Atom prop) + return atom; + } + ++static uint32_t prealpha(uint32_t p) { ++ uint8_t a = p >> 24u; ++ uint32_t rb = (a * (p & 0xFF00FFu)) >> 8u; ++ uint32_t g = (a * (p & 0x00FF00u)) >> 8u; ++ return (rb & 0xFF00FFu) | (g & 0x00FF00u) | (a << 24u); ++} ++ ++Picture ++geticonprop(Window win, unsigned int *picw, unsigned int *pich) ++{ ++ int format; ++ unsigned long n, extra, *p = NULL; ++ Atom real; ++ ++ if (XGetWindowProperty(dpy, win, netatom[NetWMIcon], 0L, LONG_MAX, False, AnyPropertyType, ++ &real, &format, &n, &extra, (unsigned char **)&p) != Success) ++ return None; ++ if (n == 0 || format != 32) { XFree(p); return None; } ++ ++ unsigned long *bstp = NULL; ++ uint32_t w, h, sz; ++ { ++ unsigned long *i; const unsigned long *end = p + n; ++ uint32_t bstd = UINT32_MAX, d, m; ++ for (i = p; i < end - 1; i += sz) { ++ if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; } ++ if ((sz = w * h) > end - i) break; ++ if ((m = w > h ? w : h) >= ICONSIZE && (d = m - ICONSIZE) < bstd) { bstd = d; bstp = i; } ++ } ++ if (!bstp) { ++ for (i = p; i < end - 1; i += sz) { ++ if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; } ++ if ((sz = w * h) > end - i) break; ++ if ((d = ICONSIZE - (w > h ? w : h)) < bstd) { bstd = d; bstp = i; } ++ } ++ } ++ if (!bstp) { XFree(p); return None; } ++ } ++ ++ if ((w = *(bstp - 2)) == 0 || (h = *(bstp - 1)) == 0) { XFree(p); return None; } ++ ++ uint32_t icw, ich; ++ if (w <= h) { ++ ich = ICONSIZE; icw = w * ICONSIZE / h; ++ if (icw == 0) icw = 1; ++ } ++ else { ++ icw = ICONSIZE; ich = h * ICONSIZE / w; ++ if (ich == 0) ich = 1; ++ } ++ *picw = icw; *pich = ich; ++ ++ uint32_t i, *bstp32 = (uint32_t *)bstp; ++ for (sz = w * h, i = 0; i < sz; ++i) bstp32[i] = prealpha(bstp[i]); ++ ++ Picture ret = drw_picture_create_resized(drw, (char *)bstp, w, h, icw, ich); ++ XFree(p); ++ ++ return ret; ++} ++ + int + getrootptr(int *x, int *y) + { +@@ -1034,6 +1102,7 @@ manage(Window w, XWindowAttributes *wa) + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + ++ updateicon(c); + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; +@@ -1244,6 +1313,11 @@ propertynotify(XEvent *e) + if (c == c->mon->sel) + drawbar(c->mon); + } ++ else if (ev->atom == netatom[NetWMIcon]) { ++ updateicon(c); ++ if (c == c->mon->sel) ++ drawbar(c->mon); ++ } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +@@ -1560,6 +1634,7 @@ setup(void) + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); ++ netatom[NetWMIcon] = XInternAtom(dpy, "_NET_WM_ICON", 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); +@@ -1752,6 +1827,15 @@ toggleview(const Arg *arg) + } + } + ++void ++freeicon(Client *c) ++{ ++ if (c->icon) { ++ XRenderFreePicture(dpy, c->icon); ++ c->icon = None; ++ } ++} ++ + void + unfocus(Client *c, int setfocus) + { +@@ -1773,6 +1857,7 @@ unmanage(Client *c, int destroyed) + + detach(c); + detachstack(c); ++ freeicon(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ +@@ -2007,6 +2092,13 @@ updatetitle(Client *c) + strcpy(c->name, broken); + } + ++void ++updateicon(Client *c) ++{ ++ freeicon(c); ++ c->icon = geticonprop(c->win, &c->icw, &c->ich); ++} ++ + void + updatewindowtype(Client *c) + { diff --git a/aarch64dwm/transient.c b/aarch64dwm/transient.c new file mode 100644 index 0000000..040adb5 --- /dev/null +++ b/aarch64dwm/transient.c @@ -0,0 +1,42 @@ +/* cc transient.c -o transient -lX11 */ + +#include +#include +#include +#include + +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/aarch64dwm/util.c b/aarch64dwm/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/aarch64dwm/util.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +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); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/aarch64dwm/util.h b/aarch64dwm/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/aarch64dwm/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/aarch64dwm/util.o b/aarch64dwm/util.o new file mode 100644 index 0000000000000000000000000000000000000000..8abf21897a220fa7a7c5a1d6dba79d726b8d95be GIT binary patch literal 2256 zcmb<-^>JfjWMqH=MuzPS2p&w7fnfqGg6#liIxz4u@G$(XUbpfuGsDEs!Hg?EGc&Au z(9AON0W-_W2hGeAA80fDe8AQq^dOpH=L7x*p$G8{J4F~6CQM;+`1wG(!SJ7F}v>R-n>NnUvGj6bbZr)(~cryRWr?UlCKJRDv z>A=FUwt>mvCj$e+lmsRR%L9xICJ&f}SAqD&Obp!#pW-d#UoaFhGBSiTg4D4z1cBrj z7#c2tXaxocA0+3%2x0&J|6lxX^?tBlKx!Ho7`8GnFic=zV3-Kv|E)f_;sA5}wdBN{ zocv@f1_pO$XDbB_m(--p#5@HH=031dP42)Gl3@jCLjM6;v3^EK1 z3=&ZBGa%ZLZvoR>m3hiela;0@GB7AGFfdqv#2FYER6w*NpTJzEd5rG&?(%`S_%JBW zSQtM3$EJoEYm725Ff%Y?Q_jH5z=9#p0Tuz-0%o8P%nV4014)LNfgOnlVRC~h4h9i0 zjY=>xKx{!31-X+AU5Eh`AVNs~0*5C91A`C)13c`JHA&(SSHdB#i9=i;hqxIIaci)f z7#U#h1IH@^1A`3?^$evYnK^pN3@Q~2Dk%&pnW+rLB`K*zMGR$W1x1;8C20)BB}F-@ zc?@X1*t_v`9%z=6`3UrsgSe@@wEYjVlZc9VBmqWL6ixI0HrfD@fxVOG$R88 zFO&_UdO!pN0|Q7L6y{Ja!wM(^B+d_IgQyK40;(RC2S7qQKm=4=5XuHoM?eHr9NC;J zNaDg!Hi)_dA{ZDLgc%takW1 z2XZF5-5~i5APEKrh6hj@l$SslT|XxSsJsU0K*bZF5$cR83=cP$FsN*RDuoGwX$@!< NlL6)-2&i-50sy_3$twT= literal 0 HcmV?d00001 diff --git a/aarch64statusbar/colorvars.sh b/aarch64statusbar/colorvars.sh new file mode 100644 index 0000000..6f88f91 --- /dev/null +++ b/aarch64statusbar/colorvars.sh @@ -0,0 +1,11 @@ +#Colors for status bar, allows for various theme Colors + +## Default Colors + +black="#000000" +white="#ffffff" +grey="#555555" +green="#00ff00" +yellow="#ffff00" +red="#ff0000" + diff --git a/aarch64statusbar/install.sh b/aarch64statusbar/install.sh new file mode 100755 index 0000000..c7bccb8 --- /dev/null +++ b/aarch64statusbar/install.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# DWM dependency check (uncomment if necessary) +# if ! pgrep -x "dwm" > /dev/null; then +# echo "DWM is not running. Please ensure that DWM is installed and is your window manager." +# exit 1 +# fi + +# Check for the status2D dependency +if command_exists dwm; then + echo "DWM is installed. Make sure you have the status2D patch applied for proper rendering." +fi + +# Required packages list +required_packages=("grep" "gawk" "procps" "coreutils" "lm-sensors" "network-manager" "x11-xserver-utils") + +# Install required packages +for pkg in "${required_packages[@]}"; do + if ! dpkg -s "$pkg" >/dev/null 2>&1; then + echo "Package $pkg is not installed. Installing..." + sudo apt update + sudo apt install -y "$pkg" + if [ $? -ne 0 ]; then + echo "Failed to install $pkg. Please install it manually, or its equivalent, and edit the source code." + exit 1 + fi + else + echo "Package $pkg is already installed." + fi +done + +# Copy statusbar.sh to /usr/local/bin and make it executable +sudo cp statusbar.sh /usr/local/bin/statusbar +sudo chmod +x /usr/local/bin/statusbar + +# Create the directory and copy colorvars.sh +PREFIX="$HOME/.local/share/statusbar" +mkdir -p "$PREFIX" +cp colorvars.sh "$PREFIX" + +# Setup the systemd service +SERVICE_DIR="$HOME/.config/systemd/user" +mkdir -p "$SERVICE_DIR" +cp statusbar.service "$SERVICE_DIR" + +# Enable and restart the service +systemctl --user enable statusbar.service +systemctl --user restart statusbar.service + +echo "Installation completed successfully. The statusbar is installed and the service has been enabled and restarted." diff --git a/aarch64statusbar/statusbar.service b/aarch64statusbar/statusbar.service new file mode 100644 index 0000000..ffe2983 --- /dev/null +++ b/aarch64statusbar/statusbar.service @@ -0,0 +1,14 @@ +[Unit] +[Unit] +Description=SystemBar for DWM from suckless +After=default.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/statusbar +Restart=on-failure +Environment=DISPLAY=:0 +Environment=XAUTHORITY=/home/klein/.Xauthority + +[Install] +WantedBy=default.target diff --git a/aarch64statusbar/statusbar.sh b/aarch64statusbar/statusbar.sh new file mode 100755 index 0000000..047c3b6 --- /dev/null +++ b/aarch64statusbar/statusbar.sh @@ -0,0 +1,234 @@ +#!/usr/bin/env bash + +# Source color vars +source "$HOME/.local/share/statusbar/colorvars.sh" + +# Define Basic Dimentions +base_x=0 +base_y=2 +max_height=15 +bar_width=3 +gap=1 + +cpu() { + local cpu_line1=$(grep '^cpu ' /proc/stat) + sleep 2 + local cpu_line2=$(grep '^cpu ' /proc/stat) + local -a cpu1=(${cpu_line1//cpu/}) + local -a cpu2=(${cpu_line2//cpu/}) + local total1=0 + local total2=0 + local idle1=${cpu1[3]} + local idle2=${cpu2[3]} + for i in "${cpu1[@]}"; do + total1=$((total1 + i)) + done + for i in "${cpu2[@]}"; do + total2=$((total2 + i)) + done + local total_delta=$((total2 - total1)) + local idle_delta=$((idle2 - idle1)) + + local usage=$((100 * (total_delta - idle_delta) / total_delta)) + + local usage_height=$(( (max_height * usage) / 100 )) + local usage_y=$((base_y + max_height - usage_height)) + local color=$white + if [ $usage -gt 50 ]; then + color=$red + fi + local status_line="" + status_line+="^c${grey}^^r${base_x},${base_y},${bar_width},${max_height}^" + status_line+="^c${color}^^r${base_x},${usage_y},${bar_width},${usage_height}^" + status_line+="^d^^f4^" # buffer of 1 +# local topcon=$( ps -eo %cpu,comm --sort=-%cpu | head -n 2 | tail -n 1 | awk '{print $2}') +# topcon="${topcon:0:5}" # trunkate output + echo "{CPU:${status_line}${usage}%" +} + +ram() { + local m_mem=$(free -m) + local t_mem=$(echo "$m_mem" | awk '/^Mem:/ {print $2}') + local u_mem=$(echo "$m_mem" | awk '/^Mem:/ {print $3}') + local p_mem=$(awk "BEGIN {printf \"%.0f\", ($u_mem/$t_mem)*100}") + local usage_height=$((max_height * p_mem / 100)) + local usage_y=$((base_y + max_height - usage_height)) + local status_line="" + status_line+="^c$grey^^r$base_x,${base_y},${bar_width},${max_height}^" + status_line+="^c$white^^r${base_x},${usage_y},${bar_width},${usage_height}^" + status_line+="^d^^f4^" # buffer of 1 + status_line+="${p_mem}%" + echo "M:$status_line" +} + +swap() { + local m_swap=$(free -m) + local t_swap=$(echo "$m_swap" | awk '/^Swap:/ {print $2}') + local u_swap=$(echo "$m_swap" | awk '/^Swap:/ {print $3}') + if [[ "$u_swap" -eq 0 ]]; then + return + fi + local p_swap=$(awk "BEGIN {printf \"%.0f\", ($u_swap/$t_swap)*100}") + local usage_height=$((max_height * u_swap / 100)) + local usage_y=$((base_y + max_height - usage_height)) + local status_line="" + status_line+="^c$grey^^r$base_x,${base_y},${bar_width},${max_height}^" + status_line+="^c$white^^r${base_x},${usage_y},${bar_width},${usage_height}^" + status_line+="^d^^f4^" # buffer of 1 + status_line+="${p_swap}%" + echo "|S:$status_line" +} + +disk() { + local usage_p2=$(df -h | grep '/dev/mmcblk0p2' | awk '{print $5}' | tr -d '%') + local usage_p4=$(df -h | grep '/dev/mmcblk0p1' | awk '{print $5}' | tr -d '%') + if [[ ! "$usage_p2" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + usage_p2=0 + fi + if [[ ! "$usage_p4" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + usage_p4=0 + fi + local status_line="" + local -A usages=( + [p2]=$usage_p2 + [p4]=$usage_p4 + ) + for partition in p2 p4; do + local percentage=${usages[$partition]} + local usage_height=$(($percentage * $max_height / 100)) + local usage_y=$((base_y + max_height - usage_height)) + status_line+="^c$grey^^r${base_x},${base_y},${bar_width},${max_height}^" + status_line+="^c$white^^r${base_x},${usage_y},${bar_width},${usage_height}^" + base_x=$((base_x + bar_width + gap)) + done + status_line+="^d^^f8^" #Buffer of 1 + echo "DF:${status_line}R:${usage_p2}%-U:${usage_p4}%" +} + +cpu_temperature(){ + local temp=$(awk '{print int($1 / 1000)}' /sys/class/thermal/thermal_zone0/temp) + local max_temp=80 + local color=$white + if [ "$temp" -gt "$max_temp" ]; then + color=$red + elif [ "$temp" -lt "$max_temp" ]; then + color=$green + fi + local adj_y=5 + local usage_height=$(($temp * $max_height / $max_temp)) + local usage_y=$((adj_y + 10 - usage_height)) + local temp_icon="^c$black^" + temp_icon+="^r2,${base_y},5,${max_height}^" #BG bar + temp_icon+="^c$color^" + temp_icon+="^r3,${usage_y},3,${usage_height}^" #C bar + temp_icon+="^c$black^" + temp_icon+="^r1,11,8,5^" #two rectangles + temp_icon+="^r2,10,6,7^" #to mimic a circle + temp_icon+="^d^^f11^" #Buffer of 1 + echo "${temp_icon}${temp}°C" +} + +battery() { + local throttled=$(cat /sys/devices/platform/soc/soc:firmware/get_throttled) + local capacity=0 + local status="" + + if [ $((throttled & 0x1)) -ne 0 ]; then + status+="Under-voltage detected" + fi + if [ $((throttled & 0x2)) -ne 0 ]; then + status+="ARM frequency capped" + fi + if [ $((throttled & 0x4)) -ne 0 ]; then + status+="Currently throttled" + fi + if [ $((throttled & 0x8)) -ne 0 ]; then + status+="Soft temperature limit active" + fi + if [ $((throttled & 0x10000)) -ne 0 ]; then + status+="Under-voltage has occurred since last reboot" + fi + if [ $((throttled & 0x20000)) -ne 0 ]; then + status+="ARM frequency capped has occurred since last reboot" + fi + if [ $((throttled & 0x40000)) -ne 0 ]; then + status+="Throttling has occurred since last reboot" + fi + if [ $((throttled & 0x80000)) -ne 0 ]; then + status+="Soft temperature limit has occurred since last reboot" + fi + + if [ "$throttled" -eq 0 ]; then + status+="No issues detected" + capacity=100 + fi + + # Check the core voltage using vcgencmd + local volt=$(vcgencmd measure_volts core | awk -F'=' '{print $2}') + local fill_width=$(($capacity * 9 / 100)) # corresponds to width of bar + local battery_icon="^c$black^" + battery_icon+="^r1,6,13,8^" + battery_icon+="^c$grey^" + battery_icon+="^r3,8,9,4^" + battery_icon+="^c$green^" + battery_icon+="^r3,8,$fill_width,4^" + battery_icon+="^c$black^" + battery_icon+="^r14,7,2,4^" + battery_icon+="^d^^f17^" #Buffer of 1 + echo "${battery_icon}${volt}" +} + +wifi() { + local iface=$(ip -o link show | grep -v "lo:" | awk -F': ' '{print $2}'); + local ssid=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d':' -f2) + ssid="${ssid:-No WiFi}" + ssid="${ssid:0:5}" + local dwm=$(grep "$iface" /proc/net/wireless | awk '{ print int($4) }') + if [ "$ssid" = "No WiFi" ]; then + local signal=0 + else + local signal_normalized=$(( (dwm + 90) * 100 / 60 )) + if [ $signal_normalized -gt 100 ]; then + signal=100 + elif [ $signal_normalized -lt 0 ]; then + signal=0 + else + signal=$signal_normalized + fi + fi + + local color=$white + if [ $signal -ge 66 ]; then + color=$green + elif [ $signal -le 33 ]; then + color=$red + elif [ $signal -gt 33 ] && [ $signal -lt 66 ]; then + color=$yellow + fi + + local max_bars=5 + local bars_filled=$((signal / 20)) + + local wifi_icon="^c$color^" + for i in 1 2 3 4 5; do + local width=$(( (2 * i + 1) )) + local height=$(( (2 * i + 1) )) + local adj_y=$((max_height - height)) + if [ $i -le $bars_filled ]; then + wifi_icon+="^c$color^" + else + wifi_icon+="^c$grey^" + fi + wifi_icon+="^r$((base_x + 2 * ( i - 3 ))),$((adj_y + 1)),$width,$height^" + done + wifi_icon+="^d^^f7^" + + echo " ${wifi_icon}${ssid} ${signal}%}" +} +status(){ + echo "$(cpu)|$(ram)|$(swap)$(disk)|$(cpu_temperature)|$(battery)|$(wifi)" +} +while true; do + DISPLAY=:0 + xsetroot -name "$(status)" +done diff --git a/dmenu/LICENSE b/dmenu/LICENSE new file mode 100644 index 0000000..2a64b28 --- /dev/null +++ b/dmenu/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2022 Hiltjo Posthuma +© 2015-2019 Quentin Rameau + +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 new file mode 100644 index 0000000..458c524 --- /dev/null +++ b/dmenu/Makefile @@ -0,0 +1,58 @@ +# 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: dmenu stest + +.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 clean dist install uninstall diff --git a/dmenu/README b/dmenu/README new file mode 100644 index 0000000..a8fcdfe --- /dev/null +++ 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 new file mode 100644 index 0000000..e94e02b --- /dev/null +++ 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 new file mode 100644 index 0000000..f9279b6 --- /dev/null +++ b/dmenu/config.def.h @@ -0,0 +1,28 @@ +/* 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" }, + [SchemeBorder] = { "#282a2e", NULL }, +}; +/* -l and -g options; controls number of lines and columns in grid if > 0 */ +static unsigned int lines = 6; +static unsigned int columns = 4; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 3; diff --git a/dmenu/config.def.h.orig b/dmenu/config.def.h.orig new file mode 100644 index 0000000..4c67620 --- /dev/null +++ b/dmenu/config.def.h.orig @@ -0,0 +1,27 @@ +/* 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 and -g options; controls number of lines and columns in grid if > 0 */ +static unsigned int lines = 6; +static unsigned int columns = 4; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 3; diff --git a/dmenu/config.def.h.rej b/dmenu/config.def.h.rej new file mode 100644 index 0000000..f551121 --- /dev/null +++ b/dmenu/config.def.h.rej @@ -0,0 +1,10 @@ +--- config.def.h ++++ config.def.h +@@ -22,3 +23,7 @@ static unsigned int lines = 0; + * for example: " /?\"&[]" + */ + static const char worddelimiters[] = " "; ++ ++/* Size of the window border */ ++static unsigned int border_width = 0; ++ diff --git a/dmenu/config.h b/dmenu/config.h new file mode 100644 index 0000000..6766195 --- /dev/null +++ b/dmenu/config.h @@ -0,0 +1,27 @@ +/* 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] = { +/* Scheme fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l and -g options; controls number of lines and columns in grid if > 0 */ +static unsigned int lines = 6; +static unsigned int columns = 4; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static unsigned int border_width = 3; diff --git a/dmenu/config.mk b/dmenu/config.mk new file mode 100644 index 0000000..137f7c8 --- /dev/null +++ b/dmenu/config.mk @@ -0,0 +1,32 @@ +# dmenu version +VERSION = 5.3 + +# 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 +#MANPREFIX = ${PREFIX}/man + +# 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 new file mode 100755 index 0000000000000000000000000000000000000000..13478acf7046dab73eabb6b5971123413f22f8fd GIT binary patch literal 77872 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeBorder, 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 unsigned int +textw_clamp(const char *str, unsigned int n) +{ + unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + +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 * columns * bh) - 1; + 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 : textw_clamp(next->text, n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) + break; +} + +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + for (i = 0; items && items[i].text; ++i) + free(items[i].text); + free(items); + 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 int +drawdate(int x, int y, int w) +{ + char date[128]; + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + /* Hour:Minute DayOfTheWeek DayOfTheMonth Month Year */ + strftime(date, sizeof(date), "%H:%M %A %d %B %Y", tm); + + drw_setscheme(drw, scheme[SchemeSel]); + + int r = drw_text(drw, x, y, w, bh, lrpad / 2, date, 0); + return r; +} + +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 grid */ + int i = 0; + for (item = curr; item != next; item = item->right, i++) + drawitem( + item, + x + ((i / lines) * ((mw - x) / columns)), + y + (((i % lines) + 1) * bh), + (mw - x) / columns + ); + } 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, textw_clamp(item->text, mw - x - TEXTW(">"))); + + drawdate(x, lines * bh, w); + 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 %zu 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[64]; + int len; + KeySym ksym = NoSymbol; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: /* composed string from input method */ + goto insert; + case XLookupKeySym: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ + 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((unsigned char)*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; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + 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 *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; + + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) + die("strdup:"); + + items[i].out = 0; + } + free(line); + if (items) + items[i].text = NULL; + 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; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#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; + + mw = MIN(MAX(max_textw() + promptw, 100), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + mw = MIN(MAX(max_textw() + promptw, 100), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } + inputw = mw / 3; /* input width: ~33% of monitor width */ + 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, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + if (border_width) + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + 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) { + XReparentWindow(dpy, win, parentwin, x, y); + 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) +{ + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); +} + +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], "-g")) { /* number of columns in grid */ + columns = atoi(argv[++i]); + if (lines == 0) lines = 1; + } else if (!strcmp(argv[i], "-l")) { /* number of lines in grid */ + lines = atoi(argv[++i]); + if (columns == 0) columns = 1; + } 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 if (!strcmp(argv[i], "-bw")) + border_width = atoi(argv[++i]); /* border width */ + 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.c.orig b/dmenu/dmenu.c.orig new file mode 100644 index 0000000..b2def05 --- /dev/null +++ b/dmenu/dmenu.c.orig @@ -0,0 +1,820 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeBorder, 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 unsigned int +textw_clamp(const char *str, unsigned int n) +{ + unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + +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 * columns * 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 : textw_clamp(next->text, n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) + break; +} + +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + for (i = 0; items && items[i].text; ++i) + free(items[i].text); + free(items); + 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 grid */ + int i = 0; + for (item = curr; item != next; item = item->right, i++) + drawitem( + item, + x + ((i / lines) * ((mw - x) / columns)), + y + (((i % lines) + 1) * bh), + (mw - x) / columns + ); + } 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, textw_clamp(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 %zu 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[64]; + int len; + KeySym ksym = NoSymbol; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: /* composed string from input method */ + goto insert; + case XLookupKeySym: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ + 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((unsigned char)*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; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + 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 *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; + + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) + die("strdup:"); + + items[i].out = 0; + } + free(line); + if (items) + items[i].text = NULL; + 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; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#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; + + mw = MIN(MAX(max_textw() + promptw, 100), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + mw = MIN(MAX(max_textw() + promptw, 100), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } + inputw = mw / 3; /* input width: ~33% of monitor width */ + 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, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + if (border_width) + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + 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) { + XReparentWindow(dpy, win, parentwin, x, y); + 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) +{ + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); +} + +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], "-g")) { /* number of columns in grid */ + columns = atoi(argv[++i]); + if (lines == 0) lines = 1; + } else if (!strcmp(argv[i], "-l")) { /* number of lines in grid */ + lines = atoi(argv[++i]); + if (columns == 0) columns = 1; + } 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 if (!strcmp(argv[i], "-bw")) + border_width = atoi(argv[++i]); /* border width */ + 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.c.rej b/dmenu/dmenu.c.rej new file mode 100644 index 0000000..d3f610a --- /dev/null +++ b/dmenu/dmenu.c.rej @@ -0,0 +1,20 @@ +--- dmenu.c ++++ dmenu.c +@@ -84,7 +84,7 @@ calcoffsets(void) + int i, n; + + if (lines > 0) +- n = lines * bh; ++ n = (lines * bh) - 1; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ +@@ -188,6 +204,8 @@ drawmenu(void) + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); ++ ++ drawdate(x, lines * bh, w); + } else if (matches) { + /* draw horizontal list */ + x += inputw; diff --git a/dmenu/dmenu.o b/dmenu/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..35a7fd9f0771913db853767cfb1b27cca6eb0afb GIT binary patch literal 28232 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 H(e(oWyTaJD literal 0 HcmV?d00001 diff --git a/dmenu/dmenu_path b/dmenu/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ 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 new file mode 100755 index 0000000..834ede5 --- /dev/null +++ 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 new file mode 100644 index 0000000..78a2b27 --- /dev/null +++ b/dmenu/drw.c @@ -0,0 +1,451 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#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."); + } + + 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) +{ + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + 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, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } 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; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = 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) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + 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; + + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + + 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); + + 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); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: + 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); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +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 new file mode 100644 index 0000000..fd7631b --- /dev/null +++ b/dmenu/drw.h @@ -0,0 +1,58 @@ +/* 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); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +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 new file mode 100644 index 0000000000000000000000000000000000000000..b5adff63e7e3d1f95e8adc78a47bb9e62145637f GIT binary patch literal 11280 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 i14BHTB*;Fn0w^KK0Gd~Ua*!wmXlDU7t_zbw*AD +Date: Sun, 14 May 2023 01:04:09 +0200 +Subject: [PATCH] Makes border colors independent from SelBg +--- + config.def.h | 5 +++++ + dmenu.c | 9 +++++++-- + 2 files changed, 12 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..ae41b06 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,6 +12,7 @@ static const char *colors[SchemeLast][2] = { + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, ++ [SchemeBorder] = { "#cccccc", NULL }, + }; + /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ + static unsigned int lines = 0; +@@ -21,3 +22,7 @@ static unsigned int lines = 0; + * for example: " /?\"&[]" + */ + static const char worddelimiters[] = " "; ++ ++/* Size of the window border */ ++static unsigned int border_width = 0; ++ +diff --git a/dmenu.c b/dmenu.c +index 62f1089..7f063b6 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -26,7 +26,7 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeOut, SchemeBorder, SchemeLast }; /* color schemes */ + + struct item { + char *text; +@@ -685,9 +685,11 @@ setup(void) + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; +- win = XCreateWindow(dpy, root, x, y, mw, mh, 0, ++ win = XCreateWindow(dpy, root, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); ++ if (border_width) ++ XSetWindowBorder(dpy, win, scheme[SchemeBorder][ColFg].pixel); + XSetClassHint(dpy, win, &ch); + + +@@ -759,6 +761,8 @@ main(int argc, char *argv[]) + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; ++ else if (!strcmp(argv[i], "-bw")) ++ border_width = atoi(argv[++i]); /* border width */ + else + usage(); + +@@ -795,3 +799,4 @@ main(int argc, char *argv[]) + + return 1; /* unreachable */ + } ++ +-- +2.40.1 diff --git a/dmenu/patches/dmenu-center-4.8.diff b/dmenu/patches/dmenu-center-4.8.diff new file mode 100644 index 0000000..a970fcb --- /dev/null +++ b/dmenu/patches/dmenu-center-4.8.diff @@ -0,0 +1,56 @@ +diff --git a/dmenu.c b/dmenu.c +index 5e9c367..2268ea9 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -88,6 +88,15 @@ calcoffsets(void) + break; + } + ++static int ++max_textw(void) ++{ ++ int len = 0; ++ for (struct item *item = items; item && item->text; item++) ++ len = MAX(TEXTW(item->text), len); ++ return len; ++} ++ + static void + cleanup(void) + { +@@ -598,6 +607,7 @@ setup(void) + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; ++ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + #ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { +@@ -624,9 +634,9 @@ setup(void) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + +- x = info[i].x_org; +- y = info[i].y_org + (topbar ? 0 : info[i].height - mh); +- mw = info[i].width; ++ mw = MIN(MAX(max_textw() + promptw, 100), info[i].width); ++ x = info[i].x_org + ((info[i].width - mw) / 2); ++ y = info[i].y_org + ((info[i].height - mh) / 2); + XFree(info); + } else + #endif +@@ -634,11 +644,10 @@ setup(void) + 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; ++ mw = MIN(MAX(max_textw() + promptw, 100), wa.width); ++ x = (wa.width - mw) / 2; ++ y = (wa.height - mh) / 2; + } +- promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = MIN(inputw, mw/3); + match(); + diff --git a/dmenu/patches/dmenu-date-5.2.diff b/dmenu/patches/dmenu-date-5.2.diff new file mode 100644 index 0000000..a46db90 --- /dev/null +++ b/dmenu/patches/dmenu-date-5.2.diff @@ -0,0 +1,57 @@ +From 64fb38e3778addc0272eb8793deb909e639e5746 Mon Sep 17 00:00:00 2001 +From: piotr-marendowski +Date: Thu, 6 Jul 2023 10:03:03 +0000 +Subject: [PATCH] Displays date and time at the bottom of the vertical layout. + +--- + dmenu.c | 20 +++++++++++++++++++- + 1 file changed, 19 insertions(+), 1 deletion(-) + +diff --git a/dmenu.c b/dmenu.c +index 7cf253b..5428f67 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -84,7 +84,7 @@ calcoffsets(void) + int i, n; + + if (lines > 0) +- n = lines * bh; ++ n = (lines * bh) - 1; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ +@@ -143,6 +143,22 @@ drawitem(struct item *item, int x, int y, int w) + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); + } + ++static int ++drawdate(int x, int y, int w) ++{ ++ char date[128]; ++ time_t t = time(NULL); ++ struct tm *tm = localtime(&t); ++ ++ /* Hour:Minute DayOfTheWeek DayOfTheMonth Month Year */ ++ strftime(date, sizeof(date), "%H:%M %A %d %B %Y", tm); ++ ++ drw_setscheme(drw, scheme[SchemeSel]); ++ ++ int r = drw_text(drw, x, y, w, bh, lrpad / 2, date, 0); ++ return r; ++} ++ + static void + drawmenu(void) + { +@@ -172,6 +188,8 @@ drawmenu(void) + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); ++ ++ drawdate(x, lines * bh, w); + } else if (matches) { + /* draw horizontal list */ + x += inputw; +-- +2.41.0 + diff --git a/dmenu/patches/dmenu-grid-4.9.diff b/dmenu/patches/dmenu-grid-4.9.diff new file mode 100644 index 0000000..c27689b --- /dev/null +++ b/dmenu/patches/dmenu-grid-4.9.diff @@ -0,0 +1,107 @@ +From 39ab9676914bd0d8105d0f96bbd7611a53077438 Mon Sep 17 00:00:00 2001 +From: Miles Alan +Date: Sat, 4 Jul 2020 11:19:04 -0500 +Subject: [PATCH] Add -g option to display entries in the given number of grid + columns + +This option can be used in conjunction with -l to format dmenu's options in +arbitrary size grids. For example, to create a 4 column by 6 line grid, you +could use: dmenu -g 4 -l 6 +--- + config.def.h | 3 ++- + dmenu.1 | 7 ++++++- + dmenu.c | 22 ++++++++++++++++------ + 3 files changed, 24 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1edb647..96cf3c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,8 +13,9 @@ static const char *colors[SchemeLast][2] = { + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, + }; +-/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ ++/* -l and -g options; controls number of lines and columns in grid if > 0 */ + static unsigned int lines = 0; ++static unsigned int columns = 0; + + /* + * Characters not considered part of a word while deleting words +diff --git a/dmenu.1 b/dmenu.1 +index 323f93c..d0a734a 100644 +--- a/dmenu.1 ++++ b/dmenu.1 +@@ -4,6 +4,8 @@ dmenu \- dynamic menu + .SH SYNOPSIS + .B dmenu + .RB [ \-bfiv ] ++.RB [ \-g ++.IR columns ] + .RB [ \-l + .IR lines ] + .RB [ \-m +@@ -47,8 +49,11 @@ is faster, but will lock up X until stdin reaches end\-of\-file. + .B \-i + dmenu matches menu items case insensitively. + .TP ++.BI \-g " columns" ++dmenu lists items in a grid with the given number of columns. ++.TP + .BI \-l " lines" +-dmenu lists items vertically, with the given number of lines. ++dmenu lists items in a grid with the given number of lines. + .TP + .BI \-m " monitor" + dmenu is displayed on the monitor number supplied. Monitor numbers are starting +diff --git a/dmenu.c b/dmenu.c +index 6b8f51b..d79b6bb 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -77,7 +77,7 @@ calcoffsets(void) + int i, n; + + if (lines > 0) +- n = lines * bh; ++ n = lines * columns * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ +@@ -152,9 +152,15 @@ drawmenu(void) + } + + if (lines > 0) { +- /* draw vertical list */ +- for (item = curr; item != next; item = item->right) +- drawitem(item, x, y += bh, mw - x); ++ /* draw grid */ ++ int i = 0; ++ for (item = curr; item != next; item = item->right, i++) ++ drawitem( ++ item, ++ x + ((i / lines) * ((mw - x) / columns)), ++ y + (((i % lines) + 1) * bh), ++ (mw - x) / columns ++ ); + } else if (matches) { + /* draw horizontal list */ + x += inputw; +@@ -708,9 +714,13 @@ main(int argc, char *argv[]) + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ +- else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ ++ else if (!strcmp(argv[i], "-g")) { /* number of columns in grid */ ++ columns = atoi(argv[++i]); ++ if (lines == 0) lines = 1; ++ } else if (!strcmp(argv[i], "-l")) { /* number of lines in grid */ + lines = atoi(argv[++i]); +- else if (!strcmp(argv[i], "-m")) ++ if (columns == 0) columns = 1; ++ } 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]; +-- +2.23.1 + diff --git a/dmenu/stest b/dmenu/stest new file mode 100755 index 0000000000000000000000000000000000000000..d1198713a3cd9a89fdd13afc8c83acc6e1053fbc GIT binary patch literal 71384 zcmb<-^>JfjWMqH=W`^wyU>+|-!~sGv%-{!$f_V-M77UyW4h*skG7M}C3=Av`3=A-J z==2Y$HWuCo`|KLN_t7C^^H-M6Wns&k&LxL>M4(?CuxJpm;>iRyfOYebUE} zr(Hkp@Rw%i&jPs{$h`|7fFOVDvLxqT@m>IYkG&nJpGc$-YOkjX| z2r8}4z`y`aGEfl+Ws5@`lpe9E-^0Mbpa9W|LdG#LFessjKsYrx%=c$tU=V~TK_Nlm zfX!Ym9OjhZ5C?@hHuH6GsK0^3U)(s zMadaO3`MDlDIjC=3sUnybYgOHYH=|`Vp4ul2?NMPP`)x_aQE?aa*j9BGd4yrP2fyJ zbB1`&5a0Ne)S}e%%;J*Nq7Yx_ocz4hki?`Mkkit0^Yfrii-#!1st}s$m>8HDm>3ut zn4pjaN;5LRSP&VI3Xm2?24;qd3=9mQ^abK?o;ZySR1(aFifsY&nHiQr`P0FCCWeh5 z#S9D#uyU9gR4{vfFus9lRyS(Ac;eR3?$58fFuse+h9Qk1_lcx zaUPf;xV}fY9~xC)Sq~)j$l(xxBo0d>APo^n;>hJv0+Kj#e$PM>7X}#s#RW*>pt=Vt z%us)5%)C;Zkzs-a1H*=&*(@u6E@GN!rOYrfvCZKJLpAfnhhYpqzeY2y z{9Vkt^1*+$mH+J-ekL$E{A^%k*uu!*U}?XFp-_>LA%u~|!SW#kg9(WLkeMNbk;B1q zAv1$%u@b|?DQpaZQ#ct+zcMg<*!s-<=a>XO$539Z?(h92StuMKL@c_nZ&Xk|NReH_y526Lu;^GE-GatSj1~E6go06gxF^&Sb)SIFf*>o zU|^Us=l_54HAdc-Wb>H)LVDn^E;OV%Ji{;@Ay@#lZYLuOI1cr*jU zl!yQSizil<{Q#K}&cHC`(f|MAuzZP3%Q7@vl4EGN^pu%l)k9{7pO2IoCO%_knD~H! z;RC}P_n*uR4VPFL8ZHU`|3BRV6c!-y7w$hD7#Ozb{QoZwid&d@42%pD7#f%+GBPqu zP;6kDXwSg#`Tzq%$U_E(4~jS3eMuL;Gnqh{P%zQ4NzQw(q0pjyohOHQk=uicm{^-2mA~n30@9675E`?TmSu^p0J2%A_IrR zPEh(@z~o>lzk{LB;otudusDyy&IJq%CJ&q)b~5mY2+2!06ej+K$_b#!aX1t<{(;Jg zpveh16fXP?m6LGT`GB3l6l}f>nmPrC!o*)tbqWqU6QFh<_!({nRvNCJ)^melnyu`~<~&?+=JxMh7IhB@nq)-yw1gE@*bLI20cI29@)1*m-~jVm?Cv zI800)7(4s~l|^g6Le)ed+{N%U9^$S9hn)+Uq54uBc0OQcFnPc%vPzMmA*kmcxGV#g z2XjIBn^|NPBSS+_+rR(QAAJ9BG98p34}-=|8#{v|4mx{{})$aXb5VC(g{V3J0CEMuFC)Se>$kV z{3;Aef0yCu50vge=?zx5g4&j)#fj;uRtl=c3emcWNy#axY3Ug`1%*Y$rDf$6u?o?; zc?xNnIjJC4K8ytt(9_e4te*gb(0#fJ5E(cNv>TBKj{r|rKNDS201-GXd7{2`e z|KA2G2I@@=Cl@7~?Z;3gNyERZ{W{`~(BYb%1)zd~m#=^f z28GK71_p+dAOHUsfYdtiH7GjHoxn1$pT%i%AIp?p7U$_*EHgS;oM(2h%xY(GpW4GR zt(#?b8_S$lmJF`D_io?0dF%R(tJgs0IU+YFK|UG9qaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O0}uiNd=N_y@Iq)t z2*ofFO7lQz4NeFHs*IsS0K%7qPz*c%LUc+)`5aJM3Q8Y<%F96cF!`_l{^x^)W-x(B z1_o9L#UKD>utE7dpz>T$J|o0B25u-{0!oWQX&A)}WxV(g(Ev4uVFQHAAOe+VhtlFu zx&kTyr5HM(4uQG*15_O}SQudT|Nam04+8@OSm{3~-vg@uKa>x(p5X(OF9enU3FWgu z&8IeX0vZp*n%e*iZ~__}paC&hXqa@{bdD;{%>c{qsNy^fu=qt4=Vic_PM8_^;N=9WdVYpE(DDOS zT!7&RR&hZF4?$#=5H>S|5WL)hh(JhY24MzRc>@uFkjxAs4A{~?GlM9@3TXKNQ3)ZL z8N?W1PF~c9^LTv7VnQww-KFEG123`i3IIJ9n zsK-n{5OK`(pv%C(fZJYf1`CLTp}t~ZfSEG|EuEO)HHVi$1X|C*46`Rw0L)CU|?WmkYLyV70-a0?*kQ=V1yXl02L2_iqAk3 z4}pqHFhSJA>II0sf(#eX;x~#wI0T@@V=C0Y=;G;6@dh;Y5PQWLVC5>*`3zN1^{{pX zOnd^={jhuiO7|c+h&h<~bPiZOrubo~`LOm1$P5sMn1fjkoClkODGpID$WVY5ZV++I z{EI6*A?gJgQiMRRK;U;^dojg9DzK&VKVbDA|D%^ntl)scEDt3ZLFww_fAsPSl0GrZ zXFWy+1~eyvoWfv-Lp&OXcrGIY11QgfH6sz4OdxU0^01SUfk6OEc{rVsfk6pLKa9B% zhrLG_85q!_3asS^)O-bKz5oj{Ffd#Li;H8F!*_9*{}Ck4gjp_q2b&|ofNnlB6ZY^F z!6B~B1PTYt@G)V+?k`sy=0t+S8Rkm_&4NRH2FQFS%zRpkLwy@aJp-a%fR!^-aj0L8 zLwq|9@l)V%zzomxIMnNc#S!%ysC)O`GbsI|nhoNafy`&ZtOspC;vfa+I5@ee#85Av zK_!JDK0YxiGrlA-ok697A-Sj|Q!k$(IWZ?EzC1NCJ3cKBB$lU_&rn>FT3n)+%urgK zn4Zc2;xMG;B&IXurIs_~=cF*?CYB^;fD{*(l%}QWB{QU?7Nw?V7MG+J#h2v9C+Fnn zr4}>9$EW1Sr|0A+CFaDZl;jr`$0wFnFeK;a7UZOsq^9T@U{?{JmYJ6spIB6sSQ($1 zS5j2TkXDqKn;M@|nwwk65Fd}ki_grgN(4&k*kt>F4O{>C6xx z@9q~G@9F`S^>7Jdhgv+4Ds>F6^S6PCgx;Tr6QY=Uyz!Yl8It$aY;&Q5sFBBYD!{BB13UrK~ZL2NgA?# zkfE7S+fwsV;^P@|ic1m^zA`puC`wICL9-%09%LZIc?`uRDM&swG-pW8$uCaD(2<^- zp9j$a3hVfk;{5oG#JrT8REET){33*n;DGUu2b&sSTAZ4~keHmDT3n3m>Vnb|6n=VY zNls=SO2EXY=A|&C6{VsIf<(dA!$X&$xTGjK10}dXF~^WtlwJl(b0NOYu=D|nE?CM% zjVw^gVo1XbT?W14%G{E~BnG|Wk|GG50b^z6m82FGFzDswm!#^Y=auRum1gFo=w_xs zg&dtcbwQaAESHg3oWY=%Qkhp=nG2yyiWtB$WvNBQnfZArocJOJy`t2dM34q3t01R@ zK@aRqy^_?55(Yg`zG2WS$_GUxgI;O|G^b>w6d`yJ9gr*o(E($pROTh-W+pS}rRSG` z2|cg{5W|v+iy8Ei^K)}k^GeXm7c%*v@f=W71X?`8`@yjOFi0NRXpmZDzA^&?1858d zrXSW{hS4g>iotDHQ~@(I{jh#Dj7D#h!0IiSDJ~2Q4B!6$&xhF$>wm*&*mx9lxCt_7 z1JlgFz~INg03Kt4>4){xVRQsk0yKOB8f${p7qIc5Fa`z&kT_Hsygv^c--3-}!K_3N zzc>a42GE!hOh2sO52KBs8e#5-se{qk3=9mQF(?=xHa-BO9iaL_?g!Zob3arV+^z?O zDNH|X91KRo#=&6r!}P=a-vW(OP`?UfCTzR`M#IM8K>9&#Sakh;;Px>C1IT?K{jhNf z7!9)rBnQGEw}NOG?nKiM8^3_lAWy>-gX(!CHi$U|O+Rd$14eVe90pR%z`y_tPbimR z7Mgz8xG9W=^_yYp(cQlQsvj2quyGR@4eFo3^nz$~{VUM)!^T%&G^jrc(g(xn`nRF! zhmFI)Xa%(JgT)`ryaNmj44|$wC=Y{u>OCv;(sr*3UWt)&BtMepocZ?1c?4JcRlmrXSYtS^*uoL62{kei;1>O+Rcr z=>=3jD1Jcxf?=3G82uhiKWyA-0t3Y1Fn6Nshw;Bc2clu_hqaFvK=n63jDab~DF1#z z?T3wzz!bvt%fkeqG|V_;8kTmUO2E_snElZ4Wl%lh33V$-0W4p0BE>yO9EKgZAvXBK lJO-73h(b9Gptd}u{6&aBSp9qumCO(dA_(;>oWsDt0035|j4J>D literal 0 HcmV?d00001 diff --git a/dmenu/stest.1 b/dmenu/stest.1 new file mode 100644 index 0000000..2667d8a --- /dev/null +++ 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 new file mode 100644 index 0000000..e27d3a5 --- /dev/null +++ b/dmenu/stest.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#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 new file mode 100644 index 0000000000000000000000000000000000000000..ec73ba84171aaaccdba5730425366db26f395ab7 GIT binary patch literal 5240 zcmb<-^>JfjWMqH=MuzPS2p&w7fnkRLg6#liIxz4v@G<fvFqz9|lj3?>W=4MD|B3=@;$9CpStFl0aAX9!8~a@eWB50L|zmB7R>k%7ZuX9EL+ z=>jGPOZgoPg%1Dzhk(U-9Cj{XU@&>$?68x8M?^?o!l5wnFH}wdO^(B%u<;L6P6SO( zz@c#AZ>XGv!_Ein45nc7Wzf_qI20!Sf~r$+*qH#e`@qj|Gq8FUhn)-9AbJ^X9Cj{b zU@&>;?(mZ##o?zt1HVHSE!l@gu58N#zWkd;IMN6GgM!S!_EiH3?>hlMOG<7<4c}_q40tD ze5twr|BHjf8KL42zW+Cw{{O%D!^2DyLE_-}`1IfO!C`24Yd91>{P;iQ2vlDqR3F3d z|0bPKF-NGF!k_;pE&u5kfxbmy8!_U9fmscEMj=u&< zex=2U>8VxKS?x`|22DXD4c894=oMa89MXHurUNq;bbrsWnh@FmC51fm&c3~ zKObhCxQNN&=R;CDe zIx;Yr%wS+J0l8lVg9c@~Dk4rYds18feKpgg#Z z&EaPl6T?!=iMegP zOtfTXm?*~3a4FH;VP`^B*^h-Rpt6Et$`59SRSql+TYfQv(y9d54GK&QTOTk(*dVvg zXA;YH{P#a(9VktM!jy&KqEbeJMZ5+>p(6uBh<%2F#Y1a{pAVQB!TRU?|1X|gRrW*F zhhgFqX3<7$@NB{qe zGq^iDTPbL`q$Xu1<|!B&=^5!6=o*@6LYWM(VwbThh=HX-j!~LNoIZF@9x#hSNIqh7k7Y_Pi}Um@mKmKa&NDk$X0@}pPwiou*3B}z zjb%XFa>cB7dXv; z1&|141}-EXg2@bbB|=P>fdQTt5uyy>3S#l;Lc#U+U)3)%N=z0sNfJcz#;B~Lp%eAcnc13P{#x0RCM>Oz@dH*4)GH>#P2}GLGcbtOCbBc z;1K5k)uqsQmjo$Oti;ND4iFb$|pJ7#KkL2$pw1LOmb?nvamhmq5iq;U)`ZgQzVa0vbMYP&SC# z0U{U}7(nhp&KE97;S3EDu*w7^ape3p14$fIw}KTjFfc4Z5{HE$NNx*M92Rb{e7gfG zj_w}h^aBbvnEDG)^&tN$f((Y@CrIK-P%#ko0ZANG*Mr1BSOw}ykU1bGte}LoM?m7R zxCW_@04aioCx{7Cp8?`v5r@SQDq4)%GPfi#i9xTp zqzFQ1z*t48If;5DsTCy*dMSw|i41y4#l;MIMfo5ONV%b&A%h-VkzR30Vo^zH0fSz0 zer|4RUI~L0BuqkK%20j_9n<4SbYY|`)c5J9H^}e(uKfV(CmfDfXslg zK{O~|gUp0l0}=w`9Z>xxXc|Cj!3v;+A=G{&C?7__^oPL&pft=l80`wxj~;#kPzS-< zT~O^PbvsB56mFn=4x&MP7~a4OVZq8pQ22rDfVm$QexP<9$StsR3em;D&;ZJm3=Hsc z5+)8(3u41)P(2J|gT!HY0aQPX52Ml57DD|G4Q`k+2AKX8a1jUt=6(o|fguM+`uV^Q zQ2}cAf>j&j!s1lGiIKBarU|?Wa g2p54cK=wgc5K<7_e}k}*NegHLtr}Sn8dk_c0C>^8HUIzs literal 0 HcmV?d00001 diff --git a/dmenu/util.c b/dmenu/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/dmenu/util.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +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); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/dmenu/util.h b/dmenu/util.h new file mode 100644 index 0000000..c0a50d4 --- /dev/null +++ b/dmenu/util.h @@ -0,0 +1,9 @@ +/* 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)) +#define LENGTH(X) (sizeof (X) / sizeof (X)[0]) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dmenu/util.o b/dmenu/util.o new file mode 100644 index 0000000000000000000000000000000000000000..8abf21897a220fa7a7c5a1d6dba79d726b8d95be GIT binary patch literal 2256 zcmb<-^>JfjWMqH=MuzPS2p&w7fnfqGg6#liIxz4u@G$(XUbpfuGsDEs!Hg?EGc&Au z(9AON0W-_W2hGeAA80fDe8AQq^dOpH=L7x*p$G8{J4F~6CQM;+`1wG(!SJ7F}v>R-n>NnUvGj6bbZr)(~cryRWr?UlCKJRDv z>A=FUwt>mvCj$e+lmsRR%L9xICJ&f}SAqD&Obp!#pW-d#UoaFhGBSiTg4D4z1cBrj z7#c2tXaxocA0+3%2x0&J|6lxX^?tBlKx!Ho7`8GnFic=zV3-Kv|E)f_;sA5}wdBN{ zocv@f1_pO$XDbB_m(--p#5@HH=031dP42)Gl3@jCLjM6;v3^EK1 z3=&ZBGa%ZLZvoR>m3hiela;0@GB7AGFfdqv#2FYER6w*NpTJzEd5rG&?(%`S_%JBW zSQtM3$EJoEYm725Ff%Y?Q_jH5z=9#p0Tuz-0%o8P%nV4014)LNfgOnlVRC~h4h9i0 zjY=>xKx{!31-X+AU5Eh`AVNs~0*5C91A`C)13c`JHA&(SSHdB#i9=i;hqxIIaci)f z7#U#h1IH@^1A`3?^$evYnK^pN3@Q~2Dk%&pnW+rLB`K*zMGR$W1x1;8C20)BB}F-@ zc?@X1*t_v`9%z=6`3UrsgSe@@wEYjVlZc9VBmqWL6ixI0HrfD@fxVOG$R88 zFO&_UdO!pN0|Q7L6y{Ja!wM(^B+d_IgQyK40;(RC2S7qQKm=4=5XuHoM?eHr9NC;J zNaDg!Hi)_dA{ZDLgc%takW1 z2XZF5-5~i5APEKrh6hj@l$SslT|XxSsJsU0K*bZF5$cR83=cP$FsN*RDuoGwX$@!< NlL6)-2&i-50sy_3$twT= literal 0 HcmV?d00001 diff --git a/pinentry-dmenu/.gitignore b/pinentry-dmenu/.gitignore new file mode 100644 index 0000000..7353e7e --- /dev/null +++ b/pinentry-dmenu/.gitignore @@ -0,0 +1,2 @@ +build +*.o diff --git a/pinentry-dmenu/LICENSE b/pinentry-dmenu/LICENSE new file mode 100644 index 0000000..c7aea18 --- /dev/null +++ b/pinentry-dmenu/LICENSE @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, 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 Library 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 diff --git a/pinentry-dmenu/Makefile b/pinentry-dmenu/Makefile new file mode 100644 index 0000000..bbef800 --- /dev/null +++ b/pinentry-dmenu/Makefile @@ -0,0 +1,66 @@ +# pinentry-dmenu - dmenu-like stupid pin entry +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = pinentry-dmenu.c drw.c util.c +OBJ = ${SRC:.c=.o} +OBJ_PIN = pinentry/pinentry.o pinentry/util.o pinentry/password-cache.o pinentry/argparse.o pinentry/secmem.o + +all: options pinentry-dmenu + +options: + @echo pinentry-dmenu build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +${OBJ}: config.h config.mk drw.h + +pinentry: + $(MAKE) -C pinentry + +pinentry-dmenu: pinentry pinentry-dmenu.o drw.o util.o + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${OBJ_PIN} ${LDFLAGS} -lassuan -lgpgme -lgpg-error -lconfig + +clean: + @echo cleaning + @rm -f pinentry-dmenu ${OBJ} + $(MAKE) -C pinentry/ clean + +dist: clean + @echo creating dist tarball + @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 + @echo installing executable to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f pinentry-dmenu ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/pinentry-dmenu + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g;s/DATE/${DATE}/g;s/BUGREPORT/${BUGREPORT}/g" < pinentry-dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/pinentry-dmenu.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/pinentry-dmenu.1 + +uninstall: + @echo removing executable from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/pinentry-dmenu + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/pinentry-dmenu.1 + +.PHONY: all options clean dist install pinentry uninstall diff --git a/pinentry-dmenu/README.md b/pinentry-dmenu/README.md new file mode 100644 index 0000000..2b0bf08 --- /dev/null +++ b/pinentry-dmenu/README.md @@ -0,0 +1,71 @@ +pinentry-dmenu +============== + +pinentry-dmenu is a pinentry program with the charm of [dmenu](https://tools.suckless.org/dmenu). + +This program is a fork from [spine](https://gitgud.io/zavok/spine.git) which is also a fork from [dmenu](https://tools.suckless.org/dmenu). + + +NO FURTHER DEVELOPMENT +---------------------- + +This project is no longer under development. If you have another opinion feel free to fork it. + + +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 + + +Config +------ +To use pinentry-dmenu add in `~/.gnupg/gpg-agent.conf`: + + pinentry-program + +The config is located in `~/.gnupg/pinentry-dmenu.conf`. + +Parameter | Default | Description +:------------------ |:----------------- |:----------- +asterisk | * | Defines the symbol which is showed for each typed character +bottom | false | pinentry-dmenu appears at the bottom of the screen +min_password_length | 32 | The minimal space of the password field. This value has affect to the description field after the password field +monitor | -1 | pinentry-dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0 +prompt | "" | Defines the prompt to be displayed to the left of the input field +font | monospace:size=10 | Defines the font or font set used +prompt_bg | #bbbbbb | Defines the prompt background color +prompt_fg | #222222 | Defines the prompt foreground color +normal_bg | #bbbbbb | Defines the normal background color +normal_fg | #222222 | Defines the normal foreground color +select_bg | #eeeeee | Defines the selected background color +select_fg | #005577 | Defines the selected foreground color +desc_bg | #bbbbbb | Defines the description background color +desc_fg | #222222 | Defines the description foreground color +embedded | false | Embed into window + + +Example +------- +``` +asterisk= "# "; +prompt = "$"; +font = "Noto Sans UI:size=13"; +prompt_fg = "#eeeeee"; +prompt_bg = "#d9904a"; +normal_fg = "#ffffff"; +normal_bg = "#000000"; +select_fg = "#eeeeee"; +select_bg = "#d9904a"; +desc_fg = "#eeeeee"; +desc_bg = "#d9904a"; +``` diff --git a/pinentry-dmenu/config.h b/pinentry-dmenu/config.h new file mode 100644 index 0000000..6d64672 --- /dev/null +++ b/pinentry-dmenu/config.h @@ -0,0 +1,21 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int bottom = 0; +static int embedded = 0; +static int minpwlen = 32; +static int mon = -1; +static int lineheight = 0; +static int min_lineheight = 8; + +static const char *asterisk = "*"; +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; +static const char *colors[SchemeLast][4] = { + [SchemePrompt] = { "#bbbbbb", "#222222" }, + [SchemeNormal] = { "#bbbbbb", "#222222" }, + [SchemeSelect] = { "#eeeeee", "#005577" }, + [SchemeDesc] = { "#bbbbbb", "#222222" } +}; diff --git a/pinentry-dmenu/config.mk b/pinentry-dmenu/config.mk new file mode 100644 index 0000000..4c71211 --- /dev/null +++ b/pinentry-dmenu/config.mk @@ -0,0 +1,33 @@ +# Pinentry settings +DATE = $$(date +'%B %Y') +VERSION = 0.1 +BUGREPORT = https:\/\/github.com\/ritze\/pinentry-dmenu + +# 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_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} -DPACKAGE_VERSION=\"${VERSION}\" -DPACKAGE_BUGREPORT=\"${BUGREPORT}\" +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Compiler and linker +CC = cc diff --git a/pinentry-dmenu/drw.c b/pinentry-dmenu/drw.c new file mode 100644 index 0000000..c1582e7 --- /dev/null +++ b/pinentry-dmenu/drw.c @@ -0,0 +1,421 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#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); + 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."); + } + + 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); + + 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/pinentry-dmenu/drw.h b/pinentry-dmenu/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/pinentry-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/pinentry-dmenu/pinentry-dmenu b/pinentry-dmenu/pinentry-dmenu new file mode 100755 index 0000000000000000000000000000000000000000..b73d43f9b698911fa99fe7f9a91ef94f3c20e991 GIT binary patch literal 69248 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 literal 0 HcmV?d00001 diff --git a/pinentry-dmenu/pinentry-dmenu.1 b/pinentry-dmenu/pinentry-dmenu.1 new file mode 100644 index 0000000..67aafd1 --- /dev/null +++ b/pinentry-dmenu/pinentry-dmenu.1 @@ -0,0 +1,228 @@ +.TH PINENTRY-DMENU 1 "DATE" pinentry-dmenu\-VERSION "pinentry-dmenu Manual" + + +.SH NAME +pinentry-dmenu - a pinentry program with the charm of dmenu +.SH DESCRIPTION +.B pinentry-dmenu +is a dmenu- and pinentry-based passphrase dialog called from the +.BR gpg-agent (1) +daemon. It is not intended to be invoked directly. + + +.SH SYNOPSIS +Set the +.B pinentry-program +in +.IR ~/.gnupg/gpg-agent.conf +to +.B pinentry-dmenu +to use the program as the regular dialog for +.BR gpg-agent . +.PP +The configuration is placed in +.IR ~/.gnupg/pinentry-dmenu.conf . +You can change the path to the config file with the environment variable +.IR GNUPGHOME . + + +.SH OPTIONS +.TP +.BI "asterisk =" " *" +Defines the symbol which is showed for each typed character. +.TP +.BI "bottom =" " false" +pinentry-dmenu appears at the bottom of the screen. +.TP +.BI "min_password_length =" " 32" +The minimal space of the password field. This value has affect to the description field after the password field. +.TP +.BI "height =" " 8" +Height of pinentry-dmenu in pixels (no less than 8) +.TP +.BI "monitor =" " -1" +pinentry-dmenu is displayed on the monitor number supplied. Monitor numbers are starting from 0. +.TP +.BI "prompt =" " """" +Defines the prompt to be displayed to the left of the input field. +.TP +.BI "font =" " monospace:size=10" +Defines the font or font set used. +.TP +.BI "prompt_bg =" " #bbbbbb" +Defines the prompt background color. +.IR #RGB , +.I #RRGGBB +and X color names are supported. +.TP +.BI "prompt_fg =" " #222222" +Defines the prompt foreground color. +.TP +.BI "normal_bg =" " #bbbbbb" +Defines the normal background color. +.TP +.BI "normal_fg =" " #222222" +Defines the normal foreground color. +.TP +.BI "select_bg =" " #eeeeee" +Defines the selected background color. +.TP +.BI "select_fg =" " #005577" +Defines the selected foreground color. +.TP +.BI "desc_bg =" " #bbbbbb" +Defines the description background color. +.TP +.BI "desc_fg =" " #222222" +Defines the description foreground color. +.TP +.BI "embedded =" " false" +Embed into window. + + +.SH USAGE +pinentry-dmenu is completely controlled by the keyboard. +.TP +.B Return +Confirm input +.TP +.B Ctrl-Return +Confirm input +.TP +.B Shift\-Return +Confirm input +.TP +.B Escape +Cancel input +.TP +.B C\-c +Escape + +.SS Confirm Mode +.TP +.B Down +Right +.TP +.B End +Right +.TP +.B Home +Left +.TP +.B Next +Right +.TP +.B Prior +Left +.TP +.B Up +Left +.TP +.B g +Cancel input +.TP +.B G +Cancel input +.TP +.B h +Left +.TP +.B j +Left +.TP +.B k +Right +.TP +.B l +Right +.TP +.B n +Confirm with no +.TP +.B N +Confirm with no +.TP +.B y +Confirm with yes +.TP +.B Y +Confirm with yes + +.SS Pin Mode +.TP +.B End +Move cursor to the line end +.TP +.B Home +Move cursor to the line begin +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.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\-k +Delete line right +.TP +.B C\-u +Delete line left +.TP +.B C\-v +Paste from primary X selection + + +.SH EXAMPLES +.sp +.if n \{ +.RS 4 +.\} +.nf +asterisk= "# "; +prompt = "$"; +font = "Noto Sans UI:size=13"; +prompt_fg = "#eeeeee"; +prompt_bg = "#d9904a"; +normal_fg = "#ffffff"; +normal_bg = "#000000"; +select_fg = "#eeeeee"; +select_bg = "#d9904a"; +desc_fg = "#eeeeee"; +desc_bg = "#d9904a"; + + +.SH AUTHORS +.B pinentry-dmenu +is a fork of +.B dmenu + +and uses the api of +.B pinentry +, a GnuPG tool. +.B pinentry-dmenu +itself was written by Moritz Lüdecke . + + +.SH REPORTING BUGS +Report pinentry-dmenu bugs to + + +.SH SEE ALSO +.BR dmenu (1), +.BR dwm (1), +.BR gpg-agent (1) diff --git a/pinentry-dmenu/pinentry-dmenu.c b/pinentry-dmenu/pinentry-dmenu.c new file mode 100644 index 0000000..3086b43 --- /dev/null +++ b/pinentry-dmenu/pinentry-dmenu.c @@ -0,0 +1,803 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#include "drw.h" +#include "util.h" + +#include "pinentry/pinentry.h" +#include "pinentry/memory.h" + +#define CONFIG_DIR "/.gnupg" +#define CONFIG_FILE "/pinentry-dmenu.conf" +#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) +#define MINDESCLEN 8 + + +enum { SchemePrompt, SchemeNormal, SchemeSelect, SchemeDesc, SchemeLast }; +enum { WinPin, WinConfirm }; +enum { Ok, NotOk, Cancel }; +enum { Nothing, Yes, No }; + +static int bh, mw, mh; +static int sel; +static int promptw, pdescw; +/* Sum of left and right padding */ +static int lrpad; +static size_t cursor; +static int screen; + +static char* pin; +static int pin_len; +static char* pin_repeat; +static int pin_repeat_len; +static int repeat; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +static int timed_out; +static int winmode; +pinentry_t pinentry_info; + +#include "config.h" + +static int +drawitem(const char* text, Bool sel, int x, int y, int w) { + unsigned int i = (sel) ? SchemeSelect : SchemeNormal; + + drw_setscheme(drw, scheme[i]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, text, 0); +} + +static void +grabfocus(void) { + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) { + return; + } + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + usleep(1000); + } + + die("cannot grab focus"); +} + +static void +grabkeyboard(void) { + int i; + + if (embedded) { + 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; + } + usleep(1000); + } + + die("cannot grab keyboard"); +} + +static size_t +nextrune(int cursor, 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 && (pin[n] & 0xc0) == 0x80; + n += inc); + + return n; +} + +static void +setup_pin(char* pin_ptr, int len, int reset) { + pin = pin_ptr; + pin_len = len; + + if (reset) { + promptw = (prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + cursor = 0; + + if (pin) { + pin[0] = '\0'; + } + } +} + +static void +insert(const char *str, ssize_t n) { + size_t len = strlen(pin); + + // FIXME: Pinentry crashes when increasing the pin buffer the second time. + // Other pinentry programs has a limited passwort field length. + if (len + n > pin_len - 1) { + if (repeat) { + pin_repeat_len = 2 * pin_repeat_len; + pin_repeat = secmem_realloc(pin_repeat, pin_repeat_len); + setup_pin(pin_repeat, pin_repeat_len, 0); + if (!pin_repeat) { + pin_len = 0; + } + } else { + if (!pinentry_setbufferlen(pinentry_info, 2 * pinentry_info->pin_len)) { + pin_len = 0; + } else { + setup_pin(pinentry_info->pin, pinentry_info->pin_len, 0); + } + } + if (pin_len == 0) { + printf("Error: Couldn't allocate secure memory\n"); + return; + } + } + + /* Move existing text out of the way, insert new text, and update cursor */ + memmove(&pin[cursor + n], &pin[cursor], pin_len - cursor - MAX(n, 0)); + + if (n > 0) { + memcpy(&pin[cursor], str, n); + } + + cursor += n; + pin[len + n] = '\0'; +} + +static void +drawwin(void) { + unsigned int curpos; + int x = 0, fh = drw->fonts->h, pb, pbw = 0, i; + size_t asterlen = strlen(asterisk); + size_t pdesclen; + int leftinput; + char* censort; + + char* pprompt = (repeat) ? pinentry_info->repeat_passphrase : pinentry_info->prompt; + int ppromptw = (pprompt) ? TEXTW(pprompt) : 0; + + unsigned int censortl = minpwlen * TEXTW(asterisk) / strlen(asterisk); + unsigned int confirml = TEXTW(" YesNo ") + 3 * lrpad; + + drw_setscheme(drw, scheme[SchemeNormal]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt) { + drw_setscheme(drw, scheme[SchemePrompt]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + + if (pprompt) { + drw_setscheme(drw, scheme[SchemePrompt]); + drw_text(drw, x, 0, ppromptw, bh, lrpad / 2, pprompt, 0); + x += ppromptw; + } + + if (pinentry_info->description) { + pb = mw - x; + pdesclen = strlen(pinentry_info->description); + + if (pb > 0) { + pb -= (winmode == WinPin) ? censortl : confirml; + pbw = MINDESCLEN * pdescw / pdesclen; + pbw = MIN(pbw, pdescw); + + if (pb >= pbw) { + pbw = MAX(pbw, pdescw); + pbw = MIN(pbw, pb); + pb = mw - pbw; + + for (i = 0; i < pdesclen; i++) { + if (pinentry_info->description[i] == '\n') { + pinentry_info->description[i] = ' '; + } + } + + drw_setscheme(drw, scheme[SchemeDesc]); + drw_text(drw, pb, 0, pbw, bh, lrpad / 2, pinentry_info->description, + 0); + } else { + pbw = 0; + } + } + } + + /* Draw input field */ + drw_setscheme(drw, scheme[SchemeNormal]); + + if (winmode == WinPin) { + censort = ecalloc(1, asterlen * pin_len); + + for (i = 0; i < asterlen * strlen(pin); i += asterlen) { + memcpy(&censort[i], asterisk, asterlen); + } + + censort[i+1] = '\n'; + leftinput = mw - x - pbw; + drw_text(drw, x, 0, leftinput, bh, lrpad / 2, censort, 0); + drw_font_getexts(drw->fonts, censort, cursor * asterlen, &curpos, NULL); + + if ((curpos += lrpad / 2 - 1) < leftinput) { + drw_setscheme(drw, scheme[SchemeNormal]); + drw_rect(drw, x + curpos, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); + } + + free(censort); + } else { + x += TEXTW(" "); + x = drawitem("No", (sel == No), x, 0, TEXTW("No")); + x = drawitem("Yes", (sel == Yes), x, 0, TEXTW("Yes")); + } + + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +setup(void) { + int x, y, i = 0; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, j, di, n, area = 0; +#endif + + /* Init appearance */ + scheme[SchemePrompt] = drw_scm_create(drw, colors[SchemePrompt], 2); + scheme[SchemeNormal] = drw_scm_create(drw, colors[SchemeNormal], 2); + scheme[SchemeSelect] = drw_scm_create(drw, colors[SchemeSelect], 2); + scheme[SchemeDesc] = drw_scm_create(drw, colors[SchemeDesc], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* Calculate menu geometry */ + bh = drw->fonts->h + 2; + bh = MAX(bh, lineheight); + mh = bh; +#ifdef XINERAMA + info = XineramaQueryScreens(dpy, &n); + + if (parentwin == root && info) { + 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++) { + a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j]); + if (a > 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])) { + break; + } + } + } + + x = info[i].x_org; + y = info[i].y_org + (bottom ? info[i].height - mh : 0); + 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 = bottom ? wa.height - mh : 0; + mw = wa.width; + } + + pdescw = (pinentry_info->description) ? TEXTW(pinentry_info->description) : 0; + + /* Create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemePrompt][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + + /* Open input methods */ + xim = XOpenIM(dpy, NULL, NULL, NULL); + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + XMapRaised(dpy, win); + + if (embedded) { + XSelectInput(dpy, parentwin, FocusChangeMask); + + 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); +} + +static void +cleanup(void) { + XUngrabKey(dpy, AnyKey, AnyModifier, root); + free(scheme[SchemeDesc]); + free(scheme[SchemeSelect]); + free(scheme[SchemeNormal]); + free(scheme[SchemePrompt]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static int +keypress_confirm(XKeyEvent *ev, KeySym ksym) { + if (ev->state & ControlMask) { + switch(ksym) { + case XK_c: + pinentry_info->canceled = 1; + sel = No; + return 1; + default: + return 1; + } + } + + switch(ksym) { + case XK_KP_Enter: + case XK_Return: + if (sel != Nothing) { + return 1; + } + break; + case XK_y: + case XK_Y: + sel = Yes; + return 1; + case XK_n: + case XK_N: + sel = No; + return 1; + case XK_g: + case XK_G: + case XK_Escape: + pinentry_info->canceled = 1; + sel = No; + return 1; + case XK_h: + case XK_j: + case XK_Home: + case XK_Left: + case XK_Prior: + case XK_Up: + sel = No; + break; + case XK_k: + case XK_l: + case XK_Down: + case XK_End: + case XK_Next: + case XK_Right: + sel = Yes; + break; + } + + return 0; +} + +static int +keypress_pin(XKeyEvent *ev, KeySym ksym, char* buf, int len) { + int old; + + 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_k: + old = cursor; + cursor = strlen(pin); + insert(NULL, old - cursor); + break; + case XK_u: + insert(NULL, -cursor); + break; + case XK_v: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return 0; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + pinentry_info->canceled = 1; + return 1; + default: + return 1; + } + } + + switch(ksym) { + case XK_Delete: + if (pin[cursor] == '\0') { + return 0; + } + cursor = nextrune(cursor, +1); + /* Fallthrough */ + case XK_BackSpace: + if (cursor == 0) { + return 0; + } + insert(NULL, nextrune(cursor, -1) - cursor); + break; + case XK_Escape: + pinentry_info->canceled = 1; + return 1; + case XK_Left: + if (cursor > 0) { + cursor = nextrune(cursor, -1); + } + break; + case XK_Right: + if (pin[cursor] != '\0') { + cursor = nextrune(cursor, +1); + } + break; + case XK_Home: + cursor = 0; + break; + case XK_End: + cursor = strlen(pin); + break; + case XK_Return: + case XK_KP_Enter: + return 1; + break; + default: + if (!iscntrl(*buf)) { + insert(buf, len); + } + } + + return 0; +} + +static int +keypress(XKeyEvent *ev) { + char buf[32]; + int len; + int ret = 1; + + KeySym ksym = NoSymbol; + Status status; + len = XmbLookupString(xic, ev, buf, sizeof(buf), &ksym, &status); + + if (status != XBufferOverflow) { + if (winmode == WinConfirm) { + ret = keypress_confirm(ev, ksym); + } else { + ret = keypress_pin(ev, ksym, buf, len); + } + + if (ret == 0) { + drawwin(); + } + } + + return ret; +} + +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 */ + XGetWindowProperty(dpy, win, utf8, 0, pin_len / 4, False, utf8, &da, &di, + &dl, &dl, (unsigned char **)&p); + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t) strlen(p)); + XFree(p); + drawwin(); +} + +void +run(void) { + XEvent ev; + + drawwin(); + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) { + continue; + } + switch(ev.type) { + case Expose: + if (ev.xexpose.count == 0) { + drw_map(drw, win, 0, 0, mw, mh); + } + break; + case KeyPress: + if (keypress(&ev.xkey)) { + return; + } + break; + case SelectionNotify: + if (ev.xselection.property == utf8) { + paste(); + } + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) { + XRaiseWindow(dpy, win); + } + break; + } + } +} + +static void +catchsig(int sig) { + if (sig == SIGALRM) { + timed_out = 1; + } +} + +static void +password(void) { + winmode = WinPin; + repeat = 0; + setup_pin(pinentry_info->pin, pinentry_info->pin_len, 1); + run(); + + if (!pinentry_info->canceled && pinentry_info->repeat_passphrase) { + repeat = 1; + pin_repeat_len = pinentry_info->pin_len; + pin_repeat = secmem_malloc(pinentry_info->pin_len); + setup_pin(pin_repeat, pin_repeat_len, 1); + run(); + + pinentry_info->repeat_okay = (strcmp(pinentry_info->pin, pin_repeat) == 0)? 1 : 0; + secmem_free(pin_repeat); + + if (!pinentry_info->repeat_okay) { + pinentry_info->result = -1; + return; + } + } + + if (pinentry_info->canceled) { + pinentry_info->result = -1; + return; + } + + pinentry_info->result = strlen(pinentry_info->pin); +} + +static void +confirm(void) { + winmode = WinConfirm; + sel = Nothing; + run(); + pinentry_info->result = sel != No; +} + +static int +cmdhandler(pinentry_t received_pinentry) { + struct sigaction sa; + XWindowAttributes wa; + + pinentry_info = received_pinentry; + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) { + fputs("warning: no locale support\n", stderr); + } + if (!(dpy = XOpenDisplay(pinentry_info->display))) { + die("cannot open display"); + } + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + embedded = (pinentry_info->parent_wid) ? embedded : 0; + parentwin = (embedded) ? pinentry_info->parent_wid : 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; + drw_setscheme(drw, scheme[SchemePrompt]); + + if (pinentry_info->timeout) { + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = catchsig; + sigaction(SIGALRM, &sa, NULL); + alarm(pinentry_info->timeout); + } + + grabkeyboard(); + setup(); + + if (pinentry_info->pin) { + do { + password(); + } while (!pinentry_info->canceled && pinentry_info->repeat_passphrase + && !pinentry_info->repeat_okay); + } else { + confirm(); + } + + cleanup(); + + return pinentry_info->result; +} + +pinentry_cmd_handler_t pinentry_cmd_handler = cmdhandler; + +int +main(int argc, char *argv[]) { + Bool bval; + int i, val; + const char *str; + struct passwd *pw; + char path[PATH_MAX]; + char *sudo_uid = getenv("SUDO_UID"); + char *home = getenv("HOME"); + char *gnupghome = getenv("GNUPGHOME"); + config_t cfg; + + if (gnupghome) { + i = strlen(gnupghome); + strcpy(path, gnupghome); + } else { + /* Get the home dir even if the user used sudo or logged in as root */ + if (sudo_uid) { + i = atoi(sudo_uid); + pw = getpwuid(i); + home = pw->pw_dir; + } + + i = strlen(home); + strcpy(path, home); + strcpy(&path[i], CONFIG_DIR); + i += strlen(CONFIG_DIR); + } + + strcpy(&path[i], CONFIG_FILE); + endpwent(); + + config_init(&cfg); + + /* Read the file. If there is an error, report it and exit. */ + if (config_read_file(&cfg, path)) { + if (config_lookup_string(&cfg, "asterisk", &str)) { + asterisk = str; + } + if (config_lookup_bool(&cfg, "bottom", &bval)) { + bottom = bval; + } + if (config_lookup_int(&cfg, "min_password_length", &val)) { + minpwlen = val; + } + if (config_lookup_int(&cfg, "height", &val)) { + lineheight = MAX(val, min_lineheight); + } + if (config_lookup_int(&cfg, "monitor", &val)) { + mon = val; + } + if (config_lookup_string(&cfg, "prompt", &str)) { + prompt = str; + } + if (config_lookup_string(&cfg, "font", &str)) { + fonts[0] = str; + } + if (config_lookup_string(&cfg, "prompt_bg", &str)) { + colors[SchemePrompt][ColBg] = str; + } + if (config_lookup_string(&cfg, "prompt_fg", &str)) { + colors[SchemePrompt][ColFg] = str; + } + if (config_lookup_string(&cfg, "normal_bg", &str)) { + colors[SchemeNormal][ColBg] = str; + } + if (config_lookup_string(&cfg, "normal_fg", &str)) { + colors[SchemeNormal][ColFg] = str; + } + if (config_lookup_string(&cfg, "select_bg", &str)) { + colors[SchemeSelect][ColBg] = str; + } + if (config_lookup_string(&cfg, "select_fg", &str)) { + colors[SchemeSelect][ColFg] = str; + } + if (config_lookup_string(&cfg, "desc_bg", &str)) { + colors[SchemeDesc][ColBg] = str; + } + if (config_lookup_string(&cfg, "desc_fg", &str)) { + colors[SchemeDesc][ColFg] = str; + } + if (config_lookup_bool(&cfg, "embedded", &bval)) { + embedded = bval; + } + } else if ((str = config_error_file(&cfg))) { + fprintf(stderr, "%s:%d: %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + return(EXIT_FAILURE); + } + + pinentry_init("pinentry-dmenu"); + pinentry_parse_opts(argc, argv); + + if (pinentry_loop()) { + return 1; + } + + config_destroy(&cfg); + + return 0; +} diff --git a/pinentry-dmenu/pinentry/AUTHORS b/pinentry-dmenu/pinentry/AUTHORS new file mode 100644 index 0000000..695d0ba --- /dev/null +++ b/pinentry-dmenu/pinentry/AUTHORS @@ -0,0 +1,11 @@ +Program: Pinentry +Bug reports: +Security related bug reports: +License: GPLv2+ + +Robert Bihlmeyer +Werner Koch, g10 Code GmbH +Steffen Hansen, Klarlvdalens Datakonsult AB +Marcus Brinkmann, g10 Code GmbH +Timo Schulz, g10 Code GmbH +Neal Walfied, g10 Code GmbH diff --git a/pinentry-dmenu/pinentry/COPYING b/pinentry-dmenu/pinentry/COPYING new file mode 100644 index 0000000..c7aea18 --- /dev/null +++ b/pinentry-dmenu/pinentry/COPYING @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, 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 Library 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 diff --git a/pinentry-dmenu/pinentry/Makefile b/pinentry-dmenu/pinentry/Makefile new file mode 100644 index 0000000..4de2470 --- /dev/null +++ b/pinentry-dmenu/pinentry/Makefile @@ -0,0 +1,21 @@ +include ../config.mk + +SRC = util.c pinentry.c argparse.c password-cache.c secmem.c +OBJ = ${SRC:.c=.o} +CFLAGS += -DHAVE_MLOCK + +all: pinentry + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: pinentry.h argparse.h password-cache.h memory.h util.h + +pinentry: pinentry.o argparse.o password-cache.o secmem.o util.o + +clean: + @echo cleaning + @rm -f ${OBJ} + +.PHONY: all clean pinentry diff --git a/pinentry-dmenu/pinentry/argparse.c b/pinentry-dmenu/pinentry/argparse.c new file mode 100644 index 0000000..e31b67e --- /dev/null +++ b/pinentry-dmenu/pinentry/argparse.c @@ -0,0 +1,1607 @@ +/* [argparse.c wk 17.06.97] Argument Parser for option handling + * Copyright (C) 1998-2001, 2006-2008, 2012 Free Software Foundation, Inc. + * Copyright (C) 1997-2001, 2006-2008, 2013-2015 Werner Koch + * + * This file is part of JNLIB, which is a subsystem of GnuPG. + * + * JNLIB is free software; you can redistribute it and/or modify it + * under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * JNLIB 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 copies of the GNU General Public License + * and the GNU Lesser General Public License along with this program; + * if not, see . + */ + +/* This file may be used as part of GnuPG or standalone. A GnuPG + build is detected by the presence of the macro GNUPG_MAJOR_VERSION. + Some feature are only availalbe in the GnuPG build mode. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef GNUPG_MAJOR_VERSION +# include "libjnlib-config.h" +# include "mischelp.h" +# include "stringhelp.h" +# include "logging.h" +# ifdef JNLIB_NEED_UTF8CONV +# include "utf8conv.h" +# endif +#endif /*GNUPG_MAJOR_VERSION*/ + +#include "argparse.h" + +/* GnuPG uses GPLv3+ but a standalone version of this defaults to + GPLv2+ because that is the license of this file. Change this if + you include it in a program which uses GPLv3. If you don't want to + set a a copyright string for your usage() you may also hardcode it + here. */ +#ifndef GNUPG_MAJOR_VERSION + +# define ARGPARSE_GPL_VERSION 2 +# define ARGPARSE_CRIGHT_STR "Copyright (C) YEAR NAME" + +#else /* Used by GnuPG */ + +# define ARGPARSE_GPL_VERSION 3 +# define ARGPARSE_CRIGHT_STR "Copyright (C) 2015 Free Software Foundation, Inc." + +#endif /*GNUPG_MAJOR_VERSION*/ + +/* Replacements for standalone builds. */ +#ifndef GNUPG_MAJOR_VERSION +# ifndef _ +# define _(a) (a) +# endif +# ifndef DIM +# define DIM(v) (sizeof(v)/sizeof((v)[0])) +# endif +# define jnlib_malloc(a) malloc ((a)) +# define jnlib_realloc(a,b) realloc ((a), (b)) +# define jnlib_strdup(a) strdup ((a)) +# define jnlib_free(a) free ((a)) +# define jnlib_log_error my_log_error +# define jnlib_log_bug my_log_bug +# define trim_spaces(a) my_trim_spaces ((a)) +# define map_static_macro_string(a) (a) +#endif /*!GNUPG_MAJOR_VERSION*/ + + +#define ARGPARSE_STR(v) #v +#define ARGPARSE_STR2(v) ARGPARSE_STR(v) + + +/* Replacements for standalone builds. */ +#ifndef GNUPG_MAJOR_VERSION +static void +my_log_error (const char *fmt, ...) +{ + va_list arg_ptr ; + + va_start (arg_ptr, fmt); + fprintf (stderr, "%s: ", strusage (11)); + vfprintf (stderr, fmt, arg_ptr); + va_end (arg_ptr); +} + +static void +my_log_bug (const char *fmt, ...) +{ + va_list arg_ptr ; + + va_start (arg_ptr, fmt); + fprintf (stderr, "%s: Ohhhh jeeee: ", strusage (11)); + vfprintf (stderr, fmt, arg_ptr); + va_end (arg_ptr); + abort (); +} + +static char * +my_trim_spaces (char *str) +{ + char *string, *p, *mark; + + string = str; + /* Find first non space character. */ + for (p=string; *p && isspace (*(unsigned char*)p) ; p++) + ; + /* Move characters. */ + for ((mark = NULL); (*string = *p); string++, p++) + if (isspace (*(unsigned char*)p)) + { + if (!mark) + mark = string; + } + else + mark = NULL; + if (mark) + *mark = '\0' ; /* Remove trailing spaces. */ + + return str ; +} + +#endif /*!GNUPG_MAJOR_VERSION*/ + + + +/********************************* + * @Summary arg_parse + * #include "argparse.h" + * + * typedef struct { + * char *argc; pointer to argc (value subject to change) + * char ***argv; pointer to argv (value subject to change) + * unsigned flags; Global flags (DO NOT CHANGE) + * int err; print error about last option + * 1 = warning, 2 = abort + * int r_opt; return option + * int r_type; type of return value (0 = no argument found) + * union { + * int ret_int; + * long ret_long + * ulong ret_ulong; + * char *ret_str; + * } r; Return values + * struct { + * int idx; + * const char *last; + * void *aliases; + * } internal; DO NOT CHANGE + * } ARGPARSE_ARGS; + * + * typedef struct { + * int short_opt; + * const char *long_opt; + * unsigned flags; + * } ARGPARSE_OPTS; + * + * int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts ); + * + * @Description + * This is my replacement for getopt(). See the example for a typical usage. + * Global flags are: + * Bit 0 : Do not remove options form argv + * Bit 1 : Do not stop at last option but return other args + * with r_opt set to -1. + * Bit 2 : Assume options and real args are mixed. + * Bit 3 : Do not use -- to stop option processing. + * Bit 4 : Do not skip the first arg. + * Bit 5 : allow usage of long option with only one dash + * Bit 6 : ignore --version + * all other bits must be set to zero, this value is modified by the + * function, so assume this is write only. + * Local flags (for each option): + * Bit 2-0 : 0 = does not take an argument + * 1 = takes int argument + * 2 = takes string argument + * 3 = takes long argument + * 4 = takes ulong argument + * Bit 3 : argument is optional (r_type will the be set to 0) + * Bit 4 : allow 0x etc. prefixed values. + * Bit 6 : Ignore this option + * Bit 7 : This is a command and not an option + * You stop the option processing by setting opts to NULL, the function will + * then return 0. + * @Return Value + * Returns the args.r_opt or 0 if ready + * r_opt may be -2/-7 to indicate an unknown option/command. + * @See Also + * ArgExpand + * @Notes + * You do not need to process the options 'h', '--help' or '--version' + * because this function includes standard help processing; but if you + * specify '-h', '--help' or '--version' you have to do it yourself. + * The option '--' stops argument processing; if bit 1 is set the function + * continues to return normal arguments. + * To process float args or unsigned args you must use a string args and do + * the conversion yourself. + * @Example + * + * ARGPARSE_OPTS opts[] = { + * { 'v', "verbose", 0 }, + * { 'd', "debug", 0 }, + * { 'o', "output", 2 }, + * { 'c', "cross-ref", 2|8 }, + * { 'm', "my-option", 1|8 }, + * { 300, "ignored-long-option, ARGPARSE_OP_IGNORE}, + * { 500, "have-no-short-option-for-this-long-option", 0 }, + * {0} }; + * ARGPARSE_ARGS pargs = { &argc, &argv, 0 } + * + * while( ArgParse( &pargs, &opts) ) { + * switch( pargs.r_opt ) { + * case 'v': opt.verbose++; break; + * case 'd': opt.debug++; break; + * case 'o': opt.outfile = pargs.r.ret_str; break; + * case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; + * case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; + * case 500: opt.a_long_one++; break + * default : pargs.err = 1; break; -- force warning output -- + * } + * } + * if( argc > 1 ) + * log_fatal( "Too many args"); + * + */ + +typedef struct alias_def_s *ALIAS_DEF; +struct alias_def_s { + ALIAS_DEF next; + char *name; /* malloced buffer with name, \0, value */ + const char *value; /* ptr into name */ +}; + + +/* Object to store the names for the --ignore-invalid-option option. + This is a simple linked list. */ +typedef struct iio_item_def_s *IIO_ITEM_DEF; +struct iio_item_def_s +{ + IIO_ITEM_DEF next; + char name[1]; /* String with the long option name. */ +}; + +static const char *(*strusage_handler)( int ) = NULL; +static int (*custom_outfnc) (int, const char *); + +static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s); +static void show_help(ARGPARSE_OPTS *opts, unsigned flags); +static void show_version(void); +static int writestrings (int is_error, const char *string, ...) +#if __GNUC__ >= 4 + __attribute__ ((sentinel(0))) +#endif + ; + + +void +argparse_register_outfnc (int (*fnc)(int, const char *)) +{ + custom_outfnc = fnc; +} + + +/* Write STRING and all following const char * arguments either to + stdout or, if IS_ERROR is set, to stderr. The list of strings must + be terminated by a NULL. */ +static int +writestrings (int is_error, const char *string, ...) +{ + va_list arg_ptr; + const char *s; + int count = 0; + + if (string) + { + s = string; + va_start (arg_ptr, string); + do + { + if (custom_outfnc) + custom_outfnc (is_error? 2:1, s); + else + fputs (s, is_error? stderr : stdout); + count += strlen (s); + } + while ((s = va_arg (arg_ptr, const char *))); + va_end (arg_ptr); + } + return count; +} + + +static void +flushstrings (int is_error) +{ + if (custom_outfnc) + custom_outfnc (is_error? 2:1, NULL); + else + fflush (is_error? stderr : stdout); +} + + +static void +initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) +{ + if( !(arg->flags & (1<<15)) ) + { + /* Initialize this instance. */ + arg->internal.idx = 0; + arg->internal.last = NULL; + arg->internal.inarg = 0; + arg->internal.stopped = 0; + arg->internal.aliases = NULL; + arg->internal.cur_alias = NULL; + arg->internal.iio_list = NULL; + arg->err = 0; + arg->flags |= 1<<15; /* Mark as initialized. */ + if ( *arg->argc < 0 ) + jnlib_log_bug ("invalid argument for arg_parse\n"); + } + + + if (arg->err) + { + /* Last option was erroneous. */ + const char *s; + + if (filename) + { + if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) + s = _("argument not expected"); + else if ( arg->r_opt == ARGPARSE_READ_ERROR ) + s = _("read error"); + else if ( arg->r_opt == ARGPARSE_KEYWORD_TOO_LONG ) + s = _("keyword too long"); + else if ( arg->r_opt == ARGPARSE_MISSING_ARG ) + s = _("missing argument"); + else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) + s = _("invalid argument"); + else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) + s = _("invalid command"); + else if ( arg->r_opt == ARGPARSE_INVALID_ALIAS ) + s = _("invalid alias definition"); + else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) + s = _("out of core"); + else + s = _("invalid option"); + jnlib_log_error ("%s:%u: %s\n", filename, *lineno, s); + } + else + { + s = arg->internal.last? arg->internal.last:"[??]"; + + if ( arg->r_opt == ARGPARSE_MISSING_ARG ) + jnlib_log_error (_("missing argument for option \"%.50s\"\n"), s); + else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) + jnlib_log_error (_("invalid argument for option \"%.50s\"\n"), s); + else if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) + jnlib_log_error (_("option \"%.50s\" does not expect an " + "argument\n"), s ); + else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) + jnlib_log_error (_("invalid command \"%.50s\"\n"), s); + else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION ) + jnlib_log_error (_("option \"%.50s\" is ambiguous\n"), s); + else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_COMMAND ) + jnlib_log_error (_("command \"%.50s\" is ambiguous\n"),s ); + else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) + jnlib_log_error ("%s\n", _("out of core\n")); + else + jnlib_log_error (_("invalid option \"%.50s\"\n"), s); + } + if (arg->err != ARGPARSE_PRINT_WARNING) + exit (2); + arg->err = 0; + } + + /* Zero out the return value union. */ + arg->r.ret_str = NULL; + arg->r.ret_long = 0; +} + + +static void +store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) +{ + /* TODO: replace this dummy function with a rea one + * and fix the probelms IRIX has with (ALIAS_DEV)arg.. + * used as lvalue + */ + (void)arg; + (void)name; + (void)value; +#if 0 + ALIAS_DEF a = jnlib_xmalloc( sizeof *a ); + a->name = name; + a->value = value; + a->next = (ALIAS_DEF)arg->internal.aliases; + (ALIAS_DEF)arg->internal.aliases = a; +#endif +} + + +/* Return true if KEYWORD is in the ignore-invalid-option list. */ +static int +ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) +{ + IIO_ITEM_DEF item = arg->internal.iio_list; + + for (; item; item = item->next) + if (!strcmp (item->name, keyword)) + return 1; + return 0; +} + + +/* Add the keywords up to the next LF to the list of to be ignored + options. After returning FP will either be at EOF or the next + character read wll be the first of a new line. The function + returns 0 on success or true on malloc failure. */ +static int +ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) +{ + IIO_ITEM_DEF item; + int c; + char name[100]; + int namelen = 0; + int ready = 0; + enum { skipWS, collectNAME, skipNAME, addNAME} state = skipWS; + + while (!ready) + { + c = getc (fp); + if (c == '\n') + ready = 1; + else if (c == EOF) + { + c = '\n'; + ready = 1; + } + again: + switch (state) + { + case skipWS: + if (!isascii (c) || !isspace(c)) + { + namelen = 0; + state = collectNAME; + goto again; + } + break; + + case collectNAME: + if (isspace (c)) + { + state = addNAME; + goto again; + } + else if (namelen < DIM(name)-1) + name[namelen++] = c; + else /* Too long. */ + state = skipNAME; + break; + + case skipNAME: + if (isspace (c)) + { + state = skipWS; + goto again; + } + break; + + case addNAME: + name[namelen] = 0; + if (!ignore_invalid_option_p (arg, name)) + { + item = jnlib_malloc (sizeof *item + namelen); + if (!item) + return 1; + strcpy (item->name, name); + item->next = (IIO_ITEM_DEF)arg->internal.iio_list; + arg->internal.iio_list = item; + } + state = skipWS; + goto again; + } + } + return 0; +} + + +/* Clear the entire ignore-invalid-option list. */ +static void +ignore_invalid_option_clear (ARGPARSE_ARGS *arg) +{ + IIO_ITEM_DEF item, tmpitem; + + for (item = arg->internal.iio_list; item; item = tmpitem) + { + tmpitem = item->next; + jnlib_free (item); + } + arg->internal.iio_list = NULL; +} + + + +/**************** + * Get options from a file. + * Lines starting with '#' are comment lines. + * Syntax is simply a keyword and the argument. + * Valid keywords are all keywords from the long_opt list without + * the leading dashes. The special keywords "help", "warranty" and "version" + * are not valid here. + * The special keyword "alias" may be used to store alias definitions, + * which are later expanded like long options. + * The option + * ignore-invalid-option OPTIONNAMEs + * is recognized and updates a list of option which should be ignored if they + * are not defined. + * Caller must free returned strings. + * If called with FP set to NULL command line args are parse instead. + * + * Q: Should we allow the syntax + * keyword = value + * and accept for boolean options a value of 1/0, yes/no or true/false? + * Note: Abbreviation of options is here not allowed. + */ +int +optfile_parse (FILE *fp, const char *filename, unsigned *lineno, + ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) +{ + int state, i, c; + int idx=0; + char keyword[100]; + char *buffer = NULL; + size_t buflen = 0; + int in_alias=0; + + if (!fp) /* Divert to to arg_parse() in this case. */ + return arg_parse (arg, opts); + + initialize (arg, filename, lineno); + + /* Find the next keyword. */ + state = i = 0; + for (;;) + { + c = getc (fp); + if (c == '\n' || c== EOF ) + { + if ( c != EOF ) + ++*lineno; + if (state == -1) + break; + else if (state == 2) + { + keyword[i] = 0; + for (i=0; opts[i].short_opt; i++ ) + { + if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) + break; + } + idx = i; + arg->r_opt = opts[idx].short_opt; + if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) + { + state = i = 0; + continue; + } + else if (!opts[idx].short_opt ) + { + if (!strcmp (keyword, "ignore-invalid-option")) + { + /* No argument - ignore this meta option. */ + state = i = 0; + continue; + } + else if (ignore_invalid_option_p (arg, keyword)) + { + /* This invalid option is in the iio list. */ + state = i = 0; + continue; + } + arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) + ? ARGPARSE_INVALID_COMMAND + : ARGPARSE_INVALID_OPTION); + } + else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_type = 0; /* Does not take an arg. */ + else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL) ) + arg->r_type = 0; /* Arg is optional. */ + else + arg->r_opt = ARGPARSE_MISSING_ARG; + + break; + } + else if (state == 3) + { + /* No argument found. */ + if (in_alias) + arg->r_opt = ARGPARSE_MISSING_ARG; + else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_type = 0; /* Does not take an arg. */ + else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL)) + arg->r_type = 0; /* No optional argument. */ + else + arg->r_opt = ARGPARSE_MISSING_ARG; + + break; + } + else if (state == 4) + { + /* Has an argument. */ + if (in_alias) + { + if (!buffer) + arg->r_opt = ARGPARSE_UNEXPECTED_ARG; + else + { + char *p; + + buffer[i] = 0; + p = strpbrk (buffer, " \t"); + if (p) + { + *p++ = 0; + trim_spaces (p); + } + if (!p || !*p) + { + jnlib_free (buffer); + arg->r_opt = ARGPARSE_INVALID_ALIAS; + } + else + { + store_alias (arg, buffer, p); + } + } + } + else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) + arg->r_opt = ARGPARSE_UNEXPECTED_ARG; + else + { + char *p; + + if (!buffer) + { + keyword[i] = 0; + buffer = jnlib_strdup (keyword); + if (!buffer) + arg->r_opt = ARGPARSE_OUT_OF_CORE; + } + else + buffer[i] = 0; + + if (buffer) + { + trim_spaces (buffer); + p = buffer; + if (*p == '"') + { + /* Remove quotes. */ + p++; + if (*p && p[strlen(p)-1] == '\"' ) + p[strlen(p)-1] = 0; + } + if (!set_opt_arg (arg, opts[idx].flags, p)) + jnlib_free(buffer); + } + } + break; + } + else if (c == EOF) + { + ignore_invalid_option_clear (arg); + if (ferror (fp)) + arg->r_opt = ARGPARSE_READ_ERROR; + else + arg->r_opt = 0; /* EOF. */ + break; + } + state = 0; + i = 0; + } + else if (state == -1) + ; /* Skip. */ + else if (state == 0 && isascii (c) && isspace(c)) + ; /* Skip leading white space. */ + else if (state == 0 && c == '#' ) + state = 1; /* Start of a comment. */ + else if (state == 1) + ; /* Skip comments. */ + else if (state == 2 && isascii (c) && isspace(c)) + { + /* Check keyword. */ + keyword[i] = 0; + for (i=0; opts[i].short_opt; i++ ) + if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) + break; + idx = i; + arg->r_opt = opts[idx].short_opt; + if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) + { + state = 1; /* Process like a comment. */ + } + else if (!opts[idx].short_opt) + { + if (!strcmp (keyword, "alias")) + { + in_alias = 1; + state = 3; + } + else if (!strcmp (keyword, "ignore-invalid-option")) + { + if (ignore_invalid_option_add (arg, fp)) + { + arg->r_opt = ARGPARSE_OUT_OF_CORE; + break; + } + state = i = 0; + ++*lineno; + } + else if (ignore_invalid_option_p (arg, keyword)) + state = 1; /* Process like a comment. */ + else + { + arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) + ? ARGPARSE_INVALID_COMMAND + : ARGPARSE_INVALID_OPTION); + state = -1; /* Skip rest of line and leave. */ + } + } + else + state = 3; + } + else if (state == 3) + { + /* Skip leading spaces of the argument. */ + if (!isascii (c) || !isspace(c)) + { + i = 0; + keyword[i++] = c; + state = 4; + } + } + else if (state == 4) + { + /* Collect the argument. */ + if (buffer) + { + if (i < buflen-1) + buffer[i++] = c; + else + { + char *tmp; + size_t tmplen = buflen + 50; + + tmp = jnlib_realloc (buffer, tmplen); + if (tmp) + { + buflen = tmplen; + buffer = tmp; + buffer[i++] = c; + } + else + { + jnlib_free (buffer); + arg->r_opt = ARGPARSE_OUT_OF_CORE; + break; + } + } + } + else if (i < DIM(keyword)-1) + keyword[i++] = c; + else + { + size_t tmplen = DIM(keyword) + 50; + buffer = jnlib_malloc (tmplen); + if (buffer) + { + buflen = tmplen; + memcpy(buffer, keyword, i); + buffer[i++] = c; + } + else + { + arg->r_opt = ARGPARSE_OUT_OF_CORE; + break; + } + } + } + else if (i >= DIM(keyword)-1) + { + arg->r_opt = ARGPARSE_KEYWORD_TOO_LONG; + state = -1; /* Skip rest of line and leave. */ + } + else + { + keyword[i++] = c; + state = 2; + } + } + + return arg->r_opt; +} + + + +static int +find_long_option( ARGPARSE_ARGS *arg, + ARGPARSE_OPTS *opts, const char *keyword ) +{ + int i; + size_t n; + + (void)arg; + + /* Would be better if we can do a binary search, but it is not + possible to reorder our option table because we would mess + up our help strings - What we can do is: Build a nice option + lookup table wehn this function is first invoked */ + if( !*keyword ) + return -1; + for(i=0; opts[i].short_opt; i++ ) + if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) + return i; +#if 0 + { + ALIAS_DEF a; + /* see whether it is an alias */ + for( a = args->internal.aliases; a; a = a->next ) { + if( !strcmp( a->name, keyword) ) { + /* todo: must parse the alias here */ + args->internal.cur_alias = a; + return -3; /* alias available */ + } + } + } +#endif + /* not found, see whether it is an abbreviation */ + /* aliases may not be abbreviated */ + n = strlen( keyword ); + for(i=0; opts[i].short_opt; i++ ) { + if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) { + int j; + for(j=i+1; opts[j].short_opt; j++ ) { + if( opts[j].long_opt + && !strncmp( opts[j].long_opt, keyword, n ) ) + return -2; /* abbreviation is ambiguous */ + } + return i; + } + } + return -1; /* Not found. */ +} + +int +arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) +{ + int idx; + int argc; + char **argv; + char *s, *s2; + int i; + + initialize( arg, NULL, NULL ); + argc = *arg->argc; + argv = *arg->argv; + idx = arg->internal.idx; + + if (!idx && argc && !(arg->flags & ARGPARSE_FLAG_ARG0)) + { + /* Skip the first argument. */ + argc--; argv++; idx++; + } + + next_one: + if (!argc) + { + /* No more args. */ + arg->r_opt = 0; + goto leave; /* Ready. */ + } + + s = *argv; + arg->internal.last = s; + + if (arg->internal.stopped && (arg->flags & ARGPARSE_FLAG_ALL)) + { + arg->r_opt = ARGPARSE_IS_ARG; /* Not an option but an argument. */ + arg->r_type = 2; + arg->r.ret_str = s; + argc--; argv++; idx++; /* set to next one */ + } + else if( arg->internal.stopped ) + { + arg->r_opt = 0; + goto leave; /* Ready. */ + } + else if ( *s == '-' && s[1] == '-' ) + { + /* Long option. */ + char *argpos; + + arg->internal.inarg = 0; + if (!s[2] && !(arg->flags & ARGPARSE_FLAG_NOSTOP)) + { + /* Stop option processing. */ + arg->internal.stopped = 1; + arg->flags |= ARGPARSE_FLAG_STOP_SEEN; + argc--; argv++; idx++; + goto next_one; + } + + argpos = strchr( s+2, '=' ); + if ( argpos ) + *argpos = 0; + i = find_long_option ( arg, opts, s+2 ); + if ( argpos ) + *argpos = '='; + + if ( i < 0 && !strcmp ( "help", s+2) ) + show_help (opts, arg->flags); + else if ( i < 0 && !strcmp ( "version", s+2) ) + { + if (!(arg->flags & ARGPARSE_FLAG_NOVERSION)) + { + show_version (); + exit(0); + } + } + else if ( i < 0 && !strcmp( "warranty", s+2)) + { + writestrings (0, strusage (16), "\n", NULL); + exit (0); + } + else if ( i < 0 && !strcmp( "dump-options", s+2) ) + { + for (i=0; opts[i].short_opt; i++ ) + { + if (opts[i].long_opt && !(opts[i].flags & ARGPARSE_OPT_IGNORE)) + writestrings (0, "--", opts[i].long_opt, "\n", NULL); + } + writestrings (0, "--dump-options\n--help\n--version\n--warranty\n", + NULL); + exit (0); + } + + if ( i == -2 ) + arg->r_opt = ARGPARSE_AMBIGUOUS_OPTION; + else if ( i == -1 ) + { + arg->r_opt = ARGPARSE_INVALID_OPTION; + arg->r.ret_str = s+2; + } + else + arg->r_opt = opts[i].short_opt; + if ( i < 0 ) + ; + else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) + { + if ( argpos ) + { + s2 = argpos+1; + if ( !*s2 ) + s2 = NULL; + } + else + s2 = argv[1]; + if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + arg->r_type = ARGPARSE_TYPE_NONE; /* Argument is optional. */ + } + else if ( !s2 ) + { + arg->r_opt = ARGPARSE_MISSING_ARG; + } + else if ( !argpos && *s2 == '-' + && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + /* The argument is optional and the next seems to be an + option. We do not check this possible option but + assume no argument */ + arg->r_type = ARGPARSE_TYPE_NONE; + } + else + { + set_opt_arg (arg, opts[i].flags, s2); + if ( !argpos ) + { + argc--; argv++; idx++; /* Skip one. */ + } + } + } + else + { + /* Does not take an argument. */ + if ( argpos ) + arg->r_type = ARGPARSE_UNEXPECTED_ARG; + else + arg->r_type = 0; + } + argc--; argv++; idx++; /* Set to next one. */ + } + else if ( (*s == '-' && s[1]) || arg->internal.inarg ) + { + /* Short option. */ + int dash_kludge = 0; + + i = 0; + if ( !arg->internal.inarg ) + { + arg->internal.inarg++; + if ( (arg->flags & ARGPARSE_FLAG_ONEDASH) ) + { + for (i=0; opts[i].short_opt; i++ ) + if ( opts[i].long_opt && !strcmp (opts[i].long_opt, s+1)) + { + dash_kludge = 1; + break; + } + } + } + s += arg->internal.inarg; + + if (!dash_kludge ) + { + for (i=0; opts[i].short_opt; i++ ) + if ( opts[i].short_opt == *s ) + break; + } + + if ( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) ) + show_help (opts, arg->flags); + + arg->r_opt = opts[i].short_opt; + if (!opts[i].short_opt ) + { + arg->r_opt = (opts[i].flags & ARGPARSE_OPT_COMMAND)? + ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION; + arg->internal.inarg++; /* Point to the next arg. */ + arg->r.ret_str = s; + } + else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) + { + if ( s[1] && !dash_kludge ) + { + s2 = s+1; + set_opt_arg (arg, opts[i].flags, s2); + } + else + { + s2 = argv[1]; + if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + arg->r_type = ARGPARSE_TYPE_NONE; + } + else if ( !s2 ) + { + arg->r_opt = ARGPARSE_MISSING_ARG; + } + else if ( *s2 == '-' && s2[1] + && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) + { + /* The argument is optional and the next seems to + be an option. We do not check this possible + option but assume no argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; + } + else + { + set_opt_arg (arg, opts[i].flags, s2); + argc--; argv++; idx++; /* Skip one. */ + } + } + s = "x"; /* This is so that !s[1] yields false. */ + } + else + { + /* Does not take an argument. */ + arg->r_type = ARGPARSE_TYPE_NONE; + arg->internal.inarg++; /* Point to the next arg. */ + } + if ( !s[1] || dash_kludge ) + { + /* No more concatenated short options. */ + arg->internal.inarg = 0; + argc--; argv++; idx++; + } + } + else if ( arg->flags & ARGPARSE_FLAG_MIXED ) + { + arg->r_opt = ARGPARSE_IS_ARG; + arg->r_type = 2; + arg->r.ret_str = s; + argc--; argv++; idx++; /* Set to next one. */ + } + else + { + arg->internal.stopped = 1; /* Stop option processing. */ + goto next_one; + } + + leave: + *arg->argc = argc; + *arg->argv = argv; + arg->internal.idx = idx; + return arg->r_opt; +} + + +/* Returns: -1 on error, 0 for an integer type and 1 for a non integer + type argument. */ +static int +set_opt_arg (ARGPARSE_ARGS *arg, unsigned flags, char *s) +{ + int base = (flags & ARGPARSE_OPT_PREFIX)? 0 : 10; + long l; + + switch ( (arg->r_type = (flags & ARGPARSE_TYPE_MASK)) ) + { + case ARGPARSE_TYPE_LONG: + case ARGPARSE_TYPE_INT: + errno = 0; + l = strtol (s, NULL, base); + if ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) + { + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + if (arg->r_type == ARGPARSE_TYPE_LONG) + arg->r.ret_long = l; + else if ( (l < 0 && l < INT_MIN) || l > INT_MAX ) + { + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + else + arg->r.ret_int = (int)l; + return 0; + + case ARGPARSE_TYPE_ULONG: + while (isascii (*s) && isspace(*s)) + s++; + if (*s == '-') + { + arg->r.ret_ulong = 0; + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + errno = 0; + arg->r.ret_ulong = strtoul (s, NULL, base); + if (arg->r.ret_ulong == ULONG_MAX && errno == ERANGE) + { + arg->r_opt = ARGPARSE_INVALID_ARG; + return -1; + } + return 0; + + case ARGPARSE_TYPE_STRING: + default: + arg->r.ret_str = s; + return 1; + } +} + + +static size_t +long_opt_strlen( ARGPARSE_OPTS *o ) +{ + size_t n = strlen (o->long_opt); + + if ( o->description && *o->description == '|' ) + { + const char *s; +#ifdef JNLIB_NEED_UTF8CONV + int is_utf8 = is_native_utf8 (); +#endif + + s=o->description+1; + if ( *s != '=' ) + n++; + /* For a (mostly) correct length calculation we exclude + continuation bytes (10xxxxxx) if we are on a native utf8 + terminal. */ + for (; *s && *s != '|'; s++ ) +#ifdef JNLIB_NEED_UTF8CONV + if ( is_utf8 && (*s&0xc0) != 0x80 ) +#endif + n++; + } + return n; +} + + +/**************** + * Print formatted help. The description string has some special + * meanings: + * - A description string which is "@" suppresses help output for + * this option + * - a description,ine which starts with a '@' and is followed by + * any other characters is printed as is; this may be used for examples + * ans such. + * - A description which starts with a '|' outputs the string between this + * bar and the next one as arguments of the long option. + */ +static void +show_help (ARGPARSE_OPTS *opts, unsigned int flags) +{ + const char *s; + char tmp[2]; + + show_version (); + writestrings (0, "\n", NULL); + s = strusage (42); + if (s && *s == '1') + { + s = strusage (40); + writestrings (1, s, NULL); + if (*s && s[strlen(s)] != '\n') + writestrings (1, "\n", NULL); + } + s = strusage(41); + writestrings (0, s, "\n", NULL); + if ( opts[0].description ) + { + /* Auto format the option description. */ + int i,j, indent; + + /* Get max. length of long options. */ + for (i=indent=0; opts[i].short_opt; i++ ) + { + if ( opts[i].long_opt ) + if ( !opts[i].description || *opts[i].description != '@' ) + if ( (j=long_opt_strlen(opts+i)) > indent && j < 35 ) + indent = j; + } + + /* Example: " -v, --verbose Viele Sachen ausgeben" */ + indent += 10; + if ( *opts[0].description != '@' ) + writestrings (0, "Options:", "\n", NULL); + for (i=0; opts[i].short_opt; i++ ) + { + s = map_static_macro_string (_( opts[i].description )); + if ( s && *s== '@' && !s[1] ) /* Hide this line. */ + continue; + if ( s && *s == '@' ) /* Unindented comment only line. */ + { + for (s++; *s; s++ ) + { + if ( *s == '\n' ) + { + if( s[1] ) + writestrings (0, "\n", NULL); + } + else + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + } + writestrings (0, "\n", NULL); + continue; + } + + j = 3; + if ( opts[i].short_opt < 256 ) + { + tmp[0] = opts[i].short_opt; + tmp[1] = 0; + writestrings (0, " -", tmp, NULL ); + if ( !opts[i].long_opt ) + { + if (s && *s == '|' ) + { + writestrings (0, " ", NULL); j++; + for (s++ ; *s && *s != '|'; s++, j++ ) + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + if ( *s ) + s++; + } + } + } + else + writestrings (0, " ", NULL); + if ( opts[i].long_opt ) + { + tmp[0] = opts[i].short_opt < 256?',':' '; + tmp[1] = 0; + j += writestrings (0, tmp, " --", opts[i].long_opt, NULL); + if (s && *s == '|' ) + { + if ( *++s != '=' ) + { + writestrings (0, " ", NULL); + j++; + } + for ( ; *s && *s != '|'; s++, j++ ) + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + if ( *s ) + s++; + } + writestrings (0, " ", NULL); + j += 3; + } + for (;j < indent; j++ ) + writestrings (0, " ", NULL); + if ( s ) + { + if ( *s && j > indent ) + { + writestrings (0, "\n", NULL); + for (j=0;j < indent; j++ ) + writestrings (0, " ", NULL); + } + for (; *s; s++ ) + { + if ( *s == '\n' ) + { + if ( s[1] ) + { + writestrings (0, "\n", NULL); + for (j=0; j < indent; j++ ) + writestrings (0, " ", NULL); + } + } + else + { + tmp[0] = *s; + tmp[1] = 0; + writestrings (0, tmp, NULL); + } + } + } + writestrings (0, "\n", NULL); + } + if ( (flags & ARGPARSE_FLAG_ONEDASH) ) + writestrings (0, "\n(A single dash may be used " + "instead of the double ones)\n", NULL); + } + if ( (s=strusage(19)) ) + { + writestrings (0, "\n", NULL); + writestrings (0, s, NULL); + } + flushstrings (0); + exit(0); +} + +static void +show_version () +{ + const char *s; + int i; + + /* Version line. */ + writestrings (0, strusage (11), NULL); + if ((s=strusage (12))) + writestrings (0, " (", s, ")", NULL); + writestrings (0, " ", strusage (13), "\n", NULL); + /* Additional version lines. */ + for (i=20; i < 30; i++) + if ((s=strusage (i))) + writestrings (0, s, "\n", NULL); + /* Copyright string. */ + if ((s=strusage (14))) + writestrings (0, s, "\n", NULL); + /* Licence string. */ + if( (s=strusage (10)) ) + writestrings (0, s, "\n", NULL); + /* Copying conditions. */ + if ( (s=strusage(15)) ) + writestrings (0, s, NULL); + /* Thanks. */ + if ((s=strusage(18))) + writestrings (0, s, NULL); + /* Additional program info. */ + for (i=30; i < 40; i++ ) + if ( (s=strusage (i)) ) + writestrings (0, s, NULL); + flushstrings (0); +} + + +void +usage (int level) +{ + const char *p; + + if (!level) + { + writestrings (1, strusage(11), " ", strusage(13), "; ", + strusage (14), "\n", NULL); + flushstrings (1); + } + else if (level == 1) + { + p = strusage (40); + writestrings (1, p, NULL); + if (*p && p[strlen(p)] != '\n') + writestrings (1, "\n", NULL); + exit (2); + } + else if (level == 2) + { + p = strusage (42); + if (p && *p == '1') + { + p = strusage (40); + writestrings (1, p, NULL); + if (*p && p[strlen(p)] != '\n') + writestrings (1, "\n", NULL); + } + writestrings (0, strusage(41), "\n", NULL); + exit (0); + } +} + +/* Level + * 0: Print copyright string to stderr + * 1: Print a short usage hint to stderr and terminate + * 2: Print a long usage hint to stdout and terminate + * 10: Return license info string + * 11: Return the name of the program + * 12: Return optional name of package which includes this program. + * 13: version string + * 14: copyright string + * 15: Short copying conditions (with LFs) + * 16: Long copying conditions (with LFs) + * 17: Optional printable OS name + * 18: Optional thanks list (with LFs) + * 19: Bug report info + *20..29: Additional lib version strings. + *30..39: Additional program info (with LFs) + * 40: short usage note (with LF) + * 41: long usage note (with LF) + * 42: Flag string: + * First char is '1': + * The short usage notes needs to be printed + * before the long usage note. + */ +const char * +strusage( int level ) +{ + const char *p = strusage_handler? strusage_handler(level) : NULL; + + if ( p ) + return map_static_macro_string (p); + + switch ( level ) + { + + case 10: +#if ARGPARSE_GPL_VERSION == 3 + p = ("License GPLv3+: GNU GPL version 3 or later " + ""); +#else + p = ("License GPLv2+: GNU GPL version 2 or later " + ""); +#endif + break; + case 11: p = "foo"; break; + case 13: p = "0.0"; break; + case 14: p = ARGPARSE_CRIGHT_STR; break; + case 15: p = +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n"; + break; + case 16: p = +"This is free software; you can redistribute it and/or modify\n" +"it under the terms of the GNU General Public License as published by\n" +"the Free Software Foundation; either version " +ARGPARSE_STR2(ARGPARSE_GPL_VERSION) +" of the License, or\n" +"(at your option) any later version.\n\n" +"It is distributed in the hope that it will be useful,\n" +"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +"GNU General Public License for more details.\n\n" +"You should have received a copy of the GNU General Public License\n" +"along with this software. If not, see .\n"; + break; + case 40: /* short and long usage */ + case 41: p = ""; break; + } + + return p; +} + + +/* Set the usage handler. This function is basically a constructor. */ +void +set_strusage ( const char *(*f)( int ) ) +{ + strusage_handler = f; +} + + +#ifdef TEST +static struct { + int verbose; + int debug; + char *outfile; + char *crf; + int myopt; + int echo; + int a_long_one; +} opt; + +int +main(int argc, char **argv) +{ + ARGPARSE_OPTS opts[] = { + ARGPARSE_x('v', "verbose", NONE, 0, "Laut sein"), + ARGPARSE_s_n('e', "echo" , ("Zeile ausgeben, damit wir sehen, " + "was wir eingegeben haben")), + ARGPARSE_s_n('d', "debug", "Debug\nfalls mal etwas\nschief geht"), + ARGPARSE_s_s('o', "output", 0 ), + ARGPARSE_o_s('c', "cross-ref", "cross-reference erzeugen\n" ), + /* Note that on a non-utf8 terminal the ß might garble the output. */ + ARGPARSE_s_n('s', "street","|Straße|set the name of the street to Straße"), + ARGPARSE_o_i('m', "my-option", 0), + ARGPARSE_s_n(500, "a-long-option", 0 ), + ARGPARSE_end() + }; + ARGPARSE_ARGS pargs = { &argc, &argv, (ARGPARSE_FLAG_ALL + | ARGPARSE_FLAG_MIXED + | ARGPARSE_FLAG_ONEDASH) }; + int i; + + while (arg_parse (&pargs, opts)) + { + switch (pargs.r_opt) + { + case ARGPARSE_IS_ARG : + printf ("arg='%s'\n", pargs.r.ret_str); + break; + case 'v': opt.verbose++; break; + case 'e': opt.echo++; break; + case 'd': opt.debug++; break; + case 'o': opt.outfile = pargs.r.ret_str; break; + case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; + case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; + case 500: opt.a_long_one++; break; + default : pargs.err = ARGPARSE_PRINT_WARNING; break; + } + } + for (i=0; i < argc; i++ ) + printf ("%3d -> (%s)\n", i, argv[i] ); + puts ("Options:"); + if (opt.verbose) + printf (" verbose=%d\n", opt.verbose ); + if (opt.debug) + printf (" debug=%d\n", opt.debug ); + if (opt.outfile) + printf (" outfile='%s'\n", opt.outfile ); + if (opt.crf) + printf (" crffile='%s'\n", opt.crf ); + if (opt.myopt) + printf (" myopt=%d\n", opt.myopt ); + if (opt.a_long_one) + printf (" a-long-one=%d\n", opt.a_long_one ); + if (opt.echo) + printf (" echo=%d\n", opt.echo ); + + return 0; +} +#endif /*TEST*/ + +/**** bottom of file ****/ diff --git a/pinentry-dmenu/pinentry/argparse.h b/pinentry-dmenu/pinentry/argparse.h new file mode 100644 index 0000000..b4dc253 --- /dev/null +++ b/pinentry-dmenu/pinentry/argparse.h @@ -0,0 +1,203 @@ +/* argparse.h - Argument parser for option handling. + * Copyright (C) 1998,1999,2000,2001,2006 Free Software Foundation, Inc. + * + * This file is part of JNLIB, which is a subsystem of GnuPG. + * + * JNLIB is free software; you can redistribute it and/or modify it + * under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * JNLIB 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 copies of the GNU General Public License + * and the GNU Lesser General Public License along with this program; + * if not, see . + */ + +#ifndef LIBJNLIB_ARGPARSE_H +#define LIBJNLIB_ARGPARSE_H + +#include + +typedef struct +{ + int *argc; /* Pointer to ARGC (value subject to change). */ + char ***argv; /* Pointer to ARGV (value subject to change). */ + unsigned int flags; /* Global flags. May be set prior to calling the + parser. The parser may change the value. */ + int err; /* Print error description for last option. + Either 0, ARGPARSE_PRINT_WARNING or + ARGPARSE_PRINT_ERROR. */ + + int r_opt; /* Returns option code. */ + int r_type; /* Returns type of option value. */ + union { + int ret_int; + long ret_long; + unsigned long ret_ulong; + char *ret_str; + } r; /* Return values */ + + struct { + int idx; + int inarg; + int stopped; + const char *last; + void *aliases; + const void *cur_alias; + void *iio_list; + } internal; /* Private - do not change. */ +} ARGPARSE_ARGS; + +typedef struct +{ + int short_opt; + const char *long_opt; + unsigned int flags; + const char *description; /* Optional option description. */ +} ARGPARSE_OPTS; + + +/* Global flags (ARGPARSE_ARGS). */ +#define ARGPARSE_FLAG_KEEP 1 /* Do not remove options form argv. */ +#define ARGPARSE_FLAG_ALL 2 /* Do not stop at last option but return + remaining args with R_OPT set to -1. */ +#define ARGPARSE_FLAG_MIXED 4 /* Assume options and args are mixed. */ +#define ARGPARSE_FLAG_NOSTOP 8 /* Do not stop processing at "--". */ +#define ARGPARSE_FLAG_ARG0 16 /* Do not skip the first arg. */ +#define ARGPARSE_FLAG_ONEDASH 32 /* Allow long options with one dash. */ +#define ARGPARSE_FLAG_NOVERSION 64 /* No output for "--version". */ + +#define ARGPARSE_FLAG_STOP_SEEN 256 /* Set to true if a "--" has been seen. */ + +/* Flags for each option (ARGPARSE_OPTS). The type code may be + ORed with the OPT flags. */ +#define ARGPARSE_TYPE_NONE 0 /* Does not take an argument. */ +#define ARGPARSE_TYPE_INT 1 /* Takes an int argument. */ +#define ARGPARSE_TYPE_STRING 2 /* Takes a string argument. */ +#define ARGPARSE_TYPE_LONG 3 /* Takes a long argument. */ +#define ARGPARSE_TYPE_ULONG 4 /* Takes an unsigned long argument. */ +#define ARGPARSE_OPT_OPTIONAL (1<<3) /* Argument is optional. */ +#define ARGPARSE_OPT_PREFIX (1<<4) /* Allow 0x etc. prefixed values. */ +#define ARGPARSE_OPT_IGNORE (1<<6) /* Ignore command or option. */ +#define ARGPARSE_OPT_COMMAND (1<<7) /* The argument is a command. */ + +#define ARGPARSE_TYPE_MASK 7 /* Mask for the type values (internal). */ + +/* A set of macros to make option definitions easier to read. */ +#define ARGPARSE_x(s,l,t,f,d) \ + { (s), (l), ARGPARSE_TYPE_ ## t | (f), (d) } + +#define ARGPARSE_s(s,l,t,d) \ + { (s), (l), ARGPARSE_TYPE_ ## t, (d) } +#define ARGPARSE_s_n(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_NONE, (d) } +#define ARGPARSE_s_i(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_INT, (d) } +#define ARGPARSE_s_s(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_STRING, (d) } +#define ARGPARSE_s_l(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_LONG, (d) } +#define ARGPARSE_s_u(s,l,d) \ + { (s), (l), ARGPARSE_TYPE_ULONG, (d) } + +#define ARGPARSE_o(s,l,t,d) \ + { (s), (l), (ARGPARSE_TYPE_ ## t | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_n(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_i(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_INT | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_s(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_STRING | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_l(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_LONG | ARGPARSE_OPT_OPTIONAL), (d) } +#define ARGPARSE_o_u(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_ULONG | ARGPARSE_OPT_OPTIONAL), (d) } + +#define ARGPARSE_p(s,l,t,d) \ + { (s), (l), (ARGPARSE_TYPE_ ## t | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_n(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_i(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_INT | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_s(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_STRING | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_l(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_LONG | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_p_u(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_ULONG | ARGPARSE_OPT_PREFIX), (d) } + +#define ARGPARSE_op(s,l,t,d) \ + { (s), (l), (ARGPARSE_TYPE_ ## t \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_n(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_i(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_INT \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_s(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_STRING \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_l(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_LONG \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } +#define ARGPARSE_op_u(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_ULONG \ + | ARGPARSE_OPT_OPTIONAL | ARGPARSE_OPT_PREFIX), (d) } + +#define ARGPARSE_c(s,l,d) \ + { (s), (l), (ARGPARSE_TYPE_NONE | ARGPARSE_OPT_COMMAND), (d) } + +#define ARGPARSE_ignore(s,l) \ + { (s), (l), (ARGPARSE_OPT_IGNORE), "@" } + +#define ARGPARSE_group(s,d) \ + { (s), NULL, 0, (d) } + +#define ARGPARSE_end() { 0, NULL, 0, NULL } + + +/* Other constants. */ +#define ARGPARSE_PRINT_WARNING 1 +#define ARGPARSE_PRINT_ERROR 2 + + +/* Error values. */ +#define ARGPARSE_IS_ARG (-1) +#define ARGPARSE_INVALID_OPTION (-2) +#define ARGPARSE_MISSING_ARG (-3) +#define ARGPARSE_KEYWORD_TOO_LONG (-4) +#define ARGPARSE_READ_ERROR (-5) +#define ARGPARSE_UNEXPECTED_ARG (-6) +#define ARGPARSE_INVALID_COMMAND (-7) +#define ARGPARSE_AMBIGUOUS_OPTION (-8) +#define ARGPARSE_AMBIGUOUS_COMMAND (-9) +#define ARGPARSE_INVALID_ALIAS (-10) +#define ARGPARSE_OUT_OF_CORE (-11) +#define ARGPARSE_INVALID_ARG (-12) + + +int arg_parse (ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); +int optfile_parse (FILE *fp, const char *filename, unsigned *lineno, + ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts); +void usage (int level); +const char *strusage (int level); +void set_strusage (const char *(*f)( int )); +void argparse_register_outfnc (int (*fnc)(int, const char *)); + +#endif /*LIBJNLIB_ARGPARSE_H*/ diff --git a/pinentry-dmenu/pinentry/memory.h b/pinentry-dmenu/pinentry/memory.h new file mode 100644 index 0000000..354c6c9 --- /dev/null +++ b/pinentry-dmenu/pinentry/memory.h @@ -0,0 +1,55 @@ +/* Quintuple Agent secure memory allocation + * Copyright (C) 1998,1999 Free Software Foundation, Inc. + * Copyright (C) 1999,2000 Robert Bihlmeyer + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _MEMORY_H +#define _MEMORY_H + +#include + +#ifdef __cplusplus +extern "C" { +#if 0 +} +#endif +#endif + + +/* values for flags, hardcoded in secmem.c */ +#define SECMEM_WARN 0 +#define SECMEM_DONT_WARN 1 +#define SECMEM_SUSPEND_WARN 2 + +void secmem_init( size_t npool ); +void secmem_term( void ); +void *secmem_malloc( size_t size ); +void *secmem_realloc( void *a, size_t newsize ); +void secmem_free( void *a ); +int m_is_secure( const void *p ); +void secmem_dump_stats(void); +void secmem_set_flags( unsigned flags ); +unsigned secmem_get_flags(void); +size_t secmem_get_max_size (void); + +#if 0 +{ +#endif +#ifdef __cplusplus +} +#endif +#endif /* _MEMORY_H */ diff --git a/pinentry-dmenu/pinentry/password-cache.c b/pinentry-dmenu/pinentry/password-cache.c new file mode 100644 index 0000000..70b33f4 --- /dev/null +++ b/pinentry-dmenu/pinentry/password-cache.c @@ -0,0 +1,163 @@ +/* password-cache.c - Password cache support. + Copyright (C) 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY 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. + + PINENTRY 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, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#ifdef HAVE_LIBSECRET +# include +#endif + +#include "password-cache.h" +#include "memory.h" + +#ifdef HAVE_LIBSECRET +static const SecretSchema * +gpg_schema (void) +{ + static const SecretSchema the_schema = { + "org.gnupg.Passphrase", SECRET_SCHEMA_NONE, + { + { "stored-by", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "keygrip", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "NULL", 0 }, + } + }; + return &the_schema; +} + +static char * +keygrip_to_label (const char *keygrip) +{ + char const prefix[] = "GnuPG: "; + char *label; + + label = malloc (sizeof (prefix) + strlen (keygrip)); + if (label) + { + memcpy (label, prefix, sizeof (prefix) - 1); + strcpy (&label[sizeof (prefix) - 1], keygrip); + } + return label; +} +#endif + +void +password_cache_save (const char *keygrip, const char *password) +{ +#ifdef HAVE_LIBSECRET + char *label; + GError *error = NULL; + + if (! *keygrip) + return; + + label = keygrip_to_label (keygrip); + if (! label) + return; + + if (! secret_password_store_sync (gpg_schema (), + SECRET_COLLECTION_DEFAULT, + label, password, NULL, &error, + "stored-by", "GnuPG Pinentry", + "keygrip", keygrip, NULL)) + { + printf("Failed to cache password for key %s with secret service: %s\n", + keygrip, error->message); + + g_error_free (error); + } + + free (label); +#else + return; +#endif +} + +char * +password_cache_lookup (const char *keygrip) +{ +#ifdef HAVE_LIBSECRET + GError *error = NULL; + char *password; + char *password2; + + if (! *keygrip) + return NULL; + + password = secret_password_lookup_nonpageable_sync + (gpg_schema (), NULL, &error, + "keygrip", keygrip, NULL); + + if (error != NULL) + { + printf("Failed to lookup password for key %s with secret service: %s\n", + keygrip, error->message); + g_error_free (error); + return NULL; + } + if (! password) + /* The password for this key is not cached. Just return NULL. */ + return NULL; + + /* The password needs to be returned in secmem allocated memory. */ + password2 = secmem_malloc (strlen (password) + 1); + if (password2) + strcpy(password2, password); + else + printf("secmem_malloc failed: can't copy password!\n"); + + secret_password_free (password); + + return password2; +#else + return NULL; +#endif +} + +/* Try and remove the cached password for key grip. Returns -1 on + error, 0 if the key is not found and 1 if the password was + removed. */ +int +password_cache_clear (const char *keygrip) +{ +#ifdef HAVE_LIBSECRET + GError *error = NULL; + int removed = secret_password_clear_sync (gpg_schema (), NULL, &error, + "keygrip", keygrip, NULL); + if (error != NULL) + { + printf("Failed to clear password for key %s with secret service: %s\n", + keygrip, error->message); + g_debug("%s", error->message); + g_error_free (error); + return -1; + } + if (removed) + return 1; + return 0; +#else + return -1; +#endif +} diff --git a/pinentry-dmenu/pinentry/password-cache.h b/pinentry-dmenu/pinentry/password-cache.h new file mode 100644 index 0000000..0bc8788 --- /dev/null +++ b/pinentry-dmenu/pinentry/password-cache.h @@ -0,0 +1,29 @@ +/* password-cache.h - Password cache support interfaces. + Copyright (C) 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY 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. + + PINENTRY 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, see . + */ + +#ifndef PASSWORD_CACHE_H +#define PASSWORD_CACHE_H + +void password_cache_save (const char *key_grip, const char *password); + +char *password_cache_lookup (const char *key_grip); + +int password_cache_clear (const char *keygrip); + +#endif diff --git a/pinentry-dmenu/pinentry/pinentry.c b/pinentry-dmenu/pinentry/pinentry.c new file mode 100644 index 0000000..f305a7d --- /dev/null +++ b/pinentry-dmenu/pinentry/pinentry.c @@ -0,0 +1,1308 @@ +/* pinentry.c - The PIN entry support library + Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY 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. + + PINENTRY 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, see . + */ + +#include +#include +#include +#include + +#include + +#include "memory.h" +#include "secmem-util.h" +#include "argparse.h" +#include "pinentry.h" +#include "password-cache.h" + +/* Keep the name of our program here. */ +static char this_pgmname[50]; + +struct pinentry pinentry; + +static void +pinentry_reset (int use_defaults) +{ + /* GPG Agent sets these options once when it starts the pinentry. + Don't reset them. */ + int grab = pinentry.grab; + char *ttyname = pinentry.ttyname; + char *ttytype = pinentry.ttytype; + char *lc_ctype = pinentry.lc_ctype; + char *lc_messages = pinentry.lc_messages; + int allow_external_password_cache = pinentry.allow_external_password_cache; + char *default_ok = pinentry.default_ok; + char *default_cancel = pinentry.default_cancel; + char *default_prompt = pinentry.default_prompt; + char *default_pwmngr = pinentry.default_pwmngr; + char *touch_file = pinentry.touch_file; + + /* These options are set from the command line. Don't reset + them. */ + int debug = pinentry.debug; + char *display = pinentry.display; + int parent_wid = pinentry.parent_wid; + + pinentry_color_t color_fg = pinentry.color_fg; + int color_fg_bright = pinentry.color_fg_bright; + pinentry_color_t color_bg = pinentry.color_bg; + pinentry_color_t color_so = pinentry.color_so; + int color_so_bright = pinentry.color_so_bright; + + int timout = pinentry.timeout; + + /* Free any allocated memory. */ + if (use_defaults) + { + free (pinentry.ttyname); + free (pinentry.ttytype); + free (pinentry.lc_ctype); + free (pinentry.lc_messages); + free (pinentry.default_ok); + free (pinentry.default_cancel); + free (pinentry.default_prompt); + free (pinentry.default_pwmngr); + free (pinentry.touch_file); + free (pinentry.display); + } + + free (pinentry.title); + free (pinentry.description); + free (pinentry.error); + free (pinentry.prompt); + free (pinentry.ok); + free (pinentry.notok); + free (pinentry.cancel); + secmem_free (pinentry.pin); + free (pinentry.repeat_passphrase); + free (pinentry.repeat_error_string); + free (pinentry.quality_bar); + free (pinentry.quality_bar_tt); + free (pinentry.keyinfo); + + /* Reset the pinentry structure. */ + memset (&pinentry, 0, sizeof (pinentry)); + + if (use_defaults) + { + /* Pinentry timeout in seconds. */ + pinentry.timeout = 60; + + /* Global grab. */ + pinentry.grab = 1; + + pinentry.color_fg = PINENTRY_COLOR_DEFAULT; + pinentry.color_fg_bright = 0; + pinentry.color_bg = PINENTRY_COLOR_DEFAULT; + pinentry.color_so = PINENTRY_COLOR_DEFAULT; + pinentry.color_so_bright = 0; + } + else + /* Restore the options. */ + { + pinentry.grab = grab; + pinentry.ttyname = ttyname; + pinentry.ttytype = ttytype; + pinentry.lc_ctype = lc_ctype; + pinentry.lc_messages = lc_messages; + pinentry.allow_external_password_cache = allow_external_password_cache; + pinentry.default_ok = default_ok; + pinentry.default_cancel = default_cancel; + pinentry.default_prompt = default_prompt; + pinentry.default_pwmngr = default_pwmngr; + pinentry.touch_file = touch_file; + + pinentry.debug = debug; + pinentry.display = display; + pinentry.parent_wid = parent_wid; + + pinentry.color_fg = color_fg; + pinentry.color_fg_bright = color_fg_bright; + pinentry.color_bg = color_bg; + pinentry.color_so = color_so; + pinentry.color_so_bright = color_so_bright; + + pinentry.timeout = timout; + } +} + +static gpg_error_t +pinentry_assuan_reset_handler (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + + pinentry_reset (0); + + return 0; +} + + + +static int lc_ctype_unknown_warning = 0; + +/* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a + pointer to the end of the new buffer. Note that BUFFER must be + large enough to keep the entire text; allocataing it 3 times of + TEXTLEN is sufficient. */ +static char * +copy_and_escape (char *buffer, const void *text, size_t textlen) +{ + int i; + const unsigned char *s = (unsigned char *)text; + char *p = buffer; + + for (i=0; i < textlen; i++) + { + if (s[i] < ' ' || s[i] == '+') + { + snprintf (p, 4, "%%%02X", s[i]); + p += 3; + } + else if (s[i] == ' ') + *p++ = '+'; + else + *p++ = s[i]; + } + return p; +} + + + +/* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH + because not all backends might be able to return a proper + C-string.). Returns: A value between -100 and 100 to give an + estimate of the passphrase's quality. Negative values are use if + the caller won't even accept that passphrase. Note that we expect + just one data line which should not be escaped in any represent a + numeric signed decimal value. Extra data is currently ignored but + should not be send at all. */ +int +pinentry_inq_quality (pinentry_t pin, const char *passphrase, size_t length) +{ + assuan_context_t ctx = pin->ctx_assuan; + const char prefix[] = "INQUIRE QUALITY "; + char *command; + char *line; + size_t linelen; + int gotvalue = 0; + int value = 0; + int rc; + + if (!ctx) + return 0; /* Can't run the callback. */ + + if (length > 300) + length = 300; /* Limit so that it definitely fits into an Assuan + line. */ + + command = secmem_malloc (strlen (prefix) + 3*length + 1); + if (!command) + return 0; + strcpy (command, prefix); + copy_and_escape (command + strlen(command), passphrase, length); + rc = assuan_write_line (ctx, command); + secmem_free (command); + if (rc) + { + fprintf (stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); + return 0; + } + + for (;;) + { + do + { + rc = assuan_read_line (ctx, &line, &linelen); + if (rc) + { + fprintf (stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); + return 0; + } + } + while (*line == '#' || !linelen); + if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' + && (!line[3] || line[3] == ' ')) + break; /* END command received*/ + if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' + && (!line[3] || line[3] == ' ')) + break; /* CAN command received*/ + if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' + && (!line[3] || line[3] == ' ')) + break; /* ERR command received*/ + if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) + continue; + gotvalue = 1; + value = atoi (line+2); + } + if (value < -100) + value = -100; + else if (value > 100) + value = 100; + + return value; +} + + + +/* Try to make room for at least LEN bytes in the pinentry. Returns + new buffer on success and 0 on failure or when the old buffer is + sufficient. */ +char * +pinentry_setbufferlen (pinentry_t pin, int len) +{ + char *newp; + + if (pin->pin_len) + assert (pin->pin); + else + assert (!pin->pin); + + if (len < 2048) + len = 2048; + + if (len <= pin->pin_len) + return pin->pin; + + newp = secmem_realloc (pin->pin, len); + if (newp) + { + pin->pin = newp; + pin->pin_len = len; + } + else + { + secmem_free (pin->pin); + pin->pin = 0; + pin->pin_len = 0; + } + return newp; +} + +static void +pinentry_setbuffer_clear (pinentry_t pin) +{ + if (! pin->pin) + { + assert (pin->pin_len == 0); + return; + } + + assert (pin->pin_len > 0); + + secmem_free (pin->pin); + pin->pin = NULL; + pin->pin_len = 0; +} + +static void +pinentry_setbuffer_init (pinentry_t pin) +{ + pinentry_setbuffer_clear (pin); + pinentry_setbufferlen (pin, 0); +} + +/* passphrase better be alloced with secmem_alloc. */ +void +pinentry_setbuffer_use (pinentry_t pin, char *passphrase, int len) +{ + if (! passphrase) + { + assert (len == 0); + pinentry_setbuffer_clear (pin); + + return; + } + + if (passphrase && len == 0) + len = strlen (passphrase) + 1; + + if (pin->pin) + secmem_free (pin->pin); + + pin->pin = passphrase; + pin->pin_len = len; +} + +static struct assuan_malloc_hooks assuan_malloc_hooks = { + secmem_malloc, secmem_realloc, secmem_free +}; + +/* Initialize the secure memory subsystem, drop privileges and return. + Must be called early. */ +void +pinentry_init (const char *pgmname) +{ + /* Store away our name. */ + if (strlen (pgmname) > sizeof this_pgmname - 2) + abort (); + strcpy (this_pgmname, pgmname); + + gpgrt_check_version (NULL); + + /* Initialize secure memory. 1 is too small, so the default size + will be used. */ + secmem_init (1); + secmem_set_flags (SECMEM_WARN); + drop_privs (); + + if (atexit (secmem_term)) + { + /* FIXME: Could not register at-exit function, bail out. */ + } + + assuan_set_malloc_hooks (&assuan_malloc_hooks); +} + +/* Simple test to check whether DISPLAY is set or the option --display + was given. Used to decide whether the GUI or curses should be + initialized. */ +int +pinentry_have_display (int argc, char **argv) +{ + for (; argc; argc--, argv++) + if (!strcmp (*argv, "--display") || !strncmp (*argv, "--display=", 10)) + return 1; + return 0; +} + + + +/* Print usage information and and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 11: p = this_pgmname; break; + case 12: p = "pinentry"; break; + case 13: p = PACKAGE_VERSION; break; + case 14: p = "Copyright (C) 2015 g10 Code GmbH"; break; + case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break; + case 1: + case 40: + { + static char *str; + + if (!str) + { + size_t n = 50 + strlen (this_pgmname); + str = malloc (n); + if (str) + snprintf (str, n, "Usage: %s [options] (-h for help)", + this_pgmname); + } + p = str; + } + break; + case 41: + p = "Ask securely for a secret and print it to stdout."; + break; + + case 42: + p = "1"; /* Flag print 40 as part of 41. */ + break; + + default: p = NULL; break; + } + return p; +} + + +char * +parse_color (char *arg, pinentry_color_t *color_p, int *bright_p) +{ + static struct + { + const char *name; + pinentry_color_t color; + } colors[] = { { "none", PINENTRY_COLOR_NONE }, + { "default", PINENTRY_COLOR_DEFAULT }, + { "black", PINENTRY_COLOR_BLACK }, + { "red", PINENTRY_COLOR_RED }, + { "green", PINENTRY_COLOR_GREEN }, + { "yellow", PINENTRY_COLOR_YELLOW }, + { "blue", PINENTRY_COLOR_BLUE }, + { "magenta", PINENTRY_COLOR_MAGENTA }, + { "cyan", PINENTRY_COLOR_CYAN }, + { "white", PINENTRY_COLOR_WHITE } }; + + int i; + char *new_arg; + pinentry_color_t color = PINENTRY_COLOR_DEFAULT; + + if (!arg) + return NULL; + + new_arg = strchr (arg, ','); + if (new_arg) + new_arg++; + + if (bright_p) + { + const char *bname[] = { "bright-", "bright", "bold-", "bold" }; + + *bright_p = 0; + for (i = 0; i < sizeof (bname) / sizeof (bname[0]); i++) + if (!strncasecmp (arg, bname[i], strlen (bname[i]))) + { + *bright_p = 1; + arg += strlen (bname[i]); + } + } + + for (i = 0; i < sizeof (colors) / sizeof (colors[0]); i++) + if (!strncasecmp (arg, colors[i].name, strlen (colors[i].name))) + color = colors[i].color; + + *color_p = color; + return new_arg; +} + +/* Parse the command line options. May exit the program if only help + or version output is requested. */ +void +pinentry_parse_opts (int argc, char *argv[]) +{ + static ARGPARSE_OPTS opts[] = { + ARGPARSE_s_n('d', "debug", "Turn on debugging output"), + ARGPARSE_s_s('D', "display", "|DISPLAY|Set the X display"), + ARGPARSE_s_s('T', "ttyname", "|FILE|Set the tty terminal node name"), + ARGPARSE_s_s('N', "ttytype", "|NAME|Set the tty terminal type"), + ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"), + ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"), + ARGPARSE_s_i('o', "timeout", + "|SECS|Timeout waiting for input after this many seconds"), + ARGPARSE_s_n('g', "no-global-grab", + "Grab keyboard only while window is focused"), + ARGPARSE_s_u('W', "parent-wid", "Parent window ID (for positioning)"), + ARGPARSE_s_s('c', "colors", "|STRING|Set custom colors for ncurses"), + ARGPARSE_end() + }; + ARGPARSE_ARGS pargs = { &argc, &argv, 0 }; + + set_strusage (my_strusage); + + pinentry_reset (1); + + while (arg_parse (&pargs, opts)) + { + switch (pargs.r_opt) + { + case 'd': + pinentry.debug = 1; + break; + case 'g': + pinentry.grab = 0; + break; + + case 'D': + /* Note, this is currently not used because the GUI engine + has already been initialized when parsing these options. */ + pinentry.display = strdup (pargs.r.ret_str); + if (!pinentry.display) + { + exit (EXIT_FAILURE); + } + break; + case 'T': + pinentry.ttyname = strdup (pargs.r.ret_str); + if (!pinentry.ttyname) + { + exit (EXIT_FAILURE); + } + break; + case 'N': + pinentry.ttytype = strdup (pargs.r.ret_str); + if (!pinentry.ttytype) + { + exit (EXIT_FAILURE); + } + break; + case 'C': + pinentry.lc_ctype = strdup (pargs.r.ret_str); + if (!pinentry.lc_ctype) + { + exit (EXIT_FAILURE); + } + break; + case 'M': + pinentry.lc_messages = strdup (pargs.r.ret_str); + if (!pinentry.lc_messages) + { + exit (EXIT_FAILURE); + } + break; + case 'W': + pinentry.parent_wid = pargs.r.ret_ulong; + break; + + case 'c': + { + char *tmpstr = pargs.r.ret_str; + + tmpstr = parse_color (tmpstr, &pinentry.color_fg, + &pinentry.color_fg_bright); + tmpstr = parse_color (tmpstr, &pinentry.color_bg, NULL); + tmpstr = parse_color (tmpstr, &pinentry.color_so, + &pinentry.color_so_bright); + } + break; + + case 'o': + pinentry.timeout = pargs.r.ret_int; + break; + + default: + pargs.err = ARGPARSE_PRINT_WARNING; + break; + } + } +} + + +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + (void)ctx; + + if (!strcmp (key, "no-grab") && !*value) + pinentry.grab = 0; + else if (!strcmp (key, "grab") && !*value) + pinentry.grab = 1; + else if (!strcmp (key, "debug-wait")) + { + } + else if (!strcmp (key, "display")) + { + if (pinentry.display) + free (pinentry.display); + pinentry.display = strdup (value); + if (!pinentry.display) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "ttyname")) + { + if (pinentry.ttyname) + free (pinentry.ttyname); + pinentry.ttyname = strdup (value); + if (!pinentry.ttyname) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "ttytype")) + { + if (pinentry.ttytype) + free (pinentry.ttytype); + pinentry.ttytype = strdup (value); + if (!pinentry.ttytype) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "lc-ctype")) + { + if (pinentry.lc_ctype) + free (pinentry.lc_ctype); + pinentry.lc_ctype = strdup (value); + if (!pinentry.lc_ctype) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "lc-messages")) + { + if (pinentry.lc_messages) + free (pinentry.lc_messages); + pinentry.lc_messages = strdup (value); + if (!pinentry.lc_messages) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "parent-wid")) + { + pinentry.parent_wid = atoi (value); + /* FIXME: Use strtol and add some error handling. */ + } + else if (!strcmp (key, "touch-file")) + { + if (pinentry.touch_file) + free (pinentry.touch_file); + pinentry.touch_file = strdup (value); + if (!pinentry.touch_file) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-ok")) + { + pinentry.default_ok = strdup (value); + if (!pinentry.default_ok) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-cancel")) + { + pinentry.default_cancel = strdup (value); + if (!pinentry.default_cancel) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-prompt")) + { + pinentry.default_prompt = strdup (value); + if (!pinentry.default_prompt) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "default-pwmngr")) + { + pinentry.default_pwmngr = strdup (value); + if (!pinentry.default_pwmngr) + return gpg_error_from_syserror (); + } + else if (!strcmp (key, "allow-external-password-cache") && !*value) + { + pinentry.allow_external_password_cache = 1; + pinentry.tried_password_cache = 0; + } + else if (!strcmp (key, "allow-emacs-prompt") && !*value) + { + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + return gpg_error (GPG_ERR_UNKNOWN_OPTION); + return 0; +} + + +/* Note, that it is sufficient to allocate the target string D as + long as the source string S, i.e.: strlen(s)+1; */ +static void +strcpy_escaped (char *d, const char *s) +{ + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 ( s); + s += 2; + } + else + *d++ = *s++; + } + *d = 0; +} + + +static gpg_error_t +cmd_setdesc (assuan_context_t ctx, char *line) +{ + char *newd; + + (void)ctx; + + newd = malloc (strlen (line) + 1); + if (!newd) + return gpg_error_from_syserror (); + + strcpy_escaped (newd, line); + if (pinentry.description) + free (pinentry.description); + pinentry.description = newd; + return 0; +} + + +static gpg_error_t +cmd_setprompt (assuan_context_t ctx, char *line) +{ + char *newp; + + (void)ctx; + + newp = malloc (strlen (line) + 1); + if (!newp) + return gpg_error_from_syserror (); + + strcpy_escaped (newp, line); + if (pinentry.prompt) + free (pinentry.prompt); + pinentry.prompt = newp; + return 0; +} + + +/* The data provided at LINE may be used by pinentry implementations + to identify a key for caching strategies of its own. The empty + string and --clear mean that the key does not have a stable + identifier. */ +static gpg_error_t +cmd_setkeyinfo (assuan_context_t ctx, char *line) +{ + (void)ctx; + + if (pinentry.keyinfo) + free (pinentry.keyinfo); + + if (*line && strcmp(line, "--clear") != 0) + pinentry.keyinfo = strdup (line); + else + pinentry.keyinfo = NULL; + + return 0; +} + + +static gpg_error_t +cmd_setrepeat (assuan_context_t ctx, char *line) +{ + char *p; + + (void)ctx; + + p = malloc (strlen (line) + 1); + if (!p) + return gpg_error_from_syserror (); + + strcpy_escaped (p, line); + free (pinentry.repeat_passphrase); + pinentry.repeat_passphrase = p; + return 0; +} + + +static gpg_error_t +cmd_setrepeaterror (assuan_context_t ctx, char *line) +{ + char *p; + + (void)ctx; + + p = malloc (strlen (line) + 1); + if (!p) + return gpg_error_from_syserror (); + + strcpy_escaped (p, line); + free (pinentry.repeat_error_string); + pinentry.repeat_error_string = p; + return 0; +} + + +static gpg_error_t +cmd_seterror (assuan_context_t ctx, char *line) +{ + char *newe; + + (void)ctx; + + newe = malloc (strlen (line) + 1); + if (!newe) + return gpg_error_from_syserror (); + + strcpy_escaped (newe, line); + if (pinentry.error) + free (pinentry.error); + pinentry.error = newe; + return 0; +} + + +static gpg_error_t +cmd_setok (assuan_context_t ctx, char *line) +{ + char *newo; + + (void)ctx; + + newo = malloc (strlen (line) + 1); + if (!newo) + return gpg_error_from_syserror (); + + strcpy_escaped (newo, line); + if (pinentry.ok) + free (pinentry.ok); + pinentry.ok = newo; + return 0; +} + + +static gpg_error_t +cmd_setnotok (assuan_context_t ctx, char *line) +{ + char *newo; + + (void)ctx; + + newo = malloc (strlen (line) + 1); + if (!newo) + return gpg_error_from_syserror (); + + strcpy_escaped (newo, line); + if (pinentry.notok) + free (pinentry.notok); + pinentry.notok = newo; + return 0; +} + + +static gpg_error_t +cmd_setcancel (assuan_context_t ctx, char *line) +{ + char *newc; + + (void)ctx; + + newc = malloc (strlen (line) + 1); + if (!newc) + return gpg_error_from_syserror (); + + strcpy_escaped (newc, line); + if (pinentry.cancel) + free (pinentry.cancel); + pinentry.cancel = newc; + return 0; +} + + +static gpg_error_t +cmd_settimeout (assuan_context_t ctx, char *line) +{ + (void)ctx; + + if (line && *line) + pinentry.timeout = atoi (line); + + return 0; +} + +static gpg_error_t +cmd_settitle (assuan_context_t ctx, char *line) +{ + char *newt; + + (void)ctx; + + newt = malloc (strlen (line) + 1); + if (!newt) + return gpg_error_from_syserror (); + + strcpy_escaped (newt, line); + if (pinentry.title) + free (pinentry.title); + pinentry.title = newt; + return 0; +} + +static gpg_error_t +cmd_setqualitybar (assuan_context_t ctx, char *line) +{ + char *newval; + + (void)ctx; + + if (!*line) + line = "Quality:"; + + newval = malloc (strlen (line) + 1); + if (!newval) + return gpg_error_from_syserror (); + + strcpy_escaped (newval, line); + if (pinentry.quality_bar) + free (pinentry.quality_bar); + pinentry.quality_bar = newval; + return 0; +} + +/* Set the tooltip to be used for a quality bar. */ +static gpg_error_t +cmd_setqualitybar_tt (assuan_context_t ctx, char *line) +{ + char *newval; + + (void)ctx; + + if (*line) + { + newval = malloc (strlen (line) + 1); + if (!newval) + return gpg_error_from_syserror (); + + strcpy_escaped (newval, line); + } + else + newval = NULL; + if (pinentry.quality_bar_tt) + free (pinentry.quality_bar_tt); + pinentry.quality_bar_tt = newval; + return 0; +} + + +static gpg_error_t +cmd_getpin (assuan_context_t ctx, char *line) +{ + int result; + int set_prompt = 0; + int just_read_password_from_cache = 0; + + (void)line; + + pinentry_setbuffer_init (&pinentry); + if (!pinentry.pin) + return gpg_error (GPG_ERR_ENOMEM); + + /* Try reading from the password cache. */ + if (/* If repeat passphrase is set, then we don't want to read from + the cache. */ + ! pinentry.repeat_passphrase + /* Are we allowed to read from the cache? */ + && pinentry.allow_external_password_cache + && pinentry.keyinfo + /* Only read from the cache if we haven't already tried it. */ + && ! pinentry.tried_password_cache + /* If the last read resulted in an error, then don't read from + the cache. */ + && ! pinentry.error) + { + char *password; + + pinentry.tried_password_cache = 1; + + password = password_cache_lookup (pinentry.keyinfo); + if (password) + /* There is a cached password. Try it. */ + { + int len = strlen(password) + 1; + if (len > pinentry.pin_len) + len = pinentry.pin_len; + + memcpy (pinentry.pin, password, len); + pinentry.pin[len] = '\0'; + + secmem_free (password); + + pinentry.pin_from_cache = 1; + + assuan_write_status (ctx, "PASSWORD_FROM_CACHE", ""); + + /* Result is the length of the password not including the + NUL terminator. */ + result = len - 1; + + just_read_password_from_cache = 1; + + goto out; + } + } + + /* The password was not cached (or we are not allowed to / cannot + use the cache). Prompt the user. */ + pinentry.pin_from_cache = 0; + + if (!pinentry.prompt) + { + pinentry.prompt = pinentry.default_prompt?pinentry.default_prompt:"PIN:"; + set_prompt = 1; + } + pinentry.locale_err = 0; + pinentry.specific_err = 0; + pinentry.close_button = 0; + pinentry.repeat_okay = 0; + pinentry.one_button = 0; + pinentry.ctx_assuan = ctx; + result = (*pinentry_cmd_handler) (&pinentry); + pinentry.ctx_assuan = NULL; + if (pinentry.error) + { + free (pinentry.error); + pinentry.error = NULL; + } + if (pinentry.repeat_passphrase) + { + free (pinentry.repeat_passphrase); + pinentry.repeat_passphrase = NULL; + } + if (set_prompt) + pinentry.prompt = NULL; + + pinentry.quality_bar = 0; /* Reset it after the command. */ + + if (pinentry.close_button) + assuan_write_status (ctx, "BUTTON_INFO", "close"); + + if (result < 0) + { + pinentry_setbuffer_clear (&pinentry); + if (pinentry.specific_err) + return pinentry.specific_err; + return (pinentry.locale_err + ? gpg_error (GPG_ERR_LOCALE_PROBLEM) + : gpg_error (GPG_ERR_CANCELED)); + } + + out: + if (result) + { + if (pinentry.repeat_okay) + assuan_write_status (ctx, "PIN_REPEATED", ""); + result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin)); + if (!result) + result = assuan_send_data (ctx, NULL, 0); + + if (/* GPG Agent says it's okay. */ + pinentry.allow_external_password_cache && pinentry.keyinfo + /* We didn't just read it from the cache. */ + && ! just_read_password_from_cache + /* And the user said it's okay. */ + && pinentry.may_cache_password) + /* Cache the password. */ + password_cache_save (pinentry.keyinfo, pinentry.pin); + } + + pinentry_setbuffer_clear (&pinentry); + + return result; +} + + +/* Note that the option --one-button is a hack to allow the use of old + pinentries while the caller is ignoring the result. Given that + options have never been used or flagged as an error the new option + is an easy way to enable the messsage mode while not requiring to + update pinentry or to have the caller test for the message + command. New applications which are free to require an updated + pinentry should use MESSAGE instead. */ +static gpg_error_t +cmd_confirm (assuan_context_t ctx, char *line) +{ + int result; + + pinentry.one_button = !!strstr (line, "--one-button"); + pinentry.quality_bar = 0; + pinentry.close_button = 0; + pinentry.locale_err = 0; + pinentry.specific_err = 0; + pinentry.canceled = 0; + pinentry_setbuffer_clear (&pinentry); + result = (*pinentry_cmd_handler) (&pinentry); + if (pinentry.error) + { + free (pinentry.error); + pinentry.error = NULL; + } + + if (pinentry.close_button) + assuan_write_status (ctx, "BUTTON_INFO", "close"); + + if (result) + return 0; + + if (pinentry.specific_err) + return pinentry.specific_err; + + if (pinentry.locale_err) + return gpg_error (GPG_ERR_LOCALE_PROBLEM); + + if (pinentry.one_button) + return 0; + + if (pinentry.canceled) + return gpg_error (GPG_ERR_CANCELED); + return gpg_error (GPG_ERR_NOT_CONFIRMED); +} + + +static gpg_error_t +cmd_message (assuan_context_t ctx, char *line) +{ + (void)line; + + return cmd_confirm (ctx, "--one-button"); +} + +/* GETINFO + + Multipurpose function to return a variety of information. + Supported values for WHAT are: + + version - Return the version of the program. + pid - Return the process id of the server. + */ +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + rc = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + char numbuf[50]; + + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else + rc = gpg_error (GPG_ERR_ASS_PARAMETER); + return rc; +} + +/* CLEARPASSPHRASE + + Clear the cache passphrase associated with the key identified by + cacheid. + */ +static gpg_error_t +cmd_clear_passphrase (assuan_context_t ctx, char *line) +{ + (void)ctx; + + if (! line) + return gpg_error (GPG_ERR_ASS_INV_VALUE); + + /* Remove leading and trailing white space. */ + while (*line == ' ') + line ++; + while (line[strlen (line) - 1] == ' ') + line[strlen (line) - 1] = 0; + + switch (password_cache_clear (line)) + { + case 1: return 0; + case 0: return gpg_error (GPG_ERR_ASS_INV_VALUE); + default: return gpg_error (GPG_ERR_ASS_GENERAL); + } +} + +/* Tell the assuan library about our commands. */ +static gpg_error_t +register_commands (assuan_context_t ctx) +{ + static struct + { + const char *name; + gpg_error_t (*handler) (assuan_context_t, char *line); + } table[] = + { + { "SETDESC", cmd_setdesc }, + { "SETPROMPT", cmd_setprompt }, + { "SETKEYINFO", cmd_setkeyinfo }, + { "SETREPEAT", cmd_setrepeat }, + { "SETREPEATERROR", cmd_setrepeaterror }, + { "SETERROR", cmd_seterror }, + { "SETOK", cmd_setok }, + { "SETNOTOK", cmd_setnotok }, + { "SETCANCEL", cmd_setcancel }, + { "GETPIN", cmd_getpin }, + { "CONFIRM", cmd_confirm }, + { "MESSAGE", cmd_message }, + { "SETQUALITYBAR", cmd_setqualitybar }, + { "SETQUALITYBAR_TT", cmd_setqualitybar_tt }, + { "GETINFO", cmd_getinfo }, + { "SETTITLE", cmd_settitle }, + { "SETTIMEOUT", cmd_settimeout }, + { "CLEARPASSPHRASE", cmd_clear_passphrase }, + { NULL } + }; + int i, j; + gpg_error_t rc; + + for (i = j = 0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, NULL); + if (rc) + return rc; + } + return 0; +} + + +int +pinentry_loop2 (int infd, int outfd) +{ + gpg_error_t rc; + assuan_fd_t filedes[2]; + assuan_context_t ctx; + + /* Extra check to make sure we have dropped privs. */ + if (getuid() != geteuid()) + abort (); + + rc = assuan_new (&ctx); + if (rc) + { + fprintf (stderr, "server context creation failed: %s\n", + gpg_strerror (rc)); + return -1; + } + + /* For now we use a simple pipe based server so that we can work + from scripts. We will later add options to run as a daemon and + wait for requests on a Unix domain socket. */ + filedes[0] = assuan_fdopen (infd); + filedes[1] = assuan_fdopen (outfd); + rc = assuan_init_pipe_server (ctx, filedes); + if (rc) + { + fprintf (stderr, "%s: failed to initialize the server: %s\n", + this_pgmname, gpg_strerror (rc)); + return -1; + } + rc = register_commands (ctx); + if (rc) + { + fprintf (stderr, "%s: failed to the register commands with Assuan: %s\n", + this_pgmname, gpg_strerror (rc)); + return -1; + } + + assuan_register_option_handler (ctx, option_handler); + assuan_register_reset_notify (ctx, pinentry_assuan_reset_handler); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + break; + else if (rc) + { + fprintf (stderr, "%s: Assuan accept problem: %s\n", + this_pgmname, gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + fprintf (stderr, "%s: Assuan processing failed: %s\n", + this_pgmname, gpg_strerror (rc)); + continue; + } + } + + assuan_release (ctx); + return 0; +} + + +/* Start the pinentry event loop. The program will start to process + Assuan commands until it is finished or an error occurs. If an + error occurs, -1 is returned. Otherwise, 0 is returned. */ +int +pinentry_loop (void) +{ + return pinentry_loop2 (STDIN_FILENO, STDOUT_FILENO); +} diff --git a/pinentry-dmenu/pinentry/pinentry.h b/pinentry-dmenu/pinentry/pinentry.h new file mode 100644 index 0000000..e154ac5 --- /dev/null +++ b/pinentry-dmenu/pinentry/pinentry.h @@ -0,0 +1,280 @@ +/* pinentry.h - The interface for the PIN entry support library. + Copyright (C) 2002, 2003, 2010, 2015 g10 Code GmbH + + This file is part of PINENTRY. + + PINENTRY 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. + + PINENTRY 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, see . + */ + +#ifndef PINENTRY_H +#define PINENTRY_H + +#ifdef __cplusplus +extern "C" { +#if 0 +} +#endif +#endif + +typedef enum { + PINENTRY_COLOR_NONE, PINENTRY_COLOR_DEFAULT, + PINENTRY_COLOR_BLACK, PINENTRY_COLOR_RED, + PINENTRY_COLOR_GREEN, PINENTRY_COLOR_YELLOW, + PINENTRY_COLOR_BLUE, PINENTRY_COLOR_MAGENTA, + PINENTRY_COLOR_CYAN, PINENTRY_COLOR_WHITE +} pinentry_color_t; + +struct pinentry +{ + /* The window title, or NULL. (Assuan: "SETTITLE TITLE".) */ + char *title; + /* The description to display, or NULL. (Assuan: "SETDESC + DESC".) */ + char *description; + /* The error message to display, or NULL. (Assuan: "SETERROR + MESSAGE".) */ + char *error; + /* The prompt to display, or NULL. (Assuan: "SETPROMPT + prompt".) */ + char *prompt; + /* The OK button text to display, or NULL. (Assuan: "SETOK + OK".) */ + char *ok; + /* The Not-OK button text to display, or NULL. This is the text for + the alternative option shown by the third button. (Assuan: + "SETNOTOK NOTOK".) */ + char *notok; + /* The Cancel button text to display, or NULL. (Assuan: "SETCANCEL + CANCEL".) */ + char *cancel; + + /* The buffer to store the secret into. */ + char *pin; + /* The length of the buffer. */ + int pin_len; + /* Whether the pin was read from an external cache (1) or entered by + the user (0). */ + int pin_from_cache; + + /* The name of the X display to use if X is available and supported. + (Assuan: "OPTION display DISPLAY".) */ + char *display; + /* The name of the terminal node to open if X not available or + supported. (Assuan: "OPTION ttyname TTYNAME".) */ + char *ttyname; + /* The type of the terminal. (Assuan: "OPTION ttytype TTYTYPE".) */ + char *ttytype; + /* The LC_CTYPE value for the terminal. (Assuan: "OPTION lc-ctype + LC_CTYPE".) */ + char *lc_ctype; + /* The LC_MESSAGES value for the terminal. (Assuan: "OPTION + lc-messages LC_MESSAGES".) */ + char *lc_messages; + + /* True if debug mode is requested. */ + int debug; + + /* The number of seconds before giving up while waiting for user input. */ + int timeout; + + /* True if caller should grab the keyboard. (Assuan: "OPTION grab" + or "OPTION no-grab".) */ + int grab; + /* The window ID of the parent window over which the pinentry window + should be displayed. (Assuan: "OPTION parent-wid WID".) */ + int parent_wid; + + /* The name of an optional file which will be touched after a curses + entry has been displayed. (Assuan: "OPTION touch-file + FILENAME".) */ + char *touch_file; + + /* The frontend should set this to -1 if the user canceled the + request, and to the length of the PIN stored in pin + otherwise. */ + int result; + + /* The frontend should set this if the NOTOK button was pressed. */ + int canceled; + + /* The frontend should set this to true if an error with the local + conversion occured. */ + int locale_err; + + /* The frontend should set this to a gpg-error so that commands are + able to return specific error codes. This is an ugly hack due to + the fact that pinentry_cmd_handler_t returns the length of the + passphrase or a negative error code. */ + int specific_err; + + /* The frontend should set this to true if the window close button + has been used. This flag is used in addition to a regular return + value. */ + int close_button; + + /* The caller should set this to true if only one button is + required. This is useful for notification dialogs where only a + dismiss button is required. */ + int one_button; + + /* If true a second prompt for the passphrase is shown and the user + is expected to enter the same passphrase again. Pinentry checks + that both match. (Assuan: "SETREPEAT".) */ + char *repeat_passphrase; + + /* The string to show if a repeated passphrase does not match. + (Assuan: "SETREPEATERROR ERROR".) */ + char *repeat_error_string; + + /* Set to true if the passphrase has been entered a second time and + matches the first passphrase. */ + int repeat_okay; + + /* If this is not NULL, a passphrase quality indicator is shown. + There will also be an inquiry back to the caller to get an + indication of the quality for the passphrase entered so far. The + string is used as a label for the quality bar. (Assuan: + "SETQUALITYBAR LABEL".) */ + char *quality_bar; + + /* The tooltip to be show for the qualitybar. Malloced or NULL. + (Assuan: "SETQUALITYBAR_TT TOOLTIP".) */ + char *quality_bar_tt; + + /* For the curses pinentry, the color of error messages. */ + pinentry_color_t color_fg; + int color_fg_bright; + pinentry_color_t color_bg; + pinentry_color_t color_so; + int color_so_bright; + + /* Malloced and i18ned default strings or NULL. These strings may + include an underscore character to indicate an accelerator key. + A double underscore represents a plain one. */ + /* (Assuan: "OPTION default-ok OK"). */ + char *default_ok; + /* (Assuan: "OPTION default-cancel CANCEL"). */ + char *default_cancel; + /* (Assuan: "OPTION default-prompt PROMPT"). */ + char *default_prompt; + /* (Assuan: "OPTION default-pwmngr + SAVE_PASSWORD_WITH_PASSWORD_MANAGER?"). */ + char *default_pwmngr; + + /* Whether we are allowed to read the password from an external + cache. (Assuan: "OPTION allow-external-password-cache") */ + int allow_external_password_cache; + + /* We only try the cache once. */ + int tried_password_cache; + + /* A stable identifier for the key. (Assuan: "SETKEYINFO + KEYINFO".) */ + char *keyinfo; + + /* Whether we may cache the password (according to the user). */ + int may_cache_password; + + /* NOTE: If you add any additional fields to this structure, be sure + to update the initializer in pinentry/pinentry.c!!! */ + + /* For the quality indicator we need to do an inquiry. Thus we need + to save the assuan ctx. */ + void *ctx_assuan; + +}; +typedef struct pinentry *pinentry_t; + + +/* The pinentry command handler type processes the pinentry request + PIN. If PIN->pin is zero, request a confirmation, otherwise a PIN + entry. On confirmation, the function should return TRUE if + confirmed, and FALSE otherwise. On PIN entry, the function should + return -1 if an error occured or the user cancelled the operation + and 1 otherwise. */ +typedef int (*pinentry_cmd_handler_t) (pinentry_t pin); + +/* Start the pinentry event loop. The program will start to process + Assuan commands until it is finished or an error occurs. If an + error occurs, -1 is returned and errno indicates the type of an + error. Otherwise, 0 is returned. */ +int pinentry_loop (void); + +/* The same as above but allows to specify the i/o descriptors. + * infd and outfd will be duplicated in this function so the caller + * still has to close them if necessary. + */ +int pinentry_loop2 (int infd, int outfd); + + +/* Convert the UTF-8 encoded string TEXT to the encoding given in + LC_CTYPE. Return NULL on error. */ +char *pinentry_utf8_to_local (const char *lc_ctype, const char *text); + +/* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With + SECURE set to true, use secure memory for the returned buffer. + Return NULL on error. */ +char *pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure); + + +/* Run a quality inquiry for PASSPHRASE of LENGTH. */ +int pinentry_inq_quality (pinentry_t pin, + const char *passphrase, size_t length); + +/* Try to make room for at least LEN bytes for the pin in the pinentry + PIN. Returns new buffer on success and 0 on failure. */ +char *pinentry_setbufferlen (pinentry_t pin, int len); + +/* Use the buffer at BUFFER for PIN->PIN. BUFFER must be NULL or + allocated using secmem_alloc. LEN is the size of the buffer. If + it is unknown, but BUFFER is a NUL terminated string, you pass 0 to + just use strlen(buffer)+1. */ +void pinentry_setbuffer_use (pinentry_t pin, char *buffer, int len); + +/* Initialize the secure memory subsystem, drop privileges and + return. Must be called early. */ +void pinentry_init (const char *pgmname); + +/* Return true if either DISPLAY is set or ARGV contains the string + "--display". */ +int pinentry_have_display (int argc, char **argv); + +/* Parse the command line options. May exit the program if only help + or version output is requested. */ +void pinentry_parse_opts (int argc, char *argv[]); + + +/* The caller must define this variable to process assuan commands. */ +extern pinentry_cmd_handler_t pinentry_cmd_handler; + + + + + +#ifdef HAVE_W32_SYSTEM +/* Windows declares sleep as obsolete, but provides a definition for + _sleep but non for the still existing sleep. */ +#define sleep(a) _sleep ((a)) +#endif /*HAVE_W32_SYSTEM*/ + + + +#if 0 +{ +#endif +#ifdef __cplusplus +} +#endif + +#endif /* PINENTRY_H */ diff --git a/pinentry-dmenu/pinentry/secmem++.h b/pinentry-dmenu/pinentry/secmem++.h new file mode 100644 index 0000000..88a5d45 --- /dev/null +++ b/pinentry-dmenu/pinentry/secmem++.h @@ -0,0 +1,91 @@ +/* STL allocator for secmem + * Copyright (C) 2008 Marc Mutz + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SECMEM_SECMEMPP_H__ +#define __SECMEM_SECMEMPP_H__ + +#include "secmem/memory.h" +#include + +namespace secmem { + + template + class alloc { + public: + // type definitions: + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; + + // rebind + template + struct rebind { + typedef alloc other; + }; + + // address + pointer address( reference value ) const { + return &value; + } + const_pointer address( const_reference value ) const { + return &value; + } + + // (trivial) ctors and dtors + alloc() {} + alloc( const alloc & ) {} + template alloc( const alloc & ) {} + // copy ctor is ok + ~alloc() {} + + // de/allocation + size_type max_size() const { + return secmem_get_max_size(); + } + + pointer allocate( size_type n, void * =0 ) { + return static_cast( secmem_malloc( n * sizeof(T) ) ); + } + + void deallocate( pointer p, size_type ) { + secmem_free( p ); + } + + // de/construct + void construct( pointer p, const T & value ) { + void * loc = p; + new (loc)T(value); + } + void destruct( pointer p ) { + p->~T(); + } + }; + + // equality comparison + template + bool operator==( const alloc &, const alloc & ) { return true; } + template + bool operator!=( const alloc &, const alloc & ) { return false; } + +} + +#endif /* __SECMEM_SECMEMPP_H__ */ diff --git a/pinentry-dmenu/pinentry/secmem-util.h b/pinentry-dmenu/pinentry/secmem-util.h new file mode 100644 index 0000000..b422182 --- /dev/null +++ b/pinentry-dmenu/pinentry/secmem-util.h @@ -0,0 +1,3 @@ +/* This file exists because "util.h" is such a generic name that it is + likely to clash with other such files. */ +#include "util.h" diff --git a/pinentry-dmenu/pinentry/secmem.c b/pinentry-dmenu/pinentry/secmem.c new file mode 100644 index 0000000..db25ec3 --- /dev/null +++ b/pinentry-dmenu/pinentry/secmem.c @@ -0,0 +1,460 @@ +/* secmem.c - memory allocation from a secure heap + * Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#if defined(HAVE_MLOCK) || defined(HAVE_MMAP) +# include +# include +# include +# ifdef USE_CAPABILITIES +# include +# endif +#endif +#include + +#include "memory.h" + +#ifdef ORIGINAL_GPG_VERSION +#include "types.h" +#include "util.h" +#else /* ORIGINAL_GPG_VERSION */ + +#include "util.h" + +typedef union { + int a; + short b; + char c[1]; + long d; +#ifdef HAVE_U64_TYPEDEF + u64 e; +#endif + float f; + double g; +} PROPERLY_ALIGNED_TYPE; + +#define log_error log_info +#define log_bug log_fatal + +void +log_info(char *template, ...) +{ + va_list args; + + va_start(args, template); + vfprintf(stderr, template, args); + va_end(args); +} + +void +log_fatal(char *template, ...) +{ + va_list args; + + va_start(args, template); + vfprintf(stderr, template, args); + va_end(args); + exit(EXIT_FAILURE); +} + +#endif /* ORIGINAL_GPG_VERSION */ + +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +# define MAP_ANONYMOUS MAP_ANON +#endif + +#define DEFAULT_POOLSIZE 16384 + +typedef struct memblock_struct MEMBLOCK; +struct memblock_struct { + unsigned size; + union { + MEMBLOCK *next; + PROPERLY_ALIGNED_TYPE aligned; + } u; +}; + + + +static void *pool; +static volatile int pool_okay; /* may be checked in an atexit function */ +static int pool_is_mmapped; +static size_t poolsize; /* allocated length */ +static size_t poollen; /* used length */ +static MEMBLOCK *unused_blocks; +static unsigned max_alloced; +static unsigned cur_alloced; +static unsigned max_blocks; +static unsigned cur_blocks; +static int disable_secmem; +static int show_warning; +static int no_warning; +static int suspend_warning; + + +static void +print_warn(void) +{ + if( !no_warning ) + log_info("Warning: using insecure memory!\n"); +} + + +static void +lock_pool( void *p, size_t n ) +{ +#if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK) + int err; + + cap_set_proc( cap_from_text("cap_ipc_lock+ep") ); + err = mlock( p, n ); + if( err && errno ) + err = errno; + cap_set_proc( cap_from_text("cap_ipc_lock+p") ); + + if( err ) { + if( errno != EPERM + #ifdef EAGAIN /* OpenBSD returns this */ + && errno != EAGAIN + #endif + ) + log_error("can't lock memory: %s\n", strerror(err)); + show_warning = 1; + } + +#elif defined(HAVE_MLOCK) + uid_t uid; + int err; + + uid = getuid(); + +#ifdef HAVE_BROKEN_MLOCK + if( uid ) { + errno = EPERM; + err = errno; + } + else { + err = mlock( p, n ); + if( err && errno ) + err = errno; + } +#else + err = mlock( p, n ); + if( err && errno ) + err = errno; +#endif + + if( uid && !geteuid() ) { + if( setuid( uid ) || getuid() != geteuid() ) + log_fatal("failed to reset uid: %s\n", strerror(errno)); + } + + if( err ) { + if( errno != EPERM +#ifdef EAGAIN /* OpenBSD returns this */ + && errno != EAGAIN +#endif + ) + log_error("can't lock memory: %s\n", strerror(err)); + show_warning = 1; + } + +#else + log_info("Please note that you don't have secure memory on this system\n"); +#endif +} + + +static void +init_pool( size_t n) +{ + size_t pgsize; + + poolsize = n; + + if( disable_secmem ) + log_bug("secure memory is disabled"); + +#ifdef HAVE_GETPAGESIZE + pgsize = getpagesize(); +#else + pgsize = 4096; +#endif + +#if HAVE_MMAP + poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1); +# ifdef MAP_ANONYMOUS + pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +# else /* map /dev/zero instead */ + { int fd; + + fd = open("/dev/zero", O_RDWR); + if( fd == -1 ) { + log_error("can't open /dev/zero: %s\n", strerror(errno) ); + pool = (void*)-1; + } + else { + pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE, fd, 0); + close (fd); + } + } +# endif + if( pool == (void*)-1 ) + log_info("can't mmap pool of %u bytes: %s - using malloc\n", + (unsigned)poolsize, strerror(errno)); + else { + pool_is_mmapped = 1; + pool_okay = 1; + } + +#endif + if( !pool_okay ) { + pool = malloc( poolsize ); + if( !pool ) + log_fatal("can't allocate memory pool of %u bytes\n", + (unsigned)poolsize); + else + pool_okay = 1; + } + lock_pool( pool, poolsize ); + poollen = 0; +} + + +/* concatenate unused blocks */ +static void +compress_pool(void) +{ + /* fixme: we really should do this */ +} + +void +secmem_set_flags( unsigned flags ) +{ + int was_susp = suspend_warning; + + no_warning = flags & 1; + suspend_warning = flags & 2; + + /* and now issue the warning if it is not longer suspended */ + if( was_susp && !suspend_warning && show_warning ) { + show_warning = 0; + print_warn(); + } +} + +unsigned +secmem_get_flags(void) +{ + unsigned flags; + + flags = no_warning ? 1:0; + flags |= suspend_warning ? 2:0; + return flags; +} + +void +secmem_init( size_t n ) +{ + if( !n ) { +#ifdef USE_CAPABILITIES + /* drop all capabilities */ + cap_set_proc( cap_from_text("all-eip") ); + +#elif !defined(HAVE_DOSISH_SYSTEM) + uid_t uid; + + disable_secmem=1; + uid = getuid(); + if( uid != geteuid() ) { + if( setuid( uid ) || getuid() != geteuid() ) + log_fatal("failed to drop setuid\n" ); + } +#endif + } + else { + if( n < DEFAULT_POOLSIZE ) + n = DEFAULT_POOLSIZE; + if( !pool_okay ) + init_pool(n); + else + log_error("Oops, secure memory pool already initialized\n"); + } +} + + +void * +secmem_malloc( size_t size ) +{ + MEMBLOCK *mb, *mb2; + int compressed=0; + + if( !pool_okay ) { + log_info( + "operation is not possible without initialized secure memory\n"); + log_info("(you may have used the wrong program for this task)\n"); + exit(2); + } + if( show_warning && !suspend_warning ) { + show_warning = 0; + print_warn(); + } + + /* blocks are always a multiple of 32 */ + size += sizeof(MEMBLOCK); + size = ((size + 31) / 32) * 32; + + retry: + /* try to get it from the used blocks */ + for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next ) + if( mb->size >= size ) { + if( mb2 ) + mb2->u.next = mb->u.next; + else + unused_blocks = mb->u.next; + goto leave; + } + /* allocate a new block */ + if( (poollen + size <= poolsize) ) { + mb = (void*)((char*)pool + poollen); + poollen += size; + mb->size = size; + } + else if( !compressed ) { + compressed=1; + compress_pool(); + goto retry; + } + else + return NULL; + + leave: + cur_alloced += mb->size; + cur_blocks++; + if( cur_alloced > max_alloced ) + max_alloced = cur_alloced; + if( cur_blocks > max_blocks ) + max_blocks = cur_blocks; + + memset (&mb->u.aligned.c, 0, + size - (size_t) &((struct memblock_struct *) 0)->u.aligned.c); + + return &mb->u.aligned.c; +} + + +void * +secmem_realloc( void *p, size_t newsize ) +{ + MEMBLOCK *mb; + size_t size; + void *a; + + if (! p) + return secmem_malloc(newsize); + + mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c)); + size = mb->size; + if( newsize < size ) + return p; /* it is easier not to shrink the memory */ + a = secmem_malloc( newsize ); + memcpy(a, p, size); + memset((char*)a+size, 0, newsize-size); + secmem_free(p); + return a; +} + + +void +secmem_free( void *a ) +{ + MEMBLOCK *mb; + size_t size; + + if( !a ) + return; + + mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c)); + size = mb->size; + /* This does not make much sense: probably this memory is held in the + * cache. We do it anyway: */ + wipememory2(mb, 0xff, size ); + wipememory2(mb, 0xaa, size ); + wipememory2(mb, 0x55, size ); + wipememory2(mb, 0x00, size ); + mb->size = size; + mb->u.next = unused_blocks; + unused_blocks = mb; + cur_blocks--; + cur_alloced -= size; +} + +int +m_is_secure( const void *p ) +{ + return p >= pool && p < (void*)((char*)pool+poolsize); +} + +void +secmem_term() +{ + if( !pool_okay ) + return; + + wipememory2( pool, 0xff, poolsize); + wipememory2( pool, 0xaa, poolsize); + wipememory2( pool, 0x55, poolsize); + wipememory2( pool, 0x00, poolsize); +#if HAVE_MMAP + if( pool_is_mmapped ) + munmap( pool, poolsize ); +#endif + pool = NULL; + pool_okay = 0; + poolsize=0; + poollen=0; + unused_blocks=NULL; +} + + +void +secmem_dump_stats() +{ + if( disable_secmem ) + return; + fprintf(stderr, + "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n", + cur_alloced, max_alloced, cur_blocks, max_blocks, + (ulong)poollen, (ulong)poolsize ); +} + + +size_t +secmem_get_max_size (void) +{ + return poolsize; +} diff --git a/pinentry-dmenu/pinentry/util.c b/pinentry-dmenu/pinentry/util.c new file mode 100644 index 0000000..a47164a --- /dev/null +++ b/pinentry-dmenu/pinentry/util.c @@ -0,0 +1,150 @@ +/* Quintuple Agent + * Copyright (C) 1999 Robert Bihlmeyer + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE 1 + +#include +#ifndef HAVE_W32CE_SYSTEM +# include +#endif +#include +#include +#include +#include +#include + +#include "util.h" + +#ifndef HAVE_DOSISH_SYSTEM +static int uid_set = 0; +static uid_t real_uid, file_uid; +#endif /*!HAVE_DOSISH_SYSTEM*/ + +/* Write DATA of size BYTES to FD, until all is written or an error + occurs. */ +ssize_t +xwrite(int fd, const void *data, size_t bytes) +{ + char *ptr; + size_t todo; + ssize_t written = 0; + + for (ptr = (char *)data, todo = bytes; todo; ptr += written, todo -= written) + { + do + written = write (fd, ptr, todo); + while ( +#ifdef HAVE_W32CE_SYSTEM + 0 +#else + written == -1 && errno == EINTR +#endif + ); + if (written < 0) + break; + } + return written; +} + +#if 0 +extern int debug; + +int +debugmsg(const char *fmt, ...) +{ + va_list va; + int ret; + + if (debug) { + va_start(va, fmt); + fprintf(stderr, "\e[4m"); + ret = vfprintf(stderr, fmt, va); + fprintf(stderr, "\e[24m"); + va_end(va); + return ret; + } else + return 0; +} +#endif + +/* initialize uid variables */ +#ifndef HAVE_DOSISH_SYSTEM +static void +init_uids(void) +{ + real_uid = getuid(); + file_uid = geteuid(); + uid_set = 1; +} +#endif + + +#if 0 /* Not used. */ +/* lower privileges to the real user's */ +void +lower_privs() +{ + if (!uid_set) + init_uids(); + if (real_uid != file_uid) { +#ifdef HAVE_SETEUID + if (seteuid(real_uid) < 0) { + perror("lowering privileges failed"); + exit(EXIT_FAILURE); + } +#else + fprintf(stderr, _("Warning: running q-agent setuid on this system is dangerous\n")); +#endif /* HAVE_SETEUID */ + } +} +#endif /* if 0 */ + +#if 0 /* Not used. */ +/* raise privileges to the effective user's */ +void +raise_privs() +{ + assert(real_uid >= 0); /* lower_privs() must be called before this */ +#ifdef HAVE_SETEUID + if (real_uid != file_uid && seteuid(file_uid) < 0) { + perror("Warning: raising privileges failed"); + } +#endif /* HAVE_SETEUID */ +} +#endif /* if 0 */ + +/* drop all additional privileges */ +void +drop_privs() +{ +#ifndef HAVE_DOSISH_SYSTEM + if (!uid_set) + init_uids(); + if (real_uid != file_uid) { + if (setuid(real_uid) < 0) { + perror("dropping privileges failed"); + exit(EXIT_FAILURE); + } + file_uid = real_uid; + } +#endif +} diff --git a/pinentry-dmenu/pinentry/util.h b/pinentry-dmenu/pinentry/util.h new file mode 100644 index 0000000..7986c99 --- /dev/null +++ b/pinentry-dmenu/pinentry/util.h @@ -0,0 +1,66 @@ +/* Quintuple Agent utilities + * Copyright (C) 1999 Robert Bihlmeyer + * Copyright (C) 2003 g10 Code GmbH + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _UTIL_H +#define _UTIL_H + +#include + +#ifndef HAVE_BYTE_TYPEDEF +# undef byte +# ifdef __riscos__ + /* Norcroft treats char == unsigned char but char* != unsigned char* */ + typedef char byte; +# else + typedef unsigned char byte; +# endif +# define HAVE_BYTE_TYPEDEF +#endif + +#ifndef HAVE_ULONG_TYPEDEF +# undef ulong + typedef unsigned long ulong; +# define HAVE_ULONG_TYPEDEF +#endif + + +ssize_t xwrite(int, const void *, size_t); /* write until finished */ +int debugmsg(const char *, ...); /* output a debug message if debugging==on */ +void drop_privs(void); /* finally drop privileges */ + + +/* To avoid that a compiler optimizes certain memset calls away, these + macros may be used instead. */ +#define wipememory2(_ptr,_set,_len) do { \ + volatile char *_vptr=(volatile char *)(_ptr); \ + size_t _vlen=(_len); \ + while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ + } while(0) +#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) +#define wipe(_ptr,_len) wipememory2(_ptr,0,_len) + + + + +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + +#endif diff --git a/pinentry-dmenu/test b/pinentry-dmenu/test new file mode 100755 index 0000000..c63c8ce --- /dev/null +++ b/pinentry-dmenu/test @@ -0,0 +1,43 @@ +#!/bin/sh + +if [[ $1 -eq 1 ]]; then + +echo "SETTITLE title +SETPROMPT prompt +SETDESC PROMPT +GETPIN +BYE" | ./pinentry-dmenu + +elif [[ $1 -eq 2 ]]; then + +echo "SETTITLE title +SETPROMPT confirm +SETDESC CONFIRM +confirm +BYE" | ./pinentry-dmenu + +elif [[ $1 -eq 3 ]]; then + +echo "SETTITLE title +SETPROMPT prompt +SETDESC REPEAT +SETREPEAT repeat +GETPIN +BYE" | ./pinentry-dmenu + +else + +echo "SETTITLE title +SETPROMPT prompt +SETDESC PROMPT +GETPIN +SETPROMPT confirm +SETDESC CONFIRM +confirm +SETPROMPT repeat +SETDESC REPEAT +SETREPEAT +GETPIN +BYE" | ./pinentry-dmenu + +fi diff --git a/pinentry-dmenu/util.c b/pinentry-dmenu/util.c new file mode 100644 index 0000000..fe044fc --- /dev/null +++ b/pinentry-dmenu/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#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/pinentry-dmenu/util.h b/pinentry-dmenu/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/pinentry-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/slstatus/.gitignore b/slstatus/.gitignore new file mode 100644 index 0000000..7001fe3 --- /dev/null +++ b/slstatus/.gitignore @@ -0,0 +1,2 @@ +*.o +slstatus diff --git a/slstatus/LICENSE b/slstatus/LICENSE new file mode 100644 index 0000000..70b9fb3 --- /dev/null +++ b/slstatus/LICENSE @@ -0,0 +1,41 @@ +ISC License + +Copyright 2016-2022 Aaron Marcher + +Copyright 2016 Roy Freytag +Copyright 2016 Vincent Loupmon +Copyright 2016 Daniel Walter +Copyright 2016-2018 Ali H. Fardan +Copyright 2016 Jody Leonard +Copyright 2016-2018 Quentin Rameau +Copyright 2016 Mike Coddington +Copyright 2016-2018 parazyd +Copyright 2017 Tobias Stoeckmann +Copyright 2017-2018 Laslo Hunhold +Copyright 2018 Darron Anderson +Copyright 2018 Josuah Demangeon +Copyright 2018 Tobias Tschinkowitz +Copyright 2018 David Demelier +Copyright 2018-2012 Michael Buch +Copyright 2018 Ian Remmler +Copyright 2016-2019 Joerg Jung +Copyright 2019 Ryan Kes +Copyright 2019 Cem Keylan +Copyright 2019 dsp +Copyright 2019-2022 Ingo Feinerer +Copyright 2020 Alexandre Ratchov +Copyright 2020 Mart Lubbers +Copyright 2020 Daniel Moch +Copyright 2022 NRK + +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/slstatus/Makefile b/slstatus/Makefile new file mode 100644 index 0000000..e6fb825 --- /dev/null +++ b/slstatus/Makefile @@ -0,0 +1,68 @@ +# See LICENSE file for copyright and license details +# slstatus - suckless status monitor +.POSIX: + +include config.mk + +REQ = util +COM =\ + components/alsa_master_vol\ + components/battery\ + components/cpu\ + components/datetime\ + components/disk\ + components/entropy\ + components/hostname\ + components/ip\ + components/kernel_release\ + components/keyboard_indicators\ + components/keymap\ + components/load_avg\ + components/netspeeds\ + components/num_files\ + components/ram\ + components/run_command\ + components/swap\ + components/temperature\ + components/uptime\ + components/user\ + components/volume\ + components/wifi + +all: slstatus + +$(COM:=.o): config.mk $(REQ:=.h) slstatus.h +slstatus.o: slstatus.c slstatus.h arg.h config.h config.mk $(REQ:=.h) + +.c.o: + $(CC) -o $@ -c $(CPPFLAGS) $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +slstatus: slstatus.o $(COM:=.o) $(REQ:=.o) + $(CC) -o $@ $(LDFLAGS) $(COM:=.o) $(REQ:=.o) slstatus.o $(LDLIBS) + +clean: + rm -f slstatus slstatus.o $(COM:=.o) $(REQ:=.o) + +dist: + rm -rf "slstatus-$(VERSION)" + mkdir -p "slstatus-$(VERSION)/components" + cp -R LICENSE Makefile README config.mk config.def.h \ + arg.h slstatus.c $(COM:=.c) $(REQ:=.c) $(REQ:=.h) \ + slstatus.1 "slstatus-$(VERSION)" + tar -cf - "slstatus-$(VERSION)" | gzip -c > "slstatus-$(VERSION).tar.gz" + rm -rf "slstatus-$(VERSION)" + +install: all + mkdir -p "$(DESTDIR)$(PREFIX)/bin" + cp -f slstatus "$(DESTDIR)$(PREFIX)/bin" + chmod 755 "$(DESTDIR)$(PREFIX)/bin/slstatus" + mkdir -p "$(DESTDIR)$(MANPREFIX)/man1" + cp -f slstatus.1 "$(DESTDIR)$(MANPREFIX)/man1" + chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)/bin/slstatus" + rm -f "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1" diff --git a/slstatus/README b/slstatus/README new file mode 100644 index 0000000..4da0756 --- /dev/null +++ b/slstatus/README @@ -0,0 +1,69 @@ +slstatus - suckless status +========================== +slstatus is a suckless status monitor for window managers that use WM_NAME +(e.g. dwm) or stdin to fill the status bar. + + +Features +-------- +- Battery percentage/state/time left +- CPU usage +- CPU frequency +- Custom shell commands +- Date and time +- Disk status (free storage, percentage, total storage and used storage) +- Available entropy +- Username/GID/UID +- Hostname +- IP address (IPv4 and IPv6) +- Kernel version +- Keyboard indicators +- Keymap +- Load average +- Network speeds (RX and TX) +- Number of files in a directory (hint: Maildir) +- Memory status (free memory, percentage, total memory and used memory) +- Swap status (free swap, percentage, total swap and used swap) +- Temperature +- Uptime +- Volume percentage +- WiFi signal percentage and ESSID + + +Requirements +------------ +Currently slstatus works on FreeBSD, Linux and OpenBSD. +In order to build slstatus you need the Xlib header files. + +- For volume percentage on Linux the kernel module `snd-mixer-oss` must be + loaded. +- For volume percentage on FreeBSD, `sndio` must be installed. + + +Installation +------------ +Edit config.mk to match your local setup (slstatus is installed into the +/usr/local namespace by default). + +Afterwards enter the following command to build and install slstatus (if +necessary as root): + + make clean install + + +Running slstatus +---------------- +See the man page for details. + + +Configuration +------------- +slstatus can be customized by creating a custom config.h and (re)compiling the +source code. This keeps it fast, secure and simple. + + +Upcoming +-------- + +A release (v1.0) will come soon... ;) +After a long phase of inactivity, development has been continued! diff --git a/slstatus/arg.h b/slstatus/arg.h new file mode 100644 index 0000000..5f1f408 --- /dev/null +++ b/slstatus/arg.h @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef ARG_H +#define ARG_H + +extern char *argv0; + +/* int main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \ + *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \ + int i_, argused_; \ + if ((*argv)[1] == '-' && !(*argv)[2]) { \ + argc--, argv++; \ + break; \ + } \ + for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \ + switch ((*argv)[i_]) +#define ARGEND if (argused_) { \ + if ((*argv)[i_ + 1]) { \ + break; \ + } else { \ + argc--, argv++; \ + break; \ + } \ + } \ + } \ + } +#define ARGC() ((*argv)[i_]) +#define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \ + (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x)) +#define EARGF(x) ARGF_(((x), exit(1), (char *)0)) +#define ARGF() ARGF_((char *)0) + +#endif diff --git a/slstatus/components/battery.c b/slstatus/components/battery.c new file mode 100644 index 0000000..582af58 --- /dev/null +++ b/slstatus/components/battery.c @@ -0,0 +1,294 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + #include + #include + #include + + #define POWER_SUPPLY_CAPACITY "/sys/class/power_supply/%s/capacity" + #define POWER_SUPPLY_STATUS "/sys/class/power_supply/%s/status" + #define POWER_SUPPLY_CHARGE "/sys/class/power_supply/%s/charge_now" + #define POWER_SUPPLY_ENERGY "/sys/class/power_supply/%s/energy_now" + #define POWER_SUPPLY_CURRENT "/sys/class/power_supply/%s/current" + #define POWER_SUPPLY_POWER "/sys/class/power_supply/%s/power" + + const char notify_cmd[] = "notify-send"; + const char battery_str[] = "Battery"; + int last_notified_level = 0; + + extern const int notifiable_levels[]; + + static const char * + pick(const char *bat, const char *f1, const char *f2, char *path, + size_t length) + { + if (esnprintf(path, length, f1, bat) > 0 && + access(path, R_OK) == 0) + return f1; + + if (esnprintf(path, length, f2, bat) > 0 && + access(path, R_OK) == 0) + return f2; + + return NULL; + } + + const char * + battery_perc(const char *bat) + { + int perc; + char path[PATH_MAX]; + + if (esnprintf(path, sizeof(path), POWER_SUPPLY_CAPACITY, bat) < 0) + return NULL; + if (pscanf(path, "%d", &perc) != 1) + return NULL; + + return bprintf("%d", perc); + } + + void battery_notify(const char *bat) +{ + int cap_perc; + char state[12]; + char path[PATH_MAX]; + + if (esnprintf(path, sizeof(path), POWER_SUPPLY_CAPACITY, bat) < 0 || pscanf(path, "%d", &cap_perc) != 1) + return; + + if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0 || pscanf(path, "%12[a-zA-Z ]", &state) != 1) + return; + + if (strcmp("Charging", state) == 0) + { + last_notified_level = 0; + + return; + } + + if (strcmp("Discharging", state) != 0) + return; + + size_t i; + const int size = sizeof(*notifiable_levels); + char cmd[28]; + + for (i = 0; i < size; i++) + { + if (notifiable_levels[i] != cap_perc) + continue; + + if (notifiable_levels[i] != last_notified_level) + { + last_notified_level = notifiable_levels[i]; + + snprintf(cmd, 100, "%s %s %d%%", notify_cmd, battery_str, cap_perc); + system(cmd); + + break; + } + } +} + + const char * + battery_state(const char *bat) + { + static struct { + char *state; + char *symbol; + } map[] = { + { "Charging", "⚡" }, + { "Discharging", "🔌" }, + { "Full", "🔋" }, + { "Not charging", "🪫" }, + }; + size_t i; + char path[PATH_MAX], state[12]; + + if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0) + return NULL; + if (pscanf(path, "%12[a-zA-Z ]", state) != 1) + return NULL; + + for (i = 0; i < LEN(map); i++) + if (!strcmp(map[i].state, state)) + break; + + return (i == LEN(map)) ? "?" : map[i].symbol; + } + + const char * + battery_remaining(const char *bat) + { + uintmax_t charge_now, current_now, m, h; + double timeleft; + char path[PATH_MAX], state[12]; + + if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0) + return NULL; + if (pscanf(path, "%12[a-zA-Z ]", state) != 1) + return NULL; + + if (!pick(bat, POWER_SUPPLY_CHARGE, POWER_SUPPLY_ENERGY, path, + sizeof(path)) || + pscanf(path, "%ju", &charge_now) < 0) + return NULL; + + if (!strcmp(state, "Discharging")) { + if (!pick(bat, POWER_SUPPLY_CURRENT, POWER_SUPPLY_POWER, path, + sizeof(path)) || + pscanf(path, "%ju", ¤t_now) < 0) + return NULL; + + if (current_now == 0) + return NULL; + + timeleft = (double)charge_now / (double)current_now; + h = timeleft; + m = (timeleft - (double)h) * 60; + + return bprintf("%juh %jum", h, m); + } + + return ""; + } +#elif defined(__OpenBSD__) + #include + #include + #include + #include + + static int + load_apm_power_info(struct apm_power_info *apm_info) + { + int fd; + + fd = open("/dev/apm", O_RDONLY); + if (fd < 0) { + warn("open '/dev/apm':"); + return 0; + } + + memset(apm_info, 0, sizeof(struct apm_power_info)); + if (ioctl(fd, APM_IOC_GETPOWER, apm_info) < 0) { + warn("ioctl 'APM_IOC_GETPOWER':"); + close(fd); + return 0; + } + return close(fd), 1; + } + + const char * + battery_perc(const char *unused) + { + struct apm_power_info apm_info; + + if (load_apm_power_info(&apm_info)) + return bprintf("%d", apm_info.battery_life); + + return NULL; + } + + const char * + battery_state(const char *unused) + { + struct { + unsigned int state; + char *symbol; + } map[] = { + { APM_AC_ON, "+" }, + { APM_AC_OFF, "-" }, + }; + struct apm_power_info apm_info; + size_t i; + + if (load_apm_power_info(&apm_info)) { + for (i = 0; i < LEN(map); i++) + if (map[i].state == apm_info.ac_state) + break; + + return (i == LEN(map)) ? "?" : map[i].symbol; + } + + return NULL; + } + + const char * + battery_remaining(const char *unused) + { + struct apm_power_info apm_info; + unsigned int h, m; + + if (load_apm_power_info(&apm_info)) { + if (apm_info.ac_state != APM_AC_ON) { + h = apm_info.minutes_left / 60; + m = apm_info.minutes_left % 60; + return bprintf("%uh %02um", h, m); + } else { + return ""; + } + } + + return NULL; + } +#elif defined(__FreeBSD__) + #include + + #define BATTERY_LIFE "hw.acpi.battery.life" + #define BATTERY_STATE "hw.acpi.battery.state" + #define BATTERY_TIME "hw.acpi.battery.time" + + const char * + battery_perc(const char *unused) + { + int cap; + size_t len; + + len = sizeof(cap); + if (sysctlbyname(BATTERY_LIFE, &cap, &len, NULL, 0) < 0 || !len) + return NULL; + + return bprintf("%d", cap); + } + + const char * + battery_state(const char *unused) + { + int state; + size_t len; + + len = sizeof(state); + if (sysctlbyname(BATTERY_STATE, &state, &len, NULL, 0) < 0 || !len) + return NULL; + + switch (state) { + case 0: /* FALLTHROUGH */ + case 2: + return "+"; + case 1: + return "-"; + default: + return "?"; + } + } + + const char * + battery_remaining(const char *unused) + { + int rem; + size_t len; + + len = sizeof(rem); + if (sysctlbyname(BATTERY_TIME, &rem, &len, NULL, 0) < 0 || !len + || rem < 0) + return NULL; + + return bprintf("%uh %02um", rem / 60, rem % 60); + } +#endif diff --git a/slstatus/components/cpu.c b/slstatus/components/cpu.c new file mode 100644 index 0000000..d0d03c7 --- /dev/null +++ b/slstatus/components/cpu.c @@ -0,0 +1,157 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + #define CPU_FREQ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" + + const char * + cpu_freq(const char *unused) + { + uintmax_t freq; + + /* in kHz */ + if (pscanf(CPU_FREQ, "%ju", &freq) != 1) + return NULL; + + return fmt_human(freq * 1000, 1000); + } + + const char * + cpu_perc(const char *unused) + { + static long double a[7]; + long double b[7], sum; + + memcpy(b, a, sizeof(b)); + /* cpu user nice system idle iowait irq softirq */ + if (pscanf("/proc/stat", "%*s %Lf %Lf %Lf %Lf %Lf %Lf %Lf", + &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6]) + != 7) + return NULL; + + if (b[0] == 0) + return NULL; + + sum = (b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6]) - + (a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6]); + + if (sum == 0) + return NULL; + + return bprintf("%d", (int)(100 * + ((b[0] + b[1] + b[2] + b[5] + b[6]) - + (a[0] + a[1] + a[2] + a[5] + a[6])) / sum)); + } +#elif defined(__OpenBSD__) + #include + #include + #include + + const char * + cpu_freq(const char *unused) + { + int freq, mib[2]; + size_t size; + + mib[0] = CTL_HW; + mib[1] = HW_CPUSPEED; + + size = sizeof(freq); + + /* in MHz */ + if (sysctl(mib, 2, &freq, &size, NULL, 0) < 0) { + warn("sysctl 'HW_CPUSPEED':"); + return NULL; + } + + return fmt_human(freq * 1E6, 1000); + } + + const char * + cpu_perc(const char *unused) + { + int mib[2]; + static uintmax_t a[CPUSTATES]; + uintmax_t b[CPUSTATES], sum; + size_t size; + + mib[0] = CTL_KERN; + mib[1] = KERN_CPTIME; + + size = sizeof(a); + + memcpy(b, a, sizeof(b)); + if (sysctl(mib, 2, &a, &size, NULL, 0) < 0) { + warn("sysctl 'KERN_CPTIME':"); + return NULL; + } + if (b[0] == 0) + return NULL; + + sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) - + (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]); + + if (sum == 0) + return NULL; + + return bprintf("%d", 100 * + ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] + + a[CP_INTR]) - + (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + + b[CP_INTR])) / sum); + } +#elif defined(__FreeBSD__) + #include + #include + #include + + const char * + cpu_freq(const char *unused) + { + int freq; + size_t size; + + size = sizeof(freq); + /* in MHz */ + if (sysctlbyname("hw.clockrate", &freq, &size, NULL, 0) < 0 || !size) { + warn("sysctlbyname 'hw.clockrate':"); + return NULL; + } + + return fmt_human(freq * 1E6, 1000); + } + + const char * + cpu_perc(const char *unused) + { + size_t size; + static long a[CPUSTATES]; + long b[CPUSTATES], sum; + + size = sizeof(a); + memcpy(b, a, sizeof(b)); + if (sysctlbyname("kern.cp_time", &a, &size, NULL, 0) < 0 || !size) { + warn("sysctlbyname 'kern.cp_time':"); + return NULL; + } + if (b[0] == 0) + return NULL; + + sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) - + (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]); + + if (sum == 0) + return NULL; + + return bprintf("%d", 100 * + ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] + + a[CP_INTR]) - + (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + + b[CP_INTR])) / sum); + } +#endif diff --git a/slstatus/components/datetime.c b/slstatus/components/datetime.c new file mode 100644 index 0000000..5b10daf --- /dev/null +++ b/slstatus/components/datetime.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +datetime(const char *fmt) +{ + time_t t; + + t = time(NULL); + if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) { + warn("strftime: Result string exceeds buffer size"); + return NULL; + } + + return buf; +} diff --git a/slstatus/components/disk.c b/slstatus/components/disk.c new file mode 100644 index 0000000..0d1c13e --- /dev/null +++ b/slstatus/components/disk.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +disk_free(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return fmt_human(fs.f_frsize * fs.f_bavail, 1024); +} + +const char * +disk_perc(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return bprintf("%d", (int)(100 * + (1.0f - ((float)fs.f_bavail / (float)fs.f_blocks)))); +} + +const char * +disk_total(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return fmt_human(fs.f_frsize * fs.f_blocks, 1024); +} + +const char * +disk_used(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return fmt_human(fs.f_frsize * (fs.f_blocks - fs.f_bfree), 1024); +} diff --git a/slstatus/components/entropy.c b/slstatus/components/entropy.c new file mode 100644 index 0000000..66b2e5a --- /dev/null +++ b/slstatus/components/entropy.c @@ -0,0 +1,28 @@ +/* See LICENSE file for copyright and license details. */ +#include "../slstatus.h" +#if defined(__linux__) + #include + #include + + #include "../util.h" + + #define ENTROPY_AVAIL "/proc/sys/kernel/random/entropy_avail" + + const char * + entropy(const char *unused) + { + uintmax_t num; + + if (pscanf(ENTROPY_AVAIL, "%ju", &num) != 1) + return NULL; + + return bprintf("%ju", num); + } +#elif defined(__OpenBSD__) | defined(__FreeBSD__) + const char * + entropy(const char *unused) + { + /* Unicode Character 'INFINITY' (U+221E) */ + return "\xe2\x88\x9e"; + } +#endif diff --git a/slstatus/components/hostname.c b/slstatus/components/hostname.c new file mode 100644 index 0000000..dab8b63 --- /dev/null +++ b/slstatus/components/hostname.c @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +hostname(const char *unused) +{ + if (gethostname(buf, sizeof(buf)) < 0) { + warn("gethostbyname:"); + return NULL; + } + + return buf; +} diff --git a/slstatus/components/ip.c b/slstatus/components/ip.c new file mode 100644 index 0000000..9476549 --- /dev/null +++ b/slstatus/components/ip.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#if defined(__OpenBSD__) + #include + #include +#elif defined(__FreeBSD__) + #include + #include +#endif + +#include "../slstatus.h" +#include "../util.h" + +static const char * +ip(const char *interface, unsigned short sa_family) +{ + struct ifaddrs *ifaddr, *ifa; + int s; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr) < 0) { + warn("getifaddrs:"); + return NULL; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + + s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (!strcmp(ifa->ifa_name, interface) && + (ifa->ifa_addr->sa_family == sa_family)) { + freeifaddrs(ifaddr); + if (s != 0) { + warn("getnameinfo: %s", gai_strerror(s)); + return NULL; + } + return bprintf("%s", host); + } + } + + freeifaddrs(ifaddr); + + return NULL; +} + +const char * +ipv4(const char *interface) +{ + return ip(interface, AF_INET); +} + +const char * +ipv6(const char *interface) +{ + return ip(interface, AF_INET6); +} diff --git a/slstatus/components/kernel_release.c b/slstatus/components/kernel_release.c new file mode 100644 index 0000000..36a6a44 --- /dev/null +++ b/slstatus/components/kernel_release.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +kernel_release(const char *unused) +{ + struct utsname udata; + + if (uname(&udata) < 0) { + warn("uname:"); + return NULL; + } + + return bprintf("%s", udata.release); +} diff --git a/slstatus/components/keyboard_indicators.c b/slstatus/components/keyboard_indicators.c new file mode 100644 index 0000000..5f62bb7 --- /dev/null +++ b/slstatus/components/keyboard_indicators.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +/* + * fmt consists of uppercase or lowercase 'c' for caps lock and/or 'n' for num + * lock, each optionally followed by '?', in the order of indicators desired. + * If followed by '?', the letter with case preserved is included in the output + * if the corresponding indicator is on. Otherwise, the letter is always + * included, lowercase when off and uppercase when on. + */ +const char * +keyboard_indicators(const char *fmt) +{ + Display *dpy; + XKeyboardState state; + size_t fmtlen, i, n; + int togglecase, isset; + char key; + + if (!(dpy = XOpenDisplay(NULL))) { + warn("XOpenDisplay: Failed to open display"); + return NULL; + } + XGetKeyboardControl(dpy, &state); + XCloseDisplay(dpy); + + fmtlen = strnlen(fmt, 4); + for (i = n = 0; i < fmtlen; i++) { + key = tolower(fmt[i]); + if (key != 'c' && key != 'n') + continue; + + togglecase = (i + 1 >= fmtlen || fmt[i + 1] != '?'); + isset = (state.led_mask & (1 << (key == 'n'))); + + if (togglecase) + buf[n++] = isset ? toupper(key) : key; + else if (isset) + buf[n++] = fmt[i]; + } + + buf[n] = 0; + return buf; +} diff --git a/slstatus/components/keymap.c b/slstatus/components/keymap.c new file mode 100644 index 0000000..4740431 --- /dev/null +++ b/slstatus/components/keymap.c @@ -0,0 +1,85 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +static int +valid_layout_or_variant(char *sym) +{ + size_t i; + /* invalid symbols from xkb rules config */ + static const char *invalid[] = { "evdev", "inet", "pc", "base" }; + + for (i = 0; i < LEN(invalid); i++) + if (!strncmp(sym, invalid[i], strlen(invalid[i]))) + return 0; + + return 1; +} + +static char * +get_layout(char *syms, int grp_num) +{ + char *tok, *layout; + int grp; + + layout = NULL; + tok = strtok(syms, "+:"); + for (grp = 0; tok && grp <= grp_num; tok = strtok(NULL, "+:")) { + if (!valid_layout_or_variant(tok)) { + continue; + } else if (strlen(tok) == 1 && isdigit(tok[0])) { + /* ignore :2, :3, :4 (additional layout groups) */ + continue; + } + layout = tok; + grp++; + } + + return layout; +} + +const char * +keymap(const char *unused) +{ + Display *dpy; + XkbDescRec *desc; + XkbStateRec state; + char *symbols, *layout; + + layout = NULL; + + if (!(dpy = XOpenDisplay(NULL))) { + warn("XOpenDisplay: Failed to open display"); + return NULL; + } + if (!(desc = XkbAllocKeyboard())) { + warn("XkbAllocKeyboard: Failed to allocate keyboard"); + goto end; + } + if (XkbGetNames(dpy, XkbSymbolsNameMask, desc)) { + warn("XkbGetNames: Failed to retrieve key symbols"); + goto end; + } + if (XkbGetState(dpy, XkbUseCoreKbd, &state)) { + warn("XkbGetState: Failed to retrieve keyboard state"); + goto end; + } + if (!(symbols = XGetAtomName(dpy, desc->names->symbols))) { + warn("XGetAtomName: Failed to get atom name"); + goto end; + } + layout = (char *)bprintf("%s", get_layout(symbols, state.group)); + XFree(symbols); +end: + XkbFreeKeyboard(desc, XkbSymbolsNameMask, 1); + if (XCloseDisplay(dpy)) + warn("XCloseDisplay: Failed to close display"); + + return layout; +} diff --git a/slstatus/components/load_avg.c b/slstatus/components/load_avg.c new file mode 100644 index 0000000..f278a40 --- /dev/null +++ b/slstatus/components/load_avg.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +load_avg(const char *unused) +{ + double avgs[3]; + + if (getloadavg(avgs, 3) < 0) { + warn("getloadavg: Failed to obtain load average"); + return NULL; + } + + return bprintf("%.2f %.2f %.2f", avgs[0], avgs[1], avgs[2]); +} diff --git a/slstatus/components/netspeeds.c b/slstatus/components/netspeeds.c new file mode 100644 index 0000000..cde6fa9 --- /dev/null +++ b/slstatus/components/netspeeds.c @@ -0,0 +1,129 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + #include + + #define NET_RX_BYTES "/sys/class/net/%s/statistics/rx_bytes" + #define NET_TX_BYTES "/sys/class/net/%s/statistics/tx_bytes" + + const char * + netspeed_rx(const char *interface) + { + uintmax_t oldrxbytes; + static uintmax_t rxbytes; + extern const unsigned int interval; + char path[PATH_MAX]; + + oldrxbytes = rxbytes; + + if (esnprintf(path, sizeof(path), NET_RX_BYTES, interface) < 0) + return NULL; + if (pscanf(path, "%ju", &rxbytes) != 1) + return NULL; + if (oldrxbytes == 0) + return NULL; + + return fmt_human((rxbytes - oldrxbytes) * 1000 / interval, + 1024); + } + + const char * + netspeed_tx(const char *interface) + { + uintmax_t oldtxbytes; + static uintmax_t txbytes; + extern const unsigned int interval; + char path[PATH_MAX]; + + oldtxbytes = txbytes; + + if (esnprintf(path, sizeof(path), NET_TX_BYTES, interface) < 0) + return NULL; + if (pscanf(path, "%ju", &txbytes) != 1) + return NULL; + if (oldtxbytes == 0) + return NULL; + + return fmt_human((txbytes - oldtxbytes) * 1000 / interval, + 1024); + } +#elif defined(__OpenBSD__) | defined(__FreeBSD__) + #include + #include + #include + #include + #include + + const char * + netspeed_rx(const char *interface) + { + struct ifaddrs *ifal, *ifa; + struct if_data *ifd; + uintmax_t oldrxbytes; + static uintmax_t rxbytes; + extern const unsigned int interval; + int if_ok = 0; + + oldrxbytes = rxbytes; + + if (getifaddrs(&ifal) < 0) { + warn("getifaddrs failed"); + return NULL; + } + rxbytes = 0; + for (ifa = ifal; ifa; ifa = ifa->ifa_next) + if (!strcmp(ifa->ifa_name, interface) && + (ifd = (struct if_data *)ifa->ifa_data)) + rxbytes += ifd->ifi_ibytes, if_ok = 1; + + freeifaddrs(ifal); + if (!if_ok) { + warn("reading 'if_data' failed"); + return NULL; + } + if (oldrxbytes == 0) + return NULL; + + return fmt_human((rxbytes - oldrxbytes) * 1000 / interval, + 1024); + } + + const char * + netspeed_tx(const char *interface) + { + struct ifaddrs *ifal, *ifa; + struct if_data *ifd; + uintmax_t oldtxbytes; + static uintmax_t txbytes; + extern const unsigned int interval; + int if_ok = 0; + + oldtxbytes = txbytes; + + if (getifaddrs(&ifal) < 0) { + warn("getifaddrs failed"); + return NULL; + } + txbytes = 0; + for (ifa = ifal; ifa; ifa = ifa->ifa_next) + if (!strcmp(ifa->ifa_name, interface) && + (ifd = (struct if_data *)ifa->ifa_data)) + txbytes += ifd->ifi_obytes, if_ok = 1; + + freeifaddrs(ifal); + if (!if_ok) { + warn("reading 'if_data' failed"); + return NULL; + } + if (oldtxbytes == 0) + return NULL; + + return fmt_human((txbytes - oldtxbytes) * 1000 / interval, + 1024); + } +#endif diff --git a/slstatus/components/num_files.c b/slstatus/components/num_files.c new file mode 100644 index 0000000..e4b4281 --- /dev/null +++ b/slstatus/components/num_files.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +num_files(const char *path) +{ + struct dirent *dp; + DIR *fd; + int num; + + if (!(fd = opendir(path))) { + warn("opendir '%s':", path); + return NULL; + } + + num = 0; + while ((dp = readdir(fd))) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; /* skip self and parent */ + + num++; + } + + closedir(fd); + + return bprintf("%d", num); +} diff --git a/slstatus/components/ram.c b/slstatus/components/ram.c new file mode 100644 index 0000000..15c4b74 --- /dev/null +++ b/slstatus/components/ram.c @@ -0,0 +1,212 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + #include + + const char * + ram_free(const char *unused) + { + uintmax_t free; + + if (pscanf("/proc/meminfo", + "MemTotal: %ju kB\n" + "MemFree: %ju kB\n" + "MemAvailable: %ju kB\n", + &free, &free, &free) != 3) + return NULL; + + return fmt_human(free * 1024, 1024); + } + + const char * + ram_perc(const char *unused) + { + uintmax_t total, free, buffers, cached; + int percent; + + if (pscanf("/proc/meminfo", + "MemTotal: %ju kB\n" + "MemFree: %ju kB\n" + "MemAvailable: %ju kB\n" + "Buffers: %ju kB\n" + "Cached: %ju kB\n", + &total, &free, &buffers, &buffers, &cached) != 5) + return NULL; + + if (total == 0) + return NULL; + + percent = 100 * ((total - free) - (buffers + cached)) / total; + return bprintf("%d", percent); + } + + const char * + ram_total(const char *unused) + { + uintmax_t total; + + if (pscanf("/proc/meminfo", "MemTotal: %ju kB\n", &total) + != 1) + return NULL; + + return fmt_human(total * 1024, 1024); + } + + const char * + ram_used(const char *unused) + { + uintmax_t total, free, buffers, cached, used; + + if (pscanf("/proc/meminfo", + "MemTotal: %ju kB\n" + "MemFree: %ju kB\n" + "MemAvailable: %ju kB\n" + "Buffers: %ju kB\n" + "Cached: %ju kB\n", + &total, &free, &buffers, &buffers, &cached) != 5) + return NULL; + + used = (total - free - buffers - cached); + return fmt_human(used * 1024, 1024); + } +#elif defined(__OpenBSD__) + #include + #include + #include + #include + + #define LOG1024 10 + #define pagetok(size, pageshift) (size_t)(size << (pageshift - LOG1024)) + + inline int + load_uvmexp(struct uvmexp *uvmexp) + { + int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; + size_t size; + + size = sizeof(*uvmexp); + + if (sysctl(uvmexp_mib, 2, uvmexp, &size, NULL, 0) >= 0) + return 1; + + return 0; + } + + const char * + ram_free(const char *unused) + { + struct uvmexp uvmexp; + int free_pages; + + if (!load_uvmexp(&uvmexp)) + return NULL; + + free_pages = uvmexp.npages - uvmexp.active; + return fmt_human(pagetok(free_pages, uvmexp.pageshift) * + 1024, 1024); + } + + const char * + ram_perc(const char *unused) + { + struct uvmexp uvmexp; + int percent; + + if (!load_uvmexp(&uvmexp)) + return NULL; + + percent = uvmexp.active * 100 / uvmexp.npages; + return bprintf("%d", percent); + } + + const char * + ram_total(const char *unused) + { + struct uvmexp uvmexp; + + if (!load_uvmexp(&uvmexp)) + return NULL; + + return fmt_human(pagetok(uvmexp.npages, + uvmexp.pageshift) * 1024, 1024); + } + + const char * + ram_used(const char *unused) + { + struct uvmexp uvmexp; + + if (!load_uvmexp(&uvmexp)) + return NULL; + + return fmt_human(pagetok(uvmexp.active, + uvmexp.pageshift) * 1024, 1024); + } +#elif defined(__FreeBSD__) + #include + #include + #include + #include + + const char * + ram_free(const char *unused) { + struct vmtotal vm_stats; + int mib[] = {CTL_VM, VM_TOTAL}; + size_t len; + + len = sizeof(struct vmtotal); + if (sysctl(mib, 2, &vm_stats, &len, NULL, 0) < 0 + || !len) + return NULL; + + return fmt_human(vm_stats.t_free * getpagesize(), 1024); + } + + const char * + ram_total(const char *unused) { + unsigned int npages; + size_t len; + + len = sizeof(npages); + if (sysctlbyname("vm.stats.vm.v_page_count", + &npages, &len, NULL, 0) < 0 || !len) + return NULL; + + return fmt_human(npages * getpagesize(), 1024); + } + + const char * + ram_perc(const char *unused) { + unsigned int npages; + unsigned int active; + size_t len; + + len = sizeof(npages); + if (sysctlbyname("vm.stats.vm.v_page_count", + &npages, &len, NULL, 0) < 0 || !len) + return NULL; + + if (sysctlbyname("vm.stats.vm.v_active_count", + &active, &len, NULL, 0) < 0 || !len) + return NULL; + + return bprintf("%d", active * 100 / npages); + } + + const char * + ram_used(const char *unused) { + unsigned int active; + size_t len; + + len = sizeof(active); + if (sysctlbyname("vm.stats.vm.v_active_count", + &active, &len, NULL, 0) < 0 || !len) + return NULL; + + return fmt_human(active * getpagesize(), 1024); + } +#endif diff --git a/slstatus/components/run_command.c b/slstatus/components/run_command.c new file mode 100644 index 0000000..93bf6da --- /dev/null +++ b/slstatus/components/run_command.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +run_command(const char *cmd) +{ + char *p; + FILE *fp; + + if (!(fp = popen(cmd, "r"))) { + warn("popen '%s':", cmd); + return NULL; + } + + p = fgets(buf, sizeof(buf) - 1, fp); + if (pclose(fp) < 0) { + warn("pclose '%s':", cmd); + return NULL; + } + if (!p) + return NULL; + + if ((p = strrchr(buf, '\n'))) + p[0] = '\0'; + + return buf[0] ? buf : NULL; +} diff --git a/slstatus/components/swap.c b/slstatus/components/swap.c new file mode 100644 index 0000000..f270d93 --- /dev/null +++ b/slstatus/components/swap.c @@ -0,0 +1,274 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + static int + get_swap_info(long *s_total, long *s_free, long *s_cached) + { + FILE *fp; + struct { + const char *name; + const size_t len; + long *var; + } ent[] = { + { "SwapTotal", sizeof("SwapTotal") - 1, s_total }, + { "SwapFree", sizeof("SwapFree") - 1, s_free }, + { "SwapCached", sizeof("SwapCached") - 1, s_cached }, + }; + size_t line_len = 0, i, left; + char *line = NULL; + + /* get number of fields we want to extract */ + for (i = 0, left = 0; i < LEN(ent); i++) + if (ent[i].var) + left++; + + if (!(fp = fopen("/proc/meminfo", "r"))) { + warn("fopen '/proc/meminfo':"); + return 1; + } + + /* read file line by line and extract field information */ + while (left > 0 && getline(&line, &line_len, fp) >= 0) { + for (i = 0; i < LEN(ent); i++) { + if (ent[i].var && + !strncmp(line, ent[i].name, ent[i].len)) { + sscanf(line + ent[i].len + 1, + "%ld kB\n", ent[i].var); + left--; + break; + } + } + } + free(line); + if (ferror(fp)) { + warn("getline '/proc/meminfo':"); + return 1; + } + + fclose(fp); + return 0; + } + + const char * + swap_free(const char *unused) + { + long free; + + if (get_swap_info(NULL, &free, NULL)) + return NULL; + + return fmt_human(free * 1024, 1024); + } + + const char * + swap_perc(const char *unused) + { + long total, free, cached; + + if (get_swap_info(&total, &free, &cached) || total == 0) + return NULL; + + return bprintf("%d", 100 * (total - free - cached) / total); + } + + const char * + swap_total(const char *unused) + { + long total; + + if (get_swap_info(&total, NULL, NULL)) + return NULL; + + return fmt_human(total * 1024, 1024); + } + + const char * + swap_used(const char *unused) + { + long total, free, cached; + + if (get_swap_info(&total, &free, &cached)) + return NULL; + + return fmt_human((total - free - cached) * 1024, 1024); + } +#elif defined(__OpenBSD__) + #include + #include + #include + #include + + static int + getstats(int *total, int *used) + { + struct swapent *sep, *fsep; + int rnswap, nswap, i; + + if ((nswap = swapctl(SWAP_NSWAP, 0, 0)) < 1) { + warn("swaptctl 'SWAP_NSWAP':"); + return 1; + } + if (!(fsep = sep = calloc(nswap, sizeof(*sep)))) { + warn("calloc 'nswap':"); + return 1; + } + if ((rnswap = swapctl(SWAP_STATS, (void *)sep, nswap)) < 0) { + warn("swapctl 'SWAP_STATA':"); + return 1; + } + if (nswap != rnswap) { + warn("getstats: SWAP_STATS != SWAP_NSWAP"); + return 1; + } + + *total = 0; + *used = 0; + + for (i = 0; i < rnswap; i++) { + *total += sep->se_nblks >> 1; + *used += sep->se_inuse >> 1; + } + + free(fsep); + + return 0; + } + + const char * + swap_free(const char *unused) + { + int total, used; + + if (getstats(&total, &used)) + return NULL; + + return fmt_human((total - used) * 1024, 1024); + } + + const char * + swap_perc(const char *unused) + { + int total, used; + + if (getstats(&total, &used)) + return NULL; + + if (total == 0) + return NULL; + + return bprintf("%d", 100 * used / total); + } + + const char * + swap_total(const char *unused) + { + int total, used; + + if (getstats(&total, &used)) + return NULL; + + return fmt_human(total * 1024, 1024); + } + + const char * + swap_used(const char *unused) + { + int total, used; + + if (getstats(&total, &used)) + return NULL; + + return fmt_human(used * 1024, 1024); + } +#elif defined(__FreeBSD__) + #include + #include + #include + #include + #include + + static int getswapinfo(struct kvm_swap *swap_info, size_t size) + { + kvm_t *kd; + + kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, NULL); + if (kd == NULL) { + warn("kvm_openfiles '/dev/null':"); + return 0; + } + + if (kvm_getswapinfo(kd, swap_info, size, 0 /* Unused flags */) < 0) { + warn("kvm_getswapinfo:"); + kvm_close(kd); + return 0; + } + + kvm_close(kd); + return 1; + } + + const char * + swap_free(const char *unused) + { + struct kvm_swap swap_info[1]; + long used, total; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + total = swap_info[0].ksw_total; + used = swap_info[0].ksw_used; + + return fmt_human((total - used) * getpagesize(), 1024); + } + + const char * + swap_perc(const char *unused) + { + struct kvm_swap swap_info[1]; + long used, total; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + total = swap_info[0].ksw_total; + used = swap_info[0].ksw_used; + + return bprintf("%d", used * 100 / total); + } + + const char * + swap_total(const char *unused) + { + struct kvm_swap swap_info[1]; + long total; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + total = swap_info[0].ksw_total; + + return fmt_human(total * getpagesize(), 1024); + } + + const char * + swap_used(const char *unused) + { + struct kvm_swap swap_info[1]; + long used; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + used = swap_info[0].ksw_used; + + return fmt_human(used * getpagesize(), 1024); + } +#endif diff --git a/slstatus/components/temperature.c b/slstatus/components/temperature.c new file mode 100644 index 0000000..7cf1394 --- /dev/null +++ b/slstatus/components/temperature.c @@ -0,0 +1,73 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../slstatus.h" +#include "../util.h" + + +#if defined(__linux__) + #include + + const char * + temp(const char *file) + { + uintmax_t temp; + + if (pscanf(file, "%ju", &temp) != 1) + return NULL; + + return bprintf("%ju", temp / 1000); + } +#elif defined(__OpenBSD__) + #include + #include /* before for struct timeval */ + #include + #include + + const char * + temp(const char *unused) + { + int mib[5]; + size_t size; + struct sensor temp; + + mib[0] = CTL_HW; + mib[1] = HW_SENSORS; + mib[2] = 0; /* cpu0 */ + mib[3] = SENSOR_TEMP; + mib[4] = 0; /* temp0 */ + + size = sizeof(temp); + + if (sysctl(mib, 5, &temp, &size, NULL, 0) < 0) { + warn("sysctl 'SENSOR_TEMP':"); + return NULL; + } + + /* kelvin to celsius */ + return bprintf("%d", (int)((float)(temp.value-273150000) / 1E6)); + } +#elif defined(__FreeBSD__) + #include + #include + #include + + #define ACPI_TEMP "hw.acpi.thermal.%s.temperature" + + const char * + temp(const char *zone) + { + char buf[256]; + int temp; + size_t len; + + len = sizeof(temp); + snprintf(buf, sizeof(buf), ACPI_TEMP, zone); + if (sysctlbyname(buf, &temp, &len, NULL, 0) < 0 + || !len) + return NULL; + + /* kelvin to decimal celcius */ + return bprintf("%d.%d", (temp - 2731) / 10, abs((temp - 2731) % 10)); + } +#endif diff --git a/slstatus/components/uptime.c b/slstatus/components/uptime.c new file mode 100644 index 0000000..d97e5e8 --- /dev/null +++ b/slstatus/components/uptime.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(CLOCK_BOOTTIME) + #define UPTIME_FLAG CLOCK_BOOTTIME +#elif defined(CLOCK_UPTIME) + #define UPTIME_FLAG CLOCK_UPTIME +#else + #define UPTIME_FLAG CLOCK_MONOTONIC +#endif + +const char * +uptime(const char *unused) +{ + char warn_buf[256]; + uintmax_t h, m; + struct timespec uptime; + + if (clock_gettime(UPTIME_FLAG, &uptime) < 0) { + snprintf(warn_buf, 256, "clock_gettime %d", UPTIME_FLAG); + warn(warn_buf); + return NULL; + } + + h = uptime.tv_sec / 3600; + m = uptime.tv_sec % 3600 / 60; + + return bprintf("%juh %jum", h, m); +} diff --git a/slstatus/components/user.c b/slstatus/components/user.c new file mode 100644 index 0000000..3517495 --- /dev/null +++ b/slstatus/components/user.c @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +gid(const char *unused) +{ + return bprintf("%d", getgid()); +} + +const char * +username(const char *unused) +{ + struct passwd *pw; + + if (!(pw = getpwuid(geteuid()))) { + warn("getpwuid '%d':", geteuid()); + return NULL; + } + + return bprintf("%s", pw->pw_name); +} + +const char * +uid(const char *unused) +{ + return bprintf("%d", geteuid()); +} diff --git a/slstatus/components/volume.c b/slstatus/components/volume.c new file mode 100644 index 0000000..6cec556 --- /dev/null +++ b/slstatus/components/volume.c @@ -0,0 +1,219 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__OpenBSD__) | defined(__FreeBSD__) + #include + #include + #include + #include + + struct control { + LIST_ENTRY(control) next; + unsigned int addr; + #define CTRL_NONE 0 + #define CTRL_LEVEL 1 + #define CTRL_MUTE 2 + unsigned int type; + unsigned int maxval; + unsigned int val; + }; + + static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls); + static struct pollfd *pfds; + static struct sioctl_hdl *hdl; + static int initialized; + + /* + * Call-back to obtain the description of all audio controls. + */ + static void + ondesc(void *unused, struct sioctl_desc *desc, int val) + { + struct control *c, *ctmp; + unsigned int type = CTRL_NONE; + + if (desc == NULL) + return; + + /* Delete existing audio control with the same address. */ + LIST_FOREACH_SAFE(c, &controls, next, ctmp) { + if (desc->addr == c->addr) { + LIST_REMOVE(c, next); + free(c); + break; + } + } + + /* Only match output.level and output.mute audio controls. */ + if (desc->group[0] != 0 || + strcmp(desc->node0.name, "output") != 0) + return; + if (desc->type == SIOCTL_NUM && + strcmp(desc->func, "level") == 0) + type = CTRL_LEVEL; + else if (desc->type == SIOCTL_SW && + strcmp(desc->func, "mute") == 0) + type = CTRL_MUTE; + else + return; + + c = malloc(sizeof(struct control)); + if (c == NULL) { + warn("sndio: failed to allocate audio control\n"); + return; + } + + c->addr = desc->addr; + c->type = type; + c->maxval = desc->maxval; + c->val = val; + LIST_INSERT_HEAD(&controls, c, next); + } + + /* + * Call-back invoked whenever an audio control changes. + */ + static void + onval(void *unused, unsigned int addr, unsigned int val) + { + struct control *c; + + LIST_FOREACH(c, &controls, next) { + if (c->addr == addr) + break; + } + c->val = val; + } + + static void + cleanup(void) + { + struct control *c; + + if (hdl) { + sioctl_close(hdl); + hdl = NULL; + } + + free(pfds); + pfds = NULL; + + while (!LIST_EMPTY(&controls)) { + c = LIST_FIRST(&controls); + LIST_REMOVE(c, next); + free(c); + } + } + + static int + init(void) + { + hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0); + if (hdl == NULL) { + warn("sndio: cannot open device"); + goto failed; + } + + if (!sioctl_ondesc(hdl, ondesc, NULL)) { + warn("sndio: cannot set control description call-back"); + goto failed; + } + + if (!sioctl_onval(hdl, onval, NULL)) { + warn("sndio: cannot set control values call-back"); + goto failed; + } + + pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd)); + if (pfds == NULL) { + warn("sndio: cannot allocate pollfd structures"); + goto failed; + } + + return 1; + failed: + cleanup(); + return 0; + } + + const char * + vol_perc(const char *unused) + { + struct control *c; + int n, v, value; + + if (!initialized) + initialized = init(); + + if (hdl == NULL) + return NULL; + + n = sioctl_pollfd(hdl, pfds, POLLIN); + if (n > 0) { + n = poll(pfds, n, 0); + if (n > 0) { + if (sioctl_revents(hdl, pfds) & POLLHUP) { + warn("sndio: disconnected"); + cleanup(); + initialized = 0; + return NULL; + } + } + } + + value = 100; + LIST_FOREACH(c, &controls, next) { + if (c->type == CTRL_MUTE && c->val == 1) + value = 0; + else if (c->type == CTRL_LEVEL) { + v = (c->val * 100 + c->maxval / 2) / c->maxval; + /* For multiple channels return the minimum. */ + if (v < value) + value = v; + } + } + + return bprintf("%d", value); + } +#else + #include + + const char * + vol_perc(const char *card) + { + size_t i; + int v, afd, devmask; + char *vnames[] = SOUND_DEVICE_NAMES; + + if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) { + warn("open '%s':", card); + return NULL; + } + + if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + warn("ioctl 'SOUND_MIXER_READ_DEVMASK':"); + close(afd); + return NULL; + } + for (i = 0; i < LEN(vnames); i++) { + if (devmask & (1 << i) && !strcmp("vol", vnames[i])) { + if (ioctl(afd, MIXER_READ(i), &v) < 0) { + warn("ioctl 'MIXER_READ(%ld)':", i); + close(afd); + return NULL; + } + } + } + + close(afd); + + return bprintf("%d", v & 0xff); + } +#endif diff --git a/slstatus/components/wifi.c b/slstatus/components/wifi.c new file mode 100644 index 0000000..4543d32 --- /dev/null +++ b/slstatus/components/wifi.c @@ -0,0 +1,267 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#define RSSI_TO_PERC(rssi) \ + rssi >= -50 ? 100 : \ + (rssi <= -100 ? 0 : \ + (2 * (rssi + 100))) + +#if defined(__linux__) + #include + #include + + #define NET_OPERSTATE "/sys/class/net/%s/operstate" + + const char * + wifi_perc(const char *interface) + { + int cur; + size_t i; + char *p, *datastart; + char path[PATH_MAX]; + char status[5]; + FILE *fp; + + if (esnprintf(path, sizeof(path), NET_OPERSTATE, interface) < 0) + return NULL; + if (!(fp = fopen(path, "r"))) { + warn("fopen '%s':", path); + return NULL; + } + p = fgets(status, 5, fp); + fclose(fp); + if (!p || strcmp(status, "up\n") != 0) + return NULL; + + if (!(fp = fopen("/proc/net/wireless", "r"))) { + warn("fopen '/proc/net/wireless':"); + return NULL; + } + + for (i = 0; i < 3; i++) + if (!(p = fgets(buf, sizeof(buf) - 1, fp))) + break; + + fclose(fp); + if (i < 2 || !p) + return NULL; + + if (!(datastart = strstr(buf, interface))) + return NULL; + + datastart = (datastart+(strlen(interface)+1)); + sscanf(datastart + 1, " %*d %d %*d %*d\t\t %*d\t " + "%*d\t\t%*d\t\t %*d\t %*d\t\t %*d", &cur); + + /* 70 is the max of /proc/net/wireless */ + return bprintf("%d", (int)((float)cur / 70 * 100)); + } + + const char * + wifi_essid(const char *interface) + { + static char id[IW_ESSID_MAX_SIZE+1]; + int sockfd; + struct iwreq wreq; + + memset(&wreq, 0, sizeof(struct iwreq)); + wreq.u.essid.length = IW_ESSID_MAX_SIZE+1; + if (esnprintf(wreq.ifr_name, sizeof(wreq.ifr_name), "%s", + interface) < 0) + return NULL; + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + warn("socket 'AF_INET':"); + return NULL; + } + wreq.u.essid.pointer = id; + if (ioctl(sockfd,SIOCGIWESSID, &wreq) < 0) { + warn("ioctl 'SIOCGIWESSID':"); + close(sockfd); + return NULL; + } + + close(sockfd); + + if (!strcmp(id, "")) + return NULL; + + return id; + } +#elif defined(__OpenBSD__) + #include + #include + #include + #include /* before for NBBY */ + #include + #include + #include + + static int + load_ieee80211_nodereq(const char *interface, struct ieee80211_nodereq *nr) + { + struct ieee80211_bssid bssid; + int sockfd; + uint8_t zero_bssid[IEEE80211_ADDR_LEN]; + + memset(&bssid, 0, sizeof(bssid)); + memset(nr, 0, sizeof(struct ieee80211_nodereq)); + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + warn("socket 'AF_INET':"); + return 0; + } + strlcpy(bssid.i_name, interface, sizeof(bssid.i_name)); + if ((ioctl(sockfd, SIOCG80211BSSID, &bssid)) < 0) { + warn("ioctl 'SIOCG80211BSSID':"); + close(sockfd); + return 0; + } + memset(&zero_bssid, 0, sizeof(zero_bssid)); + if (memcmp(bssid.i_bssid, zero_bssid, + IEEE80211_ADDR_LEN) == 0) { + close(sockfd); + return 0; + } + strlcpy(nr->nr_ifname, interface, sizeof(nr->nr_ifname)); + memcpy(&nr->nr_macaddr, bssid.i_bssid, sizeof(nr->nr_macaddr)); + if ((ioctl(sockfd, SIOCG80211NODE, nr)) < 0 && nr->nr_rssi) { + warn("ioctl 'SIOCG80211NODE':"); + close(sockfd); + return 0; + } + + return close(sockfd), 1; + } + + const char * + wifi_perc(const char *interface) + { + struct ieee80211_nodereq nr; + int q; + + if (load_ieee80211_nodereq(interface, &nr)) { + if (nr.nr_max_rssi) + q = IEEE80211_NODEREQ_RSSI(&nr); + else + q = RSSI_TO_PERC(nr.nr_rssi); + + return bprintf("%d", q); + } + + return NULL; + } + + const char * + wifi_essid(const char *interface) + { + struct ieee80211_nodereq nr; + + if (load_ieee80211_nodereq(interface, &nr)) + return bprintf("%s", nr.nr_nwid); + + return NULL; + } +#elif defined(__FreeBSD__) + #include + #include + + int + load_ieee80211req(int sock, const char *interface, void *data, int type, size_t *len) + { + char warn_buf[256]; + struct ieee80211req ireq; + memset(&ireq, 0, sizeof(ireq)); + ireq.i_type = type; + ireq.i_data = (caddr_t) data; + ireq.i_len = *len; + + strlcpy(ireq.i_name, interface, sizeof(ireq.i_name)); + if (ioctl(sock, SIOCG80211, &ireq) < 0) { + snprintf(warn_buf, sizeof(warn_buf), + "ioctl: 'SIOCG80211': %d", type); + warn(warn_buf); + return 0; + } + + *len = ireq.i_len; + return 1; + } + + const char * + wifi_perc(const char *interface) + { + union { + struct ieee80211req_sta_req sta; + uint8_t buf[24 * 1024]; + } info; + uint8_t bssid[IEEE80211_ADDR_LEN]; + int rssi_dbm; + int sockfd; + size_t len; + const char *fmt; + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + warn("socket 'AF_INET':"); + return NULL; + } + + /* Retreive MAC address of interface */ + len = IEEE80211_ADDR_LEN; + fmt = NULL; + if (load_ieee80211req(sockfd, interface, &bssid, IEEE80211_IOC_BSSID, &len)) + { + /* Retrieve info on station with above BSSID */ + memset(&info, 0, sizeof(info)); + memcpy(info.sta.is_u.macaddr, bssid, sizeof(bssid)); + + len = sizeof(info); + if (load_ieee80211req(sockfd, interface, &info, IEEE80211_IOC_STA_INFO, &len)) { + rssi_dbm = info.sta.info[0].isi_noise + + info.sta.info[0].isi_rssi / 2; + + fmt = bprintf("%d", RSSI_TO_PERC(rssi_dbm)); + } + } + + close(sockfd); + return fmt; + } + + const char * + wifi_essid(const char *interface) + { + char ssid[IEEE80211_NWID_LEN + 1]; + size_t len; + int sockfd; + const char *fmt; + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + warn("socket 'AF_INET':"); + return NULL; + } + + fmt = NULL; + len = sizeof(ssid); + memset(&ssid, 0, len); + if (load_ieee80211req(sockfd, interface, &ssid, IEEE80211_IOC_SSID, &len)) { + if (len < sizeof(ssid)) + len += 1; + else + len = sizeof(ssid); + + ssid[len - 1] = '\0'; + fmt = bprintf("%s", ssid); + } + + close(sockfd); + return fmt; + } +#endif diff --git a/slstatus/config.def.h b/slstatus/config.def.h new file mode 100644 index 0000000..1626869 --- /dev/null +++ b/slstatus/config.def.h @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ + +/* interval between updates (in ms) */ +const unsigned int interval = 1000; + +/* text to show if no value can be retrieved */ +static const char unknown_str[] = "n/a"; + +/* maximum output string length */ +#define MAXLEN 2048 + +/* battery levels to notify - add any levels you want to receive notification for (in percent) */ +const int notifiable_levels[] = { + 20, + 10, + 5, +}; + + +static const struct arg args[] = { + /* function format argument */ + { battery_perc, "%s%%", "BAT0" }, + { battery_state, " %s ", "BAT0" }, + + { cpu_perc, "| CPU %s%% ", NULL }, + { cpu_freq, " %s |", NULL }, + + { datetime, " %s  ", "%F" }, + { datetime, " %s  |", "%T" }, + { battery_notify, "", "BAT0" }, + + { keymap, " %s  ", NULL }, +}; + +/* + * function description argument (example) + * + * battery_perc battery percentage battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_state battery charging state battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_notify linux battery notifications battery name (BAT0) + * OpenBSD/FreeBSD not supported + * battery_remaining battery remaining HH:MM battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * cpu_perc cpu usage in percent NULL + * cpu_freq cpu frequency in MHz NULL + * datetime date and time format string (%F %T) + * disk_free free disk space in GB mountpoint path (/) + * disk_perc disk usage in percent mountpoint path (/) + * disk_total total disk space in GB mountpoint path (/") + * disk_used used disk space in GB mountpoint path (/) + * entropy available entropy NULL + * gid GID of current user NULL + * hostname hostname NULL + * ipv4 IPv4 address interface name (eth0) + * ipv6 IPv6 address interface name (eth0) + * kernel_release `uname -r` NULL + * keyboard_indicators caps/num lock indicators format string (c?n?) + * see keyboard_indicators.c + * keymap layout (variant) of current NULL + * keymap + * load_avg load average NULL + * netspeed_rx receive network speed interface name (wlan0) + * netspeed_tx transfer network speed interface name (wlan0) + * num_files number of files in a directory path + * (/home/foo/Inbox/cur) + * ram_free free memory in GB NULL + * ram_perc memory usage in percent NULL + * ram_total total memory size in GB NULL + * ram_used used memory in GB NULL + * run_command custom shell command command (echo foo) + * swap_free free swap in GB NULL + * swap_perc swap usage in percent NULL + * swap_total total swap size in GB NULL + * swap_used used swap in GB NULL + * temp temperature in degree celsius sensor file + * (/sys/class/thermal/...) + * NULL on OpenBSD + * thermal zone on FreeBSD + * (tz0, tz1, etc.) + * uid UID of current user NULL + * uptime system uptime NULL + * username username of current user NULL + * alsa_master_vol ALSA Master device volume NULL + * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer) + * NULL on OpenBSD/FreeBSD + * wifi_perc WiFi signal in percent interface name (wlan0) + * wifi_essid WiFi ESSID interface name (wlan0) + */ + diff --git a/slstatus/config.h b/slstatus/config.h new file mode 100644 index 0000000..1626869 --- /dev/null +++ b/slstatus/config.h @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ + +/* interval between updates (in ms) */ +const unsigned int interval = 1000; + +/* text to show if no value can be retrieved */ +static const char unknown_str[] = "n/a"; + +/* maximum output string length */ +#define MAXLEN 2048 + +/* battery levels to notify - add any levels you want to receive notification for (in percent) */ +const int notifiable_levels[] = { + 20, + 10, + 5, +}; + + +static const struct arg args[] = { + /* function format argument */ + { battery_perc, "%s%%", "BAT0" }, + { battery_state, " %s ", "BAT0" }, + + { cpu_perc, "| CPU %s%% ", NULL }, + { cpu_freq, " %s |", NULL }, + + { datetime, " %s  ", "%F" }, + { datetime, " %s  |", "%T" }, + { battery_notify, "", "BAT0" }, + + { keymap, " %s  ", NULL }, +}; + +/* + * function description argument (example) + * + * battery_perc battery percentage battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_state battery charging state battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_notify linux battery notifications battery name (BAT0) + * OpenBSD/FreeBSD not supported + * battery_remaining battery remaining HH:MM battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * cpu_perc cpu usage in percent NULL + * cpu_freq cpu frequency in MHz NULL + * datetime date and time format string (%F %T) + * disk_free free disk space in GB mountpoint path (/) + * disk_perc disk usage in percent mountpoint path (/) + * disk_total total disk space in GB mountpoint path (/") + * disk_used used disk space in GB mountpoint path (/) + * entropy available entropy NULL + * gid GID of current user NULL + * hostname hostname NULL + * ipv4 IPv4 address interface name (eth0) + * ipv6 IPv6 address interface name (eth0) + * kernel_release `uname -r` NULL + * keyboard_indicators caps/num lock indicators format string (c?n?) + * see keyboard_indicators.c + * keymap layout (variant) of current NULL + * keymap + * load_avg load average NULL + * netspeed_rx receive network speed interface name (wlan0) + * netspeed_tx transfer network speed interface name (wlan0) + * num_files number of files in a directory path + * (/home/foo/Inbox/cur) + * ram_free free memory in GB NULL + * ram_perc memory usage in percent NULL + * ram_total total memory size in GB NULL + * ram_used used memory in GB NULL + * run_command custom shell command command (echo foo) + * swap_free free swap in GB NULL + * swap_perc swap usage in percent NULL + * swap_total total swap size in GB NULL + * swap_used used swap in GB NULL + * temp temperature in degree celsius sensor file + * (/sys/class/thermal/...) + * NULL on OpenBSD + * thermal zone on FreeBSD + * (tz0, tz1, etc.) + * uid UID of current user NULL + * uptime system uptime NULL + * username username of current user NULL + * alsa_master_vol ALSA Master device volume NULL + * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer) + * NULL on OpenBSD/FreeBSD + * wifi_perc WiFi signal in percent interface name (wlan0) + * wifi_essid WiFi ESSID interface name (wlan0) + */ + diff --git a/slstatus/config.mk b/slstatus/config.mk new file mode 100644 index 0000000..ead1859 --- /dev/null +++ b/slstatus/config.mk @@ -0,0 +1,22 @@ +# slstatus version +VERSION = 0 + +# customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# flags +CPPFLAGS = -I$(X11INC) -D_DEFAULT_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter -Os +LDFLAGS = -L$(X11LIB) -s +# OpenBSD: add -lsndio +# FreeBSD: add -lkvm -lsndio +LDLIBS = -lX11 + +# compiler and linker +CC = cc diff --git a/slstatus/slstatus.1 b/slstatus/slstatus.1 new file mode 100644 index 0000000..d802037 --- /dev/null +++ b/slstatus/slstatus.1 @@ -0,0 +1,28 @@ +.Dd 2020-06-23 +.Dt SLSTATUS 1 +.Os +.Sh NAME +.Nm slstatus +.Nd suckless status monitor +.Sh SYNOPSIS +.Nm +.Op Fl s +.Op Fl 1 +.Sh DESCRIPTION +.Nm +is a suckless status monitor for window managers that use WM_NAME (e.g. dwm) or +stdin to fill the status bar. +By default, +.Nm +outputs to WM_NAME. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl s +Write to stdout instead of WM_NAME. +.It Fl 1 +Write once to stdout and quit. +.El +.Sh CUSTOMIZATION +.Nm +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. diff --git a/slstatus/slstatus.c b/slstatus/slstatus.c new file mode 100644 index 0000000..cb54f29 --- /dev/null +++ b/slstatus/slstatus.c @@ -0,0 +1,132 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" +#include "slstatus.h" +#include "util.h" + +struct arg { + const char *(*func)(const char *); + const char *fmt; + const char *args; +}; + +char buf[1024]; +static volatile sig_atomic_t done; +static Display *dpy; + +#include "config.h" + +static void +terminate(const int signo) +{ + if (signo != SIGUSR1) + done = 1; +} + +static void +difftimespec(struct timespec *res, struct timespec *a, struct timespec *b) +{ + res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec); + res->tv_nsec = a->tv_nsec - b->tv_nsec + + (a->tv_nsec < b->tv_nsec) * 1E9; +} + +static void +usage(void) +{ + die("usage: %s [-s] [-1]", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct sigaction act; + struct timespec start, current, diff, intspec, wait; + size_t i, len; + int sflag, ret; + char status[MAXLEN]; + const char *res; + + sflag = 0; + ARGBEGIN { + case '1': + done = 1; + /* FALLTHROUGH */ + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (argc) + usage(); + + memset(&act, 0, sizeof(act)); + act.sa_handler = terminate; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + act.sa_flags |= SA_RESTART; + sigaction(SIGUSR1, &act, NULL); + + if (!sflag && !(dpy = XOpenDisplay(NULL))) + die("XOpenDisplay: Failed to open display"); + + do { + if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) + die("clock_gettime:"); + + status[0] = '\0'; + for (i = len = 0; i < LEN(args); i++) { + if (!(res = args[i].func(args[i].args))) + res = unknown_str; + + if ((ret = esnprintf(status + len, sizeof(status) - len, + args[i].fmt, res)) < 0) + break; + + len += ret; + } + + if (sflag) { + puts(status); + fflush(stdout); + if (ferror(stdout)) + die("puts:"); + } else { + if (XStoreName(dpy, DefaultRootWindow(dpy), status) < 0) + die("XStoreName: Allocation failed"); + XFlush(dpy); + } + + if (!done) { + if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0) + die("clock_gettime:"); + difftimespec(&diff, ¤t, &start); + + intspec.tv_sec = interval / 1000; + intspec.tv_nsec = (interval % 1000) * 1E6; + difftimespec(&wait, &intspec, &diff); + + if (wait.tv_sec >= 0 && + nanosleep(&wait, NULL) < 0 && + errno != EINTR) + die("nanosleep:"); + } + } while (!done); + + if (!sflag) { + XStoreName(dpy, DefaultRootWindow(dpy), NULL); + if (XCloseDisplay(dpy) < 0) + die("XCloseDisplay: Failed to close display"); + } + + return 0; +} diff --git a/slstatus/slstatus.h b/slstatus/slstatus.h new file mode 100644 index 0000000..9490443 --- /dev/null +++ b/slstatus/slstatus.h @@ -0,0 +1,83 @@ +/* See LICENSE file for copyright and license details. */ + +/* battery */ +const char *battery_perc(const char *); +void battery_notify(const char *); +const char *battery_state(const char *); +const char *battery_remaining(const char *); + +/* cpu */ +const char *cpu_freq(const char *unused); +const char *cpu_perc(const char *unused); + +/* datetime */ +const char *datetime(const char *fmt); + +/* disk */ +const char *disk_free(const char *path); +const char *disk_perc(const char *path); +const char *disk_total(const char *path); +const char *disk_used(const char *path); + +/* entropy */ +const char *entropy(const char *unused); + +/* hostname */ +const char *hostname(const char *unused); + +/* ip */ +const char *ipv4(const char *interface); +const char *ipv6(const char *interface); + +/* kernel_release */ +const char *kernel_release(const char *unused); + +/* keyboard_indicators */ +const char *keyboard_indicators(const char *fmt); + +/* keymap */ +const char *keymap(const char *unused); + +/* load_avg */ +const char *load_avg(const char *unused); + +/* netspeeds */ +const char *netspeed_rx(const char *interface); +const char *netspeed_tx(const char *interface); + +/* num_files */ +const char *num_files(const char *path); + +/* ram */ +const char *ram_free(const char *unused); +const char *ram_perc(const char *unused); +const char *ram_total(const char *unused); +const char *ram_used(const char *unused); + +/* run_command */ +const char *run_command(const char *cmd); + +/* swap */ +const char *swap_free(const char *unused); +const char *swap_perc(const char *unused); +const char *swap_total(const char *unused); +const char *swap_used(const char *unused); + +/* temperature */ +const char *temp(const char *); + +/* uptime */ +const char *uptime(const char *unused); + +/* user */ +const char *gid(const char *unused); +const char *username(const char *unused); +const char *uid(const char *unused); + +/* volume */ +const char *vol_perc(const char *card); +const char *alsa_master_vol(void); + +/* wifi */ +const char *wifi_perc(const char *interface); +const char *wifi_essid(const char *interface); diff --git a/slstatus/util.c b/slstatus/util.c new file mode 100644 index 0000000..d1f6077 --- /dev/null +++ b/slstatus/util.c @@ -0,0 +1,144 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#include "util.h" + +char *argv0; + +static void +verr(const char *fmt, va_list ap) +{ + if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) + fprintf(stderr, "%s: ", argv0); + + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } +} + +void +warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verr(fmt, ap); + va_end(ap); +} + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verr(fmt, ap); + va_end(ap); + + exit(1); +} + +static int +evsnprintf(char *str, size_t size, const char *fmt, va_list ap) +{ + int ret; + + ret = vsnprintf(str, size, fmt, ap); + + if (ret < 0) { + warn("vsnprintf:"); + return -1; + } else if ((size_t)ret >= size) { + warn("vsnprintf: Output truncated"); + return -1; + } + + return ret; +} + +int +esnprintf(char *str, size_t size, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = evsnprintf(str, size, fmt, ap); + va_end(ap); + + return ret; +} + +const char * +bprintf(const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = evsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + return (ret < 0) ? NULL : buf; +} + +const char * +fmt_human(uintmax_t num, int base) +{ + double scaled; + size_t i, prefixlen; + const char **prefix; + const char *prefix_1000[] = { "", "k", "M", "G", "T", "P", "E", "Z", + "Y" }; + const char *prefix_1024[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", + "Zi", "Yi" }; + + switch (base) { + case 1000: + prefix = prefix_1000; + prefixlen = LEN(prefix_1000); + break; + case 1024: + prefix = prefix_1024; + prefixlen = LEN(prefix_1024); + break; + default: + warn("fmt_human: Invalid base"); + return NULL; + } + + scaled = num; + for (i = 0; i < prefixlen && scaled >= base; i++) + scaled /= base; + + return bprintf("%.1f %s", scaled, prefix[i]); +} + +int +pscanf(const char *path, const char *fmt, ...) +{ + FILE *fp; + va_list ap; + int n; + + if (!(fp = fopen(path, "r"))) { + warn("fopen '%s':", path); + return -1; + } + va_start(ap, fmt); + n = vfscanf(fp, fmt, ap); + va_end(ap); + fclose(fp); + + return (n == EOF) ? -1 : n; +} diff --git a/slstatus/util.h b/slstatus/util.h new file mode 100644 index 0000000..7f1f26c --- /dev/null +++ b/slstatus/util.h @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include + +extern char buf[1024]; + +#define LEN(x) (sizeof (x) / sizeof *(x)) + +extern char *argv0; + +void warn(const char *, ...); +void die(const char *, ...); + +int esnprintf(char *str, size_t size, const char *fmt, ...); +const char *bprintf(const char *fmt, ...); +const char *fmt_human(uintmax_t num, int base); +int pscanf(const char *path, const char *fmt, ...); diff --git a/st/FAQ b/st/FAQ new file mode 100644 index 0000000..6287a27 --- /dev/null +++ b/st/FAQ @@ -0,0 +1,253 @@ +## 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 +: + + 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 +. +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 +. 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. + +As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5: +https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS diff --git a/st/LEGACY b/st/LEGACY new file mode 100644 index 0000000..bf28b1e --- /dev/null +++ b/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/st/LICENSE b/st/LICENSE new file mode 100644 index 0000000..3cbf420 --- /dev/null +++ b/st/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2022 Hiltjo Posthuma +© 2018 Devin J. Pohly +© 2014-2017 Quentin Rameau +© 2009-2012 Aurélien APTEL +© 2008-2017 Anselm R Garbe +© 2012-2017 Roberto E. Vargas Caballero +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013-2014 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +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/st/Makefile b/st/Makefile new file mode 100644 index 0000000..15db421 --- /dev/null +++ b/st/Makefile @@ -0,0 +1,51 @@ +# 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: st + +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 clean dist install uninstall diff --git a/st/README b/st/README new file mode 100644 index 0000000..6a846ed --- /dev/null +++ b/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 bt source code. + diff --git a/st/TODO b/st/TODO new file mode 100644 index 0000000..5f74cd5 --- /dev/null +++ b/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/st/arg.h b/st/arg.h new file mode 100644 index 0000000..a22e019 --- /dev/null +++ b/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/st/config.def.h b/st/config.def.h new file mode 100644 index 0000000..1dd7a7c --- /dev/null +++ b/st/config.def.h @@ -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 = "Liberation Mono:pixelsize=20: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 = 2; +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; + +/* 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 */ +}; + + +/* + * 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, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { 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/st/config.h b/st/config.h new file mode 100644 index 0000000..1dd7a7c --- /dev/null +++ b/st/config.h @@ -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 = "Liberation Mono:pixelsize=20: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 = 2; +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; + +/* 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 */ +}; + + +/* + * 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, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { 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/st/config.mk b/st/config.mk new file mode 100644 index 0000000..fdc29a7 --- /dev/null +++ b/st/config.mk @@ -0,0 +1,36 @@ +# st version +VERSION = 0.9.2 + +# 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 \ + `$(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` +#MANPREFIX = ${PREFIX}/man + +# compiler and linker +# CC = c99 diff --git a/st/patches/st-scrollback-0.8.5.diff b/st/patches/st-scrollback-0.8.5.diff new file mode 100644 index 0000000..750111d --- /dev/null +++ b/st/patches/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 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 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 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/st/patches/st-scrollback-mouse-20220127-2c5edf2.diff b/st/patches/st-scrollback-mouse-20220127-2c5edf2.diff new file mode 100644 index 0000000..5c47abc --- /dev/null +++ b/st/patches/st-scrollback-mouse-20220127-2c5edf2.diff @@ -0,0 +1,25 @@ +From b5d3351a21442a842e01e8c0317603b6890b379c Mon Sep 17 00:00:00 2001 +From: asparagii +Date: Thu, 27 Jan 2022 15:44:02 +0100 +Subject: [PATCH] st-scrollback-mouse + +--- + config.def.h | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/config.def.h b/config.def.h +index e3b469b..c217315 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -176,6 +176,8 @@ static uint forcemousemod = ShiftMask; + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ ++ { ShiftMask, Button4, kscrollup, {.i = 1} }, ++ { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, +-- +2.34.1 + diff --git a/st/patches/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff b/st/patches/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff new file mode 100644 index 0000000..6a8722b --- /dev/null +++ b/st/patches/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff @@ -0,0 +1,78 @@ +From 580e3f386e9215707100e9ba44797701943fd927 Mon Sep 17 00:00:00 2001 +From: asparagii +Date: Thu, 27 Jan 2022 15:49:27 +0100 +Subject: [PATCH] st-scrollback-mouse-altscreen + +--- + config.def.h | 4 ++-- + st.c | 5 +++++ + st.h | 1 + + x.c | 2 ++ + 4 files changed, 10 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index c217315..c223706 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -176,8 +176,8 @@ static uint forcemousemod = ShiftMask; + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ +- { ShiftMask, Button4, kscrollup, {.i = 1} }, +- { ShiftMask, Button5, kscrolldown, {.i = 1} }, ++ { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, ++ { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, +diff --git a/st.c b/st.c +index f3af82b..876a6bf 100644 +--- a/st.c ++++ b/st.c +@@ -1060,6 +1060,11 @@ tnew(int col, int row) + treset(); + } + ++int tisaltscr(void) ++{ ++ return IS_SET(MODE_ALTSCREEN); ++} ++ + void + tswapscreen(void) + { +diff --git a/st.h b/st.h +index da36b34..e95c6f8 100644 +--- a/st.h ++++ b/st.h +@@ -89,6 +89,7 @@ void sendbreak(const Arg *); + void toggleprinter(const Arg *); + + int tattrset(int); ++int tisaltscr(void); + void tnew(int, int); + void tresize(int, int); + void tsetdirtattr(int); +diff --git a/x.c b/x.c +index cd96575..9274556 100644 +--- a/x.c ++++ b/x.c +@@ -34,6 +34,7 @@ typedef struct { + void (*func)(const Arg *); + const Arg arg; + uint release; ++ int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ + } MouseShortcut; + + typedef struct { +@@ -455,6 +456,7 @@ mouseaction(XEvent *e, uint release) + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && ++ (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); +-- +2.34.1 + diff --git a/st/patches/st-scrollback-reflow-0.9.diff b/st/patches/st-scrollback-reflow-0.9.diff new file mode 100644 index 0000000..4ed1811 --- /dev/null +++ b/st/patches/st-scrollback-reflow-0.9.diff @@ -0,0 +1,1466 @@ +diff --git a/st.c b/st.c +index 79ee9ba..5170cd4 100644 +--- a/st.c ++++ b/st.c +@@ -36,6 +36,7 @@ + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ + #define HISTSIZE 2000 ++#define RESIZEBUFFER 1000 + + /* macros */ + #define IS_SET(flag) ((term.mode & (flag)) != 0) +@@ -43,9 +44,22 @@ + #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]) ++ ++#define TLINE(y) ( \ ++ (y) < term.scr ? term.hist[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \ ++ : term.line[(y) - term.scr] \ ++) ++ ++#define TLINEABS(y) ( \ ++ (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \ ++) ++ ++#define UPDATEWRAPNEXT(alt, col) do { \ ++ if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \ ++ term.c.x += term.wrapcwidth[alt]; \ ++ term.c.state &= ~CURSOR_WRAPNEXT; \ ++ } \ ++} while (0); + + enum term_mode { + MODE_WRAP = 1 << 0, +@@ -57,6 +71,12 @@ enum term_mode { + MODE_UTF8 = 1 << 6, + }; + ++enum scroll_mode { ++ SCROLL_RESIZE = -1, ++ SCROLL_NOSAVEHIST = 0, ++ SCROLL_SAVEHIST = 1 ++}; ++ + enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +@@ -118,10 +138,11 @@ 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 histi; /* history index */ ++ int histf; /* nb history available */ ++ int scr; /* scroll back */ ++ int wrapcwidth[2]; /* used in updating WRAPNEXT when resizing */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ +@@ -179,26 +200,37 @@ 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 tclearregion(int, int, int, int, int); + static void tcursor(int); ++static void tclearglyph(Glyph *, int); ++static void tresetcursor(void); + static void tdeletechar(int); + static void tdeleteline(int); + static void tinsertblank(int); + static void tinsertblankline(int); +-static int tlinelen(int); ++static int tlinelen(Line len); ++static int tiswrapped(Line line); ++static char *tgetglyphs(char *, const Glyph *, const Glyph *); ++static size_t tgetline(char *, const Glyph *); + 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 tscrollup(int, int, int, int); ++static void tscrolldown(int, int); ++static void treflow(int, int); ++static void rscrolldown(int); ++static void tresizedef(int, int); ++static void tresizealt(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 tloaddefscreen(int, int); ++static void tloadaltscreen(int, int); + static void tsetmode(int, int, const int *, int); + static int twrite(const char *, int, int); + static void tfulldirt(void); +@@ -212,7 +244,10 @@ static void tstrsequence(uchar); + static void drawregion(int, int, int, int); + + static void selnormalize(void); +-static void selscroll(int, int); ++static void selscroll(int, int, int); ++static void selmove(int); ++static void selremove(void); ++static int regionselected(int, int, int, int); + static void selsnap(int *, int *, int); + + static size_t utf8decode(const char *, Rune *, size_t); +@@ -412,17 +447,46 @@ selinit(void) + } + + int +-tlinelen(int y) ++tlinelen(Line line) + { +- int i = term.col; ++ int i = term.col - 1; ++ ++ for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); ++ return i + 1; ++} + +- if (TLINE(y)[i - 1].mode & ATTR_WRAP) +- return i; ++int ++tiswrapped(Line line) ++{ ++ int len = tlinelen(line); + +- while (i > 0 && TLINE(y)[i - 1].u == ' ') +- --i; ++ return len > 0 && (line[len - 1].mode & ATTR_WRAP); ++} + +- return i; ++char * ++tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp) ++{ ++ while (gp <= lgp) ++ if (gp->mode & ATTR_WDUMMY) { ++ gp++; ++ } else { ++ buf += utf8encode((gp++)->u, buf); ++ } ++ return buf; ++} ++ ++size_t ++tgetline(char *buf, const Glyph *fgp) ++{ ++ char *ptr; ++ const Glyph *lgp = &fgp[term.col - 1]; ++ ++ while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP))) ++ lgp--; ++ ptr = tgetglyphs(buf, fgp, lgp); ++ if (!(lgp->mode & ATTR_WRAP)) ++ *(ptr++) = '\n'; ++ return ptr - buf; + } + + void +@@ -462,10 +526,11 @@ selextend(int col, int row, int type, int done) + + sel.oe.x = col; + sel.oe.y = row; +- selnormalize(); + sel.type = type; ++ selnormalize(); + +- if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) ++ 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; +@@ -492,36 +557,43 @@ selnormalize(void) + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; +- i = tlinelen(sel.nb.y); +- if (i < sel.nb.x) ++ ++ i = tlinelen(TLINE(sel.nb.y)); ++ if (sel.nb.x > i) + sel.nb.x = i; +- if (tlinelen(sel.ne.y) <= sel.ne.x) +- sel.ne.x = term.col - 1; ++ if (sel.ne.x >= tlinelen(TLINE(sel.ne.y))) ++ sel.ne.x = term.col - 1; + } + + int +-selected(int x, int y) ++regionselected(int x1, int y1, int x2, int y2) + { +- if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || +- sel.alt != IS_SET(MODE_ALTSCREEN)) ++ if (sel.ob.x == -1 || sel.mode == SEL_EMPTY || ++ sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1) + 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 (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1 ++ : (sel.nb.y != y2 || sel.nb.x <= x2) && ++ (sel.ne.y != y1 || sel.ne.x >= x1); ++} + +- 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); ++int ++selected(int x, int y) ++{ ++ return regionselected(x, y, x, y); + } + + void + selsnap(int *x, int *y, int direction) + { + int newx, newy, xt, yt; ++ int rtop = 0, rbot = term.row - 1; + int delim, prevdelim; + const Glyph *gp, *prevgp; + ++ if (!IS_SET(MODE_ALTSCREEN)) ++ rtop += -term.histf + term.scr, rbot += term.scr; ++ + switch (sel.snap) { + case SNAP_WORD: + /* +@@ -536,7 +608,7 @@ selsnap(int *x, int *y, int direction) + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; +- if (!BETWEEN(newy, 0, term.row - 1)) ++ if (!BETWEEN(newy, rtop, rbot)) + break; + + if (direction > 0) +@@ -547,13 +619,13 @@ selsnap(int *x, int *y, int direction) + break; + } + +- if (newx >= tlinelen(newy)) ++ if (newx >= tlinelen(TLINE(newy))) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); +- if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim +- || (delim && gp->u != prevgp->u))) ++ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || ++ (delim && !(gp->u == ' ' && prevgp->u == ' ')))) + break; + + *x = newx; +@@ -570,18 +642,14 @@ selsnap(int *x, int *y, int direction) + */ + *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)) { ++ for (; *y > rtop; *y -= 1) { ++ if (!tiswrapped(TLINE(*y-1))) + break; +- } + } + } else if (direction > 0) { +- for (; *y < term.row-1; *y += direction) { +- if (!(TLINE(*y)[term.col-1].mode +- & ATTR_WRAP)) { ++ for (; *y < rbot; *y += 1) { ++ if (!tiswrapped(TLINE(*y))) + break; +- } + } + } + break; +@@ -592,40 +660,34 @@ char * + getsel(void) + { + char *str, *ptr; +- int y, bufsize, lastx, linelen; +- const Glyph *gp, *last; ++ int y, lastx, linelen; ++ const Glyph *gp, *lgp; + +- if (sel.ob.x == -1) ++ if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return NULL; + +- bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; +- ptr = str = xmalloc(bufsize); ++ str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ); ++ ptr = str; + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { +- if ((linelen = tlinelen(y)) == 0) { ++ Line line = TLINE(y); ++ ++ if ((linelen = tlinelen(line)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &TLINE(y)[sel.nb.x]; ++ gp = &line[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; ++ gp = &line[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); +- } ++ lgp = &line[MIN(lastx, linelen-1)]; + ++ ptr = tgetglyphs(ptr, gp, lgp); + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. +@@ -636,10 +698,10 @@ getsel(void) + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && +- (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) ++ (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } +- *ptr = 0; ++ *ptr = '\0'; + return str; + } + +@@ -648,9 +710,15 @@ selclear(void) + { + if (sel.ob.x == -1) + return; ++ selremove(); ++ tsetdirt(sel.nb.y, sel.ne.y); ++} ++ ++void ++selremove(void) ++{ + sel.mode = SEL_IDLE; + sel.ob.x = -1; +- tsetdirt(sel.nb.y, sel.ne.y); + } + + void +@@ -851,10 +919,8 @@ void + ttywrite(const char *s, size_t n, int may_echo) + { + const char *next; +- Arg arg = (Arg) { .i = term.scr }; +- +- kscrolldown(&arg); + ++ kscrolldown(&((Arg){ .i = term.scr })); + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + +@@ -990,7 +1056,7 @@ tsetdirtattr(int attr) + 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); ++ term.dirty[i] = 1; + break; + } + } +@@ -1000,7 +1066,8 @@ tsetdirtattr(int attr) + void + tfulldirt(void) + { +- tsetdirt(0, term.row-1); ++ for (int i = 0; i < term.row; i++) ++ term.dirty[i] = 1; + } + + void +@@ -1017,51 +1084,116 @@ tcursor(int mode) + } + } + ++void ++tresetcursor(void) ++{ ++ term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, ++ .x = 0, .y = 0, .state = CURSOR_DEFAULT }; ++} ++ + void + treset(void) + { + uint i; ++ int x, y; + +- term.c = (TCursor){{ +- .mode = ATTR_NULL, +- .fg = defaultfg, +- .bg = defaultbg +- }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; ++ tresetcursor(); + + 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.histf = 0; ++ term.scr = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + ++ selremove(); + for (i = 0; i < 2; i++) { +- tmoveto(0, 0); +- tcursor(CURSOR_SAVE); +- tclearregion(0, 0, term.col-1, term.row-1); ++ tcursor(CURSOR_SAVE); /* reset saved cursor */ ++ for (y = 0; y < term.row; y++) ++ for (x = 0; x < term.col; x++) ++ tclearglyph(&term.line[y][x], 0); + tswapscreen(); + } ++ tfulldirt(); + } + + void + tnew(int col, int row) + { +- term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; +- tresize(col, row); +- treset(); ++ int i, j; ++ ++ for (i = 0; i < 2; i++) { ++ term.line = xmalloc(row * sizeof(Line)); ++ for (j = 0; j < row; j++) ++ term.line[j] = xmalloc(col * sizeof(Glyph)); ++ term.col = col, term.row = row; ++ tswapscreen(); ++ } ++ term.dirty = xmalloc(row * sizeof(*term.dirty)); ++ term.tabs = xmalloc(col * sizeof(*term.tabs)); ++ for (i = 0; i < HISTSIZE; i++) ++ term.hist[i] = xmalloc(col * sizeof(Glyph)); ++ treset(); + } + ++/* handle it with care */ + void + tswapscreen(void) + { +- Line *tmp = term.line; ++ static Line *altline; ++ static int altcol, altrow; ++ Line *tmpline = term.line; ++ int tmpcol = term.col, tmprow = term.row; + +- term.line = term.alt; +- term.alt = tmp; ++ term.line = altline; ++ term.col = altcol, term.row = altrow; ++ altline = tmpline; ++ altcol = tmpcol, altrow = tmprow; + term.mode ^= MODE_ALTSCREEN; +- tfulldirt(); ++} ++ ++void ++tloaddefscreen(int clear, int loadcursor) ++{ ++ int col, row, alt = IS_SET(MODE_ALTSCREEN); ++ ++ if (alt) { ++ if (clear) ++ tclearregion(0, 0, term.col-1, term.row-1, 1); ++ col = term.col, row = term.row; ++ tswapscreen(); ++ } ++ if (loadcursor) ++ tcursor(CURSOR_LOAD); ++ if (alt) ++ tresizedef(col, row); ++} ++ ++void ++tloadaltscreen(int clear, int savecursor) ++{ ++ int col, row, def = !IS_SET(MODE_ALTSCREEN); ++ ++ if (savecursor) ++ tcursor(CURSOR_SAVE); ++ if (def) { ++ col = term.col, row = term.row; ++ tswapscreen(); ++ term.scr = 0; ++ tresizealt(col, row); ++ } ++ if (clear) ++ tclearregion(0, 0, term.col-1, term.row-1, 1); ++} ++ ++int ++tisaltscreen(void) ++{ ++ return IS_SET(MODE_ALTSCREEN); + } + + void +@@ -1069,17 +1201,22 @@ kscrolldown(const Arg* a) + { + int n = a->i; + +- if (n < 0) +- n = term.row + n; ++ if (!term.scr || IS_SET(MODE_ALTSCREEN)) ++ return; + +- if (n > term.scr) +- n = term.scr; ++ if (n < 0) ++ n = MAX(term.row / -n, 1); + +- if (term.scr > 0) { ++ if (n <= term.scr) { + term.scr -= n; +- selscroll(0, -n); +- tfulldirt(); ++ } else { ++ n = term.scr; ++ term.scr = 0; + } ++ ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(-n); /* negate change in term.scr */ ++ tfulldirt(); + } + + void +@@ -1087,92 +1224,118 @@ kscrollup(const Arg* a) + { + int n = a->i; + ++ if (!term.histf || IS_SET(MODE_ALTSCREEN)) ++ return; ++ + if (n < 0) +- n = term.row + n; ++ n = MAX(term.row / -n, 1); + +- if (term.scr <= HISTSIZE-n) { ++ if (term.scr + n <= term.histf) { + term.scr += n; +- selscroll(0, n); +- tfulldirt(); ++ } else { ++ n = term.histf - term.scr; ++ term.scr = term.histf; + } ++ ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(n); /* negate change in term.scr */ ++ tfulldirt(); + } + + void +-tscrolldown(int orig, int n, int copyhist) ++tscrolldown(int top, int n) + { +- int i; ++ int i, bot = term.bot; + 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; +- } +- ++ if (n <= 0) ++ return; ++ n = MIN(n, bot-top+1); + +- tsetdirt(orig, term.bot-n); +- tclearregion(0, term.bot-n+1, term.col-1, term.bot); ++ tsetdirt(top, bot-n); ++ tclearregion(0, bot-n+1, term.col-1, bot, 1); + +- for (i = term.bot; i >= orig+n; i--) { ++ for (i = bot; i >= top+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); ++ if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) ++ selscroll(top, bot, n); + } + + void +-tscrollup(int orig, int n, int copyhist) ++tscrollup(int top, int bot, int n, int mode) + { +- int i; ++ int i, j, s; ++ int alt = IS_SET(MODE_ALTSCREEN); ++ int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; + 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 (n <= 0) ++ return; ++ n = MIN(n, bot-top+1); ++ ++ if (savehist) { ++ for (i = 0; i < n; i++) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ for (j = 0; j < term.col; j++) ++ tclearglyph(&temp[j], 1); ++ term.hist[term.histi] = term.line[i]; ++ term.line[i] = temp; ++ } ++ term.histf = MIN(term.histf + n, HISTSIZE); ++ s = n; ++ if (term.scr) { ++ j = term.scr; ++ term.scr = MIN(j + n, HISTSIZE); ++ s = j + n - term.scr; ++ } ++ if (mode != SCROLL_RESIZE) ++ tfulldirt(); ++ } else { ++ tclearregion(0, top, term.col-1, top+n-1, 1); ++ tsetdirt(top+n, bot); + } + +- 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++) { ++ for (i = top; i <= 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); ++ if (sel.ob.x != -1 && sel.alt == alt) { ++ if (!savehist) { ++ selscroll(top, bot, -n); ++ } else if (s > 0) { ++ selmove(-s); ++ if (-term.scr + sel.nb.y < -term.histf) ++ selremove(); ++ } ++ } + } + + void +-selscroll(int orig, int n) ++selmove(int n) + { +- if (sel.ob.x == -1) +- return; ++ sel.ob.y += n, sel.nb.y += n; ++ sel.oe.y += n, sel.ne.y += n; ++} ++ ++void ++selscroll(int top, int bot, int n) ++{ ++ /* turn absolute coordinates into relative */ ++ top += term.scr, bot += term.scr; + +- if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { ++ if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, 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) { ++ } else if (BETWEEN(sel.nb.y, top, bot)) { ++ selmove(n); ++ if (sel.nb.y < top || sel.ne.y > bot) + selclear(); +- } else { +- selnormalize(); +- } + } + } + +@@ -1182,7 +1345,7 @@ tnewline(int first_col) + int y = term.c.y; + + if (y == term.bot) { +- tscrollup(term.top, 1, 1); ++ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); + } else { + y++; + } +@@ -1272,89 +1435,93 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) + } 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; ++ term.line[y][x].mode |= ATTR_SET; + } + + void +-tclearregion(int x1, int y1, int x2, int y2) ++tclearglyph(Glyph *gp, int usecurattr) + { +- int x, y, temp; +- Glyph *gp; ++ if (usecurattr) { ++ gp->fg = term.c.attr.fg; ++ gp->bg = term.c.attr.bg; ++ } else { ++ gp->fg = defaultfg; ++ gp->bg = defaultbg; ++ } ++ gp->mode = ATTR_NULL; ++ gp->u = ' '; ++} + +- if (x1 > x2) +- temp = x1, x1 = x2, x2 = temp; +- if (y1 > y2) +- temp = y1, y1 = y2, y2 = temp; ++void ++tclearregion(int x1, int y1, int x2, int y2, int usecurattr) ++{ ++ int x, y; + +- LIMIT(x1, 0, term.col-1); +- LIMIT(x2, 0, term.col-1); +- LIMIT(y1, 0, term.row-1); +- LIMIT(y2, 0, term.row-1); ++ /* regionselected() takes relative coordinates */ ++ if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr)) ++ selremove(); + + 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 = ' '; +- } ++ for (x = x1; x <= x2; x++) ++ tclearglyph(&term.line[y][x], usecurattr); + } + } + + void + tdeletechar(int n) + { +- int dst, src, size; +- Glyph *line; +- +- LIMIT(n, 0, term.col - term.c.x); ++ int src, dst, size; ++ Line line; + ++ if (n <= 0) ++ return; + dst = term.c.x; +- src = term.c.x + n; ++ src = MIN(term.c.x + n, term.col); + 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); ++ if (size > 0) { /* otherwise src would point beyond the array ++ https://stackoverflow.com/questions/29844298 */ ++ line = term.line[term.c.y]; ++ memmove(&line[dst], &line[src], size * sizeof(Glyph)); ++ } ++ tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1); + } + + void + tinsertblank(int n) + { +- int dst, src, size; +- Glyph *line; +- +- LIMIT(n, 0, term.col - term.c.x); ++ int src, dst, size; ++ Line line; + +- dst = term.c.x + n; ++ if (n <= 0) ++ return; ++ dst = MIN(term.c.x + n, term.col); + 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); ++ if (size > 0) { /* otherwise dst would point beyond the array */ ++ line = term.line[term.c.y]; ++ memmove(&line[dst], &line[src], size * sizeof(Glyph)); ++ } ++ tclearregion(src, term.c.y, dst - 1, term.c.y, 1); + } + + void + tinsertblankline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrolldown(term.c.y, n, 0); ++ tscrolldown(term.c.y, n); + } + + void + tdeleteline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrollup(term.c.y, n, 0); ++ tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST); + } + + int32_t +@@ -1528,7 +1695,7 @@ tsetscroll(int t, int b) + void + tsetmode(int priv, int set, const int *args, int narg) + { +- int alt; const int *lim; ++ const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { +@@ -1589,25 +1756,18 @@ tsetmode(int priv, int set, const int *args, int narg) + 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: ++ case 1047: /* swap screen, clearing alternate screen */ + 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 */ ++ if (set) ++ tloadaltscreen(*args == 1049, *args == 1049); ++ else ++ tloaddefscreen(*args == 1047, *args == 1049); ++ break; + case 1048: ++ if (!allowaltscreen) ++ break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ +@@ -1659,7 +1819,7 @@ void + csihandle(void) + { + char buf[40]; +- int len; ++ int n, x; + + switch (csiescseq.mode[0]) { + default: +@@ -1757,20 +1917,30 @@ csihandle(void) + 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); ++ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); + if (term.c.y < term.row-1) { +- tclearregion(0, term.c.y+1, term.col-1, +- term.row-1); ++ tclearregion(0, term.c.y+1, term.col-1, term.row-1, 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); ++ if (term.c.y >= 1) ++ tclearregion(0, 0, term.col-1, term.c.y-1, 1); ++ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ +- tclearregion(0, 0, term.col-1, term.row-1); +- break; ++ if (IS_SET(MODE_ALTSCREEN)) { ++ tclearregion(0, 0, term.col-1, term.row-1, 1); ++ break; ++ } ++ /* vte does this: ++ tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ ++ ++ /* alacritty does this: */ ++ for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--); ++ if (n >= 0) ++ tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); ++ tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); ++ break; + default: + goto unknown; + } +@@ -1778,24 +1948,24 @@ csihandle(void) + 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); ++ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); + break; + case 1: /* left */ +- tclearregion(0, term.c.y, term.c.x, term.c.y); ++ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ +- tclearregion(0, term.c.y, term.col-1, term.c.y); ++ tclearregion(0, term.c.y, term.col-1, term.c.y, 1); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0], 0); ++ /* xterm, urxvt, alacritty save this in history */ ++ tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); +- tscrolldown(term.top, csiescseq.arg[0], 0); ++ tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); +@@ -1809,9 +1979,11 @@ csihandle(void) + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ ++ if (csiescseq.arg[0] < 0) ++ return; + DEFAULT(csiescseq.arg[0], 1); +- tclearregion(term.c.x, term.c.y, +- term.c.x + csiescseq.arg[0] - 1, term.c.y); ++ x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; ++ tclearregion(term.c.x, term.c.y, x, term.c.y, 1); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); +@@ -1833,9 +2005,9 @@ csihandle(void) + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { +- len = snprintf(buf, sizeof(buf), "\033[%i;%iR", ++ n = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); +- ttywrite(buf, len, 0); ++ ttywrite(buf, n, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ +@@ -2128,16 +2300,8 @@ tdumpsel(void) + 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); ++ char str[(term.col + 1) * UTF_SIZ]; ++ tprinter(str, tgetline(str, &term.line[n][0])); + } + + void +@@ -2358,7 +2522,7 @@ eschandle(uchar ascii) + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { +- tscrollup(term.top, 1, 1); ++ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); + } else { + tmoveto(term.c.x, term.c.y+1); + } +@@ -2371,7 +2535,7 @@ eschandle(uchar ascii) + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { +- tscrolldown(term.top, 1, 1); ++ tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } +@@ -2511,7 +2675,8 @@ check_control_code: + */ + return; + } +- if (selected(term.c.x, term.c.y)) ++ /* selected() takes relative coordinates */ ++ if (selected(term.c.x + term.scr, term.c.y + term.scr)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; +@@ -2546,6 +2711,7 @@ check_control_code: + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { ++ term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; + term.c.state |= CURSOR_WRAPNEXT; + } + } +@@ -2583,93 +2749,275 @@ twrite(const char *buf, int buflen, int show_ctrl) + } + + void +-tresize(int col, int row) ++treflow(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; ++ int oce, nce, bot, scr; ++ int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; ++ int cy = -1; /* proxy for new y coordinate of cursor */ ++ int nlines; ++ Line *buf, line; ++ ++ /* y coordinate of cursor line end */ ++ for (oce = term.c.y; oce < term.row - 1 && ++ tiswrapped(term.line[oce]); oce++); ++ ++ nlines = term.histf + oce + 1; ++ if (col < term.col) { ++ /* each line can take this many lines after reflow */ ++ j = (term.col + col - 1) / col; ++ nlines = j * nlines; ++ if (nlines > HISTSIZE + RESIZEBUFFER + row) { ++ nlines = HISTSIZE + RESIZEBUFFER + row; ++ oy = -(nlines / j - oce - 1); ++ } + } ++ buf = xmalloc(nlines * sizeof(Line)); ++ do { ++ if (!nx) ++ buf[++ny] = xmalloc(col * sizeof(Glyph)); ++ if (!ox) { ++ line = TLINEABS(oy); ++ len = tlinelen(line); ++ } ++ if (oy == term.c.y) { ++ if (!ox) ++ len = MAX(len, term.c.x + 1); ++ /* update cursor */ ++ if (cy < 0 && term.c.x - ox < col - nx) { ++ term.c.x = nx + term.c.x - ox, cy = ny; ++ UPDATEWRAPNEXT(0, col); ++ } ++ } ++ /* get reflowed lines in buf */ ++ if (col - nx > len - ox) { ++ memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph)); ++ nx += len - ox; ++ if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { ++ for (j = nx; j < col; j++) ++ tclearglyph(&buf[ny][j], 0); ++ nx = 0; ++ } else if (nx > 0) { ++ buf[ny][nx - 1].mode &= ~ATTR_WRAP; ++ } ++ ox = 0, oy++; ++ } else if (col - nx == len - ox) { ++ memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); ++ ox = 0, oy++, nx = 0; ++ } else/* if (col - nx < len - ox) */ { ++ memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); ++ ox += col - nx; ++ buf[ny][col - 1].mode |= ATTR_WRAP; ++ nx = 0; ++ } ++ } while (oy <= oce); ++ if (nx) ++ for (j = nx; j < col; j++) ++ tclearglyph(&buf[ny][j], 0); + +- /* +- * 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 extra lines */ ++ 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)); ++ ++ bot = MIN(ny, row - 1); ++ scr = MAX(row - term.row, 0); ++ /* update y coordinate of cursor line end */ ++ nce = MIN(oce + scr, bot); ++ /* update cursor y coordinate */ ++ term.c.y = nce - (ny - cy); ++ if (term.c.y < 0) { ++ j = nce, nce = MIN(nce + -term.c.y, bot); ++ term.c.y += nce - j; ++ while (term.c.y < 0) { ++ free(buf[ny--]); ++ term.c.y++; ++ } + } +- /* 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)); ++ /* allocate new rows */ ++ for (i = row - 1; i > nce; i--) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ for (j = 0; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); + } +- for (i += row; i < term.row; i++) { ++ /* fill visible area */ ++ for (/*i = nce */; i >= term.row; i--, ny--) ++ term.line[i] = buf[ny]; ++ for (/*i = term.row - 1 */; i >= 0; i--, ny--) { + free(term.line[i]); +- free(term.alt[i]); ++ term.line[i] = buf[ny]; ++ } ++ /* fill lines in history buffer and update term.histf */ ++ for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) { ++ j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; ++ free(term.hist[j]); ++ term.hist[j] = buf[ny]; ++ } ++ term.histf = -i - 1; ++ term.scr = MIN(term.scr, term.histf); ++ /* resize rest of the history lines */ ++ for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) { ++ j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; ++ term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); + } ++ free(buf); ++} + +- /* 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)); ++void ++rscrolldown(int n) ++{ ++ int i; ++ Line temp; + +- 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 = ' '; +- } +- } ++ /* can never be true as of now ++ if (IS_SET(MODE_ALTSCREEN)) ++ return; */ + +- /* 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)); +- } ++ if ((n = MIN(n, term.histf)) <= 0) ++ return; + +- /* allocate any new rows */ +- for (/* i = minrow */; i < row; i++) { +- term.line[i] = xmalloc(col * sizeof(Glyph)); +- term.alt[i] = xmalloc(col * sizeof(Glyph)); ++ for (i = term.c.y + n; i >= n; i--) { ++ temp = term.line[i]; ++ term.line[i] = term.line[i-n]; ++ term.line[i-n] = temp; + } ++ for (/*i = n - 1 */; i >= 0; i--) { ++ temp = term.line[i]; ++ term.line[i] = term.hist[term.histi]; ++ term.hist[term.histi] = temp; ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ } ++ term.c.y += n; ++ term.histf -= n; ++ if ((i = term.scr - n) >= 0) { ++ term.scr = i; ++ } else { ++ term.scr = 0; ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(-i); ++ } ++} ++ ++void ++tresize(int col, int row) ++{ ++ int *bp; ++ ++ /* col and row are always MAX(_, 1) ++ if (col < 1 || row < 1) { ++ fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); ++ return; ++ } */ ++ ++ term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); ++ term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + 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 (IS_SET(MODE_ALTSCREEN)) ++ tresizealt(col, row); ++ else ++ tresizedef(col, row); ++} ++ ++void ++tresizedef(int col, int row) ++{ ++ int i, j; ++ ++ /* return if dimensions haven't changed */ ++ if (term.col == col && term.row == row) { ++ tfulldirt(); ++ return; ++ } ++ if (col != term.col) { ++ if (!sel.alt) ++ selremove(); ++ treflow(col, row); ++ } else { ++ /* slide screen up if otherwise cursor would get out of the screen */ ++ if (term.c.y >= row) { ++ tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE); ++ term.c.y = row - 1; + } +- if (0 < col && minrow < row) { +- tclearregion(0, minrow, col - 1, row - 1); ++ for (i = row; i < term.row; i++) ++ free(term.line[i]); ++ ++ /* resize to new height */ ++ term.line = xrealloc(term.line, row * sizeof(Line)); ++ /* allocate any new rows */ ++ for (i = term.row; i < row; i++) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ for (j = 0; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); + } +- tswapscreen(); +- tcursor(CURSOR_LOAD); ++ /* scroll down as much as height has increased */ ++ rscrolldown(row - term.row); ++ } ++ /* update terminal size */ ++ term.col = col, term.row = row; ++ /* reset scrolling region */ ++ term.top = 0, term.bot = row - 1; ++ /* dirty all lines */ ++ tfulldirt(); ++} ++ ++void ++tresizealt(int col, int row) ++{ ++ int i, j; ++ ++ /* return if dimensions haven't changed */ ++ if (term.col == col && term.row == row) { ++ tfulldirt(); ++ return; + } +- term.c = c; ++ if (sel.alt) ++ selremove(); ++ /* slide screen up if otherwise cursor would get out of the screen */ ++ for (i = 0; i <= term.c.y - row; i++) ++ free(term.line[i]); ++ if (i > 0) { ++ /* ensure that both src and dst are not NULL */ ++ memmove(term.line, term.line + i, row * sizeof(Line)); ++ term.c.y = row - 1; ++ } ++ for (i += row; i < term.row; i++) ++ free(term.line[i]); ++ /* resize to new height */ ++ term.line = xrealloc(term.line, row * sizeof(Line)); ++ /* resize to new width */ ++ for (i = 0; i < MIN(row, term.row); i++) { ++ term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); ++ for (j = term.col; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); ++ } ++ /* allocate any new rows */ ++ for (/*i = MIN(row, term.row) */; i < row; i++) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ for (j = 0; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); ++ } ++ /* update cursor */ ++ if (term.c.x >= col) { ++ term.c.state &= ~CURSOR_WRAPNEXT; ++ term.c.x = col - 1; ++ } else { ++ UPDATEWRAPNEXT(1, col); ++ } ++ /* update terminal size */ ++ term.col = col, term.row = row; ++ /* reset scrolling region */ ++ term.top = 0, term.bot = row - 1; ++ /* dirty all lines */ ++ tfulldirt(); + } + + void +diff --git a/st.h b/st.h +index 818a6f8..514ec08 100644 +--- a/st.h ++++ b/st.h +@@ -22,17 +22,19 @@ + + 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_SET = 1 << 0, ++ ATTR_BOLD = 1 << 1, ++ ATTR_FAINT = 1 << 2, ++ ATTR_ITALIC = 1 << 3, ++ ATTR_UNDERLINE = 1 << 4, ++ ATTR_BLINK = 1 << 5, ++ ATTR_REVERSE = 1 << 6, ++ ATTR_INVISIBLE = 1 << 7, ++ ATTR_STRUCK = 1 << 8, ++ ATTR_WRAP = 1 << 9, ++ ATTR_WIDE = 1 << 10, ++ ATTR_WDUMMY = 1 << 11, ++ ATTR_SELECTED = 1 << 12, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +@@ -90,6 +92,7 @@ void toggleprinter(const Arg *); + + int tattrset(int); + void tnew(int, int); ++int tisaltscreen(void); + void tresize(int, int); + void tsetdirtattr(int); + void ttyhangup(void); diff --git a/st/st b/st/st new file mode 100755 index 0000000000000000000000000000000000000000..9f9745ff985ff0e3c83c6e06f33a8322a7647f9d GIT binary patch literal 157256 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][{+-}{+-}]. 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/st/st.c b/st/st.c new file mode 100644 index 0000000..55ed70b --- /dev/null +++ b/st/st.c @@ -0,0 +1,3104 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#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 +#define RESIZEBUFFER 1000 + +/* 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[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \ + : term.line[(y) - term.scr] \ +) + +#define TLINEABS(y) ( \ + (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \ +) + +#define UPDATEWRAPNEXT(alt, col) do { \ + if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \ + term.c.x += term.wrapcwidth[alt]; \ + term.c.state &= ~CURSOR_WRAPNEXT; \ + } \ +} while (0); + +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 scroll_mode { + SCROLL_RESIZE = -1, + SCROLL_NOSAVEHIST = 0, + SCROLL_SAVEHIST = 1 +}; + +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 hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int histf; /* nb history available */ + int scr; /* scroll back */ + int wrapcwidth[2]; /* used in updating WRAPNEXT when resizing */ + 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 '[' [[ [] [;]] []] */ +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 [[ [] [;]] ] 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 void osc_color_response(int, int, int); +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, int); +static void tcursor(int); +static void tclearglyph(Glyph *, int); +static void tresetcursor(void); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(Line len); +static int tiswrapped(Line line); +static char *tgetglyphs(char *, const Glyph *, const Glyph *); +static size_t tgetline(char *, const Glyph *); +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, int); +static void tscrolldown(int, int); +static void treflow(int, int); +static void rscrolldown(int); +static void tresizedef(int, int); +static void tresizealt(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 tloaddefscreen(int, int); +static void tloadaltscreen(int, int); +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, int); +static void selmove(int); +static void selremove(void); +static int regionselected(int, int, 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; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 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 + }; + + 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(Line line) +{ + int i = term.col - 1; + + for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); + return i + 1; +} + +int +tiswrapped(Line line) +{ + int len = tlinelen(line); + + return len > 0 && (line[len - 1].mode & ATTR_WRAP); +} + +char * +tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp) +{ + while (gp <= lgp) + if (gp->mode & ATTR_WDUMMY) { + gp++; + } else { + buf += utf8encode((gp++)->u, buf); + } + return buf; +} + +size_t +tgetline(char *buf, const Glyph *fgp) +{ + char *ptr; + const Glyph *lgp = &fgp[term.col - 1]; + + while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP))) + lgp--; + ptr = tgetglyphs(buf, fgp, lgp); + if (!(lgp->mode & ATTR_WRAP)) + *(ptr++) = '\n'; + return ptr - buf; +} + +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; + sel.type = type; + selnormalize(); + + 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(TLINE(sel.nb.y)); + if (sel.nb.x > i) + sel.nb.x = i; + if (sel.ne.x >= tlinelen(TLINE(sel.ne.y))) + sel.ne.x = term.col - 1; +} + +int +regionselected(int x1, int y1, int x2, int y2) +{ + if (sel.ob.x == -1 || sel.mode == SEL_EMPTY || + sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1) + return 0; + + return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1 + : (sel.nb.y != y2 || sel.nb.x <= x2) && + (sel.ne.y != y1 || sel.ne.x >= x1); +} + +int +selected(int x, int y) +{ + return regionselected(x, y, x, y); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int rtop = 0, rbot = term.row - 1; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + if (!IS_SET(MODE_ALTSCREEN)) + rtop += -term.histf + term.scr, rbot += term.scr; + + 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, rtop, rbot)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(TLINE(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 > rtop; *y -= 1) { + if (!tiswrapped(TLINE(*y-1))) + break; + } + } else if (direction > 0) { + for (; *y < rbot; *y += 1) { + if (!tiswrapped(TLINE(*y))) + break; + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, lastx, linelen; + const Glyph *gp, *lgp; + + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return NULL; + + str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ); + ptr = str; + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + Line line = TLINE(y); + + if ((linelen = tlinelen(line)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &line[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &line[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + lgp = &line[MIN(lastx, linelen-1)]; + + ptr = tgetglyphs(ptr, gp, lgp); + /* + * 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) && + (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = '\0'; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + selremove(); + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selremove(void) +{ + sel.mode = SEL_IDLE; + sel.ob.x = -1; +} + +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; + + kscrolldown(&((Arg){ .i = term.scr })); + 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(void) +{ + /* 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) { + term.dirty[i] = 1; + break; + } + } + } +} + +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +tfulldirt(void) +{ + for (int i = 0; i < term.row; i++) + term.dirty[i] = 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 +tresetcursor(void) +{ + term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, + .x = 0, .y = 0, .state = CURSOR_DEFAULT }; +} + +void +treset(void) +{ + uint i; + int x, y; + + tresetcursor(); + + 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.histf = 0; + term.scr = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + selremove(); + for (i = 0; i < 2; i++) { + tcursor(CURSOR_SAVE); /* reset saved cursor */ + for (y = 0; y < term.row; y++) + for (x = 0; x < term.col; x++) + tclearglyph(&term.line[y][x], 0); + tswapscreen(); + } + tfulldirt(); +} + +void +tnew(int col, int row) +{ + int i, j; + + for (i = 0; i < 2; i++) { + term.line = xmalloc(row * sizeof(Line)); + for (j = 0; j < row; j++) + term.line[j] = xmalloc(col * sizeof(Glyph)); + term.col = col, term.row = row; + tswapscreen(); + } + term.dirty = xmalloc(row * sizeof(*term.dirty)); + term.tabs = xmalloc(col * sizeof(*term.tabs)); + for (i = 0; i < HISTSIZE; i++) + term.hist[i] = xmalloc(col * sizeof(Glyph)); + treset(); +} + +/* handle it with care */ +void +tswapscreen(void) +{ + static Line *altline; + static int altcol, altrow; + Line *tmpline = term.line; + int tmpcol = term.col, tmprow = term.row; + + term.line = altline; + term.col = altcol, term.row = altrow; + altline = tmpline; + altcol = tmpcol, altrow = tmprow; + term.mode ^= MODE_ALTSCREEN; +} + +void +tloaddefscreen(int clear, int loadcursor) +{ + int col, row, alt = IS_SET(MODE_ALTSCREEN); + + if (alt) { + if (clear) + tclearregion(0, 0, term.col-1, term.row-1, 1); + col = term.col, row = term.row; + tswapscreen(); + } + if (loadcursor) + tcursor(CURSOR_LOAD); + if (alt) + tresizedef(col, row); +} + +void +tloadaltscreen(int clear, int savecursor) +{ + int col, row, def = !IS_SET(MODE_ALTSCREEN); + + if (savecursor) + tcursor(CURSOR_SAVE); + if (def) { + col = term.col, row = term.row; + tswapscreen(); + term.scr = 0; + tresizealt(col, row); + } + if (clear) + tclearregion(0, 0, term.col-1, term.row-1, 1); +} + +int +tisaltscreen(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (!term.scr || IS_SET(MODE_ALTSCREEN)) + return; + + if (n < 0) + n = MAX(term.row / -n, 1); + + if (n <= term.scr) { + term.scr -= n; + } else { + n = term.scr; + term.scr = 0; + } + + if (sel.ob.x != -1 && !sel.alt) + selmove(-n); /* negate change in term.scr */ + tfulldirt(); +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (!term.histf || IS_SET(MODE_ALTSCREEN)) + return; + + + if (n < 0) + n = MAX(term.row / -n, 1); + + if (term.scr + n <= term.histf) { + term.scr += n; + } + else { + n = term.histf - term.scr; + term.scr = term.histf; + } + + if (sel.ob.x != -1 && !sel.alt) + selmove(n); /* negate change in term.scr */ + tfulldirt(); +} + +void +tscrolldown(int top, int n) +{ + int i, bot = term.bot; + Line temp; + + if (n <= 0) + return; + n = MIN(n, bot-top+1); + + tsetdirt(top, bot-n); + tclearregion(0, bot-n+1, term.col-1, bot, 1); + + for (i = bot; i >= top+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) + selscroll(top, bot, n); +} + +void +tscrollup(int top, int bot, int n, int mode) +{ + int i, j, s; + int alt = IS_SET(MODE_ALTSCREEN); + int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; + Line temp; + + if (n <= 0) + return; + n = MIN(n, bot-top+1); + + if (savehist) { + for (i = 0; i < n; i++) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + for (j = 0; j < term.col; j++) + tclearglyph(&temp[j], 1); + term.hist[term.histi] = term.line[i]; + term.line[i] = temp; + } + term.histf = MIN(term.histf + n, HISTSIZE); + s = n; + if (term.scr) { + j = term.scr; + term.scr = MIN(j + n, HISTSIZE); + s = j + n - term.scr; + } + if (mode != SCROLL_RESIZE) + tfulldirt(); + } else { + tclearregion(0, top, term.col-1, top+n-1, 1); + tsetdirt(top+n, bot); + } + + for (i = top; i <= bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (sel.ob.x != -1 && sel.alt == alt) { + if (!savehist) { + selscroll(top, bot, -n); + } else if (s > 0) { + selmove(-s); + if (-term.scr + sel.nb.y < -term.histf) + selremove(); + } + } +} + +void +selmove(int n) +{ + sel.ob.y += n, sel.nb.y += n; + sel.oe.y += n, sel.ne.y += n; +} + +void +selscroll(int top, int bot, int n) +{ + /* turn absolute coordinates into relative */ + top += term.scr, bot += term.scr; + + if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, top, bot)) { + selmove(n); + if (sel.nb.y < top || sel.ne.y > bot) + selclear(); + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + int sep = ';'; /* colon or semi-colon, but not both */ + + 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 (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || 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; + term.line[y][x].mode |= ATTR_SET; +} + +void +tclearglyph(Glyph *gp, int usecurattr) +{ + if (usecurattr) { + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + } else { + gp->fg = defaultfg; + gp->bg = defaultbg; + } + gp->mode = ATTR_NULL; + gp->u = ' '; +} + +void +tclearregion(int x1, int y1, int x2, int y2, int usecurattr) +{ + int x, y; + + /* regionselected() takes relative coordinates */ + if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr)) + selremove(); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) + tclearglyph(&term.line[y][x], usecurattr); + } +} + +void +tdeletechar(int n) +{ + int src, dst, size; + Line line; + + if (n <= 0) + return; + dst = term.c.x; + src = MIN(term.c.x + n, term.col); + size = term.col - src; + if (size > 0) { /* otherwise src would point beyond the array + https://stackoverflow.com/questions/29844298 */ + line = term.line[term.c.y]; + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + } + tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1); +} + +void +tinsertblank(int n) +{ + int src, dst, size; + Line line; + + if (n <= 0) + return; + dst = MIN(term.c.x + n, term.col); + src = term.c.x; + size = term.col - dst; + if (size > 0) { /* otherwise dst would point beyond the array */ + line = term.line[term.c.y]; + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + } + tclearregion(src, term.c.y, dst - 1, term.c.y, 1); +} + +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, term.bot, n, SCROLL_NOSAVEHIST); +} + +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) +{ + 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 */ + case 47: /* swap screen */ + case 1047: /* swap screen, clearing alternate screen */ + if (!allowaltscreen) + break; + if (set) + tloadaltscreen(*args == 1049, *args == 1049); + else + tloaddefscreen(*args == 1047, *args == 1049); + break; + case 1048: + if (!allowaltscreen) + break; + 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 n, x; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor 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 more times */ + LIMIT(csiescseq.arg[0], 1, 65535); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor 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 */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + 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 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, 1); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1); + } + break; + case 1: /* above */ + if (term.c.y >= 1) + tclearregion(0, 0, term.col-1, term.c.y-1, 1); + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ + if (IS_SET(MODE_ALTSCREEN)) { + tclearregion(0, 0, term.col-1, term.row-1, 1); + break; + } + /* vte does this: + tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ + + /* alacritty does this: */ + for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--); + if (n >= 0) + tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); + tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); + 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, 1); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y, 1); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + /* xterm, urxvt, alacritty save this in history */ + tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert 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 lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + if (csiescseq.arg[0] < 0) + return; + DEFAULT(csiescseq.arg[0], 1); + x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; + tclearregion(term.c.x, term.c.y, x, term.c.y, 1); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + 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 */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + n = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, n, 0); + break; + default: + goto unknown; + } + 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 +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + 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: + case 11: + case 12: + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, 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, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + 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 str[(term.col + 1) * UTF_SIZ]; + tprinter(str, tgetline(str, &term.line[n][0])); +} + +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, term.bot, 1, SCROLL_SAVEHIST); + } 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(); + xsetmode(0, MODE_HIDE); + 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) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + 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; + } + /* selected() takes relative coordinates */ + if (selected(term.c.x + term.scr, term.c.y + term.scr)) + 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)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + 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.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; + 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 +treflow(int col, int row) +{ + int i, j; + int oce, nce, bot, scr; + int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; + int cy = -1; /* proxy for new y coordinate of cursor */ + int nlines; + Line *buf, line; + + /* y coordinate of cursor line end */ + for (oce = term.c.y; oce < term.row - 1 && + tiswrapped(term.line[oce]); oce++); + + nlines = term.histf + oce + 1; + if (col < term.col) { + /* each line can take this many lines after reflow */ + j = (term.col + col - 1) / col; + nlines = j * nlines; + if (nlines > HISTSIZE + RESIZEBUFFER + row) { + nlines = HISTSIZE + RESIZEBUFFER + row; + oy = -(nlines / j - oce - 1); + } + } + buf = xmalloc(nlines * sizeof(Line)); + do { + if (!nx) + buf[++ny] = xmalloc(col * sizeof(Glyph)); + if (!ox) { + line = TLINEABS(oy); + len = tlinelen(line); + } + if (oy == term.c.y) { + if (!ox) + len = MAX(len, term.c.x + 1); + /* update cursor */ + if (cy < 0 && term.c.x - ox < col - nx) { + term.c.x = nx + term.c.x - ox, cy = ny; + UPDATEWRAPNEXT(0, col); + } + } + /* get reflowed lines in buf */ + if (col - nx > len - ox) { + memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph)); + nx += len - ox; + if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { + for (j = nx; j < col; j++) + tclearglyph(&buf[ny][j], 0); + nx = 0; + } else if (nx > 0) { + buf[ny][nx - 1].mode &= ~ATTR_WRAP; + } + ox = 0, oy++; + } else if (col - nx == len - ox) { + memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); + ox = 0, oy++, nx = 0; + } else/* if (col - nx < len - ox) */ { + memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); + ox += col - nx; + buf[ny][col - 1].mode |= ATTR_WRAP; + nx = 0; + } + } while (oy <= oce); + if (nx) + for (j = nx; j < col; j++) + tclearglyph(&buf[ny][j], 0); + + /* free extra lines */ + for (i = row; i < term.row; i++) + free(term.line[i]); + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + + bot = MIN(ny, row - 1); + scr = MAX(row - term.row, 0); + /* update y coordinate of cursor line end */ + nce = MIN(oce + scr, bot); + /* update cursor y coordinate */ + term.c.y = nce - (ny - cy); + if (term.c.y < 0) { + j = nce, nce = MIN(nce + -term.c.y, bot); + term.c.y += nce - j; + while (term.c.y < 0) { + free(buf[ny--]); + term.c.y++; + } + } + /* allocate new rows */ + for (i = row - 1; i > nce; i--) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* fill visible area */ + for (/*i = nce */; i >= term.row; i--, ny--) + term.line[i] = buf[ny]; + for (/*i = term.row - 1 */; i >= 0; i--, ny--) { + free(term.line[i]); + term.line[i] = buf[ny]; + } + /* fill lines in history buffer and update term.histf */ + for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) { + j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; + free(term.hist[j]); + term.hist[j] = buf[ny]; + } + term.histf = -i - 1; + term.scr = MIN(term.scr, term.histf); + /* resize rest of the history lines */ + for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) { + j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; + term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); + } + free(buf); +} + +void +rscrolldown(int n) +{ + int i; + Line temp; + + /* can never be true as of now + if (IS_SET(MODE_ALTSCREEN)) + return; */ + + if ((n = MIN(n, term.histf)) <= 0) + return; + + for (i = term.c.y + n; i >= n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + for (/*i = n - 1 */; i >= 0; i--) { + temp = term.line[i]; + term.line[i] = term.hist[term.histi]; + term.hist[term.histi] = temp; + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + } + term.c.y += n; + term.histf -= n; + if ((i = term.scr - n) >= 0) { + term.scr = i; + } else { + term.scr = 0; + if (sel.ob.x != -1 && !sel.alt) + selmove(-i); + } +} + +void +tresize(int col, int row) +{ + int *bp; + + /* col and row are always MAX(_, 1) + if (col < 1 || row < 1) { + fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); + return; + } */ + + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + 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; + } + + if (IS_SET(MODE_ALTSCREEN)) + tresizealt(col, row); + else + tresizedef(col, row); +} + +void +tresizedef(int col, int row) +{ + int i, j; + + /* return if dimensions haven't changed */ + if (term.col == col && term.row == row) { + tfulldirt(); + return; + } + if (col != term.col) { + if (!sel.alt) + selremove(); + treflow(col, row); + } else { + /* slide screen up if otherwise cursor would get out of the screen */ + if (term.c.y >= row) { + tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE); + term.c.y = row - 1; + } + for (i = row; i < term.row; i++) + free(term.line[i]); + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + /* allocate any new rows */ + for (i = term.row; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* scroll down as much as height has increased */ + rscrolldown(row - term.row); + } + /* update terminal size */ + term.col = col, term.row = row; + /* reset scrolling region */ + term.top = 0, term.bot = row - 1; + /* dirty all lines */ + tfulldirt(); +} + +void +tresizealt(int col, int row) +{ + int i, j; + + /* return if dimensions haven't changed */ + if (term.col == col && term.row == row) { + tfulldirt(); + return; + } + if (sel.alt) + selremove(); + /* slide screen up if otherwise cursor would get out of the screen */ + for (i = 0; i <= term.c.y - row; i++) + free(term.line[i]); + if (i > 0) { + /* ensure that both src and dst are not NULL */ + memmove(term.line, term.line + i, row * sizeof(Line)); + term.c.y = row - 1; + } + for (i += row; i < term.row; i++) + free(term.line[i]); + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + /* resize to new width */ + for (i = 0; i < MIN(row, term.row); i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + for (j = term.col; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* allocate any new rows */ + for (/*i = MIN(row, term.row) */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* update cursor */ + if (term.c.x >= col) { + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = col - 1; + } else { + UPDATEWRAPNEXT(1, col); + } + /* update terminal size */ + term.col = col, term.row = row; + /* reset scrolling region */ + term.top = 0, term.bot = row - 1; + /* dirty all lines */ + tfulldirt(); +} + +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/st/st.c.orig b/st/st.c.orig new file mode 100644 index 0000000..7b0499a --- /dev/null +++ b/st/st.c.orig @@ -0,0 +1,3099 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#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 +#define RESIZEBUFFER 1000 + +/* 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[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \ + : term.line[(y) - term.scr] \ +) + +#define TLINEABS(y) ( \ + (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \ +) + +#define UPDATEWRAPNEXT(alt, col) do { \ + if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \ + term.c.x += term.wrapcwidth[alt]; \ + term.c.state &= ~CURSOR_WRAPNEXT; \ + } \ +} while (0); + +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 scroll_mode { + SCROLL_RESIZE = -1, + SCROLL_NOSAVEHIST = 0, + SCROLL_SAVEHIST = 1 +}; + +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 hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int histf; /* nb history available */ + int scr; /* scroll back */ + int wrapcwidth[2]; /* used in updating WRAPNEXT when resizing */ + 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 '[' [[ [] [;]] []] */ +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 [[ [] [;]] ] 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 void osc_color_response(int, int, int); +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, int); +static void tcursor(int); +static void tclearglyph(Glyph *, int); +static void tresetcursor(void); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(Line len); +static int tiswrapped(Line line); +static char *tgetglyphs(char *, const Glyph *, const Glyph *); +static size_t tgetline(char *, const Glyph *); +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, int); +static void tscrolldown(int, int); +static void treflow(int, int); +static void rscrolldown(int); +static void tresizedef(int, int); +static void tresizealt(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 tloaddefscreen(int, int); +static void tloadaltscreen(int, int); +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, int); +static void selmove(int); +static void selremove(void); +static int regionselected(int, int, 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; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 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 + }; + + 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(Line line) +{ + int i = term.col - 1; + + for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); + return i + 1; +} + +int +tiswrapped(Line line) +{ + int len = tlinelen(line); + + return len > 0 && (line[len - 1].mode & ATTR_WRAP); +} + +char * +tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp) +{ + while (gp <= lgp) + if (gp->mode & ATTR_WDUMMY) { + gp++; + } else { + buf += utf8encode((gp++)->u, buf); + } + return buf; +} + +size_t +tgetline(char *buf, const Glyph *fgp) +{ + char *ptr; + const Glyph *lgp = &fgp[term.col - 1]; + + while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP))) + lgp--; + ptr = tgetglyphs(buf, fgp, lgp); + if (!(lgp->mode & ATTR_WRAP)) + *(ptr++) = '\n'; + return ptr - buf; +} + +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; + sel.type = type; + selnormalize(); + + 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(TLINE(sel.nb.y)); + if (sel.nb.x > i) + sel.nb.x = i; + if (sel.ne.x >= tlinelen(TLINE(sel.ne.y))) + sel.ne.x = term.col - 1; +} + +int +regionselected(int x1, int y1, int x2, int y2) +{ + if (sel.ob.x == -1 || sel.mode == SEL_EMPTY || + sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1) + return 0; + + return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1 + : (sel.nb.y != y2 || sel.nb.x <= x2) && + (sel.ne.y != y1 || sel.ne.x >= x1); +} + +int +selected(int x, int y) +{ + return regionselected(x, y, x, y); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int rtop = 0, rbot = term.row - 1; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + if (!IS_SET(MODE_ALTSCREEN)) + rtop += -term.histf + term.scr, rbot += term.scr; + + 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, rtop, rbot)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(TLINE(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 > rtop; *y -= 1) { + if (!tiswrapped(TLINE(*y-1))) + break; + } + } else if (direction > 0) { + for (; *y < rbot; *y += 1) { + if (!tiswrapped(TLINE(*y))) + break; + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, lastx, linelen; + const Glyph *gp, *lgp; + + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return NULL; + + str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ); + ptr = str; + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + Line line = TLINE(y); + + if ((linelen = tlinelen(line)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &line[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &line[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + lgp = &line[MIN(lastx, linelen-1)]; + + ptr = tgetglyphs(ptr, gp, lgp); + /* + * 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) && + (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = '\0'; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + selremove(); + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selremove(void) +{ + sel.mode = SEL_IDLE; + sel.ob.x = -1; +} + +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; + + kscrolldown(&((Arg){ .i = term.scr })); + 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(void) +{ + /* 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) { + term.dirty[i] = 1; + break; + } + } + } +} + +void +tfulldirt(void) +{ + for (int i = 0; i < term.row; i++) + term.dirty[i] = 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 +tresetcursor(void) +{ + term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, + .x = 0, .y = 0, .state = CURSOR_DEFAULT }; +} + +void +treset(void) +{ + uint i; + int x, y; + + tresetcursor(); + + 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.histf = 0; + term.scr = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + selremove(); + for (i = 0; i < 2; i++) { + tcursor(CURSOR_SAVE); /* reset saved cursor */ + for (y = 0; y < term.row; y++) + for (x = 0; x < term.col; x++) + tclearglyph(&term.line[y][x], 0); + tswapscreen(); + } + tfulldirt(); +} + +void +tnew(int col, int row) +{ + int i, j; + + for (i = 0; i < 2; i++) { + term.line = xmalloc(row * sizeof(Line)); + for (j = 0; j < row; j++) + term.line[j] = xmalloc(col * sizeof(Glyph)); + term.col = col, term.row = row; + tswapscreen(); + } + term.dirty = xmalloc(row * sizeof(*term.dirty)); + term.tabs = xmalloc(col * sizeof(*term.tabs)); + for (i = 0; i < HISTSIZE; i++) + term.hist[i] = xmalloc(col * sizeof(Glyph)); + treset(); +} + +/* handle it with care */ +void +tswapscreen(void) +{ + static Line *altline; + static int altcol, altrow; + Line *tmpline = term.line; + int tmpcol = term.col, tmprow = term.row; + + term.line = altline; + term.col = altcol, term.row = altrow; + altline = tmpline; + altcol = tmpcol, altrow = tmprow; + term.mode ^= MODE_ALTSCREEN; +} + +void +tloaddefscreen(int clear, int loadcursor) +{ + int col, row, alt = IS_SET(MODE_ALTSCREEN); + + if (alt) { + if (clear) + tclearregion(0, 0, term.col-1, term.row-1, 1); + col = term.col, row = term.row; + tswapscreen(); + } + if (loadcursor) + tcursor(CURSOR_LOAD); + if (alt) + tresizedef(col, row); +} + +void +tloadaltscreen(int clear, int savecursor) +{ + int col, row, def = !IS_SET(MODE_ALTSCREEN); + + if (savecursor) + tcursor(CURSOR_SAVE); + if (def) { + col = term.col, row = term.row; + tswapscreen(); + term.scr = 0; + tresizealt(col, row); + } + if (clear) + tclearregion(0, 0, term.col-1, term.row-1, 1); +} + +int +tisaltscreen(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (!term.scr || IS_SET(MODE_ALTSCREEN)) + return; + + if (n < 0) + n = MAX(term.row / -n, 1); + + if (n <= term.scr) { + term.scr -= n; + } else { + n = term.scr; + term.scr = 0; + } + + if (sel.ob.x != -1 && !sel.alt) + selmove(-n); /* negate change in term.scr */ + tfulldirt(); +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (!term.histf || IS_SET(MODE_ALTSCREEN)) + return; + + + if (n < 0) + n = MAX(term.row / -n, 1); + + if (term.scr + n <= term.histf) { + term.scr += n; + } + else { + n = term.histf - term.scr; + term.scr = term.histf; + } + + if (sel.ob.x != -1 && !sel.alt) + selmove(n); /* negate change in term.scr */ + tfulldirt(); +} + +void +tscrolldown(int top, int n) +{ + int i, bot = term.bot; + Line temp; + + if (n <= 0) + return; + n = MIN(n, bot-top+1); + + tsetdirt(top, bot-n); + tclearregion(0, bot-n+1, term.col-1, bot, 1); + + for (i = bot; i >= top+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) + selscroll(top, bot, n); +} + +void +tscrollup(int top, int bot, int n, int mode) +{ + int i, j, s; + int alt = IS_SET(MODE_ALTSCREEN); + int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; + Line temp; + + if (n <= 0) + return; + n = MIN(n, bot-top+1); + + if (savehist) { + for (i = 0; i < n; i++) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + for (j = 0; j < term.col; j++) + tclearglyph(&temp[j], 1); + term.hist[term.histi] = term.line[i]; + term.line[i] = temp; + } + term.histf = MIN(term.histf + n, HISTSIZE); + s = n; + if (term.scr) { + j = term.scr; + term.scr = MIN(j + n, HISTSIZE); + s = j + n - term.scr; + } + if (mode != SCROLL_RESIZE) + tfulldirt(); + } else { + tclearregion(0, top, term.col-1, top+n-1, 1); + tsetdirt(top+n, bot); + } + + for (i = top; i <= bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (sel.ob.x != -1 && sel.alt == alt) { + if (!savehist) { + selscroll(top, bot, -n); + } else if (s > 0) { + selmove(-s); + if (-term.scr + sel.nb.y < -term.histf) + selremove(); + } + } +} + +void +selmove(int n) +{ + sel.ob.y += n, sel.nb.y += n; + sel.oe.y += n, sel.ne.y += n; +} + +void +selscroll(int top, int bot, int n) +{ + /* turn absolute coordinates into relative */ + top += term.scr, bot += term.scr; + + if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, top, bot)) { + selmove(n); + if (sel.nb.y < top || sel.ne.y > bot) + selclear(); + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + int sep = ';'; /* colon or semi-colon, but not both */ + + 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 (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || 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; + term.line[y][x].mode |= ATTR_SET; +} + +void +tclearglyph(Glyph *gp, int usecurattr) +{ + if (usecurattr) { + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + } else { + gp->fg = defaultfg; + gp->bg = defaultbg; + } + gp->mode = ATTR_NULL; + gp->u = ' '; +} + +void +tclearregion(int x1, int y1, int x2, int y2, int usecurattr) +{ + int x, y; + + /* regionselected() takes relative coordinates */ + if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr)) + selremove(); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) + tclearglyph(&term.line[y][x], usecurattr); + } +} + +void +tdeletechar(int n) +{ + int src, dst, size; + Line line; + + if (n <= 0) + return; + dst = term.c.x; + src = MIN(term.c.x + n, term.col); + size = term.col - src; + if (size > 0) { /* otherwise src would point beyond the array + https://stackoverflow.com/questions/29844298 */ + line = term.line[term.c.y]; + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + } + tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1); +} + +void +tinsertblank(int n) +{ + int src, dst, size; + Line line; + + if (n <= 0) + return; + dst = MIN(term.c.x + n, term.col); + src = term.c.x; + size = term.col - dst; + if (size > 0) { /* otherwise dst would point beyond the array */ + line = term.line[term.c.y]; + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + } + tclearregion(src, term.c.y, dst - 1, term.c.y, 1); +} + +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, term.bot, n, SCROLL_NOSAVEHIST); +} + +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) +{ + 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 */ + case 47: /* swap screen */ + case 1047: /* swap screen, clearing alternate screen */ + if (!allowaltscreen) + break; + if (set) + tloadaltscreen(*args == 1049, *args == 1049); + else + tloaddefscreen(*args == 1047, *args == 1049); + break; + case 1048: + if (!allowaltscreen) + break; + 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 n, x; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor 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 more times */ + LIMIT(csiescseq.arg[0], 1, 65535); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor 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 */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + 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 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, 1); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1); + } + break; + case 1: /* above */ + if (term.c.y >= 1) + tclearregion(0, 0, term.col-1, term.c.y-1, 1); + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ + if (IS_SET(MODE_ALTSCREEN)) { + tclearregion(0, 0, term.col-1, term.row-1, 1); + break; + } + /* vte does this: + tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ + + /* alacritty does this: */ + for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--); + if (n >= 0) + tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); + tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); + 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, 1); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y, 1); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + /* xterm, urxvt, alacritty save this in history */ + tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert 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 lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + if (csiescseq.arg[0] < 0) + return; + DEFAULT(csiescseq.arg[0], 1); + x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; + tclearregion(term.c.x, term.c.y, x, term.c.y, 1); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + 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 */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + n = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, n, 0); + break; + default: + goto unknown; + } + 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 +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + 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: + case 11: + case 12: + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, 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, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + 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 str[(term.col + 1) * UTF_SIZ]; + tprinter(str, tgetline(str, &term.line[n][0])); +} + +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, term.bot, 1, SCROLL_SAVEHIST); + } 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(); + xsetmode(0, MODE_HIDE); + 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) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + 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; + } + /* selected() takes relative coordinates */ + if (selected(term.c.x + term.scr, term.c.y + term.scr)) + 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)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + 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.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; + 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 +treflow(int col, int row) +{ + int i, j; + int oce, nce, bot, scr; + int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; + int cy = -1; /* proxy for new y coordinate of cursor */ + int nlines; + Line *buf, line; + + /* y coordinate of cursor line end */ + for (oce = term.c.y; oce < term.row - 1 && + tiswrapped(term.line[oce]); oce++); + + nlines = term.histf + oce + 1; + if (col < term.col) { + /* each line can take this many lines after reflow */ + j = (term.col + col - 1) / col; + nlines = j * nlines; + if (nlines > HISTSIZE + RESIZEBUFFER + row) { + nlines = HISTSIZE + RESIZEBUFFER + row; + oy = -(nlines / j - oce - 1); + } + } + buf = xmalloc(nlines * sizeof(Line)); + do { + if (!nx) + buf[++ny] = xmalloc(col * sizeof(Glyph)); + if (!ox) { + line = TLINEABS(oy); + len = tlinelen(line); + } + if (oy == term.c.y) { + if (!ox) + len = MAX(len, term.c.x + 1); + /* update cursor */ + if (cy < 0 && term.c.x - ox < col - nx) { + term.c.x = nx + term.c.x - ox, cy = ny; + UPDATEWRAPNEXT(0, col); + } + } + /* get reflowed lines in buf */ + if (col - nx > len - ox) { + memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph)); + nx += len - ox; + if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { + for (j = nx; j < col; j++) + tclearglyph(&buf[ny][j], 0); + nx = 0; + } else if (nx > 0) { + buf[ny][nx - 1].mode &= ~ATTR_WRAP; + } + ox = 0, oy++; + } else if (col - nx == len - ox) { + memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); + ox = 0, oy++, nx = 0; + } else/* if (col - nx < len - ox) */ { + memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); + ox += col - nx; + buf[ny][col - 1].mode |= ATTR_WRAP; + nx = 0; + } + } while (oy <= oce); + if (nx) + for (j = nx; j < col; j++) + tclearglyph(&buf[ny][j], 0); + + /* free extra lines */ + for (i = row; i < term.row; i++) + free(term.line[i]); + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + + bot = MIN(ny, row - 1); + scr = MAX(row - term.row, 0); + /* update y coordinate of cursor line end */ + nce = MIN(oce + scr, bot); + /* update cursor y coordinate */ + term.c.y = nce - (ny - cy); + if (term.c.y < 0) { + j = nce, nce = MIN(nce + -term.c.y, bot); + term.c.y += nce - j; + while (term.c.y < 0) { + free(buf[ny--]); + term.c.y++; + } + } + /* allocate new rows */ + for (i = row - 1; i > nce; i--) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* fill visible area */ + for (/*i = nce */; i >= term.row; i--, ny--) + term.line[i] = buf[ny]; + for (/*i = term.row - 1 */; i >= 0; i--, ny--) { + free(term.line[i]); + term.line[i] = buf[ny]; + } + /* fill lines in history buffer and update term.histf */ + for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) { + j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; + free(term.hist[j]); + term.hist[j] = buf[ny]; + } + term.histf = -i - 1; + term.scr = MIN(term.scr, term.histf); + /* resize rest of the history lines */ + for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) { + j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; + term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); + } + free(buf); +} + +void +rscrolldown(int n) +{ + int i; + Line temp; + + /* can never be true as of now + if (IS_SET(MODE_ALTSCREEN)) + return; */ + + if ((n = MIN(n, term.histf)) <= 0) + return; + + for (i = term.c.y + n; i >= n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + for (/*i = n - 1 */; i >= 0; i--) { + temp = term.line[i]; + term.line[i] = term.hist[term.histi]; + term.hist[term.histi] = temp; + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + } + term.c.y += n; + term.histf -= n; + if ((i = term.scr - n) >= 0) { + term.scr = i; + } else { + term.scr = 0; + if (sel.ob.x != -1 && !sel.alt) + selmove(-i); + } +} + +void +tresize(int col, int row) +{ + int *bp; + + /* col and row are always MAX(_, 1) + if (col < 1 || row < 1) { + fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); + return; + } */ + + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + 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; + } + + if (IS_SET(MODE_ALTSCREEN)) + tresizealt(col, row); + else + tresizedef(col, row); +} + +void +tresizedef(int col, int row) +{ + int i, j; + + /* return if dimensions haven't changed */ + if (term.col == col && term.row == row) { + tfulldirt(); + return; + } + if (col != term.col) { + if (!sel.alt) + selremove(); + treflow(col, row); + } else { + /* slide screen up if otherwise cursor would get out of the screen */ + if (term.c.y >= row) { + tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE); + term.c.y = row - 1; + } + for (i = row; i < term.row; i++) + free(term.line[i]); + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + /* allocate any new rows */ + for (i = term.row; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* scroll down as much as height has increased */ + rscrolldown(row - term.row); + } + /* update terminal size */ + term.col = col, term.row = row; + /* reset scrolling region */ + term.top = 0, term.bot = row - 1; + /* dirty all lines */ + tfulldirt(); +} + +void +tresizealt(int col, int row) +{ + int i, j; + + /* return if dimensions haven't changed */ + if (term.col == col && term.row == row) { + tfulldirt(); + return; + } + if (sel.alt) + selremove(); + /* slide screen up if otherwise cursor would get out of the screen */ + for (i = 0; i <= term.c.y - row; i++) + free(term.line[i]); + if (i > 0) { + /* ensure that both src and dst are not NULL */ + memmove(term.line, term.line + i, row * sizeof(Line)); + term.c.y = row - 1; + } + for (i += row; i < term.row; i++) + free(term.line[i]); + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + /* resize to new width */ + for (i = 0; i < MIN(row, term.row); i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + for (j = term.col; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* allocate any new rows */ + for (/*i = MIN(row, term.row) */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + for (j = 0; j < col; j++) + tclearglyph(&term.line[i][j], 0); + } + /* update cursor */ + if (term.c.x >= col) { + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = col - 1; + } else { + UPDATEWRAPNEXT(1, col); + } + /* update terminal size */ + term.col = col, term.row = row; + /* reset scrolling region */ + term.top = 0, term.bot = row - 1; + /* dirty all lines */ + tfulldirt(); +} + +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/st/st.c.rej b/st/st.c.rej new file mode 100644 index 0000000..51cd95a --- /dev/null +++ b/st/st.c.rej @@ -0,0 +1,213 @@ +--- st.c ++++ st.c +@@ -1224,92 +1361,118 @@ kscrollup(const Arg* a) + { + int n = a->i; + ++ if (!term.histf || IS_SET(MODE_ALTSCREEN)) ++ return; ++ + if (n < 0) +- n = term.row + n; ++ n = MAX(term.row / -n, 1); + +- if (term.scr <= HISTSIZE-n) { ++ if (term.scr + n <= term.histf) { + term.scr += n; +- selscroll(0, n); +- tfulldirt(); ++ } else { ++ n = term.histf - term.scr; ++ term.scr = term.histf; + } ++ ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(n); /* negate change in term.scr */ ++ tfulldirt(); + } + + void +-tscrolldown(int orig, int n, int copyhist) ++tscrolldown(int top, int n) + { +- int i; ++ int i, bot = term.bot; + 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; +- } +- ++ if (n <= 0) ++ return; ++ n = MIN(n, bot-top+1); + +- tsetdirt(orig, term.bot-n); +- tclearregion(0, term.bot-n+1, term.col-1, term.bot); ++ tsetdirt(top, bot-n); ++ tclearregion(0, bot-n+1, term.col-1, bot, 1); + +- for (i = term.bot; i >= orig+n; i--) { ++ for (i = bot; i >= top+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); ++ if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) ++ selscroll(top, bot, n); + } + + void +-tscrollup(int orig, int n, int copyhist) ++tscrollup(int top, int bot, int n, int mode) + { +- int i; ++ int i, j, s; ++ int alt = IS_SET(MODE_ALTSCREEN); ++ int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; + 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 (n <= 0) ++ return; ++ n = MIN(n, bot-top+1); ++ ++ if (savehist) { ++ for (i = 0; i < n; i++) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ for (j = 0; j < term.col; j++) ++ tclearglyph(&temp[j], 1); ++ term.hist[term.histi] = term.line[i]; ++ term.line[i] = temp; ++ } ++ term.histf = MIN(term.histf + n, HISTSIZE); ++ s = n; ++ if (term.scr) { ++ j = term.scr; ++ term.scr = MIN(j + n, HISTSIZE); ++ s = j + n - term.scr; ++ } ++ if (mode != SCROLL_RESIZE) ++ tfulldirt(); ++ } else { ++ tclearregion(0, top, term.col-1, top+n-1, 1); ++ tsetdirt(top+n, bot); + } + +- 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++) { ++ for (i = top; i <= 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); ++ if (sel.ob.x != -1 && sel.alt == alt) { ++ if (!savehist) { ++ selscroll(top, bot, -n); ++ } else if (s > 0) { ++ selmove(-s); ++ if (-term.scr + sel.nb.y < -term.histf) ++ selremove(); ++ } ++ } + } + + void +-selscroll(int orig, int n) ++selmove(int n) + { +- if (sel.ob.x == -1) +- return; ++ sel.ob.y += n, sel.nb.y += n; ++ sel.oe.y += n, sel.ne.y += n; ++} ++ ++void ++selscroll(int top, int bot, int n) ++{ ++ /* turn absolute coordinates into relative */ ++ top += term.scr, bot += term.scr; + +- if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { ++ if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, 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) { ++ } else if (BETWEEN(sel.nb.y, top, bot)) { ++ selmove(n); ++ if (sel.nb.y < top || sel.ne.y > bot) + selclear(); +- } else { +- selnormalize(); +- } + } + } + +@@ -1922,24 +2092,24 @@ csihandle(void) + 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); ++ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); + break; + case 1: /* left */ +- tclearregion(0, term.c.y, term.c.x, term.c.y); ++ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ +- tclearregion(0, term.c.y, term.col-1, term.c.y); ++ tclearregion(0, term.c.y, term.col-1, term.c.y, 1); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0], 0); ++ /* xterm, urxvt, alacritty save this in history */ ++ tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); +- tscrolldown(term.top, csiescseq.arg[0], 0); ++ tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); +@@ -1979,9 +2151,9 @@ csihandle(void) + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { +- len = snprintf(buf, sizeof(buf), "\033[%i;%iR", ++ n = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); +- ttywrite(buf, len, 0); ++ ttywrite(buf, n, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ diff --git a/st/st.h b/st/st.h new file mode 100644 index 0000000..eda7eeb --- /dev/null +++ b/st/st.h @@ -0,0 +1,132 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* 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_SET = 1 << 0, + ATTR_BOLD = 1 << 1, + ATTR_FAINT = 1 << 2, + ATTR_ITALIC = 1 << 3, + ATTR_UNDERLINE = 1 << 4, + ATTR_BLINK = 1 << 5, + ATTR_REVERSE = 1 << 6, + ATTR_INVISIBLE = 1 << 7, + ATTR_STRUCK = 1 << 8, + ATTR_WRAP = 1 << 9, + ATTR_WIDE = 1 << 10, + ATTR_WDUMMY = 1 << 11, + ATTR_SELECTED = 1 << 12, + 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); +int tisaltscr(void); +void tnew(int, int); +int tisaltscreen(void); +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 *); + +/* 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; diff --git a/st/st.h.orig b/st/st.h.orig new file mode 100644 index 0000000..514ec08 --- /dev/null +++ b/st/st.h.orig @@ -0,0 +1,131 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* 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_SET = 1 << 0, + ATTR_BOLD = 1 << 1, + ATTR_FAINT = 1 << 2, + ATTR_ITALIC = 1 << 3, + ATTR_UNDERLINE = 1 << 4, + ATTR_BLINK = 1 << 5, + ATTR_REVERSE = 1 << 6, + ATTR_INVISIBLE = 1 << 7, + ATTR_STRUCK = 1 << 8, + ATTR_WRAP = 1 << 9, + ATTR_WIDE = 1 << 10, + ATTR_WDUMMY = 1 << 11, + ATTR_SELECTED = 1 << 12, + 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); +int tisaltscreen(void); +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 *); + +/* 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; diff --git a/st/st.info b/st/st.info new file mode 100644 index 0000000..efab2cf --- /dev/null +++ b/st/st.info @@ -0,0 +1,243 @@ +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, + BE=\E[?2004h, + BD=\E[?2004l, + PS=\E[200~, + PE=\E[201~, +# 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/st/st.o b/st/st.o new file mode 100644 index 0000000000000000000000000000000000000000..0a72709ae81dc644fae8524559eff2cce8b5a614 GIT binary patch literal 82624 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ +} 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 Buttonmask for Button - 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 && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (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 - 1)) + 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 - 1)) + 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 * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 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 (p[0] == '\0') + 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 (p[0] == '\0') + 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 = NoSymbol; + 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); + if (status == XBufferOverflow) + return; + } 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/st/x.o b/st/x.o new file mode 100644 index 0000000000000000000000000000000000000000..6ae9473f0ecd414cb7964e95dda232c602176080 GIT binary patch literal 72040 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 literal 0 HcmV?d00001